Наивная фильтрация дубликатов Haskell

Я не понимаю пример решения следующей задачи: по заданному списку элементов удалить дубликаты. Затем подсчитайте уникальные цифры числа. Никакая явная рекурсия не может использоваться ни для одной из задач.

Мой код:


removeDuplicates :: Eq a => [a] -> [a]
removeDuplicates = foldr (\x ys -> x:(filter (x /=) ys)) [] 

differentDigits :: Int -> Int
differentDigits xs = length (removeDuplicates (show xs))

Решение, которое я пытаюсь понять, имеет другое определение для разныецифры, а именно

differentDigits xs = foldr (\ _ x -> x + 1) 0 ( removeDuplicates ( filter (/= '_') ( show xs )))

Оба подхода работают, но я не могу понять образец решения. Чтобы разбить мой вопрос на подвопросы,

  1. Как работает первый аргумент фильтра? Я имею в виду

(/= '_')

  1. Как работает лямбда для папки? В

папка (\_ х -> х + 1)

       ^

переменная x все еще должна быть списком Char? Как Haskell выясняет, что на самом деле нужно увеличить 0?


person mushishi    schedule 05.08.2019    source источник
comment
Я бы использовал length так же, как вы здесь. Понятия не имею, почему они написали это foldr (\_ x -> x+1) 0 (по крайней мере, должно быть foldl'!).   -  person luqui    schedule 05.08.2019


Ответы (2)


  1. filter (/= '_'), я уверен, избыточен. Он отфильтровывает символы подчеркивания, которые не должны присутствовать в результате show xs, предполагая, что xs является каким-то числом.

  2. foldr (\ _ x -> x + 1) 0 эквивалентно length. Как работает foldr, он принимает второй аргумент (который в вашем примере равен нулю) в качестве отправной точки, а затем применяет к нему первый аргумент (в вашем примере, лямбда) снова и снова для каждого элемента входного списка. Элемент входного списка передается в лямбду в качестве первого аргумента (обозначается _ в вашем примере), а текущая сумма передается в качестве второго аргумента (обозначается x). Поскольку лямбда просто возвращает число «плюс один» при каждом проходе, результатом будет число, представляющее, сколько раз лямбда вызывалась, что является длиной списка.

person Fyodor Soikin    schedule 05.08.2019
comment
Для (1) я полагаю, что это должно быть filter (/= '-'), чтобы учитывать отрицательные числа. - person luqui; 05.08.2019
comment
То есть, второй аргумент foldr == второй аргумент лямбда; третий аргумент foldr == первый аргумент лямбда? - person mushishi; 05.08.2019
comment
В каком-то смысле можно так сказать. Если вы считаете, что == означает, относится к - person Fyodor Soikin; 06.08.2019

Во-первых, обратите внимание, что (2) написано в так называемом стиле без точек, без третьего аргумента foldr.

https://en.wikipedia.org/wiki/Tacit_programming#Functional_programming

Кроме того, символ подчеркивания в \_ x -> x + 1 — это подстановочный знак, который просто отмечает место параметра, но не дает ему имени (подстановочный знак работает как безымянный параметр).

Во-вторых, (2) на самом деле не что иное, как простая рекурсивная функция, которая складывается вправо. foldr — это компактный способ написания таких рекурсивных функций (в вашем случае length):

foldr :: (a -> b -> b) -> b -> [a]
foldr f z []     = z 
foldr f z (x:xs) = f x (foldr f z xs) 

Если мы напишем

foldr f c ls
  • ls — это список, по которому должна повторяться наша рекурсивная функция (a — это тип элементов).
  • c — это результат в базовом случае (когда рекурсивная рекурсивная функция применяется к пустому списку).
  • f computes the result in the general case (when the recursive function is applied on a non-empty list). f takes two arguments:
    1. The head of the list and
    2. результат рекурсивного вызова хвоста списка.

Итак, при заданных f и c foldr будет рекурсивно проходить по списку ls.

Первый пример На странице Википедии о стиле без точек приводится пример того, как мы можем вычислить сумму всех элементов в списке с помощью foldr:

Вместо того, чтобы писать

sum [] = 0
sum (x:xs) = x + sum xs

мы можем написать

sum = foldr (+) 0

Секция оператора (+) — это функция с двумя аргументами, которая добавляет свои аргументы. Выражение

sum [1,2,3,4] 

вычисляется как

1 + (2 + (3 + (4))) 

(отсюда и «складывание вправо»).

Пример: умножение всех элементов.

Вместо

prod [] = 1
prod (x:xs) = x * prod xs

мы можем написать

prod = foldr (*) 1

Пример. Удаление всех вхождений значения из списка.

Вместо

remove _ [] = []
remove v (x:xs) = if x==v then remove v xs else x:remove v xs

мы можем написать

remove v = foldr (\x r -> if x==v then r else x:r) []

Ваше дело, (2)

Теперь мы можем полностью понять, что

length = foldr (\ _ x -> x + 1) 0

на самом деле то же самое, что

length [] = 0
length (x:xs) = length xs + 1

то есть функция длины.

Надеюсь, этот рекурсивный просмотр папки помог вам понять код.

person Håkan    schedule 06.08.2019
comment
(2) не соответствует свободному стилю и не пропускает третий аргумент foldr. Третий аргумент — ( removeDuplicates ( filter (/= '_') ( show xs ))). - person Ben; 06.08.2019
comment
Под (2) я имел в виду выражение foldr (\ _ x -> x + 1), которое не имеет точки, когда мы его называем, но я понимаю, что вы имеете в виду. - person Håkan; 06.08.2019