그 세균은 어디로 갔습니까?


21

소개

당신은 박테리아의 움직임 패턴을 연구하는 생물 학자입니다. 연구팀은 페트리 접시에 많은 것을 가지고 있으며, 당신은 그들의 활동을 기록하고 있습니다. 불행히도, 당신은 심각하게 자금이 부족하여 비디오 카메라를 구입할 수 없으므로 정기적으로 요리 사진을 찍습니다. 당신의 임무는이 사진에서 세균의 움직임을 추적하는 프로그램을 만드는 것입니다.

입력

입력 내용은 페트리 접시의 연속적인 그림을 나타내는 합리적인 형식의 2D 문자 배열입니다. 두 배열 모두에서 문자 .는 빈 공간을 O나타내며 세균을 나타냅니다 (원하는 경우 두 개의 다른 문자를 선택할 수 있음). 또한, "후"어레이는 일부 세균을 4 개의 기본 방향 중 하나에서 한 단계 씩 이동시킴으로써 "전"어레이로부터 획득되며; 특히, 어레이는 동일한 형상을 갖는다. 세균은 동시에 움직이므로 그 중 하나가 다른 세균이 들어있는 공간으로 이동할 수 있습니다. "이전"배열의 경계에는 빈 공간 만 포함되며 세균이 하나 이상 있어야합니다. 따라서 다음은 유효한 입력 쌍입니다.

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

산출

출력은 입력과 동일한 형식의 단일 2D 문자 배열입니다. 이동 >^<v방향에 따라 이동 한 세균을 대체하여 "이전"배열에서 얻습니다 (여기서는 4 개의 다른 문자를 사용할 수도 있습니다). 몇 가지 가능한 결과가있을 수 있지만 그 중 하나만 제공해야합니다. 위의 예에서 가능한 올바른 출력 중 하나는

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

확실히, 세균은 하나 또는 제로 세포 만 움직일 수 있습니다.
Domino의

@JacqueGoupil 네, 맞습니다. 각각은 >^<v각각의 방향으로 정확히 한 단계의 움직임 에 해당합니다.
Zgarb

나는 아직 그것을 해결하려고하지 않았다, 그러나 여기에서 더 테스트 케이스를 구축 할 수있는 도구 :)의 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.

5 년된 Core i5에서 평균 경과 시간은 9 초 1 초 * 미만으로 보너스를받을 수있었습니다.

이 아이디어를 작동시키기 위해 노력하고 있지만 중첩 된 함수를 처리하는 방식에 문제가 있다고 생각합니다. (여기에 참고 용으로 작동하지 않는 이데 손 링크가 있습니다 : http://ideone.com/mQSwgZ ) 이제 이데온
의 코드 가 작동하고 있습니다. 모든 변수를 전역 변수로 강제 실행해야 했으므로 로컬에서 실행할 필요가 없었습니다.

* 골프되지 않은 버전에서 단계 중 하나가 비효율적이라는 메모를 받았으므로 실행 속도를 높일 수 있는지 확인하고 2 바이트를 더 추가하면 실행 시간이 1 초 미만으로 줄어 들었습니다. 코드 및 샘플 출력이 업데이트되었으며 IDE의 입력이 마지막 테스트 사례로 변경되었습니다.


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 초 동안 보너스를받을 수 있습니다.

이것은 첫 번째 코드 골프 제출이므로 아마도 최고의 골프 ​​프로그램이 아닙니다. 그럼에도 불구하고, 그것은 흥미로운 도전이었고 나는 그것을 즐겼습니다. @Beaker는 휴리스틱 기반 검색이 중요하다는 사실을 상기 시켜줄만한 가치가 있습니다. 그가 솔루션을 게시하고 다시 채굴하도록 영감을주기 전에, 마지막 테스트 사례에서 보너스를받을 수없는 무차별 대입 검색이 너무 길었습니다 (99 자리 숫자 인 69! 반복). ).

Beaker의 솔루션을 직접 복사하고 싶지 않았으므로 검색 휴리스틱에 시뮬레이션 어닐링을 사용하기로 결정했습니다. 이 문제는 제약 조건 만족 알고리즘이 아닌 최적화 알고리즘이기 때문에 최소 충돌보다 느리게 보이지만 여전히 10 분 안에 있습니다. 또한 코드 단위로 상당히 작다는 이점이있었습니다. 솔루션을 찾는 것보다 문제를 변환하는 데 더 많은 바이트를 사용했습니다.

설명

내 솔루션은 바이트 단위로 상당히 비효율적이지만 문제를 해결하는 방법을 개념화하는 데 어려움이 있었으므로 이해하기 쉬운 다른 문제로 변환해야했습니다. 그리드의 각 셀에 대해 네 가지 가능성이 있음을 깨달았습니다.

  • 전후에 세균이 없었으므로 무시할 수 있습니다.
  • 그것은 전에 세균이 있었지만 그 후에는 아니 었으므로 우리는 그에 대한 움직임을 찾아야합니다.
  • 그것은 전에 세균이 없었으며, 그 이후에도 세균이 없었습니다.
  • 그것은 전후에 세균을 가지고 있었기 때문에 우리는 그 움직임을 찾아야 할 수도 있지만 다시는 그렇지 않을 수도 있습니다.

데이터를 해당 클래스로 분해 한 후 문제를 더욱 변형시킬 수있었습니다. "전후가 아닌"세트에서 "전후가 아닌"세트의 세포에 세균을 공급할 수있는 방법을 찾아야한다는 것이 즉시 명백했습니다. 또한, 세균은 한 공간 만 움직일 수 있으므로, 더 멀리 떨어진 세포에 영향을 줄 수있는 유일한 방법은 그 세포에 끊어지지 않는 세균 경로를 "푸시"하는 것입니다. 즉, 문제는 그래프에서 X 정점-분리 경로를 찾는 것이되었으며, 여기서 세균을 가진 각 셀은 상기 그래프에서 정점이며 가장자리는 인접한 셀을 나타냅니다.

위에서 설명한 그래프를 먼저 작성하여 문제를 해결했습니다. 그런 다음 Before의 각 셀과 After의 각 셀에서 가능한 모든 경로 를 열거 한 다음 가능한 경로 중 하나 앞에 Before의 각 셀을 임의로 할당했습니다. 마지막으로 시뮬레이션 어닐링을 사용하여 경로에 대해 충돌이없는 솔루션을 찾을 때까지 잠재적 인 솔루션을 반 무작위로 변경했습니다.

주석이 달린 버전

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.