Как бы вы определили карту и фильтр, используя foldr в Haskell? - PullRequest
15 голосов
/ 20 апреля 2011

Я немного изучаю функциональные языки (в настоящее время использую 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]

Почему это так? И как я должен определить эти функции, чтобы получить ожидаемые результаты? Я предполагаю, что что-то не так с моими лямбда-выражениями ...

Ответы [ 7 ]

43 голосов
/ 20 апреля 2011

Хотел бы я просто прокомментировать, но, увы, мне не хватает кармы.

Все остальные ответы хорошие, но я думаю, что самая большая путаница, по-видимому, связана с использованием вами х и х.

Если вы переписали его как

map'            :: (a -> b) -> [a] -> [b]
map' f []       = []
map' f (x:xs)   = foldr (\y ys -> (f y):ys) [] xs

вы бы ясно увидели, что x даже не упоминается с правой стороны, так что это никак не может быть в решении.

Приветствия

20 голосов
/ 20 апреля 2011

Для вашего первого вопроса, 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'. В случае с минусами (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
4 голосов
/ 03 сентября 2012

Я бы определил карту, используя 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.Проблема с вашим решением заключается в том, что вы отбрасываете верхнюю часть списка, а затем накладываете карту на список, и поэтому при отображении результата заголовок списка отсутствует.

3 голосов
/ 20 апреля 2011

В ваших определениях вы выполняете сопоставление с шаблоном для 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
2 голосов
/ 24 июля 2016

Другой способ думать об этом - существует 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 для итога и [] для реверсо) вместе с оператором между первым значением и рекурсивным вызовом (+ для итога и (\q qs -> qs ++ [q]) для реверсо). Таким образом, структура функции для приведенных выше примеров в целом может рассматриваться как

-- 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 работает, мы теперь можем переписать reverso, используя 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 в Хаскеле называется foldr . Итак, мы переписали фильтр, используя foldr.

let p = even in foldr (\curr acc -> if (p curr) then curr:acc else acc) [] [1,2,3,4] 

Итак, filter можно записать с помощью 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
2 голосов
/ 28 марта 2016

Я новичок в Haskell (на самом деле я нашел эту страницу, задающую тот же вопрос), но это мое понимание списков и свёрток до сих пор:

  • - это элементы, которые связаны со следующим элементом с помощью оператора cons (:). они заканчиваются пустым списком []. (Думайте об этом как о бинарном операторе, как о сложении (+) 1+2+3+4 = 10, 1:2:3:4:[] = [1,2,3,4]
  • Функция foldr принимает функцию, которая принимает два параметра. это заменит оператор 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, что делать в случае пустого списка.

Я знаю, что этот вопрос действительно старый, но я все равно хотел на него ответить. Я надеюсь, что это не противоречит правилам.

1 голос
/ 20 апреля 2011

Ваше решение почти работает.) Проблема в том, что у вас есть две разные привязки для 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.

...