Сканирование

Функции scanl и scanr похожи на foldl и foldr, только они сохраняют все промежуточные значения аккумулятора в список. Также существуют функции scanl1 и scanr1, которые являются аналогами foldl1 и foldr1.

ghci> scanl (+) 0 [3,5,2,1]

[0,3,8,10,11]

ghci> scanr (+) 0 [3,5,2,1]

[11,8,3,1,0]

ghci> scanl1 (acc x –> if x > acc then x else acc) [3,4,5,3,7,9,2,1]

[3,4,5,5,7,9,9,9]

ghci> scanl (flip (:)) [] [3,2,1]

[[],[3],[2,3],[1,2,3]]

При использовании функции scanl финальный результат окажется в последнем элементе итогового списка, тогда как функция scanr поместит результат в первый элемент.

Функции сканирования используются для того, чтобы увидеть, как работают функции, которые можно реализовать как свёртки. Давайте ответим на вопрос: как много корней натуральных чисел нам потребуется, чтобы их сумма превысила 1000? Чтобы получить сумму квадратов натуральных чисел, воспользуемся map sqrt [1..]. Теперь, чтобы получить сумму, прибегнем к помощи свёртки, но поскольку нам интересно знать, как увеличивается сумма, будем вызывать функцию scanl1. После вызова scanl1 посмотрим, сколько элементов не превышают 1000. Первый элемент в результате работы функции scanl1 должен быть равен единице. Второй будет равен 1 плюс квадратный корень двух. Третий элемент – это корень трёх плюс второй элемент. Если у нас x сумм меньших 1000, то нам потребовалось (x+1) элементов, чтобы превзойти 1000.

sqrtSums :: Int

sqrtSums = length (takeWhile (< 1000) (scanl1 (+) (map sqrt [1..]))) + 1

ghci> sqrtSums

131

ghci> sum (map sqrt [1..131])

1005.0942035344083

ghci> sum (map sqrt [1..130])

993.6486803921487

Мы задействовали функцию takeWhile вместо filter, потому что последняя не работает на бесконечных списках. В отличие от нас, функция filter не знает, что список возрастает, поэтому мы используем takeWhile, чтобы отсечь список, как только сумма превысит 1000.