2010-02-09 2 views
19

Я искал измененную (сбалансированную) таблицу деревьев/карт/хэшей в Haskell или способ имитировать ее внутри функции. То есть когда я вызываю одну и ту же функцию несколько раз, структура сохраняется. Пока я пробовал Data.HashTable (это нормально, но несколько медленнее) и попробовал Data.Array.Judy, но мне не удалось заставить его работать с GHC 6.10.4. Есть ли другие варианты?Haskell mutable map/tree

ответ

13

Если вы хотите изменяемое состояние, вы можете иметь его. Просто продолжайте передавать обновленную карту или держите ее в государственной монаде (которая оказывается той же самой).

import qualified Data.Map as Map 
import Control.Monad.ST 
import Data.STRef 
memoize :: Ord k => (k -> ST s a) -> ST s (k -> ST s a) 
memoize f = do 
    mc <- newSTRef Map.empty 
    return $ \k -> do 
     c <- readSTRef mc 
     case Map.lookup k c of 
      Just a -> return a 
      Nothing -> do a <- f k 
          writeSTRef mc (Map.insert k a c) >> return a 

Вы можете использовать это так. (На практике, вы можете захотеть добавить способ очистить элементы из кэша тоже.)

import Control.Monad 
main :: IO() 
main = do 
    fib <- stToIO $ fixST $ \fib -> memoize $ \n -> 
     if n < 2 then return n else liftM2 (+) (fib (n-1)) (fib (n-2)) 
    mapM_ (print <=< stToIO . fib) [1..10000] 

На свой страх и риск, вы можете ненадежно побег из требования резьб состояния через все, что нуждается в этом.

import System.IO.Unsafe 
unsafeMemoize :: Ord k => (k -> a) -> k -> a 
unsafeMemoize f = unsafePerformIO $ do 
    f' <- stToIO $ memoize $ return . f 
    return $ unsafePerformIO . stToIO . f' 

fib :: Integer -> Integer 
fib = unsafeMemoize $ \n -> if n < 2 then n else fib (n-1) + fib (n-2) 

main :: IO() 
main = mapM_ (print . fib) [1..1000] 
+0

Я до сих пор не понимаю, как работает эта вещь :) Но я сделал примерно то же самое с IO monad и IOref. Удивительно, но это было довольно быстро, но это было не быстрее, чем вычисление дистанционных гониометрических функций :) По крайней мере, я узнал некоторые Haskell. – ondra

5

Несмотря на то, что вы запрашиваете изменяемый тип, позвольте мне предложить вам использовать неизменяемую структуру данных и передавать последовательные версии вашим функциям в качестве аргумента.

, в отношении которых структура данных для использования,

Проблема заключается в том, что я не могу использовать (или не знаю как) использовать не изменяемый тип.

Если вам повезет, вы можете передать структуру данных таблицы в качестве дополнительного параметра для каждой функции, которая в ней нуждается. Если, однако, ваша таблица должна быть широко распространена, вы можете использовать state monad, где состояние - это содержимое вашей таблицы.

Если вы пытаетесь memoize, вы можете попробовать некоторые ленивые трюки memoization из блога Conal Elliott, но как только вы выйдете за пределы целочисленных аргументов, ленивая memoization становится очень мутной — не то, что я бы рекомендовал вам попробовать как начинающий. Может быть, вы можете задать вопрос о более широкой проблеме, которую вы пытаетесь решить? Часто с Haskell и изменчивостью проблема заключается в том, как содержать мутацию или обновления в какой-то области.

Непросто научиться программировать без каких-либо глобальных изменяемых переменных.

+0

Проблема в том, что я не могу использовать (или не знаю, как) использовать непеременный тип. Я пытаюсь создать функцию «кэширования», и до сих пор различные решения были довольно плохими. Я попробовал подход HashTable, описанный здесь: http://stackoverflow.com/questions/2217289/haskell-caching-results-of-a-function Я понятия не имею, как писать то же самое с не изменяемыми структурами данных. – ondra

+0

@ondra: Я попытался добавить некоторые рекомендации в свой ответ, но это действительно поможет узнать больше об этой проблеме. Я видел ваш другой вопрос, и воспоминание с помощью клавиш с плавающей запятой может быть мучительно болезненным. Если вы можете больше сказать о более широком контексте проблемы, вы можете получить более полезную помощь. –

+0

Я все еще пытаюсь решить то же самое. Я изменил проблему, так что я знаю уникальный Int32 для каждого «объекта», действующего как параметр функции. Это позволяет мне кэшировать вещи достаточно эффективно, но я не уверен, разрешает ли я использовать memoization для Ints. Проблема, которую я пытаюсь решить, заключается в следующем: проблема оптимизации, которая работает на сфере. Я вычисляю расстояния между точками - я могу «лениво вычислить» некоторые гониометрические операции, но не все. Только около 5% вычислений уникальны, поэтому я мог бы сэкономить много времени, если бы мог кэшировать расстояния между точками. – ondra

8

Основываясь на ответе @ Ramsey, я также предлагаю вам восстановить свою функцию, чтобы взять карту и вернуть измененную. Затем код с использованием хорошего ol 'Data.Map, который довольно эффективен при модификациях. Вот картина:

import qualified Data.Map as Map 

-- | takes input and a map, and returns a result and a modified map 
myFunc :: a -> Map.Map k v -> (r, Map.Map k v) 
myFunc a m = … -- put your function here 

-- | run myFunc over a list of inputs, gathering the outputs 
mapFuncWithMap :: [a] -> Map.Map k v -> ([r], Map.Map k v) 
mapFuncWithMap as m0 = foldr step ([], m0) as 
    where step a (rs, m) = let (r, m') = myFunc a m in (r:rs, m') 
    -- this starts with an initial map, uses successive versions of the map 
    -- on each iteration, and returns a tuple of the results, and the final map 

-- | run myFunc over a list of inputs, gathering the outputs 
mapFunc :: [a] -> [r] 
mapFunc as = fst $ mapFuncWithMap as Map.empty 
    -- same as above, but starts with an empty map, and ignores the final map 

Легко абстрагировать этот шаблон и сделать mapFuncWithMap родовым над функциями, которые используют карты таким образом.

+3

Perfect! +1 И обратите внимание, что тип функции« Map.Map kv -> (r, Map.Map kv) 'эквивалентен монаде состояния! С типом' MonadState (Map.Map kv) r' из 'Control.Monad.State'. –

+0

Я использую функцию оптимизации, которая работает над самим деревом .. У меня много «точек», и если бы у меня была изменчивая структура, я мог бы прикрепить ее к каждой точке, что было бы более эффективно, чем все точки в одном огромном дереве. Полученная Карта имела бы около 500 000 точек, а отдельные привязанный к каждой точке, будет иметь только около 100-1000. Я мог бы попробовать этот подход, чтобы увидеть, улучшает ли он скорость. – ondra

0

Если я правильно прочитал ваши комментарии, у вас есть структура с возможными значениями ~ 500 тыс. Для вычисления. Вычисления дорогостоящие, поэтому вы хотите, чтобы они выполнялись только один раз, а при последующем доступе вы просто хотите получить значение без пересчета.

В этом случае используйте ленту Haskell в ваших интересах! ~ 500k не так велик: просто создайте карту всех ответов, а затем при необходимости извлеките. Первая выборка заставит вычисление, последующие выборки одного и того же ответа будут повторно использовать один и тот же результат, и если вы никогда не получите конкретное вычисление - это никогда не произойдет!

Вы можете найти небольшую реализацию этой идеи с использованием трехмерных точечных расстояний в качестве вычисления в файле PointCloud.hs.Этот файл используется Debug.Trace для входа, когда вычисление фактически будет сделано:

> ghc --make PointCloud.hs 
[1 of 1] Compiling Main    (PointCloud.hs, PointCloud.o) 
Linking PointCloud ... 

> ./PointCloud 
(1,2) 
(<calc (1,2)>) 
Just 1.0 
(1,2) 
Just 1.0 
(1,5) 
(<calc (1,5)>) 
Just 1.0 
(1,2) 
Just 1.0 
+0

Мне нужно ~ 500K вычислений, однако домен составляет около 3 миллиардов, и я не знаю, какой из 500K I – ondra

+0

Хорошо, возможно, просто слишком большой для большинства системы, чтобы атаковать его таким образом. Ничего страшного, было весело разрабатывать код для PointCloud.hs в любом случае. Спасибо за интригующую проблему! – MtnViewMark

1

Есть ли другие варианты?

Изменчивая ссылка на чисто функциональный словарь, такой как Data.Map.