# Глава 10. Диапазон Список — тип, который повсеместно встречается в программах на Haskell. Неудивительно, что в языке есть способы лаконично создавать список из диапазона (англ. range) значений. ## Мотивация Допустим, понадобился нам список целых чисел от одного до десяти. Пишем: ```haskell {.example_for_playground} main :: IO () main = print tenNumbers where tenNumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] ``` Неплохо, но избыточно, ведь чисел могло быть и сто, и тысяча. Есть лучший путь: ```haskell {.example_for_playground} main :: IO () main = print tenNumbers where tenNumbers = [1..10] ``` Красиво, не правда ли? Выражение в квадратных скобках называется диапазоном. Иногда его именуют также интервалом или арифметической последовательностью. Идея предельно проста: зачем указывать содержимое списка целиком в той ситуации, когда можно указать лишь интервал значений? ## Использование Значение слева от `..` — это начало диапазона, а значение справа — его конец. Компилятор сам догадается, что шаг между числами в данной последовательности равен 1: ```haskell [1..10] = [1,2,3,4,5,6,7,8,9,10] ``` Вот ещё пример: ```haskell [3..17] = [3,4,5,6,7,8,9,10,11,12,13,14,15,16,17] ``` Мы можем задать шаг и явно: ```haskell [2,4..10] = [2,4,6,8,10] ``` Получили только чётные значения. Схема проста: указываем первый элемент, второй элемент и последний. **Разница между первым и вторым даёт шаг последовательности.** Многие начинающие хаскеллисты путаются и принимают второй элемент за шаг. Это не так: шаг последовательности рассчитывается компилятором как разность второго и первого элементов! И ещё пример: ```haskell [3,9..28] = [3,9,15,21,27] ``` Что выведет этот код? В случае ошибки напишите `error`. {.task_text} ```haskell {.example_for_playground} module Main where main :: IO () main = print ([5,8..100] !! 2) ``` ```consoleoutput {.task_source #haskell_chapter_0100_task_0010} ``` Здесь формируется список, нулевой элемент которого 5, первый — 8. Получается, что шаг между элементами равен 8 - 5, то есть 3. Поэтому элемент с индексом 2, который выводится в консоль, равен 8 + 3, то есть 11. {.task_hint} ```haskell {.task_answer} 11 ``` Можно задать и нисходящий диапазон: ```haskell [9,8..1] = [9,8,7,6,5,4,3,2,1] ``` Или так: ```haskell [-9, -8.. -1] = [-9,-8,-7,-6,-5,-4,-3,-2,-1] ``` Напишите функцию `progressionSum`, которая принимает три целых числа: первый и последний элементы арифметической прогрессии и ее шаг. {.task_text} Функция должна вернуть сумму членов арифметической прогрессии. {.task_text} ```haskell {.task_source #haskell_chapter_0100_task_0020} module Main where -- Your code here main :: IO () main = do print (progressionSum 0 3 1) print (progressionSum 1 10 2) print (progressionSum (-1) (-10) (-5)) ``` Создайте диапазон чисел и примените к нему встроенную функцию `sum`. {.task_hint} ```haskell {.task_answer} module Main where progressionSum :: Int -> Int -> Int -> Int progressionSum firstEl lastEl step = sum [firstEl, firstEl + step.. lastEl] main :: IO () main = do print (progressionSum 0 3 1) print (progressionSum 1 10 2) print (progressionSum (-1) (-10) (-5)) ``` Можно взять также и числа с плавающей точкой: ```haskell [1.02,1.04..1.16] = [1.02,1.04,1.06,1.08,1.1,1.12,1.14,1.16] ``` В общем, идея ясна. Но что это мы всё с числами да с числами! Возьмём символы: ```haskell ['a'..'z'] = "abcdefghijklmnopqrstuvwxyz" ``` Диапазон от `'a'` до `'z'` — это английский алфавит в виде `[Char]` или, как мы уже знаем, просто `String`. При большом желании явно задать шаг можно и здесь: ```haskell ['a','c'..'z'] = "acegikmoqsuwy" ``` Вот такая красота. Теперь, после знакомства со списками и диапазонами, мы будем использовать их постоянно. ## Бесконечные последовательности Благодаря ленивым вычислениям в Haskell запросто можно оперировать бесконечными диапазонами. Они выглядят так, как будто при определении диапазона мы забыли указать его верхнюю границу: {#infinite-range} ```haskell [1 ..] ``` Вот мы и получили бесконечный набор чисел: `[1, 2, 3, 4, 5, 6, ...`. Мы можем использовать его внутри функции: ```haskell infiniteRange :: Int -> Int -> [Int] infiniteRange start step = [start, start+step ..] ``` Такая функция нормально скомпилируется. Проблемы начнутся, если мы попробуем ее использовать: ```haskell main :: IO () main = print (infiniteRange 10 5) ``` В рантайме действительно будет предпринята попытка генерации все новых и новых элементов списка для их вывода в консоль. Очевидно, ничем хорошим это не закончится. Однако, возвращаясь к концепции ленивости, бесконечные списки все же можно создавать и использовать без риска исчерпания всех возможных ресурсов. Например, так мы посчитаем 17-ый элемент арифметической прогрессии с первым элементом 10 и шагом 5: ```haskell main :: IO () main = print (infiniteRange 10 5 !! 17) ``` ``` 95 ``` Про ленивость мы поговорим более подробно в [посвященной ей главе.](/courses/haskell/chapters/haskell_chapter_0170/) Создайте функцию `f`, которая считает пятый по индексу член убывающей прогрессии `0, -2, -4, -6, ...`. Воспользуйтесь для этого бесконечным диапазоном. {.task_text} ```haskell {.task_source #haskell_chapter_0100_task_0030} module Main where -- Your code here main :: IO () main = print f ``` Создайте бесконечный диапазон чисел и примените к нему оператор взятия элемента по индексу `!!`. {.task_hint} ```haskell {.task_answer} module Main where f :: Int f = [0, -2..] !! 5 main :: IO () main = print f ``` Что выведет этот код? В случае ошибки или нежелательного поведения программы напишите `error`. {.task_text} ```haskell {.example_for_playground} module Main where main :: IO () main = print (sum [1,2..]) ``` ```consoleoutput {.task_source #haskell_chapter_0100_task_0040} ``` При попытке посчитать сумму членов бесконечной арифметической прогрессии воспользоваться ленивостью не получится: требуется знать значения всех элементов диапазона. Поэтому так или иначе, но процесс израсходует всю доступную ему память. {.task_hint} ```haskell {.task_answer} error ``` ## Для любопытных В разделе про диапазоны для списка мы оперировали значениями типа `Int`, `Double` и `Char`. Возникает вопрос: а можно ли использовать значения каких-нибудь других типов? Отвечаю: можно, но с оговоркой. Попробуем проделать это со строкой: ```haskell main :: IO () main = print ["a","aa".."aaaaaa"] -- Ну-ну... ``` При попытке скомпилировать такой код увидим ошибку: ``` No instance for (Enum [Char]) arising from the arithmetic sequence ‘"a", "aa" .. "aaaaaa"’ ``` И удивляться тут нечему: шаг между строками абсурден, и компилятор в замешательстве. Не все типы подходят для перечислений в силу своей природы, однако в будущем, когда мы научимся создавать наши собственные типы, мы узнаем, что их вполне можно использовать в диапазонах. Наберитесь терпения. Приоткрою секрет: этот странный пример с шагом между строками теоретически можно-таки заставить работать, но о том, как это сделать, мы узнаем во время знакомства с Третьим Китом Haskell.
Отправка...
Наша группа в telegram. Здесь можно задавать вопросы и общаться.
Задонатить. Если вам нравится курс, вы можете поддержать развитие площадки!