Давайте думать о обычной структуре reverse
:
reverse = rev [] where
rev acc [] = acc
rev acc (x : xs) = rev (x : acc) xs
Он начинается с пустым списком и галсами на элементы из передней части списка аргументов, пока это не сделано. Мы хотим сделать что-то подобное, за исключением того, что мы хотим привязать элементы к передней части части списка, которую мы не отменяем. Как мы можем это сделать, когда у нас нет , что еще не восстановленная часть?
Самый простой способ, которым я могу думать, чтобы избежать перемещения фронта списка два раза использовать лени:
reverseFirst :: Int -> [a] -> [a]
reverseFirst k xs = dis where
(dis, dat) = rf dat k xs
rf acc 0 ys = (acc, ys)
rf acc n [] = (acc, [])
rf acc n (y : ys) = rf (y : acc) (n - 1) ys
dat
представляет часть списка, который остался один. Мы вычисляем его в той же вспомогательной функции rf
, которая делает реверсирование, но мы также передаем ее на rf
при первоначальном вызове. Это никогда не рассматривается в rf
, поэтому все просто работает. Глядя на сгенерированное ядро (с использованием ghc -O2 -ddump-simpl -dsuppress-all -dno-suppress-type-signatures
), мы предполагаем, что пары скомпилированы в незашифрованные пары, а Int
s распакованы, поэтому все должно быть достаточно эффективным.
Профилирование предполагает, что эта реализация составляет около 1,3 раза быстрее, чем в списке разностных один, и выделяет около 65% больше памяти.
Код выглядит хорошо для меня. Я думаю, настоящий вопрос: зачем вы это делаете? – MathematicalOrchid
Я хотел бы решить проблему [fannkuch-redux] (https://benchmarksgame.alioth.debian.org/u64q/fannkuchredux-description.html#fannkuchredux). Нынешнее решение медленное и уродливое. – TomTom
Правильно. Итак, это контрольный показатель, чтобы узнать, как быстро вы можете обменять материал, а не создавать фактический полезный результат ... – MathematicalOrchid