Куди пішов цей зародок?


21

Вступ

Ви біолог, що вивчає закономірності руху бактерій. У вашій дослідницькій групі є купа їх у чашках Петрі, і ви записуєте їхню діяльність. На жаль, у вас серйозно недостатньо фінансуються, і ви не можете дозволити собі відеокамеру, тому ви просто фотографуєте страву через рівні проміжки часу. Ваше завдання - скласти програму, яка відстежує рухи мікробів з цих картинок.

Вхідні дані

Ваші входи - це два двовимірні масиви символів у будь-якому розумному форматі, що представляють послідовні зображення чашки Петрі. В обох масивах символ .представляє порожній простір і Oявляє собою зародок (за бажанням можна вибрати будь-які два різних символи). Також масив "після" отримується з масиву "до" шляхом переміщення деяких мікробів на один крок в одному з чотирьох кардинальних напрямків; зокрема, масиви мають однакову форму. Зародки рухаються одночасно, тому один з них може переміститися в простір, в якому вже містився інший зародок, якщо він рухається з шляху. Гарантується, що межі масиву "до" містять лише порожні пробіли, а також є щонайменше один зародок. Таким чином, наступна пара є вхідними парами:

Before  After
......  ......
.O..O.  ....O.
.OO.O.  .OO.O.
......  ..O...

Вихідні дані

Ваш вихід - це один двовимірний масив символів у тому ж форматі, що і входи. Він отримується з масиву "до" шляхом заміни тих мікробів, які перемістилися одним із >^<v, залежно від напрямку руху (тут також можна використовувати будь-які 4 різних символи). Можливо декілька можливих результатів, але ви повинні дати лише один з них. У наведеному вище прикладі можливий правильний вихід

......
.v..O.
.>v.O.
......

Допускається непотрібний рух у виході, і мікроби можуть міняти місцями, тому дійсне також:

......
.v..v.
.>v.^.
......

Правила та оцінка

Ви можете написати повну програму або функцію. Виграє найменший кількість байтів, а стандартні лазівки заборонені.

Мене цікавлять порівняно ефективні алгоритми, але я не хочу повністю забороняти грубі форсування. З цієї причини на сучасному процесорі є бонус -75% за вирішення останнього тестового випадку (я не в змозі протестувати більшість рішень, тому я вам тут просто довіряю). Відмова: Я знаю, що існує швидкий алгоритм (пошук "проблеми нерозбірних шляхів"), але я сам його не реалізував.

Додаткові тестові випадки

Before
......
.O..O.
..OO..
......
After
......
..O...
...OO.
..O...
Possible output
......
.>..v.
..vO..
......

Before
.......
.OOOOO.
.O..OO.
.OO..O.
.OOOOO.
.......
After
.......
..OOOOO
.O...O.
.O...O.
.OOOOOO
....O..
Possible output
.......
.>>>>>.
.O..>v.
.Ov..v.
.O>>v>.
.......

Before
..........
.OOO..OOO.
.OOOOOOOO.
.OOO..OOO.
..........
After
..O.......
.OOO..O.O.
..OOOOOOOO
.O.O..OOO.
.......O..
Possible output
..........
.>^O..O>v.
.^O>>>vO>.
.O>^..>vO.
..........

Before
............
.OO..OOOOOO.
.OO......OO.
...OOOOOO...
.O.OOOOOO.O.
...OOOOOO...
.OOOOOOOOOO.
............
After
..........O.
.OO..OOOOO..
.O...O...O..
.O.OOOOOOO..
.O.OOOOOO..O
...OO..OO...
....OOOOOOOO
.OOO........
Possible output
............
.OO..v<<<<^.
.v<......^<.
...OOO>>>...
.O.OOO^OO.>.
...OOv^OO...
.vvvO>>>>>>.
............

Before
................
.OOOOOO.OOOOOOO.
..OO..OOOOOOOOO.
.OOO..OOOO..OOO.
..OOOOOOOO..OOO.
.OOOOOOOOOOOOOO.
................
After
................
..OOOOO.OOOOOOOO
..OO..OOOOOOOOO.
..OO..OOOO..OOOO
..OOOOOOOO..OOO.
..OOOOOOOOOOOOOO
................
Possible output
................
.>>>>>v.>>>>>>>.
..OO..>>^>>>>>v.
.>>v..OOO^..OO>.
..O>>>>>>^..OOO.
.>>>>>>>>>>>>>>.
................

Before
..............................
.OOO.O.O.....O.....O.O.O..O...
..OOO.O...O..OO..O..O.O.......
.....O......O..O.....O....O...
.O.OOOOO......O...O..O....O...
.OO..O..OO.O..OO..O..O....O...
..O.O.O......OO.OO..O..OO.....
..O....O..O.OO...OOO.OOO...O..
.....O..OO......O..O...OO.OO..
........O..O........OO.O.O....
..O.....OO.....OO.OO.......O..
.O.....O.O..OO.OO....O......O.
..O..OOOO..O....OO..........O.
.O..O...O.O....O..O....O...OO.
....O...OO..O.......O.O..OO...
........O.O....O.O....O.......
.OO.......O.OO..O.......O..O..
....O....O.O.O...OOO..O.O.OO..
.OO..OO...O.O.O.O.O...OO...O..
..............................
After
..............................
.OOOOO.......OO.....O..O......
...OO..O...O...O....OO....O...
....O.O......O..OO...OO...O...
.OO.OOOO......OO..O..O........
O.O.OO..O..O..O..OO...O...OO..
.OO.....O....OO.O..O.OO.O.....
......O.....O.....OOO.OO...O..
....O..OOOO..O..O..O.O.O.OO...
..O......O.O........O...O.O...
.O.....OOO.....OO.OO...O...O..
.......OOO..O.O.O...........O.
.O...O.....O...OOOO..O.O....O.
.O..O.O..O.....O......O....OO.
....O..O..O.O......O.....O....
........OOO....O......O..O....
.OO......O..OO..OOO.....O..O..
..O.O....OO..O...OO...O...OO..
.O..OO....O..O...O.O.O.OO.....
..............O............O..
Possible output
..............................
.OOO.O.v.....>.....>.v.O..v...
..>>^.v...>..^>..v..O.v.......
.....<......>..>.....O....O...
.O.<O><O......O...O..O....v...
.<O..O..v<.O..O^..O..>....>...
..<.^.v......OO.O^..>..<O.....
..^....v..v.Ov...>>^.<OO...O..
.....<..OO......O..O...Ov.v<..
........>..O........O^.v.^....
..^.....Ov.....OO.OO.......O..
.^.....^.^..O>.vO....v......O.
..<..Ov^^..O....><..........O.
.O..O...>.v....O..^....^...OO.
....O...<v..O.......<.^..v<...
........O.O....O.v....O.......
.OO.......<.Ov..O.......O..O..
....O....O.<.^...O^v..O.v.OO..
.O^..<<...O.>.v.>.^...<O...v..
..............................

Щоб бути певним, мікроби можуть рухатися лише однією або нульовою клітиною, правда?
Доміно

@JacqueGoupil Так, це правильно. Кожен з них >^<vвідповідає руху рівно одного кроку у відповідному напрямку.
Згарб

Я ще не намагався її вирішити, але ось інструмент для створення більшої кількості тестових випадків :) jsfiddle.net/xd2xns64/embedded/result
Domino

О, обережно, є ймовірність, що сценарій назавжди зациклюється, якщо він спробує перемістити всі комірки до краю, але тоді крайові клітини нікуди не діватимуться.
Доміно

Відповіді:


3

Октава, 494 496 байт - 372 байт бонус = 124 байти

function o=G(b,a)
y='.O<^v>';s=(b>46)+0;t=a>46;v=t;f=s;t(:,2:end,2)=t(:,1:end-1);t(2:end,:,3)=t(1:end-1,:,1);t(1:end-1,:,4)=t(2:end,:,1);t(:,1:end-1,5)=t(:,2:end,1);t=reshape(t,[],5);m=size(s,1);p=[0 -m -1 1 m];
function z(n)
f(n+p(s(n)))--;q=find(t(n,:));w=n+p(q);d=min(f(w));q=q(f(w)==d);j=randi(numel(q));s(n)=q(j);f(n+p(q(j)))++;end
for g=find(s)' z(g);end
while any((f~=v)(:)) L=find(s);k=zeros(size(s));for h=L' k(h)=f(h+p(s(h)));end;c=find(k>1);g=c(randi(numel(c)));z(g);end
o = y(s+1);end

З цією відповіддю ще потрібно зробити багато гольфу, але я хотів отримати пояснення у невольф.

Я розглядав це як проблему задоволення обмежень, тому я пішов з улюбленою локальною евристикою пошуку, Min-конфліктами . Ідея полягає в тому, що, з огляду на початкове розміщення кожного зародка в досяжному пункті призначення, виберіть випадковий зародок, який займає ту саму клітинку призначення, що і один або кілька інших мікробів, і перемістіть її у дійсну клітинку, яка вже має мінімум інших мікробів. Повторіть по мірі необхідності, поки розміщення не відповідає цілі.

Цікаво, що цей алгоритм не гарантовано припиняється (якщо ціль недосяжна, наприклад, вона буде продовжуватися нескінченно), але якщо вона припиниться, гарантовано вийде дійсне рішення.

Ось код:

function output = germs(before, after)

%before = ['......';'.O..O.';'.OO.O.';'......'];
%after = ['......';'....O.';'.OO.O.';'..O...'];

symbs = '.O<^v>';
start = (before > 46) + 0;                   %should be called current_board
target = after > 46;                         %destinations on current cell == O
goal = target;
conflicts = start;
target(:, 2:end,2) = target(:, 1:end-1);     %destinations on cell to left
target(2:end, :,3) = target(1:end-1, :,1);   %destinations on cell above
target(1:end-1, :,4) = target(2:end, :,1);   %destinations on cell below
target(:, 1:end-1,5) = target(:, 2:end,1);   %destinations on cell to right
target=reshape(target,[],5);
m = size(start,1);                           %number of rows = offset to previous/next column
offsets = [0 -m -1 1 m];                     %offsets of neighbors from current index


function moveGerm(n)
   conflicts(n+offsets(start(n)))--;         %take germ off board
   move = find(target(n, :));                %get valid moves for this germ
   neighbors = n + offsets(move);            %valid neighbors = current position + offsets
   minVal = min(conflicts(neighbors));       %minimum number of conflicts for valid moves
   move = move(conflicts(neighbors)==minVal);
   mi = randi(numel(move));                  %choose a random move with minimum conflicts
   start(n) = move(mi);                      %add move type to board
   conflicts(n + offsets(move(mi)))++;       %add a conflict on the cell we move to
end

% Generate an initial placement
for g = find(start)'
   moveGerm(g);                              %make sure all germs are moved to valid cells
end

% Repeat until board matches goal
while any((conflicts ~= goal)(:))
   germList = find(start);                   %list of all our germs
   cost = zeros(size(start));                %calculate conflicts for each germ
   for h = germList'
      cost(h) = conflicts(h + offsets(start(h)));
   end
   conflicted = find(cost > 1);              %find those germs that occupy the same cell as another
   g = conflicted(randi(numel(conflicted))); %choose a random germ to move
   moveGerm(g);
end

output = symbs(start+1);                     %use moves as indices into symbol array for output

end

Висновок для останнього тестового випадку:

>> gtest
ans =

..............................
.OO>.O.v.....>.....>.v.O..v...
..>^O.v...>..^>..v..O.v.......
.....v......>..>.....O....O...
.O.<^<OO......>...O..O....v...
.<O..O..v<.O..^<..O..>....>...
..<.^.v......OO.O^..<..<O.....
..^....v..v.Ov...>>>.^OO...O..
.....<..OO......O..O...Ov.<<..
........>..O........O^.v.>....
..^.....OO.....OO.OO.......O..
.^.....^.O..O>.vO....v......O.
..<..Ov^^..O....OO..........O.
.O..O...>.v....O..^....^...OO.
....O...<v..O.......<.^..v<...
........O.O....O.v....O.......
.OO.......<.OO..O.......O..O..
....O....O.<.O...O^<..O.v.OO..
.O^..<<...O.>.v.>.>...<O...v..
..............................

Elapsed time is 0.681691 seconds.

Середній час, що минув, був меншим за 9 секунд на 1 секунду * для 5-річного Core i5, кваліфікованого для отримання бонусу.

Я намагаюся зробити це над ідеоном, але у мене є те, що, на мою думку, стосується вирішення проблем з тим, як він обробляє вкладені функції. (Ось непрацююче посилання на ideone для довідки: http://ideone.com/mQSwgZ )
Код на ideone зараз працює. Мені довелося змусити всі змінні до глобальних, що було зайвим запускати його локально.

* У моїй неперевершеній версії я зауважив, що один із кроків був неефективним, тому я спробував це переконатися, чи зможу я пришвидшити виконання, і для 2-х доданих байтів час виконання зараз зменшується на менше секунди. Код та вихідний зразок оновлено, а введення про ideone було змінено на останній тестовий випадок.


3

Python, 1171 байт - 878,25 байт бонус = 292,75 байт

from itertools import *;from random import *;R=range;L=len;O=choice;G='O'
def A(a,b):a.append(b)
def D(y,z):
 a=[];b=[];c=[]
 for i in R(L(y)):
  A(c,[])
  for j in R(L(y[0])):
   k=[(i,j),y[i][j]==G,z[i][j]==G,[],0];A(c[i],k)
   for l,m in [(0,1),(1,0)]:
    try:
     n=c[i-l][j-m]
     if k[2]&n[1]:A(n[3],k)
     if k[1]&n[2]:A(k[3],n)
    except:pass
   if k[1]&~k[2]:A(a,k)
   elif k[2]&~k[1]:A(b,k)
 d={}
 for i in a:
  j=[[i]]
  while j:
   k=j.pop();l=[e[0] for e in k]
   while True:
    m=k[-1];n=[o for o in m[3] if o[0] not in l]
    if not n:
     if m in b:A(d.setdefault(i[0],[]),k)
     break
    for o in n[1:]:p=k[:];A(p,o);A(j,p)
    A(k,n[0]);A(l,n[0][0])
 e={}
 for i in a:e[i[0]]=O(d[i[0]])
 def E():return sum(any(k in j for k in i) for i,j in combinations(e.values(),2))
 f=E()
 for i in count():
  t=3**-i/L(a);j=O(a);k=e[j[0]];e[j[0]]=O(d[j[0]]);l=E()
  if not l:break
  else:
   if l>f and random()>t:e[j[0]]=k
   else:f=l
 for i in e.values():
  for j in R(L(i)-1):i[j][4]=i[j+1]
 for i in c:
  for j in R(L(i)):
   k=i[j]
   if 1&~k[1]:i[j]='.'
   elif not k[4]:i[j]=G
   else:l,m=k[0];n,o=k[4][0];i[j]='v>^<'[abs((l-n+1)+2*(m-o))]
 return c

Ідеонне посилання: http://ideone.com/0Ylmwq

Займає в середньому від 1 до 8 секунд на останньому тестовому випадку, кваліфікуючись на бонус.

Це моє перше представлення коду-гольфу, тому, мабуть, це не найкраща програма для гольфу там. Тим не менше, це був цікавий виклик, і мені це дуже сподобалось. @ Beeaker заслуговує на згадку, що нагадує мені, що пошуки на основі евристики - це річ. Перш ніж він опублікував своє рішення і надихнув мене переробити шахту, мій жорсткий пошук був занадто довгим, щоб отримати право на отримання бонусу за останній тестовий випадок (це було на замовлення 69! Ітерацій, що становить 99 цифр.) .).

Я не хотів прямо копіювати рішення Beaker, тому вирішив використати імітаційний відпал для свого евристичного пошуку. Ця проблема здається повільнішою, ніж мінімальна конфліктність (швидше за все, це алгоритм оптимізації, а не задоволення обмеження), але він все ще знаходиться в межах 10-хвилинної позначки. Він також мав перевагу бути досить невеликим, кодовим. На трансформацію проблеми я витратив набагато більше байтів, ніж на пошук її рішення.

Пояснення

Моє рішення, ймовірно, є досить неефективним у байтах, але у мене виникли проблеми, коли я розумів, як вирішити проблему як є, і тому мені довелося перетворити її на іншу проблему, яку мені було легше зрозуміти. Я зрозумів, що в сітці є чотири можливості для кожної комірки:

  • У ній не було зародка до або після, а це означає, що ми можемо його ігнорувати
  • Він мав зародок раніше, але не після, а це означає, що ми повинні знайти хід для цього.
  • Він не мав зародка раніше, а одного після, що також означає, що ми повинні знайти хід для цього.
  • Він мав зародок до і після, а це означає, що нам, можливо, доведеться знайти хід для цього, але потім, можливо, ні.

Розклавши дані в ці класи, я зміг далі перетворити проблему. Для мене відразу було очевидно, що я повинен знайти спосіб подати зародок з набору "до, але не після" до клітини в наборі "після, але не раніше". Крім того, мікроби можуть переміщати лише один простір, тому єдиний спосіб для них впливати на віддалені клітини - "проштовхуючи" неперерваний шлях мікробів у цю клітину. Це означало, що проблемою стало пошук X-вершин, що розмежовуються на графіку, де кожна клітина із зародком була вершиною у вказаному графіку, а краї представляли сусідні комірки.

Я вирішив цю проблему, спочатку будуючи графік, пояснений вище. Потім я перерахував усі можливі шляхи від кожної комірки в "Перед" та "кожна клітинка" в "Після", а потім випадково призначив кожну клітинку "Перед" одним із можливих шляхів. Нарешті, я використав імітований відпал, щоб напіввипадково мутувати потенційне рішення, поки я врешті-решт не знайшов рішення, яке не має конфліктів для жодного із шляхів.

Анотована версія

from itertools import *;from random import *;

# redefine some built-in functions to be shorter
R=range;L=len;O=choice;G='O'
def A(a,b):a.append(b)

# The function itself.  Input is in the form of two 2d arrays of characters, one each for before and after.
def D(y,z):
 # Declare the Before-but-not-after set, the After-but-not-before set, and a temp cell array
 # (the cells are temporarily stored in a 2d array because I need to be able to locate neighbors)
 a=[];b=[];c=[]

 # Build the graph
 for i in R(L(y)):
  # Append a row to the 2d temp array
  A(c,[])

  for j in R(L(y[0])):
   # Define the interesting information about the cell, then add it to the temp array
   # The cell looks like this: [position, does it have a germ before?, does it have a germ after?, list of neighbors with germs, final move]
   k=[(i,j),y[i][j]==G,z[i][j]==G,[],0];A(c[i],k)
   for l,m in [(0,1),(1,0)]:
    # Fill up the neighbors by checking the above and left cell, then mutually assigning edges
    try:
     n=c[i-l][j-m]
     if k[2]&n[1]:A(n[3],k)
     if k[1]&n[2]:A(k[3],n)
    except:pass

   # Decide if it belongs in the Before or After set
   if k[1]&~k[2]:A(a,k)
   elif k[2]&~k[1]:A(b,k)

 # For each cell in the before set, define ALL possible paths from it (this is a big number of paths if the grid is dense with germs)
 # This uses a bastard form of depth-first search where different paths can cross each other, but no path will cross itself
 d={}
 for i in a:
  j=[[i]]  # Define the initial stack of incomplete paths as the starting node.
  while j:
   # While the stack is not empty, pop an incomplete path of the stack and finish it
   k=j.pop();l=[e[0] for e in k]
   while True:
    # Set the list of next possible moves to the neighbors of the current cell,
    # ignoring any that are already in the current path.
    m=k[-1];n=[o for o in m[3] if o[0] not in l]

    # If there are no more moves, save the path if it ends in an After cell and break the loop
    if not n:
     if m in b:A(d.setdefault(i[0],[]),k)
     break

    # Otherwise, set the next move in this path to be the first move,
    # then split off new paths and add them to the stack for every other move
    for o in n[1:]:p=k[:];A(p,o);A(j,p)
    A(k,n[0]);A(l,n[0][0])

 # Perform simulated annealing to calculate the solution
 e={}
 for i in a:e[i[0]]=O(d[i[0]])  # Randomly assign paths for the first potential solution

 # Define a function for calculating the number of conflicts between all paths, then do the initial calculation for the initial potential solution
 def E():return sum(any(k in j for k in i) for i,j in combinations(e.values(),2))
 f=E()

 # Do the annealing
 for i in count():
  # The "temperature" for simulated annealing is calculated as 3^-i/len(Before set).
  # 3 was chosen as an integer approximation of e, and the function e^(-i/len) itself was chosen because
  # it exponentially decays, and does so slower for larger problem sets
  t=3**-i/L(a)

  j=O(a)              # Pick a random Before cell to change
  k=e[j[0]]           # Save it's current path
  e[j[0]]=O(d[j[0]])  # Replace the current path with a new one, randomly chosen
  l=E()               # Recalculate the number of conflicts

  if not l:break  # If there are no conflicts, we have a valid solution and can terminate
  else:           # Otherwise check the temperature to see if we keep the new move
   if l>f and random()>t:e[j[0]]=k  # Always keep the move if it's better, and undo it with probability 1 - T if it's worse
   else:f=l                         # If we don't undo, remember the new conflict count

 # Set each of the cells' final moves based on the paths
 for i in e.values():
  for j in R(L(i)-1):i[j][4]=i[j+1]

 # Build the output in the form of a 2d array of characters
 # Reuse the temp 2d array from step since its the right size
 for i in c:
  for j in R(L(i)):
   k=i[j]
   # Cells that are empty in the before array are always empty in the output
   if 1&~k[1]:i[j]='.'
   # Cells that aren't empty and don't have a move are always germs in the output
   elif not k[4]:i[j]=G
   # Otherwise draw the move
   else:l,m=k[0];n,o=k[4][0];i[j]='v>^<'[abs((l-n+1)+2*(m-o))]
 return c
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.