Положить квадратные колышки в квадратные отверстия


20

Я был заинтригован дизайном этой графики из New York Times, в которой каждый штат США представлен квадратом в сетке. Я задавался вопросом, помещали ли они квадраты вручную или фактически находили оптимальное расположение квадратов (согласно некоторому определению), чтобы представить положения смежных государств.

График проверки фона пистолета из New York Times

Ваш код будет выполнять небольшую часть задачи оптимального размещения квадратов для представления состояний (или других произвольных двумерных фигур). В частности, предполагается, что у нас уже есть все географические центры или центроиды фигур в удобный формат, и что оптимальным представлением данных на диаграмме, подобной этой, является тот, в котором общее расстояние от центроидов фигур до центров квадратов, которые их представляют, минимально, с максимум одним квадратом в каждом возможная позиция.

Ваш код возьмет список уникальных пар координат X и Y с плавающей точкой от 0,0 до 100,0 (включительно) в любом удобном формате и выведет неотрицательные целочисленные координаты единичных квадратов в сетке, оптимально размещенной для представления данных. Сохраняя порядок. В случаях, когда несколько расположений квадратов являются оптимальными, вы можете вывести любое из оптимальных расположений. Будет дано от 1 до 100 пар координат.

Это код гольф, самый короткий код выигрывает.

Примеры:

Входные данные: [(0.0, 0.0), (1.0, 1.0), (0.0, 1.0), (1.0, 0.0)]

Это легко. Центры квадратов в нашей сетке равны 0,0, 1,0, 2,0 и т. Д., Поэтому эти фигуры уже идеально расположены в центрах квадратов по этой схеме:

21
03

Таким образом, ваши выходные данные должны быть точно этими координатами, но в виде целых чисел в формате по вашему выбору:

[(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), но мы должны оттолкнуть их, потому что два квадрата не могут быть в одном и том же положении. Минимизация расстояния от центра тяжести фигуры до центра квадрата, который представляет ее, дает нам такую ​​модель:

 1
402
 3

Так что ваш вывод должен быть [(2, 2), (2, 3), (3, 2), (2, 1), (1, 2)].

Тестовые случаи:

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

Общее расстояние от центроидов фигур до центров квадратов, которые представляют их в каждом случае (пожалуйста, дайте мне знать, если вы обнаружите какие-либо ошибки!):

0.0
3.6
4.087011
13.243299
2.724791
1.144123

Просто для удовольствия:

Вот представление географических центров смежных Соединенных Штатов в нашем формате ввода, примерно в масштабе, который использовала 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)]

Чтобы получить их, я взял координаты из второго списка на этой странице и использовал их 0.4 * (125.0 - longitude)для нашей координаты X и 0.4 * (latitude - 25.0)для нашей координаты Y. Вот как это выглядит на графике:

Сюжет о географических центрах сопредельных США.

Первый человек, который использует выходные данные из своего кода с указанными выше координатами в качестве входных данных для создания диаграммы с реальными квадратами, получает похлопывание по спине!


Я считаю, что последний пункт в вашем втором примере должен быть (1, 2) , а не (1, 1).
Тим Педерик

Хороший улов, спасибо!
Люк

Можете ли вы также опубликовать сумму всех расстояний в каждом тестовом случае? Это, конечно, нетривиальная проблема, и это позволило бы нам проверить, является ли альтернативное решение на самом деле также оптимальным.
flawr 18.01.16

PS: Вы действительно проверяли, что данная карта действительно является действительным результатом вашей проблемы оптимизации? Потому что интуитивно я не думаю, что это так.
flawr 18.01.16

Я могу добавить общее расстояние. Карта, используемая «Таймс», почти наверняка не оптимальна.
Люк

Ответы:


3

Mathematica, 473 байта

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)

Перед игрой в гольф:

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)

Пояснение :

Эту проблему оптимизации нетрудно описать в Mathematica. Учитывая список точек pдлины n,

  • переменные x[i]и y[i]: v=Array[{x[#],y[#]}&,n],
  • функция , чтобы минимизировать это сумма смещений: f=Total[Norm/@(p-v)],
  • ограничения являются: c=Flatten[v]∈Integers&&And@@(Or@@Thread[#1!=#2]&@@@Subsets[v,{2}]).

И, NMinimize[{f,cons},v,MaxIterations->Infinity]даст результат. Но, к сожалению, такая прямолинейная схема кажется слишком сложной, чтобы сходиться.

Чтобы обойти проблему сложности, приняты два метода:

  • большое «взаимодействие», If[#1==#2,1*^4,0]&используется, чтобы избежать столкновения между точками.
  • вместо оптимизации всех переменных одновременно, мы оптимизируем каждую точку с их соседями по очереди.

Мы начнем с первоначального предположения, округляя точки. Когда оптимизации выполняются одна за другой, ожидается, что конфликты будут разрешены, и будет создана оптимизированная схема.

Окончательное решение, по крайней мере, хорошо, если не оптимально. (Я верю :P)


Результат :

Результат Just for fun показан ниже. Темно-зеленые точки - это входы, серые квадраты - это выходы, а черные линии показывают смещения.

введите описание изображения здесь

Сумма смещений составляет 19,4595 . И решение

{{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}}

Ха! Я просто думал сделать диаграмму, похожую на последнюю. Отлично сработано.
Тим Педерик

Хорошая работа. Интуитивно, ваше решение для карты США выглядит оптимальным для меня.
Лука

2

Python 3, 877 байт

Это не правильная реализация. Он завершается неудачно во втором из «дополнительных тестовых случаев», в результате чего получается решение с общим расстоянием 13,5325, где предоставленное решение требует только 13,2433. Еще более сложным является тот факт, что моя игра в гольф не совпадает с той, которую я написал первой ...

Тем не менее, никто не ответил, и это слишком интересная задача, чтобы ускользнуть. Кроме того, у меня есть изображение, сгенерированное из данных США, так что вот оно.

Алгоритм выглядит примерно так:

  1. Переместите все точки к ближайшим целочисленным координатам (далее называемым «квадрат»).
  2. Найдите квадрат с наибольшим количеством очков.
  3. Найдите самое дешевое перераспределение этих точек в окрестности девяти квадратов этого, исключая любые квадраты, которые уже были обработаны в шаге 2.
    • Перераспределение ограничено одной точкой на квадрат, если только это не обеспечит достаточное количество квадратов (хотя даже тогда на этом квадрате останется только одна точка ).
  4. Повторяйте с шага 2, пока ни у одного квадрата не будет больше одной точки.
  5. Найдите каждую из исходных точек по порядку и выведите их квадраты по порядку.

У меня нет абсолютно никаких доказательств оптимальности для какой-либо части этого алгоритма, только сильное подозрение, что он даст «довольно хорошие» результаты. Я думаю, что это то, что мы называли «эвристическим алгоритмом» в мои дни ...!

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

И результат запуска его на данных США (благодаря служебной функции, которая превращает результаты в SVG): Схематическая карта смежных Соединенных Штатов

Это немного хуже, чем тот, который выдает неопрятный код; единственное видимое отличие состоит в том, что крайний правый квадрат находится на один левее от лучшего.


Вы получаете по спине! Похоже, мне нужно поработать над масштабированием долготы, чтобы это выглядело как диаграмма из Times.
Люк

Из любопытства, какое общее расстояние вы получаете для своей карты США?
Том Карпентер

Я, наверное, должен был задать этот вопрос самому себе ... потому что он только что показал мне, что моя игра в гольф хуже, чем я думал. Моя оригинальная версия без гольфа получила его в 20.9164, но версия, которую я выложил, дала мне 20.9987. * вздох *
Тим Педерик

1

МАТЛАБ, 316 343 326 байт

Это работа в процессе - она ​​не быстрая, но короткая. Кажется, пройти большинство тестовых случаев. В настоящее время вводится только ради забавы ввод карты, но она продолжается через 10 минут, так что ...

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

И в несколько более читаемом формате:

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

Предполагается, что формат ввода будет массивом MATLAB, например:

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

Что довольно близко к формату в вопросе, что позволяет некоторую свободу действий.

Выходные данные имеют тот же формат, что и входные данные, массив, где любой заданный индекс соответствует одной и той же точке как на входе, так и на выходе.


Хм, 8 часов и все еще работает на карте один ... это решение гарантированно найдет самое оптимальное, но оно делает это с помощью грубой силы, поэтому занимает очень много времени.

Я придумал другое решение, которое намного быстрее, но, как и другой ответ, не находит наиболее оптимального в одном из тестовых случаев. Интересно, что карта, которую я получаю для моего другого решения (не опубликовано), показана ниже. Достигается общая дистанция 20,72.

карта

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.