その胚芽はどこに行きましたか?


21

前書き

あなたは細菌の移動パターンを研究している生物学者です。あなたの研究チームはペトリ皿にそれらの束を持っており、あなたは彼らの活動を記録しています。残念ながら、あなたは深刻な資金不足で、ビデオカメラを買う余裕がないので、定期的に皿の写真を撮るだけです。あなたの仕事は、これらの写真から細菌の動きを追跡するプログラムを作成することです。

入力

入力は、適切な形式の2つの2Dキャラクター配列で、シャーレの連続写真を表します。両方の配列で、文字.は空のスペースをO表し、胚芽を表します(必要に応じて、任意の2つの異なる文字を選択できます)。また、 "after"配列は、 "before"配列から、いくつかの細菌を4つの基本方向の1つに1ステップ移動することによって取得されます。特に、配列の形状は同じです。細菌は同時に移動するため、邪魔にならない場合は、別の細菌が既に含まれているスペースに移動する可能性があります。"before"配列の境界には空のスペースのみが含まれ、少なくとも1つの胚芽があることが保証されています。したがって、次は有効な入力のペアです。

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

出力

出力は、入力と同じ形式の文字の単一の2D配列です。これは>^<v、移動の方向に応じて、移動した胚芽をのいずれかで置き換えることにより、「前の」配列から取得されます(ここでは4つの異なる文字を使用することもできます)。いくつかの出力が考えられますが、そのうちの1つだけを指定する必要があります。上記の例では、1つの可能な正しい出力は

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

不必要な移動は出力で許可され、細菌は場所を入れ替えることができるため、以下も有効です。

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

ルールとスコアリング

完全なプログラムまたは関数を作成できます。最小のバイトカウントが優先され、標準の抜け穴は許可されません。

比較的効率的なアルゴリズムに興味がありますが、ブルートフォースを完全に禁止したくありません。このため、最新のCPUで10分以内に最後のテストケースを解くと、-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..
..............................

確かに、細菌は1個または0個のセルだけしか移動できません。
ドミノ

@JacqueGoupilはい、それは正しいです。それぞれは>^<v、それぞれの方向に正確に1ステップの動きに対応します。
ズガルブ

私はまだそれを解決しようとしませんでしたが、より多くのテストケースを作成するツールがあります:) jsfiddle.net/xd2xns64/embedded/result-
ドミノ

ああ、注意してください。スクリプトがエッジに対してすべてのセルを移動しようとしても、エッジセルに行き先がない場合、スクリプトが永久にループする可能性があります。
ドミノ

回答:


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-conflictsを使用しました。アイデアは、到達可能な目的地に各胚芽がある開始配置を与えられ、1つ以上の他の胚芽と同じ目的地セルを占めるランダムな胚芽を選択し、すでに他の胚芽が最小の有効なセルに移動します。配置が目標に一致するまで、必要に応じて繰り返します。

興味深いことに、このアルゴリズムは終了することが保証されていません(目標に到達できない場合、たとえば無期限に継続します)が、終了する場合、有効なソリューションを生成することが保証されます。

コードは次のとおりです。

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.

平均経過時間は、5年前のCore i5で9秒 1秒* 未満で、ボーナスの対象となりました。

私はこれをideoneで動作させようとしていますが、ネストされた関数を処理する方法で問題をスコープしていると信じているものがあります。(参照用の機能しないideoneリンクは次のとおりです。http : //ideone.com/mQSwgZ ideone
のコードは現在機能しています。すべての変数をグローバルに強制する必要がありましたが、ローカルで実行する必要はありませんでした。

*私は自分のステップの1つが非効率的であることに注意していたので、実行を高速化できるかどうかを確認しようとし、2バイト追加すると実行時間が1秒未満になりました。コードとサンプルの出力が更新され、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

Ideoneリンク:http : //ideone.com/0Ylmwq

前回のテストケースで平均1〜8秒かかり、ボーナスの対象となります。

これは私の最初のコードゴルフの提出であるため、おそらく最高のゴルフプログラムではありません。それにもかかわらず、それは興味深い挑戦であり、私はそれをとても楽しんだ。@Beakerは、ヒューリスティックベースの検索が重要であることを思い出させてくれます。彼が解決策を投稿して私のやり直しに触発される前、私の総当たり攻撃は長すぎて最後のテストケースのボーナスの資格を得るには長すぎました(69桁の反復で、99桁の数字です)。 。)。

Beakerのソリューションを真っ直ぐにコピーしたくなかったので、検索ヒューリスティックにシミュレーテッドアニーリングを使用することにしました。この問題の場合、min-conflictよりも遅いようですが(おそらく制約充足アルゴリズムではなく最適化アルゴリズムであるため)、それでも10分以内で十分です。また、コードがかなり小さいという利点もありました。問題の変換に、解決策を見つけるよりも多くのバイトを費やしました。

説明

私のソリューションはおそらくバイト単位ではかなり非効率ですが、問題をそのまま解決する方法を概念化するのに苦労したため、理解しやすい別の問題に変換する必要がありました。グリッド上の各セルには4つの可能性があることに気付きました。

  • 前後に胚芽がなかったため、無視できます
  • それは前ではなく後の胚を持っていたので、そのための動きを見つけなければなりません。
  • それには前に胚がありましたが、後には胚がありませんでした。
  • それは前後に胚を持っていたので、私たちはそれのために動きを見つけなければならないかもしれませんが、それから再び多分そうではないかもしれません。

データをこれらのクラスに分解した後、問題をさらに変換することができました。「前ではなく前」セットから「後ではなく前」セットのセルに胚芽を供給する方法を見つけなければならなかったことはすぐに明らかでした。さらに、細菌は1つのスペースしか移動できないため、細菌がさらに離れた細胞に影響を与える唯一の方法は、その細胞に細菌の切れ目のない経路を「押し込む」ことです。つまり、問題はグラフ上でX個の頂点独立パスを見つけることになり、その場合、生殖を持つ各セルはグラフの頂点であり、エッジは隣接するセルを表していました。

最初に上記のグラフを作成することで、この問題を解決しました。次に、Beforeの各セルとAfterの各セルから可能なすべてのパスを列挙し、Beforeの各セルをランダムに割り当てて、その可能なパスの1つを割り当てました。最後に、シミュレートされたアニーリングを使用して、最終的に潜在的なソリューションを半ランダムに変更し、最終的にどのパスにも競合のないソリューションを見つけました。

注釈付きバージョン

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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.