Program untuk membangun labirin tikus

15

Anda telah dipekerjakan sebagai asisten peneliti, dan diminta untuk membuat program kecil yang akan membangun labirin tikus. Kotak tikus selalu 62x22 dan memiliki pintu masuk (a) dan keluar (A) untuk tikus, seperti ini (input 1):

#######a######################################################
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#################################################A############

Program Anda harus mengisi kotak dengan blok (#) meninggalkan jalur untuk tikus, seperti ini (keluaran 1):

#######a######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
#######                                           ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
################################################# ############
#################################################A############

Ini mudah menurut Anda! Anda mulai menulis sebuah program kecil, penuh percaya diri. Namun, Ilmuwan Prinsip telah memiliki ide baru - dia ingin dua tikus untuk menavigasi labirin pada saat yang sama. Dr Rattanshnorter menjelaskan bahwa mereka memiliki pintu dan pintu keluar yang berbeda (input 2):

#b#####a######################################################
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            #
#                                                            B
#                                                            #
#################################################A############

Tikus telah dilatih untuk bergerak lurus melalui persimpangan tetapi T-persimpangan membuat mereka bingung dan akan membatalkan percobaan. Anda memulai tugas baru Anda yang lebih kompleks ketika Dokter yang baik menjelaskan satu syarat terakhir: tikus itu ganas satu sama lain sehingga jika mereka melihat satu sama lain pada titik mana pun, perkelahian tikus akan pecah dan Anda berdua akan berada di hadapan dewan etika. Anda sekarang menyadari program Anda harus menampilkan sesuatu yang membingungkan seperti ini (keluaran 2):

#b#####a######################################################
# ##### ######################################################
# ##### ######################################################
# ##### #######################################           ####
# ##### ####################################### ######### ####
# #####                                           ####### ####
# ############################################# # ####### ####
# ############################################# # ####### ####
# ############################################# # ####### ####
# ############################################# # ####### ####
#                                               # ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# ####### ####
################################################# #######    B
################################################# ############
#################################################A############

Pada saat tikus B mencapai persimpangan, tikus A akan melakukan perjalanan menyusuri koridor untuk keluar dari A dan pertarungan tikus akan dihindari.

Aturan:

  • Program Anda harus membaca (STDIN atau file) input seperti yang di atas, dan output (STDOUT atau file) data yang sama kecuali banyak ruang sekarang akan hash (#). Anda dapat mengganti karakter tunggal (seperti ;) daripada \ndalam string input tetapi string output masih membutuhkan \nkarakter. DIPERBARUI

  • Jalur tikus harus memiliki lebar satu karakter, kecuali untuk persimpangan silang (setiap ruang harus memiliki nol atau dua #karakter yang berdekatan secara orthogonal ). Setiap tikus harus memiliki jalur tunggal yang jelas, kecuali untuk persimpangan silang. Tidak ada pertigaan.

  • Tikus dilepaskan secara bersamaan dan bergerak dengan kecepatan konstan. Tidak boleh ada waktu dua atau lebih tikus melihat satu sama lain (berada di kolom atau baris yang sama tanpa salah satu #karakter di antaranya).

  • Jika tidak ada solusi yang mungkin (seperti titik masuk yang berdekatan), cetak Impossible\ndan keluar.

  • Pintu masuk dan keluar bisa berada di sisi mana saja, namun mereka tidak akan pernah ada di sudut.

  • Jika pintu masuk dan keluar yang cocok berdekatan (misalnya:) ##aA##, tikus tidak dapat langsung pergi dari ake A. Harus ada bagian koridor 2 ruang kecil di dalam area labirin.

  • Pada belokan di mana tikus mencapai titik keluarnya (atau kapan saja setelah itu), ia tidak lagi terlihat oleh tikus lain.

  • Program Anda dapat dirancang untuk menghitung labirin untuk 1, 2, hingga 26 tikus.

  • Celah standar tidak diijinkan.

Skor:

Dengan solusi Anda, tentukan berapa banyak tikus per labirin (N) yang dapat dipecahkan oleh program Anda. Skor Anda adalah panjang kode Anda dalam byte dibagi dengan angka ini N.

Harap sertakan contoh keluaran dalam jawaban Anda sehingga kami dapat melihat apa yang dihasilkan oleh program Anda.

Ksatria Logika
sumber
Apakah satu-satunya perbedaan dalam input yang mungkin adalah lokasi a, A, b, B?
xnor
Untuk versi 2 tikus, ya. Jika program Anda dirancang hingga 3 tikus, Anda harus mengatasi semua lokasi yang mungkin dari a, b, c, A, B, C.
Logic Knight
Apakah persimpangan T diperbolehkan jika tikus hanya akan berjalan di samping bagian horizontal T?
orlp
Tidak, tikus ini mudah bingung. Hanya jalan lurus, tikungan siku, dan jalan lintas yang diizinkan.
Logic Knight
@CarpetPython Bisakah pintu masuk / keluar berada di sepanjang tepi labirin? Bisakah mereka berdekatan?
orlp

Jawaban:

2

Haskell, 26 tikus ?, ~ 5000 byte

Secara teoritis, kode ini harus bekerja untuk sejumlah tikus, tetapi saya tidak memberikan jaminan bahwa itu akan berakhir sebelum kematian panas alam semesta. Ini didasarkan pada algoritma backtracking yang mencoba untuk pergi jalan lurus dulu, dan kemudian beralih jalur jika jalan tidak bekerja. Jumlah alternatif bersifat eksponensial berkenaan dengan panjang jalan dan jumlah tikus.

Saya belum repot-repot bermain golf, karena ini sangat besar, dan karena saya ingin membuatnya lebih cepat terlebih dahulu.

{-# LANGUAGE FlexibleContexts #-}
module Main (main) where

import Control.Lens
import Control.Monad
import Data.Char
import Data.Function
import Data.List
import Data.Maybe

type Pos = (Int,Int)
type Path = [Pos]
type Maze = String
type Input = [(Pos,Char)]
type MazeState = [(Path,Path)]

type ChoiceMonad a = [a]


instance (Num a, Num b) => Num (a,b) where
  (x,y)+(x',y')=(x+x',y+y')
  (x,y)-(x',y')=(x-x',y-y')
  fromInteger n = (fromInteger n,fromInteger n)


parseMaze :: Maze -> Input
parseMaze maze = maze ^@.. inner . filtered (`notElem` "# ")

inner :: IndexedTraversal' Pos Maze Char
inner = lined <.> traversed

main :: IO ()
main = do
    maze <- readFile "Sample2.in"
    putStrLn $ solveAndShow maze

fillMaze :: Maze -> Maze
fillMaze = inner.filtered(==' ').~'#'

updateMaze :: Path -> Maze -> Maze
updateMaze path = inner.indices (`elem` path).filtered(=='#') .~ ' '

isDone :: MazeState -> Bool
isDone = all (null . snd)

showMaze :: Maze -> MazeState -> Maze
showMaze maze path = updateMaze (fst =<< path) $ fillMaze maze

showSolution :: Maze -> ChoiceMonad MazeState -> String
showSolution _    []    = "Impossible"
showSolution maze (x:_) = showMaze maze x


stopCondition :: ChoiceMonad MazeState ->  Bool
stopCondition x = not $ null x || isDone (head x)

solveAndShow :: Maze -> String
solveAndShow maze = showSolution maze . solve $ mazeToState maze

solve :: ChoiceMonad MazeState -> ChoiceMonad MazeState
solve = fromJust . find (not.stopCondition) . iterate fullStep

mazeToState :: Maze -> ChoiceMonad MazeState
mazeToState maze = do
    let startsEnds = paths $ parseMaze maze
        return $ startsEnds & traverse.both %~ (:[])


fullStep :: ChoiceMonad MazeState -> ChoiceMonad MazeState
fullStep = (>>= stepAll)

stepAll :: MazeState -> ChoiceMonad MazeState
stepAll input = do
    pths <- mapM goStep input
    guard $ iall (checkVisible pths) $ map fst pths
    return $ pths
  where
    goStep :: (Path,Path) -> ChoiceMonad (Path,Path)
    goStep (curr:rest,[]) = return (curr:curr:rest,[])
    goStep (curr:these,end:ends)
       | distance curr end == 1 = return (end:curr:these,ends)

       | curr == end = goStep (curr:these,ends)
    goStep (path,end) = do
      next <- twoSteps (head end) path
      prev <- twoSteps next end
      return $ (next:path,prev:end)
    inMaze = inMazeWith input

    twoSteps :: Pos -> Path -> ChoiceMonad Pos
    twoSteps goal path = do
      next <- oneStep goal path inMaze
      guard $ not.null $ oneStep goal (next:path) (\x -> x==next || inMaze x)
      return next

checkVisible :: MazeState -> Int -> Path -> Bool
checkVisible _    _ [] = True
checkVisible pths i xs@(x:_) = checkBack && checkNow
  where
    nBack = 1 + visibleBackwards xs
    --checkBack = none (any (==x).take nBack .fst) pths
    checkBack = hasn't (folded.indices (/=i)._1.taking nBack folded.filtered (==x)) pths
    checkNow  = inone (\i' (x':_,_) -> (i/=i') && (==x') `any` take nBack xs ) pths

-- How long have you stayed on a line
visibleBackwards :: Path -> Int
visibleBackwards as = length . takeWhile ((==headDiff as) .headDiff). filter ((>=2).length) $ tails as
      where headDiff (a:a1:_) = a-a1
            headDiff x        = error $ "Bug: Too short list " ++ show x


inMazeWith :: [(Path, Path)] -> Pos -> Bool
inMazeWith = flip elem . concatMap (\x->snd x ++ fst x)

oneStep :: MonadPlus m => Pos -> Path -> (Pos -> Bool)  -> m Pos
oneStep end (curr:prev:_) inMaze =
  if distance curr end <= 1
     then return end
     else do
    let distance' :: Pos -> Double
        distance' x = fromIntegral (distance x end) + if curr - prev == x - curr then 0 else 0.4
    next <- msum . map return $ sortBy (compare`on`distance') $ neighbors curr

    -- Don't go back
    guard $ next /= prev

    -- Stay in bounds
    guard $ isInBounds next

    let dir = (next - curr)
    let lr = neighbors next \\ [curr,next+dir,end]

    -- If next is blocked, check that the one after that is free
    if inMaze next
      then do
        guard $ not . (\x->(x/=end)&&inMaze x) $ next + dir
        -- Both sides should be blocked as well
        guard $ (==2). length . filter inMaze $ lr
      else do
        -- No neighbors if empty
        guard $ null . filter inMaze $ lr

    -- All neighbors of 'curr', including 'next'
    let neigh' = filter (\i -> inMaze i || i == next) $ neighbors curr
        -- should be an even number
        guard $ even $ length neigh'

    return next
oneStep _ [start] _ = return $ inBounds start
oneStep _ _ _ = error "Too short path given"


toBounds :: (Num a, Eq a) => (a,a) -> a -> a
toBounds (low, high) x
    | x == low  = x + 1
    | x == high = x - 1
    | otherwise = x

distance :: Pos -> Pos -> Int
distance (x1,y1) (x2,y2) = abs(x1-x2)+abs(y1-y2)

-- Moves a pos to the closest one inside the bounds
inBounds :: Pos -> Pos
inBounds = bimap (toBounds (0,21)) (toBounds (0,61))

isInBounds :: Pos -> Bool
isInBounds x = x == inBounds x

neighbors :: Pos -> [Pos]
neighbors pos = [ pos & l %~ p| l <- [_1,_2], p <- [succ,pred]]

paths :: Input -> [(Pos,Pos)]
paths pos = flip unfoldr 'a' $ \x ->
  do (y,_) <- find ((==x).snd) pos
     (z,_) <- find ((==toUpper x).snd) pos
     return ((y,z),succ x)

Output sampel, 6 tikus:

##c###B#####b#######C#######F######################f##########
##   #       #       #######                        ##########
####  ######## ###############################################
#####          ###############################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
##############################################################
#############       ##########################################
############# #####  #########################################
D             #    #     #####################################
##############  ## ##### #####################################
#########      #                 #############################
######### ###### # ##### ####### #############################
####      #      #     # #######                        ######
####E######a##########e#d##############################A######
Hjulle
sumber
2
Kapan bsampai ke persimpangan edan b, apakah dia tidak terlihat oleh e? btampaknya sampai di sana t = 11, yang akan etetap di koridor itu. Apakah saya melewatkan sesuatu?
BrainSteel
@ TrainSteel Ya, itu benar. Jawaban saya tidak valid. Saya mencatat pada diri saya sendiri sebelumnya bahwa saya perlu memeriksa tabrakan "mundur dalam waktu" juga (setelah melintasi jalur tikus lain), tetapi untuk beberapa alasan saya memutuskan bahwa itu tidak diperlukan. : P
Hjulle
@ Trainrain Saya yakin saya telah memperbaiki bug itu sekarang.
Hjulle
1

Haskell, 1 tikus, 681 Karakter

Masalahnya bisa diselesaikan secara sepele untuk semua labirin hanya dengan satu tikus. Kode ini juga "berfungsi" untuk sejumlah tikus, tetapi tidak mengikuti kendala apa pun pada interaksi antara banyak tikus dan lintasan.

module Main where
import Control.Lens
import Data.List(unfoldr,find)
import Data.Char(toUpper)
parse=(^@..lined<.>folded.filtered(`notElem`"# "))
main=interact$do i<-(naive=<<).rats.parse;(lined<.>traversed).filtered(==' ').indices (`notElem`i).~'#'
    naive(start,(ex,ey))=start':unfoldr go start' where
     start'=bnds start
     (ex',ey')=bnds(ex,ey)
     go(x,y)
      |(x,y)==(ex',ey')=Nothing
      |x== ex'=ok(x,y`t`ey')
      |otherwise=ok(x`t`ex',y)
     ok z=Just(z,z)
     t x y=if x>y then x-1 else x+1
    bnd(l,h)x |x==l=x+1 |x==h=x-1 |True=x
    bnds=bimap(bnd(0,21))(bnd(0,61))
    rats pos=(`unfoldr`'a')$ \x->
  do (y,_)<-find((==x).snd)pos
     (z,_)<-find((==toUpper x).snd)pos
     return((y,z),succ x)

Output sampel:

#######a######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
####### ######################################################
#######                                           ############
#################################################A############

Saya berencana untuk mendukung banyak tikus, jadi saya menulis kode generik, tetapi saya belum menemukan algoritma yang bagus untuk itu.

  • parse mengekstrak daftar semua pintu masuk dan keluar, dengan koordinatnya
  • rats mengambil daftar itu dan mengonversinya menjadi pasangan koordinat untuk setiap tikus.
  • bnds mengambil koordinat di tepi dan memindahkannya ke koordinat terdekat di dalam labirin.
  • naive mengambil posisi awal dan akhir dan mengembalikan jalur sederhana di antara mereka.
  • main lalu ganti semua ruang putih yang tidak ada di jalur dengan '#'
Hjulle
sumber
@ edc65 "... kendala di antara banyak tikus". Ini adalah jawaban untuk hanya 1 tikus, yang diizinkan sesuai dengan pertanyaan.
Hjulle
OK salahku. Hanya berpikir bahwa untuk 1 tikus itu tantangan yang berbeda. Saya akan menghapus komentar saya sebelumnya
edc65