Mầm đó đi đâu?


21

Giới thiệu

Bạn là một nhà sinh vật học nghiên cứu các mô hình di chuyển của vi khuẩn. Nhóm nghiên cứu của bạn có một loạt chúng trong đĩa petri và bạn đang ghi lại hoạt động của chúng. Thật không may, bạn đang bị thiếu hụt nghiêm trọng và không thể mua được máy quay video, vì vậy bạn chỉ cần chụp ảnh món ăn đều đặn. Nhiệm vụ của bạn là tạo ra một chương trình theo dõi chuyển động của vi trùng từ những bức ảnh này.

Đầu vào

Đầu vào của bạn là hai mảng ký tự 2D ở bất kỳ định dạng hợp lý nào, thể hiện các hình ảnh liên tiếp của đĩa petri. Trong cả hai mảng, ký tự .đại diện cho không gian trống và Ođại diện cho một mầm (bạn có thể chọn bất kỳ hai ký tự riêng biệt nào nếu muốn). Ngoài ra, mảng "sau" được lấy từ mảng "trước" bằng cách di chuyển một số vi trùng một bước theo một trong bốn hướng chính; đặc biệt, các mảng có hình dạng giống nhau. Các vi trùng di chuyển đồng thời, vì vậy một trong số chúng có thể di chuyển đến một không gian đã chứa vi trùng khác, nếu nó di chuyển ra khỏi đường đi. Nó được đảm bảo rằng các đường viền của mảng "trước" chỉ chứa các khoảng trống và có ít nhất một mầm. Do đó, sau đây là một cặp đầu vào hợp lệ:

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

Đầu ra

Đầu ra của bạn là một mảng các ký tự 2D có cùng định dạng với các đầu vào. Nó được lấy từ mảng "trước" bằng cách thay thế những vi trùng đã di chuyển bằng một trong số đó >^<v, tùy thuộc vào hướng di chuyển (bạn cũng có thể sử dụng bất kỳ 4 ký tự riêng biệt nào ở đây). Có thể có một số đầu ra có thể, nhưng bạn sẽ chỉ cung cấp một trong số chúng. Trong ví dụ trên, một đầu ra đúng có thể là

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

Di chuyển không cần thiết được cho phép trong đầu ra và vi trùng có thể trao đổi địa điểm, do đó, sau đây cũng hợp lệ:

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

Quy tắc và tính điểm

Bạn có thể viết một chương trình đầy đủ hoặc một chức năng. Số byte thấp nhất sẽ thắng và các sơ hở tiêu chuẩn không được phép.

Tôi quan tâm đến các thuật toán tương đối hiệu quả, nhưng tôi không muốn cấm hoàn toàn vũ phu. Vì lý do này, có phần thưởng -75% cho việc giải quyết trường hợp thử nghiệm cuối cùng trong vòng 10 phút trên CPU hiện đại (Tôi không thể kiểm tra hầu hết các giải pháp, vì vậy tôi sẽ chỉ tin tưởng bạn ở đây). Tuyên bố miễn trừ trách nhiệm: Tôi biết rằng có một thuật toán nhanh tồn tại (tìm kiếm "vấn đề đường dẫn rời rạc"), nhưng tôi đã không tự mình thực hiện nó.

Các trường hợp kiểm tra bổ sung

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

Chỉ cần chắc chắn, vi trùng chỉ có thể di chuyển bởi một hoặc không có tế bào, phải không?
Domino

@JacqueGoupil Vâng, điều đó đúng. Mỗi bước >^<vtương ứng với một chuyển động của chính xác một bước theo hướng tương ứng.
Zgarb

Tôi chưa thử giải quyết nó, nhưng đây là một công cụ để xây dựng nhiều trường hợp thử nghiệm hơn :) jsfiddle.net/xd2xns64/embedded/result
Domino

Ồ, cẩn thận, có khả năng tập lệnh sẽ lặp lại mãi mãi nếu nó cố gắng di chuyển tất cả các ô dựa vào một cạnh nhưng sau đó các ô cạnh không có nơi nào để đi.
Domino

Câu trả lời:


3

Octave, 494 496 byte - Phần thưởng 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

Vẫn còn rất nhiều việc phải chơi golf trong câu trả lời này, nhưng tôi muốn nhận được lời giải thích vô căn cứ.

Tôi thấy đây là một vấn đề thỏa mãn ràng buộc, vì vậy tôi đã đi với tìm kiếm địa phương yêu thích của mình, xung đột tối thiểu . Ý tưởng là, được đặt một vị trí bắt đầu với mỗi vi trùng ở một điểm đến có thể tiếp cận, chọn một mầm ngẫu nhiên chiếm cùng một ô đích với một hoặc nhiều vi trùng khác và di chuyển nó đến một ô hợp lệ có tối thiểu các vi trùng khác ở đó. Lặp lại khi cần thiết cho đến khi vị trí phù hợp với mục tiêu.

Thật thú vị, thuật toán này không được đảm bảo để chấm dứt (nếu mục tiêu không thể truy cập được, chẳng hạn, nó sẽ tiếp tục vô thời hạn), nhưng nếu nó chấm dứt, nó được đảm bảo để tạo ra một giải pháp hợp lệ.

Đây là mã:

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

Đầu ra cho trường hợp thử nghiệm cuối cùng:

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

Thời gian trôi qua trung bình là dưới 9 giây 1 giây * trên Core i5 5 tuổi, đủ điều kiện nhận thưởng.

Tôi đang cố gắng để điều này hoạt động trên ideone, nhưng tôi có những gì tôi tin là có vấn đề với cách xử lý các chức năng lồng nhau. (Đây là liên kết ideone không hoạt động để tham khảo: http://ideone.com/mQSwgZ )
Mã trên ideone hiện đang hoạt động. Tôi đã buộc tất cả các biến thành toàn cầu, điều không cần thiết khi chạy nó cục bộ.

* Tôi đã có một lưu ý trong phiên bản chưa được chỉnh sửa của mình rằng một trong các bước là không hiệu quả, vì vậy tôi đã thử để xem liệu tôi có thể tăng tốc độ thực thi hay không và với 2 byte được thêm vào, thời gian thực hiện giảm xuống dưới một giây. Mã và đầu ra mẫu đã được cập nhật và đầu vào trên ideone đã được thay đổi thành trường hợp thử nghiệm cuối cùng.


3

Python, 1171 byte - Phần thưởng 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

Liên kết Ideone: http://ideone.com/0Ylmwq

Trung bình mất từ ​​1 đến 8 giây trong trường hợp thử nghiệm trung bình, đủ điều kiện nhận thưởng.

Đây là lần gửi golf đầu tiên của tôi, vì vậy đây có lẽ không phải là chương trình chơi golf tốt nhất ngoài kia. Tuy nhiên, đó là một thử thách thú vị và tôi khá thích nó. @Beaker xứng đáng được đề cập để nhắc nhở tôi rằng các tìm kiếm dựa trên heuristic là một điều. Trước khi anh ấy đăng giải pháp của mình và truyền cảm hứng cho tôi để làm lại của tôi, tìm kiếm sức mạnh vũ phu của tôi đã quá lâu để đủ điều kiện nhận phần thưởng trong trường hợp thử nghiệm cuối cùng (theo thứ tự 69! Lặp lại, đó là một số có 99 chữ số .. .).

Tôi không muốn sao chép thẳng giải pháp của Beaker, vì vậy tôi quyết định sử dụng mô phỏng ủ cho tìm kiếm heuristic của mình. Nó có vẻ chậm hơn so với xung đột nhỏ đối với vấn đề này (có thể là vì đó là thuật toán tối ưu hóa chứ không phải là một sự thỏa mãn ràng buộc), nhưng nó vẫn còn trong vòng 10 phút. Nó cũng có lợi ích là khá nhỏ, khôn ngoan. Tôi đã dành nhiều byte hơn để chuyển đổi vấn đề so với việc tìm giải pháp cho nó.

Giải trình

Giải pháp của tôi có lẽ khá kém hiệu quả về byte, nhưng tôi gặp khó khăn trong việc khái niệm làm thế nào để giải quyết vấn đề và vì vậy cuối cùng tôi phải chuyển nó thành một vấn đề khác dễ hiểu hơn. Tôi nhận ra rằng có bốn khả năng cho mỗi ô trên lưới:

  • Nó không có mầm bệnh trước hoặc sau, có nghĩa là chúng ta có thể bỏ qua nó
  • Nó đã có mầm bệnh trước nhưng không phải sau đó, điều đó có nghĩa là chúng ta phải tìm một động thái cho nó.
  • Nó không có mầm trước nhưng một sau, điều đó cũng có nghĩa là chúng ta phải tìm một động thái cho nó.
  • Nó có mầm bệnh trước và sau, điều đó có nghĩa là chúng ta có thể phải tìm một động thái cho nó, nhưng sau đó có thể không.

Sau khi phân tách dữ liệu vào các lớp đó, tôi có thể chuyển đổi vấn đề hơn nữa. Rõ ràng ngay lập tức với tôi rằng tôi phải tìm cách cung cấp mầm bệnh từ "trước nhưng không sau" được đặt vào một ô trong bộ "sau nhưng không trước". Hơn nữa, vi trùng chỉ có thể di chuyển một không gian, vì vậy cách duy nhất để chúng ảnh hưởng đến các tế bào ở xa hơn là "đẩy" một con đường vi trùng không bị phá vỡ vào tế bào đó. Điều đó có nghĩa là vấn đề đã trở thành việc tìm các đường phân tách đỉnh X trên biểu đồ, trong đó mỗi ô có mầm là một đỉnh trong biểu đồ đã nói và các cạnh biểu thị các ô liền kề.

Tôi đã giải quyết vấn đề đó bằng cách đầu tiên xây dựng biểu đồ giải thích ở trên. Sau đó, tôi liệt kê mọi đường dẫn có thể từ mỗi ô trong Trước và từng ô trong Sau, sau đó gán ngẫu nhiên từng ô trong Trước một trong các đường dẫn có thể của nó. Cuối cùng, tôi đã sử dụng mô phỏng ủ để đột biến bán ngẫu nhiên giải pháp tiềm năng cho đến khi cuối cùng tôi tìm thấy một giải pháp không có xung đột cho bất kỳ con đường nào.

Phiên bản chú thích

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.