LINUX.ORG.RU

Свертки в haskell

 


0

3

Читаю «Изачай Haskell во имя добра», в качестве доп задания повышенной сложности дается задание реализовать функцию drop с помощью fold*. Помогите с решением. Прервать fold как я понял не возмножно, значит нужно собирать новый список, то есть accumulator должен быть списком. Не могу понять как дропнуть n элементов списка. С помощью рекурсии решить задачу могу.


то есть accumulator должен быть списком

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

quantum-troll ★★★★★
()
Ответ на: комментарий от quantum-troll
mydrop n xs = foldl (\x acc -> if (snd acc) <= n then ([], (snd acc) + 1) else (x : (fst acc), (snd acc))) ([], 0) xs

Выдает ошибку

<interactive>:67:81: error:
    • Occurs check: cannot construct the infinite type: t1 ~ ([t1], b)
      Expected type: [t1]
        Actual type: [([t1], b)]
    • In the expression: x : (fst acc)
      In the expression: (x : (fst acc), (snd acc))
      In the expression:
        if (snd acc) <= n then
            ([], (snd acc) + 1)
        else
            (x : (fst acc), (snd acc))
    • Relevant bindings include
        acc :: ([([t1], b)], b) (bound at <interactive>:67:25)
        x :: ([t1], b) (bound at <interactive>:67:23)
        xs :: t ([([t1], b)], b) (bound at <interactive>:67:10)
        n :: b (bound at <interactive>:67:8)
        mydrop :: b -> t ([([t1], b)], b) -> ([t1], b)
          (bound at <interactive>:67:1)

Почему такой тип acc :: ([([t1], b)], b)?

В ghci код 0 : fst ([1,2,3],0) работает нормально

RA
() автор топика

то есть accumulator должен быть списком

Тебе нужно ещё как-то отслеживать, сколько элементов уже дропнуто, поэтому в качестве аккумулятора должна быть пара <число дропнутых элементов, список>:

import Prelude hiding (drop)

drop :: Int -> [a] -> [a]
drop n = reverse . snd . foldl f (0, [])
  where f (i, acc) x | i >= n    = (i, x : acc)
                     | otherwise = (i + 1, acc)
theNamelessOne ★★★★★
()
Ответ на: комментарий от RA

по логике в ветке else я возвражаю пару первый элемент которого список с добавленным элементом а второй элемент пары это счетчик. Список строю только для первого элемента а тип асс представлен как пара первый элемент список пар...

RA
() автор топика
Ответ на: комментарий от RA

Выдает ошибку

Ты перепутал порядок аргументов в функции, которую принимает foldl. Должно быть <аккумулятор>, <элемент списка>, а у тебя <элемент списка>, <аккумулятор>.

theNamelessOne ★★★★★
()
Ответ на: комментарий от theNamelessOne

Тогда нужно переворачивать исходный список.

Тогда на foldl'. Иначе у тебя thunk вырастет.

P.S. Ну или вообще заменить список на Seq в аккумуляторе. Должно быть быстрее чем reverse.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от RA

if snd fst fst snd

Про паттерн-матчинг не слышал?

drop' :: Int -> [a] -> [a]
drop' n = fst . foldr dropElem ([], n) where
    dropElem y (xs, 0) = (y:xs, 0)
    dropElem _ (_, n) = ([], n - 1)
Esper
()

Можно красиво в аппликативном стиле, с одним проходом. Тут везде частичное применение функций:

drop' :: Int -> [a] -> [a]
drop' n = foldr (\(i, x) -> if i < n then id else (x :)) [] . zip [0 ..]

iVS ★★★★★
()
Ответ на: комментарий от Esper

drop с начала списка дропает, а не с конца, вообще-то.

iVS ★★★★★
()
Ответ на: комментарий от iVS

С foldl - тоже не drop. Как раз из-за накопления thunk. Плюс drop не проходит весь список. Это если уж мы говорим о точном совпадение семантики.

hateyoufeel ★★★★★
()
Последнее исправление: hateyoufeel (всего исправлений: 1)
Ответ на: комментарий от hateyoufeel

Я показал, как делать с foldr. Хотя в вычислении разница незаметна, но надо отметить, что drop, в отличие от foldr, проходит только первые n элементов списка.

iVS ★★★★★
()
Ответ на: комментарий от quantum-troll

Спасибо за совет, я пока не проникся духом haskell, поэтому пишу так топорно))

RA
() автор топика
Ответ на: комментарий от iVS

А можешь описать подробнее как работает? При вызове функции 1 шаг zip преобразует в лист пар [(index, value)]. Лямбда передаваемая foldr частично пременена(к текущему элемену) и возвращает функцию либо id либо (x :) и тут для меня начинается магия... В какой момент происход вызов функции возвращаемой лямбдой?

RA
() автор топика
Последнее исправление: RA (всего исправлений: 1)
Ответ на: комментарий от RA

В какой момент происход вызов функции возвращаемой лямбдой?

В таких терминах семантику свертки понять трудно. Я для себя использую другое объяснение: Проще всего запоминается foldr как право-ассоциативная свертка, для передаваемой функции в foldr исходим из того, что ее правый (второй) аргумент — накопленный результат res, а левый (первый) — текущий элемент списка. Результат вызова функции выдает накопленный результат с учетом данного элемента списка. Что примечательно, foldl (только вместо него нужно использовать не-ленивый foldl') — это лево-ассоциативная свертка, и поэтому результат и элемент списка в ее функции меняются местами (результат слева, то есть первый аргумент функции).

Поэтому мысленно мы можем дополнить аргументы функции:

\(i, x) res -> (if i < n then id else (x :)) res

Применим частичные функции к res:

\(i, x) res -> if i < n then res else (x : res)
Откуда очевидно, что элемент списка добавляется только при i>=n.

iVS ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.