Dov'è finito quel germe?


21

introduzione

Sei un biologo che studia gli schemi di movimento dei batteri. Il tuo gruppo di ricerca ne ha un sacco in una capsula di Petri e stai registrando la loro attività. Sfortunatamente, sei gravemente sottofinanziato e non puoi permetterti una videocamera, quindi scatta una foto del piatto a intervalli regolari. Il tuo compito è creare un programma che traccia i movimenti dei germi da queste immagini.

Ingresso

I tuoi input sono due matrici 2D di caratteri in qualsiasi formato ragionevole, che rappresentano immagini consecutive della capsula di Petri. In entrambi gli array, il personaggio .rappresenta uno spazio vuoto e Orappresenta un germe (se lo desideri, puoi scegliere due caratteri distinti). Inoltre, l'array "after" si ottiene dall'array "before" spostando alcuni germi di un passo in una delle quattro direzioni cardinali; in particolare, le matrici hanno la stessa forma. I germi si muovono simultaneamente, quindi uno di loro può spostarsi in uno spazio che già conteneva un altro germe, se si sposta di mezzo. È garantito che i bordi dell'array "before" contengano solo spazi vuoti e che vi sia almeno un germe. Pertanto, la seguente è una coppia valida di input:

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

Produzione

L'output è un singolo array 2D di caratteri nello stesso formato degli input. Si ottiene dall'array "prima" sostituendo quei germi che si sono mossi con uno di >^<v, a seconda della direzione del movimento (puoi anche usare 4 caratteri distinti qui). Potrebbero esserci diverse uscite possibili, ma devi fornirne solo una. Nell'esempio sopra, un possibile output corretto è

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

I movimenti non necessari sono consentiti nell'output e i germi possono scambiare posti, quindi vale anche quanto segue:

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

Regole e punteggio

È possibile scrivere un programma completo o una funzione. Vince il conteggio di byte più basso e non sono consentite scappatoie standard.

Sono interessato ad algoritmi relativamente efficienti, ma non voglio vietare del tutto la forzatura bruta. Per questo motivo, c'è un bonus del -75% per la risoluzione dell'ultimo caso di test entro 10 minuti su una CPU moderna (non riesco a testare la maggior parte delle soluzioni, quindi mi fiderò solo di te qui). Disclaimer: so che esiste un algoritmo veloce (ricerca di "problema di percorsi disgiunti"), ma non l'ho implementato da solo.

Casi di prova aggiuntivi

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

Giusto per essere sicuri, i germi possono muoversi solo di una o zero celle, giusto?
Domino,

@JacqueGoupil Sì, è corretto. Ognuno di >^<vcorrisponde a un movimento esattamente di un passo nella rispettiva direzione.
Zgarb,

Non ho ancora provato a risolverlo, ma ecco uno strumento per creare altri casi di test :) jsfiddle.net/xd2xns64/embedded/result
Domino,

Oh, attento, c'è una possibilità che lo script esegua il loop per sempre se prova a spostare tutte le celle contro un bordo ma poi le celle del bordo non hanno nessun posto dove andare.
Domino,

Risposte:


3

Ottava, 494 496 byte - 372 byte di bonus = 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

C'è ancora molto da giocare a golf su questa risposta, ma volevo ottenere la spiegazione non golfata.

Ho visto questo come un problema di soddisfazione dei vincoli, quindi sono andato con la mia ricerca euristica preferita, Min-conflitti . L'idea è, dato un posizionamento iniziale con ogni germe in una destinazione raggiungibile, selezionare un germe casuale che occupa la stessa cellula di destinazione di uno o più altri germi e spostarlo in una cellula valida che ha un minimo di altri germi già lì. Ripetere se necessario fino a quando il posizionamento non corrisponde all'obiettivo.

È interessante notare che questo algoritmo non è garantito per terminare (se l'obiettivo non è raggiungibile continuerà indefinitamente, per esempio) ma se termina è garantito per produrre una soluzione valida.

Ecco il codice:

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

Uscita per l'ultimo caso di test:

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

Il tempo medio trascorso è stato inferiore a 9 secondi 1 secondo * su un Core i5 di 5 anni, idoneo per il bonus.

Sto cercando di farlo funzionare su ideone, ma sto avendo quello che credo stia risolvendo problemi con il modo in cui gestisce le funzioni nidificate. (Ecco il link ideone non funzionante per riferimento: http://ideone.com/mQSwgZ )
Il codice su ideone ora funziona. Ho dovuto forzare tutte le variabili su global, il che non era necessario eseguirle localmente.

* Avevo notato nella mia versione non controllata che uno dei passaggi era inefficiente, quindi l'ho provato per vedere se potevo velocizzare l'esecuzione e per 2 byte aggiunti il ​​tempo di esecuzione ora è inferiore a meno di un secondo. L'output del codice e del campione è stato aggiornato e l'input su ideone è stato modificato nell'ultimo caso di test.


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

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

Richiede in media da 1 a 8 secondi sull'ultimo caso di test, qualificandosi per il bonus.

Questa è la mia prima proposta di golf da codice, quindi probabilmente non è il programma con il miglior golf in circolazione. Tuttavia, è stata una sfida interessante e mi è piaciuta molto. @Beaker merita una menzione per avermi ricordato che le ricerche euristiche sono una cosa. Prima che pubblicasse la sua soluzione e mi ispirasse a rifare la mia, la mia ricerca della forza bruta era troppo lunga per qualificarmi per il bonus sull'ultimo caso di test (era nell'ordine di 69! Iterazioni, che è un numero di 99 cifre .. .).

Non volevo copiare direttamente la soluzione di Beaker, quindi ho deciso di utilizzare la ricottura simulata per la mia ricerca euristica. Sembra più lento del conflitto minimo per questo problema (probabilmente perché è un algoritmo di ottimizzazione piuttosto che un vincolo di soddisfazione del vincolo), ma è ancora ben entro i 10 minuti. Aveva anche il vantaggio di essere abbastanza piccolo, per quanto riguarda il codice. Ho speso molti più byte per trasformare il problema rispetto a quando ho trovato una soluzione.

Spiegazione

La mia soluzione è probabilmente abbastanza inefficiente dal punto di vista dei byte, ma ho avuto problemi a concettualizzare come risolvere il problema così com'è e quindi ho finito per trasformarlo in un problema diverso che era più facile per me capire. Mi sono reso conto che ci sono quattro possibilità per ogni cella sulla griglia:

  • Non aveva germi prima o dopo, il che significa che possiamo ignorarlo
  • Aveva un germe prima ma non dopo, il che significa che dobbiamo trovare una mossa per questo.
  • Non aveva germi prima ma uno dopo, il che significa anche che dobbiamo trovare una mossa per questo.
  • Aveva un germe prima e dopo, il che significa che potremmo dover trovare una mossa per questo, ma poi forse no.

Dopo aver decomposto i dati in quelle classi, sono stato in grado di trasformare ulteriormente il problema. Fu immediatamente ovvio per me che dovevo trovare un modo per fornire un germe dall'insieme "prima ma non dopo" a una cella nell'insieme "dopo ma non prima". Inoltre, i germi possono spostare solo uno spazio, quindi l'unico modo per influenzare le cellule più lontane è "spingere" un percorso ininterrotto di germi in quella cellula. Ciò significa che il problema è diventato trovare X percorsi disgiunti di vertici su un grafico, in cui ogni cellula con un germe era un vertice in detto grafico e i bordi rappresentavano le celle adiacenti.

Ho risolto quel problema che costruendo prima il grafico spiegato sopra. Ho quindi elencato ogni possibile percorso da ciascuna cella in Prima e ogni cella in Dopo, quindi ho assegnato casualmente ogni cella in Prima di uno dei suoi possibili percorsi. Infine, ho usato la ricottura simulata per mutare in modo semi-casuale la soluzione potenziale fino a quando non ho finalmente trovato una soluzione che non ha conflitti per nessuno dei percorsi.

Versione annotata

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.