Bu mikrop nereye gitti?


21

Giriş

Bakterilerin hareket kalıplarını inceleyen bir biyologsunuz. Araştırma ekibinizde bir avuç petri kabında bir sürü var ve onların aktivitesini kaydediyorsunuz. Ne yazık ki, ciddi bir şekilde finanse edilmiyorsunuz ve video kamera kullanamıyorsunuz, bu yüzden düzenli aralıklarla yemeğin bir resmini çekiyorsunuz. Senin görevin bu resimlerden mikropların hareketlerini izleyen bir program yapmak.

Giriş

Girdileriniz, herhangi bir makul formattaki iki 2D karakter dizisidir ve petri kabının ardışık resimlerini temsil eder. Her iki dizide de karakter .boş alanı Otemsil eder ve bir mikropu temsil eder (eğer isterseniz iki ayrı karakter seçebilirsiniz). Ayrıca, "after" dizisi, "gerdan" dizisinden, bazı mikropları dört kardinal yönün birinde bir adım hareket ettirerek elde edilir; özellikle, diziler aynı şekle sahiptir. Mikroplar eşzamanlı olarak hareket eder, böylece bir tanesi, başka bir mikropu içeren bir alana, bu yoldan çıkarsa hareket edebilir. "Before" dizisinin sınırlarının sadece boş alanlar içerdiği ve en az bir mikrop olduğu garanti edilmektedir. Dolayısıyla, aşağıdakiler geçerli bir girdi çiftidir:

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

Çıktı

Çıktınız, girişlerle aynı formatta tek bir 2B karakter dizisidir. >^<vHareket yönüne bağlı olarak, biriyle taşınan mikropları değiştirerek "önce" dizisinden elde edilir (burada 4 ayrı karakter de kullanabilirsiniz). Birkaç olası çıktı olabilir, ancak bunlardan yalnızca birini vermelisiniz. Yukarıdaki örnekte, olası bir doğru çıktı

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

Çıktıda gereksiz harekete izin verilir ve mikroplar yer değiştirebilir, bu nedenle aşağıdakiler geçerlidir:

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

Kurallar ve puanlama

Tam bir program veya bir fonksiyon yazabilirsiniz. En düşük bayt sayısı kazanır ve standart boşluklar izin verilmez.

Nispeten verimli algoritmalarla ilgileniyorum, fakat kaba zorlama işlemini tamamen yasaklamak istemiyorum. Bu nedenle, son test vakasını modern bir CPU'da 10 dakika içinde çözmek için % -75 bonus var (çoğu çözümü test edemiyorum, bu yüzden sadece size güveneceğim). Feragatname: Hızlı bir algoritma olduğunu biliyorum ("ayrık yollar sorununu araştırın"), ancak kendim uygulamadım.

Ek test durumları

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

Emin olmak için, mikroplar yalnızca bir veya sıfır hücre tarafından hareket edebilir, değil mi?
Domino

@JacqueGoupil Evet, doğru. Her >^<vbiri, ilgili yönde tam olarak bir adım hareketine karşılık gelir.
Zgarb

Henüz çözmeyi denemedim, ama işte daha fazla test senaryosu
Domino

Dikkatli ol, tüm hücreleri bir kenara doğru kaydırmaya çalışırsa betiğin sonsuza dek döngüde kalması ihtimali var, ama o zaman kenar hücrelerinin gidecek bir yeri yok.
Domino

Yanıtlar:


3

Octave, 494 496 bayt - 372 bayt bonusu = 124 bayt

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

Bu cevapta hala yapılması gereken çok sayıda golf var, ama ben topraksız açıklama yapmak istedim.

Bunu bir Kısıt Memnuniyet Sorunu olarak gördüm, bu yüzden en sevdiğim yerel arama buluşsal, Min-çatışmaları ile gittim . Fikir, ulaşılabilir bir hedefteki her bir mikrop ile bir başlangıç ​​yerleştirmesi verildiğinde, bir veya daha fazla başka mikrop ile aynı hedef hücreyi kaplayan ve onu en az başka mikropları olan geçerli bir hücreye taşıyan rastgele bir mikropu seçmektir. Yerleşim hedefle eşleşene kadar gerektiği kadar tekrarlayın.

İlginç bir şekilde, bu algoritmanın sonlandırılması garanti edilmez (hedefe ulaşılamıyorsa, örneğin süresiz olarak devam eder), ancak sonlandırması durumunda geçerli bir çözüm üretmesi garanti edilir.

İşte kod:

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

Son test durumu için çıktı:

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

Ortalama geçen süre, bonus için geçerli olan 5 yaşındaki Core i5'te 1 saniyede * 9 saniyeden daha azdı .

Bunun ideone üzerinde çalışmasını sağlamaya çalışıyorum, ancak iç içe geçmiş işlevleri kullanma konusundaki sorunları kapsamayı düşündüğüme inanıyorum. (İşte başvuru için çalışmayan ideone bağlantısı: http://ideone.com/mQSwgZ ) İdeone
üzerindeki kod şimdi çalışıyor. Tüm değişkenleri global olarak zorlamak zorunda kaldım, bu da yerel olarak çalıştırmak gereksizdi.

* Ungolfed versiyonumda adımlardan birinin yetersiz kaldığına dair bir not aldım, bu yüzden yürütmeyi hızlandırabilir miyim ve 2 bayt için yürütme zamanının bir saniyenin altına düştüğünü görmeye çalıştım. Kod ve örnek çıktı güncellendi ve ideone üzerindeki girdi son test durumuyla değiştirildi.


3

Python, 1171 bayt - 878.25 bayt bonusu = 292.75 bayt

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

İdeone bağlantısı: http://ideone.com/0Ylmwq

Son testte ortalama 1 - 8 saniye sürer, bonus kazanır.

Bu benim ilk kod-golf gönderim, bu yüzden muhtemelen oradaki en iyi golf programı değil. Yine de ilginç bir zorluktu ve ben de bundan çok zevk aldım. @Beaker, buluşsal tabanlı aramaların bir şey olduğunu hatırlattığımı söylemeyi hak ediyor. Çözümünü yayınlamadan ve benimkini tekrar yapmak için bana ilham vermeden önce, kaba kuvvet araştırmam son test durumundaki bonusa hak kazanmak için çok uzun sürdü (bu, 99 basamaklı bir sayıydı. .).

Beaker'in çözümünü düzeltmek istemedim, bu yüzden arama sezgiselim için benzetilmiş tavlama kullanmaya karar verdim. Bu sorun için min-çatışmadan daha yavaş görünüyor (muhtemelen bir kısıtlama memnuniyeti yerine bir optimizasyon algoritması olduğu için), ancak yine de 10 dakikalık bir noktada bulunuyor. Ayrıca, kod bakımından oldukça küçük olma avantajına da sahipti. Sorunu dönüştürmek için, çözüm bulmak için yaptığımdan çok daha fazla bayt harcadım.

açıklama

Çözümüm muhtemelen bayt olarak oldukça yetersizdir, ancak sorunu olduğu gibi nasıl çözeceğimi kavramsallaştırmakta zorlandım ve bu yüzden bunu anlamak için kolay olan farklı bir soruna dönüştürmek zorunda kaldım. Şebekedeki her hücre için dört olasılık olduğunu fark ettim:

  • Daha önce veya sonra hiçbir mikrop yoktu, yani onu görmezden gelebiliriz
  • Öncesinde fakat sonrasında değil bir mikrop vardı, bu onun için bir hareket bulmamız gerektiği anlamına geliyor.
  • Daha önce mikrop geçirmemişti, ama sonra, bu da onun için bir hareket bulmamız gerektiği anlamına geliyor.
  • Öncesinde ve sonrasında bir mikrop vardı, bu onun için bir hareket bulmamız gerekebileceği anlamına geliyor, ama sonra belki de değil.

Verileri bu sınıflara ayırdıktan sonra, sorunu daha da değiştirebildim. Benim için hemen belirttim, "önce ama sonra değil" setinden "sonra ama sonra değil" setinden bir hücreye bir mikrop tedarik etmenin bir yolunu bulmak zorunda kaldım. Ayrıca, mikroplar sadece bir boşluk taşıyabilir, bu yüzden daha uzaktaki hücreleri etkilemelerinin tek yolu bozulmamış bir mikrop yolunu o hücreye “itmek” tir. Bu, problemin, bir grafik üzerinde X tepe noktası-ayrık yolları bulması anlamına geliyordu; burada mikroplu her hücre, söz konusu grafikte bir tepe noktasıydı ve kenarları, bitişik hücreleri temsil ediyordu.

Bu sorunu, önce yukarıda açıklanan grafiği oluşturarak çözdüm. Daha sonra sıralanan her sonra rasgele olası yollardan birine önce her bir hücre tahsis sonrasında önce her bir hücre ve her bir hücrenin mümkün bir yol. Sonunda, olası bir çözümü yarı-rasgele mutasyona geçirmek için simüle edilmiş bir tavlama kullandım ve sonunda hiçbir yol için çelişki olmayan bir çözüm bulana kadar.

Açıklamalı sürüm

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.