Новое современное состояние в неограниченном поколении последовательности Хэмминга

(это интересно!) Я знаю, предмет хорошо известен. Современное состояние (в Haskell, а также на других языках) для эффективной генерации неограниченной возрастающей последовательности чисел Хэмминга, без дубликатов и без упущений, уже давно является следующим (AFAIK - и btw эквивалентно оригинальный код Edsger Dijkstra):

hamm :: [Integer]
hamm = 1 : map (2*) hamm `union` map (3*) hamm `union` map (5*) hamm
  where
    union [email protected](x:xs) [email protected](y:ys) = case compare x y of
        LT -> x : union  xs  b
        EQ -> x : union  xs  ys
        GT -> y : union  a   ys

Вопрос, который я задаю, , вы можете найти способ сделать его более эффективным в какой-либо значительной мере? Является ли это по-прежнему актуальным, или на самом деле возможно улучшить это, чтобы работать в два раза быстрее и с лучшим эмпирическим порядком роста для загрузки?

Если ваш ответ "да", пожалуйста, покажите код и обсудите его скорость и эмпирические порядки роста по сравнению с приведенным выше (он работает примерно на ~ n^1.05 .. n^1.10 для первых нескольких сотен тысяч произведенных номеров). Кроме того, если он существует, может ли этот эффективный алгоритм быть расширен до создания последовательности гладких чисел с любым заданным набором простых чисел?

Ответ 1

Если коэффициент ускорения (1) считается столь же значительным, я могу предложить значительно более эффективную версию:

hamm :: [Integer]
hamm = mrg1 hamm3 (map (2*) hamm)
  where
    hamm5 = iterate (5*) 1
    hamm3 = mrg1 hamm5 (map (3*) hamm3)
    merge [email protected](x:xs) [email protected](y:ys)
        | x < y     = x : merge xs b
        | otherwise = y : merge a ys
    mrg1 (x:xs) ys = x : merge xs ys

Вы можете легко обобщить его на гладкие числа для заданного набора простых чисел:

hamm :: [Integer] -> [Integer]
hamm [] = [1]
hamm [p] = iterate (p*) 1
hamm ps = foldl' next (iterate (q*) 1) qs
  where
    (q:qs) = sortBy (flip compare) ps
    next prev m = let res = mrg1 prev (map (m*) res) in res
    merge [email protected](x:xs) [email protected](y:ys)
        | x < y     = x : merge xs b
        | otherwise = y : merge a ys
    mrg1 (x:xs) ys = x : merge xs ys

Это более эффективно, потому что этот алгоритм не создает дубликатов и использует меньше памяти. В вашей версии, когда номер Хэмминга рядом с h создается, часть списка между h/5 и h должна быть в памяти. В моей версии только часть между h/2 и h полного списка, а часть между h/3 и h 3-5-списка должна быть в памяти. Поскольку 3-5-список намного реже, а плотность k-гладких чисел уменьшается, эти две части списка нуждаются в гораздо меньшем объеме памяти, чем большая часть полного списка.

Некоторые тайминги для двух алгоритмов для создания числа Хэмминга k th с эмпирической сложностью каждой цели относительно предыдущего, исключая и включающее время GC:

  k            Yours (MUT/GC)               Mine (MUT/GC)
 10^5           0.03/0.01                    0.01/0.01      -- too short to say much, really
2*10^5          0.07/0.02                    0.02/0.01
5*10^5          0.17/0.06  0.968  1.024      0.06/0.04      1.199    1.314
 10^6           0.36/0.13  1.082  1.091      0.11/0.10      0.874    1.070
2*10^6          0.77/0.27  1.097  1.086      0.21/0.21      0.933    1.000
5*10^6          1.96/0.71  1.020  1.029      0.55/0.59      1.051    1.090
 10^7           4.05/1.45  1.047  1.043      1.14/1.25      1.052    1.068
2*10^7          8.73/2.99  1.108  1.091      2.31/2.65      1.019    1.053
5*10^7         21.53/7.83  0.985  1.002      6.01/7.05      1.044    1.057
 10^8          45.83/16.79 1.090  1.093     12.42/15.26     1.047    1.084

Как вы можете видеть, коэффициент между временем MUT составляет около 3,5, но время GC не сильно отличается.

(1) Ну, он выглядит постоянным, и я думаю, что оба варианта имеют одинаковую вычислительную сложность, но я не вытащил карандаш и бумагу, чтобы доказать это, и не намерен.

Ответ 2

Итак, теперь, когда Даниэль Фишер дал свой ответ, я могу сказать, что я натолкнулся на это недавно, и я думаю, что это захватывающее событие, поскольку классический код известен веками, начиная с Дейкстры.

Даниэль правильно определил избыточность генерации дубликатов, которая затем должна быть удалена в классической версии.

Кредит на первоначальное открытие (AFAIK) отправляется спонсору Rosettacode.org Ledrug, с 2012 года -08-26. И, конечно, независимое открытие Даниэля Фишера, здесь (2012-09-18).

Немного переписан, этот код:

import Data.Function (fix)

hamm = 1 : foldr (\n s -> fix (merge s . (n:) . map (n*))) [] [2,3,5]

с обычной реализацией слияния,

merge [email protected](x:xs) [email protected](y:ys) | x < y     = x : merge xs b
                        | otherwise = y : merge a ys
merge [] b = b
merge a [] = a

Он дает примерно 2.0x - 2.5x ускорение по сравнению с классической версией.

Ответ 3

Вопрос здесь Как найти список всех чисел, кратных только степеням 2, 3 и 5? the-list-of-all-numbers-that-are-multiples-of-only-powers-of-2 Тогда, я теперь не уверен, что вопрос, помеченный как дубликаты, нуждается в полномочиях или кратных числах. Я путаю способ легко.

Взятие чисел непосредственно из целых чисел исключает сортировку и объединение. Взяв дельты из списка кратных 2,3 и 5, для первых нескольких сотен вы заметите, что шаблон дельт повторяется каждые (22 из) 30. Вы можете использовать этот шаблон в scanl и cycle шаблон.

take 77 $ scanl (\b a -> a+b) 2 $ cycle  [1,1,1,1,2,1,1,2,2,1,1,2,2,1,1,2,1,1,1,1,2,2]

Это создает этот список [2,3,4,5,6,8,9,10,12,14,15,16,18,20,21,22,24,25,26,27,28,30,32, 33,34,35,36,38,39,40,42,44,45,46,48,50,51,52,54,55,56,57,58,60,62,63,64,65, 66,68,69,70,72,74,75,76,78,80,81,82,84,85,86,87,88,90,92,93,94,95,96,98,99,100,102,104,105 ]

Я использую инверсию этого списка, который использует гораздо более короткий шаблон, потому что там очень мало простых чисел.

Ответ 4

Ну, я не смог опубликовать свой последний ответ на подобный вопрос, потому что он был помечен как дубликат из-за этого вопроса.

Итак, теперь, к этому вопросу. Я все еще думаю, что лучше пройти через упорядоченный набор, чтобы не приходилось компенсировать его.

Я также нашел свою функцию no2s3s5s полезной для этого тоже. Забавно, что no2s3s5s начинается с 11. no2s3s5s просто считает определенным образом.

Это no2s3s5s

no2s3s5s = scanl (\b a -> a + b) 11 $ cycle [2,4,2,4,6,2,6,4]

Он производит в основном простые числа, с 11 по 97 есть только 49, 77 и 91, которые не простые, страшные 7. Он используется в hamseq вместо простых чисел. Виноват.

hamseq n = [ b | b <- [1..n], all (>0) [mod b f | f <- take (div b 2) (7:no2s3s5s)]]

hamseq 729

производит [1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36,40,45,48,50, 54,60,64,72,75,80,81,90,96,100,108,120,125,128,135,144,150,160,162,180,192,200,216,225,240,243,250,256,270,288,300,320,324,360,375,384,400,405,432,450,480,486,500,512,540,576,600,625,640,648,675,720,729]

@Уилл Несс, я в буквальном смысле слова. Я никогда не думал о создании этого списка с параметром 77. Мой плохой.

Но здесь

hamseq n = take n $ [ b | b <- [1..], all (>0) [mod b f | f <- take (div b 2) (7:no2s3s5s)]]

02/12/2018 Опять же из-за фундаментальной теоремы арифметики (моя первоначальная мотивация для этой функции) я перестал использовать no2s3s5 в hamseq. В списке простых чисел намного меньше значений. У меня действительно очень простая функция, но я должен ее откопать. Я сделал это чуть раньше сегодня. Функция hamseq теперь намного быстрее, не линейная, а более быстрая.

prs n =takeWhile (<n)$[p|p <-no2s3s5s, all (>0) [mod p f|f <- take (ceiling.sqrt.fromIntegral$p) (2:3:5:7:no2s3s5s)]]
hamseq' n ls = [ b | b <- [1..n], all (>0) [mod b f | f <- take (div b 3) ls]]
hamseq n = hamseq' n (7:(prs n))

(div b 3) является приблизительным. Оптимальное число - от 3 до 4. Я найду свой способ быстрее генератора праймера и заменю этот глушитель.

Я забыл об этом, и это в последний раз разрезает пополам.

base n = 1:[ d | d <- [2..n], any (==0) $ [mod d f | f <- [2,3,5]]]
so
hamseq' n ls = [ b | b <- (base n), all (>0) [mod b f | f <- take (div b 3) ls]]

12/4/2018

Ну, я сдался. Просто невозможно использовать простой список для разложения каждого x потому что длина списка становится очень большой и способствует вялости.

Альтернативой является последовательное разложение каждого числа на одно из [2,3,5]. Это означает, что последнее вычисленное значение используется в следующих вычислениях, то есть рекурсивно. Простая рекурсия в Haskell означает fold/scan.

Во- первых это divuntil (разрыв до) для scanl в facs. Он многократно делил значение на одно из [2,3,5], в зависимости от того, до или после каждого деления. Это было разделено на две функции, но теперь объединено и работает быстрее.
Во- вторых, facs функция, которая использует scanl для генерации факторизацией повторно разрыва. Раздражающая природа div заставляет scanl свернуть до 1 для чисел Хэмминга. Если частное не приводит к 1 то число не Хэмминга. Если частное не делится на 2,3 или 5, результатом будет нулевой список. Наконец, безымянное понимание списка для повторного запуска facs в списке чисел. Понимание списка, потому что в противном случае для него потребовалось бы условие if with else, то есть простой способ игнорировать или отбрасывать значения.

divuntil = \b a -> let ff=[f | f <- [2,3,5], mod b f == 0] in
                       if null ff then 0 else div b (ff !! 0)

facs= \n -> last.takeWhile (>0) $ scanl divuntil n [1..]

take 200 $ [ b | b <- [1..], facs b == 1]

Этот код ужасен, я знаю. Это наверняка можно улучшить. Наверное, из-за того, как я пишу на Хаскеле. Одна строка за раз. Виноват. Я мог бы сломать и поместить это в файл одной функции, которая, вероятно, тоже ускорила бы его.

Ускорение по сравнению с моей последней функцией в 12 раз больше, чем я предполагаю. Это все еще не близко к линейному, но быстрее.

facs может использоваться с любым значением, чтобы идентифицировать его как Хэмминга или нет.

Я начну с того, что, как я думал, было ответом на другой вопрос, то есть на все кратные [2,3,5]. Я ответил с этим.

base = scanl (\b a -> a+b) 2 $ cycle [1,1,1,1,2,1,1,2,2,1,1,2,2,1,1,2,1,1,1,1,2,2]

Это производит кратные, образец которых выше. Рекурсивное добавление снова, считая. Это быстрее, чем рекурсивное деление одного значения. Это делает следующее быстрее.

take 600 $ [ b | b <- base, facs b == 1]

take 600 составляет 18,84 секунды с [1..] на моем офисном ПК и 16,28 секунды с base.

Быстрее веселее. Пытаться

take 430 $ [ floor.log.fromIntegral $ b | b <- scbase, facs b == 1]

Вы можете [0..12] сколько из [0..12] [1,4,8,10,14,21,27,34,44,51,59,72,82] и дельт [1,3,4, 2,4,7, 6,7,10, 7,8,13, 10]

10.12.2008 Как я и подозревал, одна функция быстрее, чем многие. scanl не так управляем. Функцией div также можно управлять. Эта функция проанализирует одно число и выдаст True (Хэмминга) или False (не Хэмминга). Он запускается в понимании списка, так как понимание списка может генерировать ans x на основе логического значения.

fac n
      | n < 7 = True
      | even n       = fac (div n 2)
      | mod n 3 == 0 = fac (div n 3)
      | mod n 5 == 0 = fac (div n 5)
      | otherwise = False

take 600 $ [ d | d <- base, fac d ] take 600 $ [ d | d <- base, fac d ] 9,57 секунды - лучшее, что я могу сделать, проверив каждое значение. Далее, если у меня будет время, более аналитический подход.

12/21/2018

Скорости до 10 раз по сравнению с fac чуть выше этого, но это намного больше, испытание концепции.

Это три функции. Первый - это b25 который заменяет base сверху. b25 производит все четные и 5s (мод 10).

b25 = scanl' (\b a -> (b+a)) 2 $ cycle [2,1,1,2,2,2]

Что меня интересует, так это то, что основная функция не производит 3-кратных чисел.

Кратные 3 обеспечены второй функцией.

the3s = \n -> scanl' (\b a -> a*b) 1 $ replicate n 3

Функция fac2 изменяется, чтобы обрабатывать только четные или нечетные значения (5 с).

В этом n> 7 лишнее. Возможно, выражение о ситуации.

fac2 n
    | n <= 7 = True
    | n >  7 = if even n then fac (div n 2) else fac (div n 5)
    | otherwise = False

Запустите это как,

sort $ the3s 14 ++ [ d | d <- (take 150000 b25), fac2 d]

Ускорение примерно в 10 раз. 600 Хеммингов заняли 0,92 секунды в GHCi.

Я получил много пользы от fac выше, потому что вы можете добавить вещи к пониманию списка.

Действительно, обработка 150 000 чисел для получения 600 - это безумие, и лучший способ приблизиться к линейному - не обрабатывать список.

Ответ 5

@Уилл Несс Я обнаружил, что каждый новый номер кандидата на статус Хэмминга, который может быть равномерно разделен любым из [2,3,5], имеет частное в списке создаваемых чисел Хэмминга. Это исключает merge -ing и union. Он не использует кратные или другие факторы. Используется только сгенерированный список Хэмминга. Но это все еще не быстро.

Я разместил его в CodeReview, и Макс Талдыкин показал мне, как функции Data.Set работают намного быстрее, чем elem но все еще далеко не линейно, как описано выше.

Я проанализировал 2,3,5 кратного списка Хэмминга. Если вы возьмете все 2-х кратные и все нечетные мультипликаторы 3-х, единственное, что останется из 5-ти, которые не являются дубликатами, это, например, соотношение 15 в 6,103,515,625, которое может быть рекурсивно вычислено с базовым регистром 1 и умножьте предыдущее значение на 5 для следующего значения.

Первый параметр - это список чисел Хэмминга, меньший 10, второй параметр - это базовый список, из которого можно нарисовать числа кандидатов. Логика будет работать в несколько foldl что тоже медленно. Создание обратного списка сокращает время поиска, поскольку кандидат Хэмминга находится в конце списка, который необходимо добавить, если его частное находится в списке.

-- foldl (\b a -> let d = divm a [2,3,5] in if elem d b then b++[a] else b) [1,2,3,4,5,6,8,9] [10..n]

hamx ls (x:xs) 
   | even x        && elem (div x 2) ls = hamx (x:ls) xs
   | mod  x 3 == 0 && elem (div x 3) ls = hamx (x:ls) xs
   | mod  x 5 == 0 && elem (div x 5) ls = hamx (x:ls) xs
   | null xs = ls
   | otherwise = hamx ls xs 

Ответ 6

Я думаю, что упорство - мое второе имя. Программирование со мной - это функция времени. Когда я работаю над такой концепцией, как числа Хэмминга, я очаровываюсь. Когда я нахожу решение, это почти приводит к другому гипотетическому решению. Я только что опубликовал реализацию идеи. Это было связано с идентификацией чисел Хэмминга путем проверки их отношения равномерного деления на любое из 2,3 или 5 против ранее сгенерированных чисел Хэмминга. Это работало хорошо, но было медленно. Было рекомендовано опубликовать его в CodeReview и обнаружил, что member Set намного быстрее, чем Prelude elem. Это упражнение заставило меня задуматься о множителях, которые должны генерироваться с каждым новым числом Хэмминга.

Я обнаружил, что если вы возьмете все 2-кратные, а затем все нечетные 3-кратные, все, что останется, это горстка из пяти кратных, которые можно рассчитать независимо.

Чтобы проверить мою идею, я написал функцию. Я долго не думал о том, как лучше к этому подойти, но я хочу доказать идею. Функция scanl

iter = \hamms a -> sort (a:[ d | e <- hamms, d <- (if even e then [e*2] else [e*2,e*3])])

Вспомогательная функция генерирует 5-кратные и помещает по одному в начало каждого последующего списка.

Функция для генерации 5-кратных

get5s n = scanl (\b a -> a*b) 1 $ replicate n 5

В любом наборе Хэммингов все 0 (mod 10) генерируются 2 кратными. Подавляющее большинство из 5 (мод 10) генерируется 3-кратными.

Протестируйте get5s с небольшим параметром, меньшим, чем, скажем, 25, или вы получите огромные числа.

В моем GHCi генерация 900 Hammings занимает чуть более полсекунды.

Эта функция является проверкой идеи. Требуется много работы или полная переделка. Когда у меня будет время.

При генерации 900 Хемминга он также генерирует около 1100 Хеммингов вне очереди в конце.

Способ запустить iter для генерации 900 порядков Хэмминга

sort.concat.tail $ scanl iter [1,2,3,4,5,6,8,9,10] $ get5s 21

То, что я также должен сделать, это запустить это через Cygwin unique потому что, хотя Hoogle говорит, что Haskell unique есть в Data.List.Unique, это не так. Я использую sort из Data.List.

Ответ 7

Ну, это было проще, чем я думал. Это сделает 1000 Хеммингов за 0,05 секунды на моем медленном ПК дома. Этим днем на работе, и более быстрые времена ПК менее чем 600 выходили как ноль секунд.

Это взять Хеммингс из Хеммингс. Это основано на том, чтобы делать это быстрее всего в Excel.

Я получал неправильные номера после 250000, с Int. Числа очень быстро растут, поэтому необходимо использовать Integer, потому что Int ограничено.

mkHamm :: [Integer] -> [Integer] -> [Integer] -> [Integer] 
       -> Int -> (Integer, [Int])
mkHamm ml (x:xs) (y:ys) (z:zs) n =  
   if n <= 1
       then (last ml, map length [(x:xs), (y:ys), (z:zs)])
       else mkHamm (ml++[m]) as bs cs (n-1)
     where
         m = minimum [x,y,z]
         as = if x == m then xs ++ [m*2] else (x:xs) ++ [m*2]
         bs = if y == m then ys ++ [m*3] else (y:ys) ++ [m*3]
         cs = if z == m then zs ++ [m*5] else (z:zs) ++ [m*5]

Тестирование,

> mkHamm  [1] [2] [3] [5] 5000
(50837316566580,[306,479,692])        -- (0.41 secs)

> mkHamm  [1] [2] [3] [5] 10000
(288325195312500000,[488,767,1109])   -- (1.79 secs)

> logBase 2 (1.79/0.41)     -- log of times ratio = 
2.1262637726461726          --   empirical order of growth

> map (logBase 2) [488/306, 767/479, 1109/692] :: [Float]
[0.6733495, 0.6792009, 0.68041545]     -- leftovers sizes ratios

Это означает, что этот эмпирический порядок роста времени выполнения кода выше квадратичного (~n^2.13 как измерено, интерпретировано, по подсказке GHCi).

Кроме того, размеры трех висящих перепроизводимых сегментов последовательности составляют ~n^0.67 то есть ~n^(2/3).

Кроме того, этот код не ленив: доступ к первому элементу результирующей последовательности возможен только после вычисления самого последнего.

Код уровня техники в вопросе является линейным, перепроизводит ровно 0 элементов после интересующей точки и, по сути, ленив: он сразу начинает создавать свои числа.

Таким образом, хотя это значительное улучшение по сравнению с предыдущими ответами этого автора, оно все же значительно хуже, чем оригинал, не говоря уже о его улучшении, как в двух верхних ответах.

12.31.2018

Только самые лучшие люди обучают. @Will Ness также является автором или соавтором 19 глав на GoalKicker.com "Haskell for Professionals". Бесплатная книга - это сокровище.

Я перенес идею о функции, которая будет делать это, как это. Я был напуган, потому что думал, что это будет запутанно и связано с логикой, как в некоторых современных языках. Я решил начать писать и был поражен тем, насколько легко Хаскелл делает реализацию даже плохих идей.

У меня не было трудностей при создании уникальных списков. Моя проблема в том, что списки, которые я создаю, не заканчиваются хорошо. Даже когда я использую диагонализацию, они оставляют остаточные значения, что делает их использование ненадежным в лучшем случае.

Вот переработанный список 3 и 5 без остатка в конце. Денационализация заключается в уменьшении остаточных значений, а не в устранении дубликатов, которые никогда не включаются.

g3s5s n=[t*b|(a,b)<-[ (((d+n)-(d*2)), 5^d) | d <- [0..n]],
                t <-[ 3^e | e <- [0..a+8]],
             (t*b)<-(3^(n+6))+a]                                       


ham2 n = take n $ ham2' (drop 1.sort.g3s5s $ 48) [1]

ham2' [email protected](f:fo) [email protected](h:hx) = if h == min h f
                      then h:ham2'  o (hx ++ [h*2])
                      else f:ham2' fo ( e ++ [f*2])

Список twos может быть сгенерирован со всеми 2^e умноженными на каждый из 3s5s но когда 3s5s тождество 2^0, то, в общем, это Хэммингс.

3/25/2019

Ну наконец-то. Я знал это некоторое время назад, но не смог реализовать это без лишних значений в конце. Проблема заключалась в том, чтобы не генерировать избыток, являющийся результатом декартова произведения. Я часто использую Excel и не вижу шаблон значений, которые нужно исключить из таблицы декартовых произведений. Тогда, Эврика! Функции генерируют списки каждого ведущего фактора. Значение для ограничения значений в каждом списке является конечной точкой первого списка. Когда это сделано, все Хэмминги производятся без излишков.

Две функции для Хэмминга. Первый - это новый список 3 и 5, который затем используется для создания кратных с 2-мя. Мультипликаторы - это Хэмминги.

h35r  x = h3s5s x (5^x)
h3s5s x c = [t| n<-[3^e|e<-[0..x]],
                m<-[5^e|e<-[0..x]], 
                t<-[n*m],
             t <= c ]

a2r n = sort $ a2s n (2^n)
a2s n c =   [h| b<-h35r n, 
                a<-[2^e| e<-[0..n]],
                h<-[a*b],
             h <= c ] 

last $ a2r 50

1125899906842624

(0,16 с, 321 326 648 байт)

2 ^ 50

1125899906842624

(0,00 с, 95 424 байта

Это альтернатива, чище и быстрее с меньшим использованием памяти.

gnf n f = scanl (*) 1 $ replicate f n

mk35   n = (\c->      [m| t<- gnf 3 n, f<- gnf 5  n,    m<- [t*f], m<= c]) (2^(n+1))
mkHams n = (\c-> sort [m| t<- mk35  n, f<- gnf 2 (n+1), m<- [t*f], m<= c]) (2^(n+1))

last $ mkHams 50

2251799813685248

(0,03 с, 12 869 000 байт)

2^51

2251799813685248

5/6/2019

Ну, я пытался по-другому ограничивать, но всегда возвращаюсь к тому, что проще. Я предпочитаю наименьшее использование памяти, а также, кажется, самый быстрый.

Я также решил использовать map с неявным параметром.

Я также обнаружил, что mergeAll из Data.List.Ordered быстрее, чем sort или sort и concat.

Мне также нравится, когда создаются списки, так что я могу анализировать данные намного проще.

Затем из-за того, что @Will Ness переключился на iterate вместо того чтобы scanl создавая намного более чистый код Также из-за @Will Ness я перестал использовать последний из списка 2s и переключился на одно значение, определяющее все длины.

Я думаю, рекурсивно определенные списки более эффективны, предыдущее число умножено на коэффициент.

Простое разделение функции на две не имеет значения, поэтому 3 и 5 кратны

m35 lim = mergeAll $ 
          map (takeWhile (<=lim).iterate (*3)) $
               takeWhile (<=lim).iterate (*5)  $ 1

И каждый 2, умноженный на произведение 3 и 5

ham n = mergeAll $
        map (takeWhile (<=lim).iterate (*2)) $ m35 lim 
    where lim= 2^n

После редактирования функции я ее запустил

last $ ham 50

1125899906842624

(0,00 с, 7 029 728 байт)

затем

last $ ham 100

1267650600228229401496703205376

(0,03 с, 64 395 928 байт)

Вероятно, лучше использовать 10^n но для сравнения я снова использовал 2^n

5/11/2019

Поскольку я так предпочитаю бесконечные и рекурсивные списки, я стал немного одержим созданием этих бесконечных.

Я был так впечатлен и вдохновлен @Daniel Wagner и его Data.Universe.Helpers я начал использовать +*+ и +++ но затем добавил свой собственный бесконечный список. Я должен был mergeAll мой список, чтобы работать, но затем понял, что бесконечные 3 и 5 кратны были именно такими, какими они должны быть. Итак, я добавил 2s и mergeAll все, и они вышли. Раньше я глупо думал, что mergeAll не будет обрабатывать бесконечный список, но делает это на mergeAll.

Когда список в Haskell бесконечен, Haskell вычисляет только то, что нужно, то есть ленив. Дополнением является то, что он рассчитывает, с начала.

Теперь, поскольку Haskell умножается до предела желаемого, в функции не требуется никакого предела, то есть больше нет takeWhile. Ускорение невероятно, и память тоже снижена,

Ниже на моем медленном домашнем ПК с 3 ГБ оперативной памяти.

tia = mergeAll.map (iterate (*2)) $
      mergeAll.map (iterate (*3)) $ iterate (*5) 1

последние $ 10000 тиа

288325195312500000

(0,02 с, 5 861 656 байт)

6.5.2019 Я узнал, как ghc -02 Итак, следующее для 50000 Хэммингов до 2.38E + 30. И это еще одно доказательство того, что мой код - мусор.

INIT    time    0.000s  (  0.000s elapsed)
MUT     time    0.000s  (  0.916s elapsed)
GC      time    0.047s  (  0.041s elapsed)
EXIT    time    0.000s  (  0.005s elapsed)
Total   time    0.047s  (  0.962s elapsed)

Alloc rate    0 bytes per MUT second
Productivity   0.0% of total user, 95.8% of total elapsed

6.13.2019

@Уилл Несс Рокс. Он предоставил чистый и элегантный пересмотр tia выше, и она оказалась в пять раз быстрее в GHCi. Когда я ghc -O2 +RTS -s его против моего, мой был в несколько раз быстрее. Там должен был быть компромисс.

Итак, я начал читать о слиянии, с которым я столкнулся в R. Bird Thinking Functionally с Haskell, и почти сразу попробовал это.

mai n = mergeAll.map (iterate (*n))
mai 2 $ mai 3 $ iterate (*5) 1

Это соответствовало Уиллу в 0.08 для 100K Хеммингов в GHCi но что действительно удивило меня (это также для 100K Хэммингов.) Это и особенно прошедшие времена. 100K до 2.9e + 38.

 TASKS: 3 (1 bound, 2 peak workers (2 total), using -N1)

  SPARKS: 0 (0 converted, 0 overflowed, 0 dud, 0 GC'd, 0 fizzled)

  INIT    time    0.000s  (  0.000s elapsed)
  MUT     time    0.000s  (  0.002s elapsed)
  GC      time    0.000s  (  0.000s elapsed)
  EXIT    time    0.000s  (  0.000s elapsed)
  Total   time    0.000s  (  0.002s elapsed)

  Alloc rate    0 bytes per MUT second

  Productivity 100.0% of total user, 90.2% of total elapsed