Новое современное состояние в неограниченном поколении последовательности Хэмминга
(это интересно!) Я знаю, предмет хорошо известен. Современное состояние (в 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