Вот функция, принимающая число и возвращающая сумму его цифр:
import Data.Char
import Data.List
digitSum :: Int -> Int
digitSum = sum . map digitToInt . show
Преобразуем заданное число в строку, пройдёмся по строке функцией digitToInt
, суммируем получившийся числовой список.
Теперь нужно найти первое натуральное число, применив к которому функцию digitSum
мы получим в качестве результата число 40
. Для этого воспользуемся функцией find
из модуля Data.List
. Она принимает предикат и список и возвращает первый элемент списка, удовлетворяющий предикату. Правда, тип у неё несколько необычный:
ghci> :t find
find :: (a -> Bool) -> [a] -> Maybe a
Первый параметр – предикат, второй – список, с этим всё ясно. Но что с возвращаемым значением? Что это за Maybe a
? Это тип, который нам до сих пор не встречался. Значение с типом Maybe a
немного похоже на список типа [a]
. Если список может иметь ноль, один или много элементов, то значение типа Maybe a
может иметь либо ноль элементов, либо в точности один. Эту штуку можно использовать, если мы хотим предусмотреть возможность провала. Значение, которое ничего не содержит, – Nothing
. Оно аналогично пустому списку. Для конструирования значения, которое что-то содержит, скажем, строку "эй"
, будем писать Just "эй"
. Вот как всё это выглядит:
ghci> Nothing
Nothing
ghci> Just "эй"
Just "эй"
ghci> Just 3
Just 3
ghci> :t Just "эй"
Just "эй" :: Maybe [Char]
ghci> :t Just True
Just True :: Maybe Bool
Видите, значение Just True
имеет тип Maybe Bool
. Похоже на то, что список, содержащий значения типа Bool
, имеет тип [Bool]
.
Если функция find
находит элемент, удовлетворяющий предикату, она возвращает этот элемент, обёрнутый в Just
. Если не находит, возвращает Nothing
:
ghci> find (>4) [3,4,5,6,7]
Just 5
ghci> find odd [2,4,6,8,9]
Just 9
ghci> find (=='x') "меч-кладенец"
Nothing
Вернёмся теперь к нашей задаче. Мы уже написали функцию digitSum
и знаем, как она работает, так что пришла пора собрать всё вместе. Напомню, что мы хотим найти число, сумма цифр которого равна 40.
firstTo40 :: Maybe Int
firstTo40 = find (\x -> digitSum == 40) [1..]
Мы просто взяли бесконечный список [1..]
и начали искать первое число, значение digitSum
для которого равно 40.
ghci> firstTo40
Just 49999
А вот и ответ! Можно сделать более общую функцию, которой нужно передавать искомую сумму в качестве параметра:
firstTo :: Int -> Maybe Int
firstTo n = find (\x -> digitSum x == n) [1..]
И небольшая проверка:
ghci> firstTo 27
Just 999
ghci> firstTo 1
Just 1
ghci> firstTo 13
Just 49
Отображение ключей на значения
Зачастую, работая с данными из некоторого набора, мы совершенно не заботимся, в каком порядке они расположены. Мы просто хотим получить к ним доступ по некоторому ключу. Например, желая узнать, кто живёт по известному адресу, мы ищем имена тех, кто по этому адресу проживает. В общем случае мы говорим, что ищем значение (чьё-либо имя) по ключу (адрес этого человека).
Почти хорошо: ассоциативные списки
Существует много способов построить отображение «ключ–значение». Один из них – ассоциативные списки.
phoneBook =
[("оля","555–29-38")
,("женя","452–29-28")
,("катя","493–29-28")
,("маша","205–29-28")
,("надя","939–82-82")
,("юля","853–24-92")
]
За исключением странного выравнивания, это просто список, состоящий из пар строк. Самая частая задача при использовании ассоциативных списков – поиск некоторого значения по ключу. Давайте напишем функцию для этой задачи.