Infinite self-referencing list

蓝咒 提交于 2019-12-10 18:46:57

问题


Problem

I'm trying to implement the modified Dragon Curve from AoC Day 16 as an infinite list in Haskell.

The list is composed of True and False. We start with some list s0:

  • s1 = s0 ++ [False] ++ (map not . reverse) s0
  • s2 = s1 ++ [False] ++ (map not . reverse) s1
  • s3 = s2 ++ [False] ++ (map not . reverse) s2

Generally

sn = s(n-1) ++ [0] ++ (map not . reverse) s(n-1) 
   = s0 ++ [0] ++ (f s0) ++ [0] ++ (f (s0 ++ [0] ++ (f s0))) ++ ...
      where f = (map not . reverse)

Attempted Implementation

I can get sn quite easily using the iterate function.

modifiedDragonCurve :: [Bool] -> Int -> [Bool]
modifiedDragonCurve s n = (iterate f s)!!n
    where f s     = s ++ [False] ++ (map not . reverse) s 

This gives me a list [s0, s1, s2, ...]. However, since s(n-1) is a prefix of sn this could be built as an infinite list, but i cannot figure out how to approach it. I think I need something along the lines of

modifiedDragonCurve :: [Bool] -> [Bool]
modifiedDragonCurve s = s ++ [False] ++ (map not . reverse) listSoFar

But cannot figure out how to refer to the already generated list (listSoFar).

Any suggestions would be greatly appreciated.


回答1:


I played with this myself while solving the AoC problem, too. I found a remarkable solution that does not require reverse, hence is more memory-friendly and speedy than the other solutions listed here. It's also beautiful! The dragon curve itself is a nice short two-liner:

merge (x:xs) ys = x:merge ys xs
dragon = merge (cycle [False, True]) dragon

It can be extended to use a "seed" as the AoC problem demands just by alternating between the seed and the bits of the true dragon curve:

infinite bs = go bs (map not (reverse bs)) dragon where
    go bs bs' (d:ds) = bs ++ [d] ++ go bs' bs ds

(This does call reverse once -- but unlike other solutions, it is called just once on a chunk of data about the size of the input, and not repeatedly on chunks of data about as large as the part of the list you consume.) Some timings to justify my claims; all versions used to produce 2^25 elements with an empty seed, compiled with ghc -O2, and timed with /usr/bin/time.

freestyle's solution takes 11.64s, ~1.8Gb max resident
David Fletcher's solution takes 10.71s, ~2Gb max resident
luqui's solution takes 9.93s, ~1GB max resident
my solution takes 8.87s, ~760MB max resident

The full test program was

main = mapM_ print . take (2^25) . dragon $ []

with dragon replaced by each implementation in turn. A carefully crafted consumer can lower memory usage even further: my best solution to the second-star problem so far runs in 5Mb real residency (i.e. including all the space GHC allocated from the OS for its multiple generations, slack space, and other RTS overhead), 60Kb GHC-reported residency (i.e. just the space used by not-yet-GC'd objects, regardless of how much space GHC has allocated from the OS).

For raw speed, though, you can't beat an unboxed mutable vector of Bool: a coworker reports that his program using such ran in 0.2s, using about 35Mb memory to store the complete expanded (but not infinite!) vector.




回答2:


Here's one way. We make a list not of the s0, s1 etc but of only the new part at each step, then we can just concat them together.

dragonCurve :: [Bool]
dragonCurve = concatMap f [0..]
  where
    f n = False : (map not . reverse) (take (2^n-1) dragonCurve)

(This assumes s0 = []. If it can be something else you'll have to modify the length calculation.)

I can't think of a nice way to both be self-referential and not deal with prefix lengths. Here's a non-self-referencing solution, still using the idea of making a list of non-overlapping parts.

dragonCurve' :: [Bool]
dragonCurve' = concat (unfoldr f [])
  where
    f soFar = Just (part, soFar ++ part)
      where
        part = False : (map not . reverse) soFar



回答3:


You totally nerd sniped me with this. It's not a self-referencing list, but I did manage to come up with a "wasteless" solution -- one where we're not dropping or forgetting about anything we've computed.

dragon :: [Bool] -> [Bool]
dragon = \s -> s ++ gen [] s
    where
    inv = map not . reverse
    gen a b =
        let b' = inv b
            r = b' ++ a
        in False:r ++ gen r (False:r)

gen a b accepts as input all the data of the current sequence, such that the current sequence is inv a ++ b. Then we generate the remainder in r, output it and recursively continue generating the remainder. I accept a inverted because then all I need to do is prepend b' at each step (which does not even examine a), and we don't need to reverse more than we have to.

Being nerdsniped I investigated quite a number of other data structures, imagining that a linked list is probably not the best fit for this problem, including DList, Data.Sequence (finger trees), free monoid (which ought to be good at being reversed), and a custom tree that cancels reverses. To my surprise, list still performed the best of all of these and I'm still bewildered by that. In case you are curious, here is my code.




回答4:


For example:

dragon s0 = s0 ++ concatMap ((False :) . inv) seq
  where
    inv = map not . reverse
    seq = iterate (\s -> s ++ False : inv s) s0 


来源:https://stackoverflow.com/questions/41183330/infinite-self-referencing-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!