Який хороший спосіб впоратися із завданнями, які потребують масивів за допомогою Haskell?


11

Часто для завдання потрібні реальні масиви. Візьмемо для прикладу завдання реалізувати Befunge або> <>. Я намагався використовувати Arrayмодуль для цього, але це справді громіздко, оскільки відчувається, що я кодую занадто багатослівний. Чи може хто-небудь допомогти мені, як вирішити такі завдання з кодом-гольфу, які є менш детальними та більш функціональними?


AFAIK, цей сайт призначений лише для коду для гольфу, а не для пов'язаних питань. Я здогадуюсь, що це належить до Stackoverflow.
Джессі Мілікан

@Jesse Millikan: Будь ласка, подивіться також це питання та прочитайте надіслати запитання. У ньому не зазначається, що вам не дозволяють задавати будь-які питання про те, як грати в гольф. Цей тип питань також був важливою частиною питання "на тему" на етапі визначення цього веб-сайту. Будь ласка, двічі подумайте про свій голос і видаліть його, якщо ви розумієте, чому я прошу це тут.
FUZxxl

Хм, я поганий, мабуть.
Джессі Мілікан

@Jesse Millikan: Errare humanum est.
FUZxxl

Хоча поширені запитання не дуже зрозумілі.
Джессі Мілікан

Відповіді:


5

Перш за все, я рекомендую переглянути Data.Vector , приємнішу альтернативу Data.Array в деяких випадках.

Arrayі Vectorідеально підходять для деяких випадків запам'ятовування, як показано у моїй відповіді на "Пошук максимальних шляхів" . Однак деякі проблеми просто непросто виразити у функціональному стилі. Наприклад, проблема 28 в проекті Euler закликає підсумовувати числа по діагоналях спіралі. Зрозуміло, знайти формулу для цих чисел слід досить просто, але побудувати спіраль складніше.

Data.Array.ST забезпечує змінний тип масиву. Однак ситуація типу - це безлад: він використовує клас MArray для перевантаження кожного з своїх методів, за винятком runSTArray . Отже, якщо ви не плануєте повертати незмінний масив із дії, що змінюється, масив, вам доведеться додати один або кілька підписів типу:

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

Тим не менш, моє рішення для Euler 28 вийшло досить чудово, і він не потребував цього підпису, тому що я використовував runSTArray.

Використання Data.Map як "змінного масиву"

Якщо ви хочете реалізувати алгоритм змінного масиву, ще одним варіантом є використання Data.Map . Коли ви використовуєте масив, ви хочете, щоб у вас була така функція, яка змінює один елемент масиву:

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

На жаль, для цього знадобиться скопіювати весь масив, якщо тільки реалізація не використовувала стратегію копіювання на запис, щоб уникнути цього, коли це можливо.

Гарна новина полягає в тому , Data.Mapмає таку функцію, вставка :

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

Оскільки Mapреалізовано внутрішньо як збалансоване бінарне дерево, воно insertзаймає лише O (log n) час та простір та зберігає оригінальну копію. Отже, Mapне тільки надається дещо ефективний "мутаційний масив", сумісний з функціональною моделлю програмування, але він навіть дозволяє вам "повернутися в минуле", якщо так вам подобається.

Ось рішення для Euler 28 за допомогою Data.Map:

{-# 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

Шаблони вибуху запобігають переповненню стека, викликаному елементами акумулятора (курсором, номером та картою), які не використовуються до самого кінця. Для більшості кодових гольфів вхідні випадки не повинні бути такими великими, щоб потребувати цього положення.


9

Відповідь glib така: Не використовуйте масиви. Відповідь не дуже гліб така: Спробуйте переосмислити свою проблему, щоб вона не потребувала масивів.

Часто проблему з деякою думкою можна зробити без будь-якого масиву, як структура. Наприклад, ось моя відповідь на Ейлера 28:

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

Тут висловлюється кодовий зразок послідовності чисел, коли вони ростуть навколо прямокутної спіралі. Не було потреби насправді представляти матрицю чисел.

Ключ до роздумів за межами масивів - це думати про те, що насправді існує проблема, а не про те, як ти можеш представити її як байти в оперативній пам'яті. Це просто пов'язане з практикою (можливо, з тієї причини, що я так сильно кодую гольф!)

Ще один приклад - як я вирішив знайти максимальні шляхи код-гольф. Там метод передачі парціальних розчинів у вигляді хвилі через матрицю, рядок за рядком, виражається безпосередньо операцією складання. Пам'ятайте: на більшості процесорів ви не можете одночасно мати справу з масивом у цілому: програмі доведеться працювати через нього з часом. Може не знадобитися весь масив одразу в будь-який час.

Звичайно, деякі проблеми формулюються таким чином, що вони по своїй суті базуються на масиві. Такі мови, як> <>, Befunge або Brainfuck, мають основу масивів. Однак навіть там масиви часто не обходяться. Наприклад, дивіться моє рішення інтерпретації Brainfuck , справжньою основою його семантики є блискавка . Щоб почати мислити таким чином, зосередьтеся на шаблонах доступу та структурі, що наближається до значення проблеми. Часто це не потрібно примушувати до змінного масиву.

Коли все інше не вдається, і вам потрібно використовувати масив - @ Joey поради - це гарний початок.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.