Kemana kuman itu pergi?

21

pengantar

Anda adalah seorang ahli biologi yang mempelajari pola pergerakan bakteri. Tim peneliti Anda memiliki banyak dari mereka dalam cawan petri, dan Anda merekam aktivitas mereka. Sayangnya, Anda benar-benar kekurangan dana, dan tidak mampu membeli kamera video, jadi Anda hanya mengambil gambar hidangan secara berkala. Tugas Anda adalah membuat program yang melacak pergerakan kuman dari gambar-gambar ini.

Memasukkan

Input Anda adalah dua larik 2D karakter dalam format apa pun yang wajar, mewakili gambar cawan petri berturut-turut. Di kedua array, karakter .mewakili ruang kosong, dan Omewakili kuman (Anda dapat memilih dua karakter berbeda jika Anda mau). Juga, susunan "setelah" diperoleh dari susunan "sebelum" dengan menggerakkan beberapa kuman satu langkah di salah satu dari empat arah mata angin; khususnya, susunan memiliki bentuk yang sama. Kuman bergerak secara bersamaan, jadi salah satu dari mereka mungkin pindah ke ruang yang sudah mengandung kuman lain, jika bergerak keluar dari jalan. Dijamin bahwa batas array "sebelum" hanya berisi ruang kosong, dan setidaknya ada satu kuman. Dengan demikian, berikut adalah pasangan input yang valid:

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

Keluaran

Output Anda adalah array 2D karakter tunggal dalam format yang sama dengan input. Ini diperoleh dari susunan "sebelum" dengan mengganti kuman-kuman yang telah pindah dengan salah satunya >^<v, tergantung pada arah gerakannya (Anda juga dapat menggunakan 4 karakter berbeda di sini). Mungkin ada beberapa kemungkinan keluaran, tetapi Anda hanya akan memberikan salah satunya. Dalam contoh di atas, satu kemungkinan hasil yang benar adalah

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

Pergerakan yang tidak perlu diizinkan dalam output dan kuman dapat bertukar tempat, jadi yang berikut ini juga valid:

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

Aturan dan penilaian

Anda dapat menulis program atau fungsi lengkap. Hitungan byte terendah menang, dan celah standar tidak diizinkan.

Saya tertarik pada algoritma yang relatif efisien, tetapi saya tidak ingin melarang brute force sepenuhnya. Untuk alasan ini, ada bonus -75% untuk menyelesaikan test case terakhir dalam 10 menit pada CPU modern (saya tidak dapat menguji sebagian besar solusi, jadi saya hanya akan mempercayai Anda di sini). Penafian: Saya tahu bahwa ada algoritma cepat (mencari "masalah jalur terpisah"), tetapi saya belum mengimplementasikannya sendiri.

Kasus uji tambahan

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..
..............................
Zgarb
sumber
Hanya untuk memastikan, kuman hanya bisa bergerak dengan satu atau nol sel, bukan?
Domino
@ JacqueGoupil Ya, itu benar. Masing-masing >^<vsesuai dengan gerakan tepat satu langkah ke arah masing-masing.
Zgarb
Saya belum mencoba menyelesaikannya, tapi ini alat untuk membuat lebih banyak test case :) jsfiddle.net/xd2xns64/embedded/result
Domino
Oh, hati-hati, ada kemungkinan skrip akan berulang selamanya jika mencoba untuk memindahkan semua sel ke tepi tetapi kemudian sel tepi tidak punya tempat untuk pergi.
Domino

Jawaban:

3

Oktaf, 494 496 byte - bonus 372 byte = 124 byte

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

Masih ada banyak golf yang harus dilakukan pada jawaban ini, tetapi saya ingin mendapatkan penjelasan yang ungolfed.

Saya melihat ini sebagai Masalah Kepuasan Kendala, jadi saya pergi dengan heuristik pencarian lokal favorit saya, Min-konflik . Idenya adalah, diberikan penempatan awal dengan masing-masing kuman di tujuan yang dapat dijangkau, pilih kuman acak yang menempati sel tujuan yang sama dengan satu atau lebih kuman lain dan memindahkannya ke sel yang valid yang memiliki minimum kuman lain sudah ada di sana. Ulangi seperlunya sampai penempatan cocok dengan tujuan.

Menariknya, algoritma ini tidak dijamin akan berakhir (jika tujuannya tidak dapat dijangkau, akan terus berlanjut tanpa batas, misalnya) tetapi jika tidak diakhiri, dijamin akan menghasilkan solusi yang valid.

Ini kodenya:

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

Output untuk kasus uji terakhir:

>> 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.

Waktu rata-rata yang berlalu adalah kurang dari 9 detik 1 detik * pada Core i5 berusia 5 tahun, yang memenuhi syarat untuk bonus.

Saya mencoba menyelesaikan ideone ini, tetapi saya memiliki apa yang saya yakini sebagai pelingkupan masalah dengan cara menangani fungsi bersarang. (Inilah tautan ideone yang tidak berfungsi untuk referensi: http://ideone.com/mQSwgZ )
Kode pada ideone sekarang berfungsi. Saya harus memaksa semua variabel ke global, yang tidak perlu menjalankannya secara lokal.

* Saya mempunyai catatan dalam versi saya yang tidak disatukan bahwa salah satu langkahnya tidak efisien, jadi saya mencobanya untuk melihat apakah saya dapat mempercepat eksekusi dan untuk 2 tambahan byte waktu eksekusi sekarang turun menjadi di bawah satu detik. Kode dan output sampel telah diperbarui dan input pada ideone telah diubah menjadi kasus uji terakhir.

gelas kimia
sumber
3

Python, 1171 byte - bonus 878,25 byte = 292,75 byte

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

Tautan Ideone: http://ideone.com/0Ylmwq

Memakan waktu rata-rata 1 - 8 detik dari test case terakhir, memenuhi syarat untuk mendapatkan bonus.

Ini adalah pengiriman kode-golf pertama saya, jadi mungkin bukan program golf terbaik di luar sana. Meskipun demikian, itu adalah tantangan yang menarik dan saya cukup menikmatinya. @Beaker layak disebutkan untuk mengingatkan saya bahwa pencarian berbasis heuristik adalah suatu hal. Sebelum dia memposting solusinya dan mengilhami saya untuk mengulang milik saya, pencarian brute force saya terlalu lama untuk memenuhi syarat untuk bonus pada kasus uji terakhir (itu berada di urutan iterasi 69 !, yang merupakan angka 99 digit .. .).

Saya tidak ingin langsung menyalin solusi Beaker, jadi saya memutuskan untuk menggunakan anil simulasi untuk heuristik pencarian saya. Tampaknya lebih lambat daripada konflik kecil untuk masalah ini (kemungkinan karena ini adalah algoritma pengoptimalan daripada kepuasan kendala), tetapi masih dalam batas 10 menit. Itu juga memiliki manfaat menjadi cukup kecil, kode-bijaksana. Saya menghabiskan lebih banyak byte untuk mengubah masalah daripada yang saya lakukan untuk menemukan solusi untuk itu.

Penjelasan

Solusi saya mungkin byte-wise tidak cukup efisien, tetapi saya kesulitan mengkonsep bagaimana menyelesaikan masalah apa adanya dan akhirnya saya harus mengubahnya menjadi masalah yang berbeda yang lebih mudah bagi saya untuk mengerti. Saya menyadari bahwa ada empat kemungkinan untuk setiap sel di grid:

  • Tidak ada kuman sebelum atau sesudah, yang berarti kita dapat mengabaikannya
  • Itu memiliki kuman sebelum tetapi tidak setelah, yang berarti kita harus menemukan langkah untuk itu.
  • Itu tidak memiliki kuman sebelum tetapi satu setelah, yang juga berarti kita harus menemukan langkah untuk itu.
  • Itu memiliki kuman sebelum dan sesudah, yang berarti kita mungkin harus menemukan langkah untuk itu, tetapi sekali lagi mungkin tidak.

Setelah mendekomposisi data ke dalam kelas-kelas itu, saya dapat mengubah masalah lebih lanjut. Segera jelas bagi saya bahwa saya harus menemukan cara untuk memasok kuman dari set "sebelum tetapi tidak setelah" ke sel dalam set "setelah tetapi tidak sebelum". Lebih jauh, kuman hanya dapat memindahkan satu ruang, jadi satu-satunya cara bagi mereka untuk mempengaruhi sel yang lebih jauh adalah dengan "mendorong" jalur kuman yang tidak terputus ke dalam sel itu. Itu berarti masalah menjadi menemukan jalur X-vertex-disjoint pada grafik, di mana setiap sel dengan kuman adalah simpul dalam grafik tersebut, dan ujung-ujungnya mewakili sel yang berdekatan.

Saya memecahkan masalah itu dengan terlebih dahulu membuat grafik yang dijelaskan di atas. Saya kemudian menghitung setiap jalur yang mungkin dari setiap sel di Sebelum dan setiap sel di Setelah, lalu secara acak menempatkan setiap sel di Sebelum salah satu jalur yang mungkin. Akhirnya, saya menggunakan anil simulasi untuk semi-acak mengubah solusi potensial sampai saya akhirnya menemukan solusi yang tidak memiliki konflik untuk setiap jalur.

Versi beranotasi

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

sumber