Colocar pinos quadrados em orifícios quadrados

20

Fiquei intrigado com o design deste gráfico do New York Times, no qual cada estado dos EUA é representado por um quadrado em uma grade. Gostaria de saber se eles colocaram os quadrados à mão ou realmente encontraram uma colocação ótima de quadrados (sob alguma definição) para representar as posições dos estados contíguos.

Gráfico de verificação de antecedentes de armas do New York Times

Seu código assumirá uma pequena parte do desafio de colocar quadrados de maneira ideal para representar estados (ou outras formas bidimensionais arbitrárias). Especificamente, ele assumirá que já temos todos os centros geográficos ou centróides das formas em um formato conveniente, e que a representação ideal dos dados em um diagrama como esse é aquela em que a distância total dos centróides das formas aos centros dos quadrados que os representam é mínima, com no máximo um quadrado em cada posição possível.

Seu código fará uma lista de pares exclusivos de coordenadas X e Y de ponto flutuante de 0,0 a 100,0 (inclusive) em qualquer formato conveniente e produzirá as coordenadas inteiras não negativas dos quadrados da unidade em uma grade posicionada de maneira ideal para representar os dados , preservando a ordem. Nos casos em que vários arranjos de quadrados são ótimos, você pode produzir qualquer um dos arranjos ótimos. Serão dados entre 1 e 100 pares de coordenadas.

Este é o código de golfe, o código mais curto vence.

Exemplos:

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

Este é fácil. Os centros dos quadrados em nossa grade estão em 0,0, 1,0, 2,0, etc., portanto, essas formas já estão perfeitamente posicionadas nos centros de quadrados neste padrão:

21
03

Portanto, sua saída deve ser exatamente essas coordenadas, mas como números inteiros, em um formato de sua escolha:

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

Nesse caso, todas as formas estão próximas do centro do quadrado em (2, 2), mas precisamos afastá-las porque dois quadrados não podem estar na mesma posição. Minimizar a distância do centróide de uma forma ao centro do quadrado que a representa nos dá este padrão:

 1
402
 3

Portanto, sua saída deve ser [(2, 2), (2, 3), (3, 2), (2, 1), (1, 2)].

Casos de teste:

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

Distância total entre os centróides de formas e os centros dos quadrados que os representam em cada caso (entre em contato se detectar algum erro!):

0.0
3.6
4.087011
13.243299
2.724791
1.144123

Apenas por diversão:

Aqui está uma representação dos centros geográficos dos Estados Unidos contíguos em nosso formato de entrada, aproximadamente na escala que o Times usou:

[(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 obtê-las, peguei as coordenadas da segunda lista nesta página e usei 0.4 * (125.0 - longitude)para nossa coordenada X e 0.4 * (latitude - 25.0)nossa coordenada Y. Aqui está o que parece plotado:

Trama dos centros geográficos dos Estados Unidos contíguos.

A primeira pessoa a usar a saída de seu código com as coordenadas acima como entrada para criar um diagrama com quadrados reais recebe um tapinha nas costas!

Luke
fonte
Eu acredito que o último ponto no seu segundo exemplo deveria ser (1, 2), não (1, 1).
precisa saber é o seguinte
Boa captura, obrigado!
Lucas
Você também pode postar o total de todas as distâncias em cada caso de teste? Este é certamente um problema não trivial e que nos permitiria verificar se uma solução alternativa também é realmente ótima.
flawr
PS: Você realmente testou se o mapa fornecido é realmente um resultado válido do seu problema de otimização? Porque intuitivamente eu não acho.
flawr
Eu posso adicionar as distâncias totais. O mapa usado pelo Times quase certamente não é o ideal.
Lucas

Respostas:

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 jogar golfe:

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)

Explicação :

Esse problema de otimização não é difícil de descrever no Mathematica. Dada uma lista de pontos pde comprimento n,

  • as variáveis são x[i]e y[i]: v=Array[{x[#],y[#]}&,n],
  • a função de minimizar é o total de deslocamentos: f=Total[Norm/@(p-v)],
  • as restrições são: c=Flatten[v]∈Integers&&And@@(Or@@Thread[#1!=#2]&@@@Subsets[v,{2}]).

E, NMinimize[{f,cons},v,MaxIterations->Infinity] vai dar o resultado. Infelizmente, porém, esse esquema simples parece muito complicado para convergir.

Para contornar o problema da complexidade, duas técnicas são adotadas:

  • uma grande "interação", If[#1==#2,1*^4,0]& é usada para evitar colisões entre pontos.
  • em vez de otimizar todas as variáveis ​​ao mesmo tempo, otimizamos todos os pontos com os vizinhos por vez.

Começamos com um palpite inicial, arredondando os pontos. Quando as otimizações são feitas uma a uma, espera-se que as colisões sejam resolvidas e um arranjo otimizado é estabelecido.

A solução final é pelo menos boa, se não ótima. (Eu acredito :P)


Resultado :

O resultado de Apenas por diversão é mostrado abaixo. Pontos verdes escuros são as entradas, quadrados cinza são as saídas e linhas pretas mostram os deslocamentos.

insira a descrição da imagem aqui

A soma dos deslocamentos é 19.4595 . E a solução é

{{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
fonte
Ha! Eu só estava pensando em fazer um diagrama como esse último. Bem feito.
precisa saber é o seguinte
Bom trabalho. Intuitivamente, sua solução para o mapa dos EUA parece ótima para mim.
Lucas
2

Python 3, 877 bytes

Isto não é uma implementação correta. Ele falha no segundo dos "outros casos de teste", produzindo uma solução com uma distância total de 13,5325, onde a solução fornecida precisa apenas de 13,2433. Para complicar ainda mais, o fato de minha implementação de golfe não corresponder à não-golfada que escrevi primeiro ...

No entanto, ninguém mais respondeu, e este é um desafio muito interessante para deixar passar. Além disso, tenho uma imagem gerada a partir dos dados dos EUA, então é isso.

O algoritmo é algo como isto:

  1. Empurre todos os pontos para as coordenadas inteiras mais próximas (a seguir denominado "quadrado").
  2. Encontre o quadrado com o maior número de pontos.
  3. Encontre a redistribuição de menor custo desses pontos para o bairro de nove quadrados deste, excluindo os quadrados que já foram processados ​​na etapa 2.
    • A redistribuição é limitada a um ponto por quadrado, a menos que isso não forneça quadrados suficientes (embora mesmo assim, apenas um ponto permaneça nesse quadrado).
  4. Repita da etapa 2 até que nenhum quadrado tenha mais de um ponto.
  5. Localize cada um dos pontos originais, em ordem, e produza seus quadrados, em ordem.

Não tenho absolutamente nenhuma prova de otimização para qualquer parte desse algoritmo, apenas uma forte suspeita de que ele fornecerá resultados "muito bons". Eu acho que é isso que chamamos de "algoritmo heurístico" nos meus dias de 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

E o resultado da execução nos dados dos EUA (graças a uma função de utilitário que transforma os resultados em SVG): Um mapa esquemático dos Estados Unidos contíguos

Isso é um pouco pior do que o código não destruído produzido; a única diferença visível é que o quadrado superior direito está um mais à esquerda no melhor.

Tim Pederick
fonte
Você dá um tapinha nas costas! Parece que preciso trabalhar no dimensionamento da longitude para que isso pareça um pouco mais com o diagrama do Times.
Lucas
Por curiosidade, que distância total você tem para o seu mapa dos EUA?
Tom Carpenter
Eu provavelmente deveria ter feito essa pergunta a mim mesmo ... porque acabou de me mostrar que minha implementação no golfe é pior do que eu pensava. Minha versão original e não destruída chegou em 20.9164, mas a versão que publiquei me deu 20.9987. * Suspiro *
Tim Pederick
1

MATLAB, 316 343 326 bytes

Este é um trabalho em andamento - não é rápido, mas é curto. Parece passar na maioria dos casos de teste. Atualmente, a única entrada divertida do mapa está em execução, mas continua em 10 minutos, então ...

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

E em um formato um pouco mais legível:

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

Espera-se que o formato de entrada seja um array MATLAB, como:

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

O que é bem parecido com o formato da pergunta, que permite alguma margem de manobra.

A saída está no mesmo formato que a entrada, uma matriz em que qualquer índice corresponde ao mesmo ponto na entrada e na saída.


Hmm, 8 horas e ainda em execução no mapa 1 ... esta solução é garantida para encontrar o melhor, mas o faz por força bruta, por isso leva muito tempo.

Eu vim com outra solução que é muito mais rápida, mas como a outra resposta não consegue encontrar o melhor em um dos casos de teste. Curiosamente, o mapa que recebo para minha outra solução (não publicada) é mostrado abaixo. Atinge uma distância total de 20,72.

Mapa

Tom Carpenter
fonte