Подтвердить что ты не робот

Диапазоны в 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]
    

Почему?

4b9b3361

Ответ 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