Poner clavijas cuadradas en agujeros cuadrados

20

Me intrigó el diseño de este gráfico del New York Times, en el que cada estado de EE. UU. Está representado por un cuadrado en una cuadrícula. Me preguntaba si colocaban los cuadrados a mano o si realmente encontraban una ubicación óptima de los cuadrados (bajo alguna definición) para representar las posiciones de los estados contiguos.

Gráfico de verificación de antecedentes de armas del New York Times

Su código asumirá una pequeña parte del desafío de colocar cuadrados de manera óptima para representar estados (u otras formas bidimensionales arbitrarias). Específicamente, va a suponer que ya tenemos todos los centros geográficos o centroides de las formas en un formato conveniente, y que la representación óptima de los datos en un diagrama como este es aquella en la que la distancia total desde los centroides de las formas a los centros de los cuadrados que los representan es mínima, con a lo sumo un cuadrado en cada Posible posición.

Su código tomará una lista de pares únicos de coordenadas X e Y de coma flotante de 0.0 a 100.0 (inclusive) en cualquier formato conveniente, y generará las coordenadas enteras no negativas de cuadrados unitarios en una cuadrícula colocada de manera óptima para representar los datos , preservando el orden. En los casos en que múltiples arreglos de cuadrados son óptimos, puede generar cualquiera de los arreglos óptimos. Se darán entre 1 y 100 pares de coordenadas.

Este es el código de golf, el código más corto gana.

Ejemplos:

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

Esta es una fácil. Los centros de los cuadrados en nuestra cuadrícula están en 0.0, 1.0, 2.0, etc. por lo que estas formas ya están perfectamente ubicadas en los centros de los cuadrados en este patrón:

21
03

Por lo tanto, su salida debe ser exactamente estas coordenadas, pero como enteros, en un formato de su elección:

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

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

En este caso, todas las formas están cerca del centro del cuadrado en (2, 2), pero necesitamos alejarlas porque dos cuadrados no pueden estar en la misma posición. Minimizar la distancia desde el centroide de una forma al centro del cuadrado que lo representa nos da este patrón:

 1
402
 3

Entonces su salida debería ser [(2, 2), (2, 3), (3, 2), (2, 1), (1, 2)].

Casos de prueba:

[(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)]

Distancia total desde los centroides de formas a los centros de los cuadrados que los representan en cada caso (¡avíseme si detecta algún error!):

0.0
3.6
4.087011
13.243299
2.724791
1.144123

Solo por diversión:

Aquí hay una representación de los centros geográficos de los Estados Unidos contiguos en nuestro formato de entrada, aproximadamente a la escala utilizada por el 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)]

Para obtenerlos, tomé las coordenadas de la segunda lista de esta página y las utilicé 0.4 * (125.0 - longitude)para nuestra coordenada X y 0.4 * (latitude - 25.0)para nuestra coordenada Y. Esto es lo que parece trazado:

Parcela de los centros geográficos de los Estados Unidos contiguos.

¡La primera persona en usar la salida de su código con las coordenadas anteriores como entrada para crear un diagrama con cuadrados reales recibe una palmada en la parte posterior!

Luke
fuente
Creo que el último punto en su segundo ejemplo debería ser (1, 2), no(1, 1) .
Tim Pederick
Buena captura, gracias!
Lucas
¿Puede publicar también el total de todas las distancias en cada caso de prueba? Este es ciertamente un problema no trivial, y eso nos permitiría verificar si una solución alternativa también es realmente óptima.
flawr
PD: ¿Realmente has probado que el mapa dado es realmente un resultado válido de tu problema de optimización? Porque intuitivamente no creo que lo sea.
defecto
Puedo sumar las distancias totales. El mapa que usó el Times seguramente no es óptimo.
Lucas

Respuestas:

3

Mathematica, 473 bytes

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)

Antes de jugar al 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)

Explicacion :

Este problema de optimización no es difícil de describir en Mathematica. Dada una lista de puntos pde longitud n,

  • las variables son x[i]y y[i]: v=Array[{x[#],y[#]}&,n],
  • la función a minimizar es el total de los desplazamientos: f=Total[Norm/@(p-v)],
  • las restricciones son: c=Flatten[v]∈Integers&&And@@(Or@@Thread[#1!=#2]&@@@Subsets[v,{2}]).

Y, NMinimize[{f,cons},v,MaxIterations->Infinity]dará el resultado. Pero desafortunadamente, este esquema directo parece demasiado complicado para converger.

Para evitar el problema de la complejidad, se adoptan dos técnicas:

  • una gran "interacción", If[#1==#2,1*^4,0]&Se utiliza para evitar colisiones entre puntos.
  • en lugar de optimizar todas las variables al mismo tiempo, optimizamos en cada punto con sus vecinos a su vez.

Comenzamos desde una suposición inicial al redondear los puntos. Cuando las optimizaciones se realizan una por una, se espera que las colisiones se resuelvan y se establece una disposición optimizada.

La solución final es al menos buena, si no óptima. (Creo :P)


Resultado :

El resultado de Solo por diversión se muestra a continuación. Los puntos verde oscuro son las entradas, los cuadrados grises son las salidas y las líneas negras muestran los desplazamientos.

ingrese la descripción de la imagen aquí

La suma de los desplazamientos es 19.4595 . Y la solución es

{{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
fuente
¡Decir ah! Estaba pensando en hacer un diagrama como ese último. Bien hecho.
Tim Pederick el
Buen trabajo. Intuitivamente, su solución para el mapa de los Estados Unidos me parece óptima.
Lucas
2

Python 3, 877 bytes

Esto no es una implementación correcta. Falla en el segundo de los "casos de prueba adicionales", produciendo una solución con una distancia total de 13.5325, donde la solución proporcionada solo necesita 13.2433. Para complicar aún más las cosas es el hecho de que mi implementación de golf no coincide con la no golfizada que escribí primero ...

Sin embargo, nadie más ha respondido, y este es un desafío demasiado interesante para dejarlo pasar. Además, tengo una imagen generada a partir de los datos de EE. UU., Así que ahí está.

El algoritmo es algo como esto:

  1. Empuje todos los puntos a las coordenadas enteras más cercanas (en lo sucesivo denominado "cuadrado").
  2. Encuentra el cuadrado con la mayor cantidad de puntos.
  3. Encuentre la redistribución de menor costo de esos puntos al vecindario de nueve cuadrados de este, excluyendo cualquier cuadrado que ya haya sido procesado en el paso 2.
    • La redistribución se limita a un punto por cuadrado, a menos que eso no proporcione suficientes cuadrados (aunque incluso entonces, solo quedará un punto en este cuadrado).
  4. Repita desde el paso 2 hasta que ningún cuadrado tenga más de un punto.
  5. Localice cada uno de los puntos originales, en orden, y genere sus cuadrados, en orden.

No tengo absolutamente ninguna prueba de optimización para ninguna parte de este algoritmo, solo una fuerte sospecha de que proporcionará resultados "bastante buenos". ¡Creo que eso es lo que llamamos un "algoritmo heurístico" en mis primeros días ...!

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

Y el resultado de ejecutarlo en los datos de EE. UU. (Gracias a una función de utilidad que convierte los resultados en SVG): Un mapa esquemático de los Estados Unidos contiguos.

Esto es un poco peor que el que generó el código sin golf; La única diferencia visible es que el cuadrado superior derecho está uno más a la izquierda en el mejor.

Tim Pederick
fuente
Tienes una palmadita en la espalda! Parece que necesito trabajar en la escala de la longitud para hacer que esto se parezca un poco más al diagrama del Times.
Lucas
Por curiosidad, ¿qué distancia total obtienes para tu mapa de EE. UU.?
Tom Carpenter
Probablemente debería haberme hecho esa pregunta ... porque me acaba de mostrar que mi implementación de golf es peor de lo que pensaba. Mi versión original, sin golf, la obtuve en 20.9164, pero la versión que publiqué me dio 20.9987. * suspiro *
Tim Pederick
1

MATLAB, 316 343 326 bytes

Este es un trabajo en progreso, no es rápido, pero es corto. Parece pasar la mayoría de los casos de prueba. Actualmente se está ejecutando el que solo se usa para ingresar datos divertidos del mapa, pero aún continúa después de 10 minutos, entonces ...

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

Y en un formato algo más legible:

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

Se espera que el formato de entrada sea una matriz MATLAB, como:

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

Lo cual está bastante cerca del formato en la pregunta, lo que permite un margen de maniobra.

La salida está en el mismo formato que la entrada, una matriz donde cualquier índice dado corresponde al mismo punto tanto en la entrada como en la salida.


Hmm, 8 horas y aún se está ejecutando en el mapa ... esta solución está garantizada para encontrar la más óptima, pero lo hace a través de la fuerza bruta, por lo que lleva mucho tiempo.

Se me ocurrió otra solución que es mucho más rápida, pero como la otra respuesta no logra encontrar la más óptima en uno de los casos de prueba. Curiosamente, el mapa que obtengo para mi otra solución (no publicado) se muestra a continuación. Alcanza una distancia total de 20.72.

Mapa

Tom Carpenter
fuente