Как вы определяете карту и фильтр, используя foldr в Haskell?
Я занимаюсь самостоятельным изучением функциональных языков (в настоящее время использую Haskell). Я столкнулся с назначением на основе Haskell, которое требует определения карты и фильтра в терминах foldr. Для жизни меня я не совсем понимаю, как это сделать.
Например, когда я определяю функцию отображения, например:
map' :: (a -> b) -> [a] -> [b]
map' f [] = []
map' f (x:xs) = foldr (\x xs -> (f x):xs) [] xs
Я не знаю, почему первый элемент списка всегда игнорируется. Это означает, что:
map' (*2) [1,2,3,4]
приводит к [4,6,8] вместо [2,4,6,8]
Аналогично, моя функция фильтра:
filter' :: (a -> Bool) -> [a] -> [a]
filter' p [] = []
filter' p (x:xs) = foldr (\x xs -> if p x then x:xs else xs ) [] xs
при запуске как:
filter' even [2,3,4,5,6]
приводит к [4,6] вместо [2,4,6]
Почему это так? И как СЛЕДУЕТ Я определил эти функции, чтобы получить ожидаемые результаты? Я предполагаю, что что-то не так с моими лямбда-выражениями...
Ответы
Ответ 1
Я бы хотел просто прокомментировать, но, увы, мне не хватает кармы.
Другие ответы - все хорошие, но я думаю, что самая большая путаница, по-видимому, связана с вашим использованием x и xs.
Если вы переписали его как
map' :: (a -> b) -> [a] -> [b]
map' f [] = []
map' f (x:xs) = foldr (\y ys -> (f y):ys) [] xs
вы ясно увидите, что x
даже не упоминается с правой стороны, поэтому никак не может быть в решении.
Приветствия
Ответ 2
Для вашего первого вопроса foldr
уже имеет случай для пустого списка, поэтому вам не нужно и не следует указывать случай для него на вашей собственной карте.
map' f = foldr (\x xs -> f x : xs) []
То же самое верно для filter'
filter' p = foldr (\x xs -> if p x then x : xs else xs) []
В ваших лямбда-выражениях нет ничего плохого, но что-то не так с вашими определениями filter'
и map'
. В случае cons (x: xs) вы едите голову (x
), а затем передаете хвост на foldr
. Функция foldr
никогда не сможет увидеть первый элемент, который вы уже съели.:)
Обратите внимание, что:
filter' p = foldr (\x xs -> if p x then x : xs else xs) []
эквивалентен (η-эквивалент):
filter' p xs = foldr (\x xs -> if p x then x : xs else xs) [] xs
Ответ 3
В ваших определениях вы выполняете сопоставление образцов для x:xs
, что означает, что когда ваш аргумент [1,2,3,4]
, x
привязан к 1
и xs
привязан к остальной части списка: [2,3,4]
.
То, что вы не должны делать, это просто выбросить часть x:
. Затем ваш foldr
будет работать над полным списком.
Итак, ваши определения должны выглядеть следующим образом:
map' :: (a -> b) -> [a] -> [b]
map' f [] = []
map' f xs = foldr (\x xs -> (f x):xs) [] xs
и
filter' :: (a -> Bool) -> [a] -> [a]
filter' p [] = []
filter' p xs = foldr (\x xs -> if p x then x:xs else xs ) [] xs
Ответ 4
Я бы определил карту с помощью foldr и состав функций следующим образом:
map :: (a -> b) -> [a] -> [b]
map f = foldr ((:).f) []
И для случая фильтра:
filter :: (a -> Bool) -> [a] -> [a]
filter p = foldr (\x xs -> if p x then x:xs else xs) []
Обратите внимание, что нет необходимости передавать список при определении функций над списками с помощью foldr или foldl.
Проблема с вашим решением заключается в том, что вы удаляете головку списка, а затем применяете карту по списку и
поэтому глава списка отсутствует, когда отображается результат.
Ответ 5
Другой способ думать об этом - foldr существует, потому что часто используется следующий рекурсивный шаблон:
-- Example 1: Sum up numbers
summa :: Num a => [a] -> a
summa [] = 0
summa (x:xs) = x + suma xs
Взятие произведения чисел или даже обращение вспять списка выглядит структурно очень похожим на предыдущую рекурсивную функцию:
-- Example 2: Reverse numbers
reverso :: [a] -> [a]
reverso [] = []
reverso (x:xs) = x `op` reverso xs
where
op = (\curr acc -> acc ++ [curr])
Структура в приведенных выше примерах отличается только от начального значения (0
для summa и []
для reverseo) вместе с оператором между первым значением и рекурсивным вызовом (+
для summa и (\q qs -> qs ++ [q])
для reverseo). Таким образом, функциональную структуру для приведенных выше примеров можно рассматривать как
-- Generic function structure
foo :: (a -> [a] -> [a]) -> [a] -> [a] -> [a]
foo op init_val [] = init_val
foo op init_val (x:xs) = x `op` foo op init_val xs
Чтобы увидеть, что это "общее" foo работает, мы могли бы переписать reverseo с помощью foo и передать ему оператор, начальное значение и сам список:
-- Test: reverso using foo
foo (\curr acc -> acc ++ [curr]) [] [1,2,3,4]
Давайте дадим foo более общую подпись типа, чтобы она работала и для других проблем:
foo :: (a -> b -> b) -> b -> [a] -> b
Теперь, возвращаясь к вашему вопросу, мы могли бы написать фильтр следующим образом:
-- Example 3: filter
filtero :: (a -> Bool) -> [a] -> [a]
filtero p [] = []
filtero p (x:xs) = x `filterLogic` (filtero p xs)
where
filterLogic = (\curr acc -> if (p curr) then curr:acc else acc)
Это снова имеет очень похожую структуру для суммирования и реверсирования. Следовательно, мы должны иметь возможность использовать foo для его перезаписи. Скажем, мы хотим отфильтровать четные числа из списка [1,2,3,4]. Затем снова мы передаем оператор foo (в данном случае filterLogic
), начальное значение и сам список. filterLogic
в этом примере принимает функцию p
, называемую предикатом, которую мы должны определить для вызова:
let p = even in foo (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4]
foo в Haskell называется foldr. Итак, мы переписали фильтр с помощью foldr.
let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4]
Итак, фильтр можно записать с помощью foldr, как мы видели:
-- Solution 1: filter using foldr
filtero' :: (a -> Bool) -> [a] -> [a]
filtero' p xs = foldr (\curr acc -> if (p curr) then curr:acc else acc) [] xs
Что касается map, мы могли бы также записать его как
-- Example 4: map
mapo :: (a -> b) -> [a] -> [b]
mapo f [] = []
mapo f (x:xs) = x `op` (mapo f xs)
where
op = (\curr acc -> (f curr) : acc)
поэтому его можно переписать с помощью foldr. Например, чтобы умножить каждое число в списке на два:
let f = (* 2) in foldr (\curr acc -> (f curr) : acc) [] [1,2,3,4]
Итак, map может быть написана с помощью foldr, как мы видели:
-- Solution 2: map using foldr
mapo' :: (a -> b) -> [a] -> [b]
mapo' f xs = foldr (\curr acc -> (f curr) : acc) [] xs
Ответ 6
Ваше решение почти работает.)
Проблема в том, что у вас есть две разные привязки для x в обеих ваших функциях (внутри шаблона шаблона и внутри вашего выражения лямбда), поэтому вы теряете трек первого элемента.
map' :: (a -> b) -> [a] -> [b]
map' f [] = []
map' f (x:xs) = foldr (\x xs -> (f x):xs) [] (x:xs)
filter' :: (a -> Bool) -> [a] -> [a]
filter' p [] = []
filter' p (x:xs) = foldr (\x xs -> if p x then x:xs else xs ) [] (x:xs)
Это должно к трюку:). Кроме того: вы можете легко писать свои функции в стиле pointfree.
Ответ 7
Я новичок в Haskell (на самом деле я нашел эту страницу с тем же вопросом), но это мое понимание списков и foldr:
- - это элементы, которые связаны с следующим элементом с помощью оператора cons
(:)
. они заканчиваются пустым списком []
. (подумайте об этом как о двоичном операторе, как добавление (+)
1+2+3+4 = 10
, 1:2:3:4:[] = [1,2,3,4]
- Функция foldr принимает функцию, которая принимает два параметра. это заменит оператор cons, который определит, как каждый элемент привязан к следующему.
- он также принимает значение терминала для операции, которое может быть жестким как начальное значение, которое будет присвоено пустым спискам. для cons это пустой список
[]
. если вы свяжете пустой список с любым списком, результатом будет сам список. поэтому для функции sum
это 0
. для функции умножения это 1
и т.д.
- и он принимает сам список
Итак, мое решение таково:
filter' p = foldr (\x n -> if p x then x : n else n) []
выражение лямбда - это наша функция связи, которая будет использоваться вместо оператора cons (:)
. Пустым списком является наше значение по умолчанию для пустого списка. Если предикат выполняется, мы ссылаемся на следующий элемент, используя (:)
как обычно, иначе мы просто не связываемся вообще.
map' f = foldr (\x n -> f x : n) []
здесь мы привязываем f x
к следующему элементу вместо просто x
, который просто дублирует список.
Также обратите внимание, что вам не нужно использовать сопоставление шаблонов, поскольку мы уже говорим foldr, что делать в случае пустого списка.
Я знаю, что этот вопрос действительно старый, но я просто хотел ответить на него в любом случае. Надеюсь, это не противоречит правилам.