Вот тот, который делает это за один проход:
pairs :: [a] -> [[a]]
pairs xs = fst (go xs xs) where
go (x:xs) (_:_:ys) = f x (go xs ys)
go (x:xs) [_] = ([[x]],xs)
go xs [] = ([],xs)
f x (xs,y:ys) = ([x,y]:xs,ys)
Как это работает?Давайте рассмотрим первые два аргумента go first и, в частности, эту строку:
go (x:xs) (_:_:ys) = f x (go xs ys)
Оба эти аргумента находятся в одном и том же списке (xs
), но мы берем 2 элемента из одного,и только один от другого.Зачем?Итак, мы знаем, когда мы достигли середины.Посмотрите на эту функцию для сравнения:
halfway xs = go xs xs
where
go (_:xs) (_:_:ys) = go xs ys
go xs _ = xs
>>> halfway [1..6]
[4,5,6]
Теперь, как только мы дойдем до середины, нам нужно будет "сжать" ее с другим списком.Но это должно быть наоборот!как нам это сделать?Удобный способ перевернуть любую функцию за один проход - сначала написать ее в виде сгиба.Вот zip, записанный в виде сгиба:
zip = foldr (\x k (y:ys) -> (x,y) : k ys) (const [])
Чтобы «перевернуть» его, вы просто применяете его как foldl
, а не как foldr
(вы также должны перевернуть крышку).
Для нашего использования мы в основном строим базу по ходу (в форме k
).Так что ни одна наша функция не выглядит так:
pairs :: [a] -> [[a]]
pairs xs = go xs xs (const []) where
go (y:ys) (_:_:zs) k = go ys zs (f y k)
go (y:ys) [_] k = [y] : k ys
go ys [] k = k ys
f x k (y:ys) = [x,y] : k ys -- same `f` as from `zip`
Есть еще одна проблема: список возвращается в неправильном порядке.Чтобы исправить это, мы заменим список на список различий и поменяем порядок добавлений.
Наконец, мы снимаем CPS с функции и получаем выше.