Data.Sequence vs. Data.DList для добавления данных в конец списка - PullRequest
11 голосов
/ 13 июня 2011

Я пишу код, который нужно часто добавлять в конец списка. Я знаю, что использование "++" неэффективно. Так что вместо этого я строю список задом наперед, добавляя к голове, а затем меняю его, когда я закончу. Я понимаю, что это обычная тактика для начинающих.

Я бы предпочел построить его в правильном порядке для начала, но это означает переключение на новую структуру данных. Я рассматриваю возможность использования Data.Sequence или Data.DList для моего контейнера. Мой список состоит из строгих пар int, и мне не нужен произвольный доступ к нему. Каковы относительные достоинства Data.Sequence и Data.DList, и есть ли другие контейнеры, которые я должен рассмотреть?

Ответы [ 2 ]

27 голосов
/ 13 июня 2011

Использовать ли Data.Sequence или DList зависит от того, как вы собираетесь использовать результирующий список.DList замечательно, когда вы строите последовательность, скажем, в вычислении Writer, чтобы преобразовать ее в конец списка и использовать ее.Однако, если вам нужно использовать промежуточные результаты, например, скажем:

f (foo ++ bar)
+ f (foo ++ bar ++ baz)
+ f (foo ++ bar ++ baz ++ quux)

, тогда DList довольно плохо, потому что каждый раз нужно пересчитывать позвоночник.Data.Sequence - лучший выбор в этой ситуации.Data.Sequence также лучше, если вам нужно удалить элементы из последовательности.

Но, возможно, вам даже не нужно принимать это решение.Реверсивные списки в конце вычислений распространены в строгих функциональных языках, таких как ML и Scheme, но не в Haskell.Возьмем, к примеру, эти два способа записи map:

map_i f xs = reverse $ go [] xs
    where
    go accum [] = accum
    go accum (x:xs) = go (f x : accum) xs

map_ii f [] = []
map_ii f (x:xs) = f x : map_ii f xs

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

Хвостовая рекурсия не является святым Граалем эффективного внедрения в Haskell.При создании структуры данных, такой как список, вы действительно хотите быть co -recursive;то есть, сделайте рекурсивный вызов под приложением конструктора (например, f x : map_ii f xs выше).

Так что, если вы обнаружите, что вы переворачиваете после хвостовой рекурсивной функции, посмотрите, можете ли вы интегрировать весь лот вcorecursive функция.

1 голос
/ 29 мая 2016

Я сделал простое сравнение критериев:

let mdata = replicate 1000 (replicate 100 (13 :: Int))
let f1 = foldl' (++) []
let fr = foldr (++) []
let f2 = foldl' (\s next -> s Seq.|> next) mempty
let f3 = foldl' (DL.snoc) mempty

defaultMain [
    bgroup "time encode" [ bench "++ - foldl"   $ nf (AE.encode . f1) mdata
                         , bench "++ - foldr"   $ nf (AE.encode . fr) mdata
                         , bench "Seq |>"  $ nf (AE.encode . toList . f2) mdata
                         ,  bench "DList snoc"  $ nf (AE.encode . DL.toList . f3) mdata]
  ]

С результатом:

benchmarking time encode/++ - foldl
time                 604.1 ms   (570.0 ms .. 654.6 ms)
                     0.999 R²   (NaN R² .. 1.000 R²)
mean                 619.2 ms   (608.9 ms .. 625.6 ms)
std dev              9.761 ms   (0.0 s .. 11.21 ms)
variance introduced by outliers: 19% (moderately inflated)

benchmarking time encode/++ - foldr
time                 2.554 ms   (2.503 ms .. 2.610 ms)
                     0.995 R²   (0.990 R² .. 0.998 R²)
mean                 2.584 ms   (2.547 ms .. 2.628 ms)
std dev              134.1 μs   (111.7 μs .. 186.6 μs)
variance introduced by outliers: 34% (moderately inflated)

benchmarking time encode/Seq |>
time                 2.020 ms   (1.986 ms .. 2.049 ms)
                     0.997 R²   (0.995 R² .. 0.998 R²)
mean                 2.106 ms   (2.078 ms .. 2.138 ms)
std dev              105.8 μs   (85.60 μs .. 130.5 μs)
variance introduced by outliers: 34% (moderately inflated)

benchmarking time encode/DList snoc
time                 1.992 ms   (1.952 ms .. 2.034 ms)
                     0.996 R²   (0.994 R² .. 0.998 R²)
mean                 2.030 ms   (2.000 ms .. 2.060 ms)
std dev              97.88 μs   (82.60 μs .. 122.3 μs)
variance introduced by outliers: 34% (moderately inflated)

Вывод: используйте Data.Sequence. Он обладает наибольшей гибкостью и в то же время просто на несколько более низкую производительность, чем DList - вероятно, оно того не стоит.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...