Диапазоны в Haskell (GHCi)
Я читаю Учите вас A Haskell для отличного хорошего. Его примеры [2,2..20]
и [3, 6..20]
работают нормально, но я получил три странных результата:
- Отметьте на 17 от одного до 171:
[17, 1..171]
возвращает нулевой список.
- Подсчитайте на 17 с семнадцати до 1711111:
[17, 17..171111]
повторяет число 17
, пока я не прерываю GHCi.
-
Существует странная разница между take 54 [171, 234..]
и take 54 [171, 244..]
:
ghci> take 54 [171, 234..]
[171,234,297,360,423,486,549,612,675,738,801,864,927,990,1053,1116,1179,1242,1305,1368,1431,1494,1557,1620,1683,1746,1809,1872,1935,1998,2061,2124,2187,2250,2313,2376,2439,2502,2565,2628,2691,2754,2817,2880,2943,3006,3069,3132,3195,3258,3321,3384,3447,3510]
ghci> take 54 [171, 244..]
[171,244,317,390,463,536,609,682,755,828,901,974,1047,1120,1193,1266,1339,1412,1485,1558,1631,1704,1777,1850,1923,1996,2069,2142,2215,2288,2361,2434,2507,2580,2653,2726,2799,2872,2945,3018,3091,3164,3237,3310,3383,3456,3529,3602,3675,3748,3821,3894,3967,4040]
Почему?
Ответы
Ответ 1
У вас малозначительное значение диапазонов. Синтаксис диапазона Haskell является одной из четырех вещей: [first..]
, [first,second..]
, [first..last]
, [first,second..last]
. Примеры из Learn You A Haskell -
ghci> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
ghci> [3,6..20]
[3,6,9,12,15,18]
Обратите внимание, что в первом случае список подсчитывается по двум, а во втором случае список подсчитывается по трем. Это потому, что разница между первым и вторым элементами составляет два и три, соответственно. В синтаксисе вы пытаетесь написать [first,step..last]
, чтобы получить список [first,first+step,first+2*step,...,last]
; однако размер шага такого диапазона, фактически, является разницей между первыми двумя числами. Без второго элемента размер шага всегда один; и без конечного элемента список продолжается вечно (или до достижения максимального/минимального элемента типа).
Итак, рассмотрим три примера:
-
[17,1..171] == []
. Поскольку вы указываете 17,1
, Haskell видит, что первые два элемента вашего списка должны быть семнадцать и один, поэтому вы должны рассчитывать на -16
. В этом случае Haskell хочет остановить, как только элементы будут меньше, чем последний элемент, но они начинаются именно так, и поэтому не создаются никакие элементы. Чтобы подсчитать один, вы хотите [17,18..171]
(первые два элемента вашего списка - 17 и 18) или просто [17..171]
.
-
[17, 17..171111] == repeat 17
. Это весело. Поскольку первые два элемента вашего списка являются как 17
, Haskell определяет, что вы должны подсчитывать ноль, и он будет продолжать подсчет до тех пор, пока результат не превысит 171111
. Конечно, при подсчете на ноль этого никогда не будет, и поэтому вы получите бесконечный список семнадцати. Чтобы подсчитать до семнадцати, вы хотите [17,34..171111]
или [17,17+17..171111]
, если считаете это более ясным.
-
take 54 [171,234..]
vs. take 54 [171,244..]
. Я не уверен, какое поведение вы ожидали здесь, но то, что они делают, такое же, как и выше: первый возвращает список из пятидесяти четырех целых чисел, начиная с 171
и подсчитывая 234 - 171 = 63
; второй возвращает список из пятидесяти четырех целых чисел, начиная с 171
и рассчитывая на 244 - 171 = 73
. Каждый список проходит бесконечно далеко (или, по крайней мере, до maxBound
, если списки имеют конечный Ints
и не сколь угодно большой Integers
), и поэтому вы просто запрашиваете первые пятьдесят четыре элемента.
Для некоторых более подробных подробностей о том, что означает синтаксис диапазона (он переводится в функции класса Enum
), включая слегка удивительное поведение в диапазонах чисел с плавающей запятой, hammar имеет хороший ответ на другой вопрос.
Ответ 2
Ну, семантика этих операций немного отличается от того, что вы ожидаете. Конструкция [a,b..c]
на самом деле является просто синтаксическим сахаром для enumFromThenTo a b c
, который ведет себя немного так:
Вычислить d = b - a
. Выходной сигнал [a,b..c]
равен [a,a+d,a+d+d,a+d+d+d,...]
. Это повторяется до a+n*d > c
, если d
и c - a
имеют разные знаки (в этом случае список будет бесконечным, поэтому вместо него не будет выхода) или до достижения maxBound
или minBound
, то выход заканчивается. (Конечно, это выполняется по-другому, поскольку мы используем произвольные экземпляры Enum
здесь).
So [1,3..10]
становится [1,3,5,7,9]
, а так как 17 - 17 = 0
, [17, 17..171111]
дает [17,17+0,17+0+0...]
. И этим немного сложным правилом, [17, 1..171]
дает пустой список.
К вашему добавлению: [x,y..]
реализуется с помощью функции enumFromThen x y
, которая ведет себя точно так же, как enumFromThenTo
, за исключением того, что нет граничного условия, поэтому, если ваш Enum
бесконечен, так будет результирующий список,
Ответ 3
Я тоже был немного удивлен этим поведением, поэтому я написал функцию диапазона, которая кажется более естественной для меня (и, возможно, для вас):
range step start end = takeWhile (<=end) $ iterate (+step) start
Ссылаясь на ваши примеры:
Подсчитайте на 17 с одного на 171
выполняется range 17 1 171
, создавая [1,18,35,52,69,86,103,120,137,154,171]
Подсчитайте на 17 с семнадцати до 1711111
выполняется range 17 17 1711111
, производя [17,34,51,68,85, ...
Ответ 4
Я также был смущен этим учебным пособием: в учебнике используется шаг слова, который не объясняется, и, на мой взгляд, это не то, что я думаю как шаг. Затем он показывает пример, который легко может быть неверно истолкован. поскольку [2,4..20]
выглядит так, как будто это означает, что шаг 2 начинается с 4.
Ключ находится на выходе:
ghci> [2,4..20]
[2,4,6,8,10,12,14,16,18,20]
если вы внимательно посмотрите (чего я не сделал). Это означает начало в 2, следующее 4, с неявным шагом с этого момента (4 - 2), продолжайте вывод чисел с шагом от 2 до не более 20.
"ghci>" [1,6..20]
[1,6,11,16]
Примечание 20 не выводится, так как 16 + 5 больше 20