Haskell kullanarak diziler gerektiren görevlerle başa çıkmanın iyi bir yolu nedir?


11

Genellikle bir görev gerçek diziler gerektirir. Örneğin Befunge veya> <> uygulama görevini alın. Bunun için Arraymodülü kullanmaya çalıştım , ama çok fazla kodlama yaptığımı hissettiğim için gerçekten hantal. Herkes bana böyle kod golf görevleri daha az ayrıntılı ve daha işlevsel çözmek için yardımcı olabilir misiniz?


AFAIK, bu site sadece kod golf için değil, ilgili sorular için değil. Bunun Stackoverflow'a ait olduğunu tahmin ediyorum.
Jesse Millikan

@Jesse Millikan: Lütfen bu soruyu da görün ve sss'i okuyun. Golf nasıl oynanacağı hakkında herhangi bir soru sormanıza izin verilmediğini belirtmez. Bu tür sorular aynı zamanda bu sitenin tanımlama aşamasında "konu ile ilgili" sorunun büyük bir parçasıydı. Lütfen bu soruyu iki kere düşünün ve neden burada sorduğumu anlıyorsanız kaldırın.
FUZxxl

Hmm, sanırım kötüyüm.
Jesse Millikan

@Jesse Millikan: Errare humanum est.
FUZxxl

SSS bu konuda çok açık değil.
Jesse Millikan

Yanıtlar:


5

Her şeyden önce, ben bakarak tavsiye Data.Vector , daha hoş bir alternatif Data.Array bazı durumlarda.

Arrayve "Maksimum yolları bulma"Vector yanıtımda gösterildiği gibi bazı not dosyaları için idealdir . Bununla birlikte, bazı problemlerin işlevsel bir tarzda ifade edilmesi kolay değildir. Örneğin, Problem 28 yılında Proje Euler spiral köşegenlerinin numaralar toplayarak gerektirir. Elbette, bu sayılar için bir formül bulmak oldukça kolay olmalı, ancak spiralin inşa edilmesi daha zordur.

Data.Array.ST değiştirilebilir bir dizi türü sağlar. Ancak, tip durum bir felaket: Bir kullanır sınıf Marray süre dışında yöntemlerin her birini aşırı runSTArray . Dolayısıyla, değiştirilebilir bir dizi işleminden değiştirilemez bir dizi döndürmeyi planlamıyorsanız, bir veya daha fazla tür imza eklemeniz gerekir:

import Control.Monad.ST
import Data.Array.ST

foo :: Int -> [Int]
foo n = runST $ do
    a <- newArray (1,n) 123 :: ST s (STArray s Int Int) -- this type signature is required
    sequence [readArray a i | i <- [1..n]]

main = print $ foo 5

Yine de, Euler 28'e çözümüm oldukça güzel çıktı ve kullandığım için bu tip imza gerektirmedi runSTArray.

Data.Map'i "değiştirilebilir bir dizi" olarak kullanma

Değişken bir dizi algoritması uygulamak istiyorsanız, başka bir seçenek Data.Map kullanmaktır . Bir dizi kullandığınızda, bir dizinin tek bir öğesini değiştiren böyle bir işleve sahip olmanız dileğiyle:

writeArray :: Ix i => i -> e -> Array i e -> Array i e

Maalesef, uygulama mümkün olduğunda bunu önlemek için yazma üzerine kopyalama stratejisi kullanmadığı sürece, tüm dizinin kopyalanmasını gerektirir.

İyi haber şu, Data.Mapböyle bir işlevi var, ekle :

insert :: Ord k => k -> a -> Map k a -> Map k a

Çünkü Mapdengeli bir ikili ağacı olarak dahili olarak uygulanan, insertsadece orijinal kopyası O (log n) zaman ve mekân ve korur sürer. Bu nedenle, Mapsadece fonksiyonel programlama modeliyle uyumlu, biraz verimli bir "değişebilir dizi" sağlamakla kalmaz, aynı zamanda lütfen "zamanda geriye gitmenizi" sağlar.

İşte Data.Map kullanarak Euler 28 için bir çözüm:

{-# LANGUAGE BangPatterns #-}

import Data.Map hiding (map)
import Data.List (intercalate, foldl')

data Spiral = Spiral Int (Map (Int,Int) Int)

build :: Int -> [(Int,Int)] -> Map (Int,Int) Int
build size = snd . foldl' move ((start,start,1), empty) where
    start = (size-1) `div` 2
    move ((!x,!y,!n), !m) (dx,dy) = ((x+dx,y+dy,n+1), insert (x,y) n m)

spiral :: Int -> Spiral
spiral size
    | size < 1  = error "spiral: size < 1"
    | otherwise = Spiral size (build size moves) where
        right   = (1,0)
        down    = (0,1)
        left    = (-1,0)
        up      = (0,-1)
        over n  = replicate n up ++ replicate (n+1) right
        under n = replicate n down ++ replicate (n+1) left
        moves   = concat $ take size $ zipWith ($) (cycle [over, under]) [0..]

spiralSize :: Spiral -> Int
spiralSize (Spiral s m) = s

printSpiral :: Spiral -> IO ()
printSpiral (Spiral s m) = do
    let items = [[m ! (i,j) | j <- [0..s-1]] | i <- [0..s-1]]
    mapM_ (putStrLn . intercalate "\t" . map show) items

sumDiagonals :: Spiral -> Int
sumDiagonals (Spiral s m) =
    let total = sum [m ! (i,i) + m ! (s-i-1, i) | i <- [0..s-1]]
     in total-1 -- subtract 1 to undo counting the middle twice

main = print $ sumDiagonals $ spiral 1001

Patlama düzenleri, akümülatör öğelerinin (imleç, sayı ve Harita) sonuna kadar kullanılmamasından kaynaklanan bir yığın taşmasını önler. Çoğu kod golfü için, giriş durumları bu hükme ihtiyaç duyacak kadar büyük olmamalıdır.


9

Glib yanıtı: Dizileri kullanma. Not-so-glib cevabı: Probleminizi dizilere ihtiyaç duymayacak şekilde yeniden düşünmeye çalışın.

Çoğu zaman, bazı düşünce ile ilgili bir sorun, herhangi bir yapı gibi herhangi bir dizi olmadan yapılabilir. Örneğin, işte Euler 28'e cevabım:

-- | What is the sum of both diagonals in a 1001 by 1001 spiral?
euler28 = spiralDiagonalSum 1001

spiralDiagonalSum n
    | n < 0 || even n = error "spiralDiagonalSum needs a positive, odd number"
    | otherwise = sum $ scanl (+) 1 $ concatMap (replicate 4) [2,4..n]

Buradaki kodda ifade edilen şey, dikdörtgen sarmalın etrafında büyüdükçe sayı dizisinin paternidir. Aslında sayı matrisini temsil etmeye gerek yoktu.

Dizilerin ötesinde düşünmenin anahtarı, RAM'deki bayt olarak nasıl temsil edebileceğinizi değil, eldeki sorunun gerçekte ne anlama geldiğini düşünmektir. Bu sadece pratik ile geliyor (belki de bu kadar çok kod golf neden!)

Başka bir örnek nasıl kod golf bulma maksimum yolları çözdü . Burada, kısmi çözeltilerin bir satır olarak matriste bir dalga olarak geçirilmesi yöntemi doğrudan katlama işlemi ile ifade edilir. Unutmayın: Çoğu CPU'da, diziyi bir kerede bir bütün olarak ele alamazsınız: Program zaman içinde onunla çalışmak zorunda kalacak. Tüm diziye aynı anda ihtiyaç duymayabilir.

Tabii ki, bazı problemler doğal olarak dizi tabanlı olacak şekilde ifade edilir. > <>, Befunge veya Brainfuck gibi dillerin kalbinde diziler vardır. Bununla birlikte, orada bile, diziler genellikle dağıtılabilir. Örneğin, anlambiliminin gerçek çekirdeği Brainfuck'u yorumlama çözümüme bakın , bir fermuar . Bu şekilde düşünmeye başlamak için, erişim kalıplarına ve sorunun anlamına daha yakın olan yapıya odaklanın. Genellikle bunun değiştirilebilir bir diziye zorlanması gerekmez.

Her şey başarısız olduğunda ve bir dizi kullanmanız gerektiğinde - @ Joey'nin ipuçları iyi bir başlangıçtır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.