Mettre des chevilles carrées dans des trous carrés

20

J'ai été intrigué par la conception de ce graphique du New York Times, dans lequel chaque État américain est représenté par un carré dans une grille. Je me suis demandé s'ils avaient placé les carrés à la main ou s'ils avaient effectivement trouvé un placement optimal des carrés (sous une certaine définition) pour représenter les positions des états contigus.

Graphique de vérification des antécédents du New York Times

Votre code va relever une petite partie du défi de placer de manière optimale des carrés pour représenter des états (ou d'autres formes bidimensionnelles arbitraires). Plus précisément, il va supposer que nous avons déjà tous les centres géographiques ou centroïdes des formes dans un format pratique, et que la représentation optimale des données dans un diagramme comme celui-ci est celle dans laquelle la distance totale entre les centroïdes des formes et les centres des carrés qui les représentent est minimale, avec au plus un carré dans chaque position possible.

Votre code prendra une liste de paires uniques de coordonnées X et Y à virgule flottante de 0,0 à 100,0 (inclus) dans n'importe quel format pratique, et affichera les coordonnées entières non négatives des carrés d'unité dans une grille placée de manière optimale pour représenter les données , en préservant l'ordre. Dans les cas où plusieurs arrangements de carrés sont optimaux, vous pouvez sortir n'importe lequel des arrangements optimaux. Entre 1 et 100 paires de coordonnées seront données.

C'est le golf de code, le code le plus court gagne.

Exemples:

Contribution: [(0.0, 0.0), (1.0, 1.0), (0.0, 1.0), (1.0, 0.0)]

C'est facile. Les centres des carrés de notre grille sont à 0,0, 1,0, 2,0, etc. donc ces formes sont déjà parfaitement placées au centre des carrés dans ce modèle:

21
03

Donc, votre sortie doit être exactement ces coordonnées, mais sous forme d'entiers, dans un format de votre choix:

[(0, 0), (1, 1), (0, 1), (1, 0)]

Contribution: [(2.0, 2.1), (2.0, 2.2), (2.1, 2.0), (2.0, 1.9), (1.9, 2.0)]

Dans ce cas, toutes les formes sont proches du centre du carré en (2, 2), mais nous devons les repousser car deux carrés ne peuvent pas être dans la même position. Minimiser la distance entre le centre de gravité d'une forme et le centre du carré qui la représente nous donne ce modèle:

 1
402
 3

Votre sortie devrait donc l'être [(2, 2), (2, 3), (3, 2), (2, 1), (1, 2)].

Cas de test:

[(0.0, 0.0), (1.0, 1.0), (0.0, 1.0), (1.0, 0.0)] -> [(0, 0), (1, 1), (0, 1), (1, 0)]
[(2.0, 2.1), (2.0, 2.2), (2.1, 2.0), (2.0, 1.9), (1.9, 2.0)] -> [(2, 2), (2, 3), (3, 2), (2, 1), (1, 2)]
[(94.838, 63.634), (97.533, 1.047), (71.954, 18.17), (74.493, 30.886), (19.453, 20.396), (54.752, 56.791), (79.753, 68.383), (15.794, 25.801), (81.689, 95.885), (27.528, 71.253)] -> [(95, 64), (98, 1), (72, 18), (74, 31), (19, 20), (55, 57), (80, 68), (16, 26), (82, 96), (28, 71)]
[(0.0, 0.0), (0.1, 0.0), (0.2, 0.0), (0.0, 0.1), (0.1, 0.1), (0.2, 0.1), (0.0, 0.2), (0.1, 0.2), (0.2, 0.2)] -> [(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1), (0, 2), (1, 2), (2, 2)]
[(1.0, 0.0), (1.0, 0.1), (1.0, 0.2), (1.0, 0.3)] -> [(1, 0), (0, 0), (2, 0), (1, 1)] or [(1, 0), (2, 0), (0, 0), (1, 1)]
[(3.75, 3.75), (4.25, 4.25)] -> [(3, 4), (4, 4)] or [(4, 3), (4, 4)] or [(4, 4), (4, 5)] or [(4, 4), (5, 4)]

Distance totale entre les centroïdes des formes et les centres des carrés qui les représentent dans chaque cas (veuillez me prévenir si vous repérez des erreurs!):

0.0
3.6
4.087011
13.243299
2.724791
1.144123

Juste pour le fun:

Voici une représentation des centres géographiques des États-Unis contigus dans notre format d'entrée, à peu près à l'échelle utilisée par le Times:

[(15.2284, 3.1114), (5.3367, 3.7096), (13.0228, 3.9575), (2.2198, 4.8797), (7.7802, 5.5992), (20.9091, 6.6488), (19.798, 5.5958), (19.1941, 5.564), (17.023, 1.4513), (16.6233, 3.0576), (4.1566, 7.7415), (14.3214, 6.0164), (15.4873, 5.9575), (12.6016, 6.8301), (10.648, 5.398), (15.8792, 5.0144), (13.2019, 2.4276), (22.3025, 8.1481), (19.2836, 5.622), (21.2767, 6.9038), (15.8354, 7.7384), (12.2782, 8.5124), (14.1328, 3.094), (13.0172, 5.3427), (6.142, 8.8211), (10.0813, 6.6157), (3.3493, 5.7322), (21.3673, 7.4722), (20.1307, 6.0763), (7.5549, 3.7626), (19.7895, 7.1817), (18.2458, 4.2232), (9.813, 8.98), (16.8825, 6.1145), (11.0023, 4.2364), (1.7753, 7.5734), (18.8806, 6.3514), (21.3775, 6.6705), (17.6417, 3.5668), (9.9087, 7.7778), (15.4598, 4.3442), (10.2685, 2.5916), (5.3326, 5.7223), (20.9335, 7.6275), (18.4588, 5.0092), (1.8198, 8.9529), (17.7508, 5.4564), (14.0024, 7.8497), (6.9789, 7.1984)]

Pour les obtenir, j'ai pris les coordonnées de la deuxième liste sur cette page et utilisé 0.4 * (125.0 - longitude)pour notre coordonnée X et 0.4 * (latitude - 25.0)pour notre coordonnée Y. Voici à quoi cela ressemble:

Tracé des centres géographiques des États-Unis contigus.

La première personne à utiliser la sortie de son code avec les coordonnées ci-dessus comme entrée pour créer un diagramme avec des carrés réels obtient une tape dans le dos!

Luc
la source
Je pense que le dernier point de votre deuxième exemple devrait l'être (1, 2), non (1, 1).
Tim Pederick
Bonne prise, merci!
Luke
Pouvez-vous également afficher le total de toutes les distances dans chaque cas de test? Il s'agit certainement d'un problème non trivial, et cela nous permettrait de vérifier si une solution alternative est en fait également optimale.
flawr
PS: Avez-vous réellement testé que la carte donnée est réellement un résultat valide de votre problème d'optimisation? Parce que, intuitivement, je ne pense pas.
flawr
Je peux ajouter les distances totales. La carte utilisée par le Times n'est certainement pas optimale.
Luke

Réponses:

3

Mathematica, 473 octets

f@p_:=(s=Flatten@Round@p;v=Array[{x@#,y@#}&,n=Length@p];
  Do[w=Flatten[{g@#,h@#}&/@(b=Flatten@Position[p,x_/;Norm[x-p[[i]]]<=2,{1}])];f=Total[Norm/@(p-v)]+Total[If[#1==#2,1*^4,0]&@@@v~Subsets~{2}]/.Flatten[{x@#->g@#,y@#->h@#}&@@@w]/.Thread[Flatten@v->s];
    c=w∈Integers&&And@@MapThread[Max[#-2,0]<=#2<=Min[#+2,100]&,{Flatten@p[[b]],w}];s=Flatten@ReplacePart[s~Partition~2,Thread[b->Partition[w/.Last@Quiet@NMinimize[{f,c},w,MaxIterations->300],2]]]
    ,{i,n}]~Do~{2};s~Partition~2)

Avant de jouer au golf:

f[p_]:=(n=Length@p;s=Flatten@Round@p;v=Array[{x[#],y[#]}&,n];
  Do[
    v2=Flatten[{x2[#],y2[#]}&/@(b=Flatten@Position[p,x_/;Norm[x-p[[i]]]<=2,{1}])];
    f2=Total[Norm/@(p-v)]+Total[If[#1==#2,1*^4,0]&@@@Subsets[v,{2}]]/.Flatten[{x[#]->x2[#],y[#]->y2[#]}&@@@v2]/.Thread[Flatten@v->s];
    c2=v2∈Integers&&And@@MapThread[Max[#-2,0]<=#2<=Min[#+2,100]&,{Flatten@p[[b]],v2}];
    s=Flatten@ReplacePart[s~Partition~2,Thread[b->Partition[v2/.Last@Quiet@NMinimize[{f2,c2},v2,MaxIterations->300],2]]];
    ,{i,n}]~Do~{2};
  s~Partition~2)

Explication :

Ce problème d'optimisation n'est pas difficile à décrire dans Mathematica. Étant donné une liste de points pde longueur n,

  • les variables sont x[i]et y[i]: v=Array[{x[#],y[#]}&,n],
  • la fonction est de minimiser le total des déplacements: f=Total[Norm/@(p-v)],
  • les contraintes sont: c=Flatten[v]∈Integers&&And@@(Or@@Thread[#1!=#2]&@@@Subsets[v,{2}]).

Et, NMinimize[{f,cons},v,MaxIterations->Infinity]donnera le résultat. Mais malheureusement, un tel schéma simple semble trop compliqué pour converger.

Pour contourner le problème de la complexité, deux techniques sont adoptées:

  • une grande "interaction", If[#1==#2,1*^4,0]&est utilisée pour éviter la collision entre les points.
  • au lieu d'optimiser toutes les variables en même temps, nous optimisons à tour de rôle sur leurs points avec leurs voisins.

Nous partons d'une supposition initiale en arrondissant les points. Lorsque les optimisations sont effectuées une par une, les collisions devraient être résolues et un arrangement optimisé est établi.

La solution finale est au moins bonne, sinon optimale. (Je crois :P)


Résultat :

Le résultat de Just for fun est illustré ci-dessous. Les points vert foncé sont les entrées, les carrés gris les sorties et les lignes noires indiquent les déplacements.

entrez la description de l'image ici

La somme des déplacements est de 19,4595 . Et la solution est

{{15,3},{5,4},{13,4},{2,5},{8,6},{21,6},{20,5},{19,5},{17,1},{17,3},{4,8},{14,6},{15,6},{13,7},{11,5},{16,5},{13,2},{22,8},{19,6},{21,7},{16,8},{12,9},{14,3},{13,5},{6,9},{10,7},{3,6},{22,7},{20,6},{8,4},{20,7},{18,4},{10,9},{17,6},{11,4},{2,8},{19,7},{22,6},{18,3},{10,8},{15,4},{10,3},{5,6},{21,8},{18,5},{2,9},{18,6},{14,8},{7,7}}
njpipeorgan
la source
Ha! Je pensais juste à faire un diagramme comme celui-là dernier. Bien joué.
Tim Pederick
Bon travail. Intuitivement, votre solution à la carte des États-Unis me semble optimale.
Luke
2

Python 3, 877 octets

Ce n'est pas une implémentation correcte. Il échoue sur le deuxième des "autres cas de test", produisant une solution avec une distance totale de 13,5325, où la solution fournie n'a besoin que de 13,2433. Ce qui complique encore les choses, c'est le fait que mon implémentation au golf ne correspond pas à celle que j'ai écrite en premier ...

Cependant, personne d'autre n'a répondu, et c'est un défi trop intéressant à laisser passer. De plus, j'ai une image générée à partir des données des États-Unis, alors voilà.

L'algorithme est quelque chose comme ceci:

  1. Poussez tous les points vers les coordonnées entières les plus proches (ci-après appelé un "carré").
  2. Trouvez le carré avec le plus grand nombre de points.
  3. Trouvez la redistribution la moins chère de ces points au voisinage de neuf carrés de celui-ci, à l'exclusion de tous les carrés qui ont déjà été traités à l'étape 2.
    • La redistribution est limitée à un point par carré, à moins que cela ne fournisse pas suffisamment de carrés (bien que même alors, il ne reste qu'un point sur ce carré).
  4. Répétez à partir de l'étape 2 jusqu'à ce qu'aucun carré n'ait plus d'un point.
  5. Localisez chacun des points d'origine, dans l'ordre, et sortez leurs carrés, dans l'ordre.

Je n'ai absolument aucune preuve d'optimalité pour aucune partie de cet algorithme, juste une forte suspicion qu'il fournira de "très bons" résultats. Je pense que c'est ce que nous appelions un "algorithme heuristique" à l'époque de mon uni ...!

l=len
I,G,M=-1,101,150
d=lambda x,y,X,Y:abs(x-X+1j*(y-Y))
N=(0,0),(I,0),(0,I),(1,0),(0,1),(I,I),(1,I),(1,1),(I,I)
n=lambda p,e:[(x,y)for(x,y)in(map(sum,zip(*i))for i in zip([p]*9,N))if(x,y)not in e and I<x<G and I<y<G]
def f(p):
 g={};F=[];O=[I]*l(p)
 for P in p:
  z=*map(round,P),
  if z in g:g[z]+=[P]
  else:g[z]=[P]
 while l(g)<l(p):
  L,*P=0,
  for G in g:
   if l(g[G])>l(P):L,P=G,g[G]
  o=n(L,F);h=l(o)<l(P);c=[[d(*q,*r)for r in o]for q in P];r={}
  while l(r)<l(c):
   A=B=C=M;R=S=0
   while R<l(c):
    if R not in r:
     z=min(c[R])
     if z<A:B,A=R,z;C=c[R].index(A)
    R+=1
   while S<l(c):
    if S==B:
     v=0
     while v<l(c[S]):
      if v!=C:c[S][v]=M
      v+=1
    elif C<1or not h:c[S][C]=M
    S+=1
   r[B]=C
  for q in r:
   x,y=P[q],o[r[q]]
   if y==L or y not in g:g[y]=[x]
   else:g[y]+=[x]
  F+=[L]
 for G in g:
  O[p.index(g[G][0])]=G
 return O

Et le résultat de l'exécuter sur les données des États-Unis (grâce à une fonction utilitaire qui transforme les résultats en SVG): Une carte schématique des États-Unis contigus

C'est légèrement pire que celui que le code non golfé a produit; la seule différence visible est que le carré en haut à droite est un plus à gauche dans le meilleur.

Tim Pederick
la source
Vous obtenez une tape dans le dos! On dirait que je dois travailler sur la mise à l'échelle de la longitude pour que cela ressemble un peu plus au diagramme du Times.
Luke
Par curiosité, quelle distance totale obtenez-vous pour votre carte des États-Unis?
Tom Carpenter
J'aurais probablement dû me poser cette question ... car cela vient de me montrer que ma mise en œuvre au golf est pire que je ne le pensais. Ma version originale et non golfée l'a obtenu en 20.9164, mais la version que j'ai publiée m'a donné 20.9987. * soupir *
Tim Pederick
1

MATLAB, 316 343 326 octets

Celui-ci est un travail en cours - il n'est pas rapide, mais il est court. Il semble passer la plupart des cas de test. Actuellement, la carte juste pour le plaisir est en cours d'exécution, mais elle continue après 10 minutes, alors ...

function p=s(a)
c=ceil(a');a=a(:,1)+j*a(:,2);[~,p]=r(a,c,[],Inf);p=[real(p),imag(p)];end
function [o,p]=r(a,c,p,o)
if ~numel(c)
o=sum(abs(p-a));else
x=c(1)+(-1:1);y=c(2)+(-1:1);P=p;
for X=1:3
for Y=1:3
Q=x(X)+j*y(Y);if(x(X)>=0)&(y(Y)>=0)&all(Q~=P)
[O,Q]=r(a,c(:,2:end),[P;Q],o);
if(O<o) o=O;p=Q;disp(o);end
end;end;end;end;end

Et dans un format un peu plus lisible:

function p=squaremap(a)
%Input format: [2.0, 2.1;2.0, 2.2;2.1, 2.0;2.0, 1.9;1.9, 2.0]

    c=ceil(a'); %Convert each point to the next highest integer centre
    a=a(:,1)+j*a(:,2); %Convert each 2D point into a complex number
    [~,p]=r(a,c,[],Inf); %Recurse!
    p=[real(p),imag(p)];
end

function [o,p]=r(a,c,p,o)
    if ~numel(c) %If we are as deep as we can go
        o=sum(abs(p-a)); %See what our overall distance is
    else
        x=c(1)+(-1:1);y=c(2)+(-1:1); %For each point we try 9 points, essentially a 3x3 square
        P=p;
        for X=1:3;
            for Y=1:3
                %For each point
                Q=x(X)+j*y(Y); %Covert to a complex number
                if(x(X)>=0)&(y(Y)>=0)&all(Q~=P) %If the point is not negative and has not already been used this iteration
                    [O,Q]=r(a,c(:,2:end),[P;Q],o); %Otherwise iterate further
                    if(O<o) o=O;p=Q;end %Keep updating the smallest path and list of points we have found
                end
            end
        end
    end
end

Le format d'entrée devrait être un tableau MATLAB, tel que:

[2.0, 2.1;2.0, 2.2;2.1, 2.0;2.0, 1.9;1.9, 2.0]

Ce qui est assez proche du format de la question, ce qui laisse une certaine latitude.

La sortie est au même format que l'entrée, un tableau où tout index donné correspond au même point à la fois en entrée et en sortie.


Hmm, 8 heures et toujours en cours d'exécution sur la carte ... cette solution est garantie de trouver la plus optimale, mais elle le fait via la force brute, donc prend très longtemps.

J'ai trouvé une autre solution qui est beaucoup plus rapide, mais comme l'autre réponse ne parvient pas à trouver la plus optimale dans l'un des cas de test. Fait intéressant, la carte que j'obtiens pour mon autre solution (non publiée) est présentée ci-dessous. Il atteint une distance totale de 20,72.

Carte

Tom Carpenter
la source