it-swarm.com.ru

Как найти список всех чисел, кратных только степеням 2, 3 и 5?

Я пытаюсь создать список всех кратных, которые могут быть представлены в форме 2^a*3^b*5^cгде a, b и c - целые числа. Я попробовал следующее,

[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ] 

но он перечисляет только степени 5 и никогда не переходит к 2 или 3.

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

4
robbie0630

Причина, по которой существуют только степени 5, состоит в том, что Haskell пытается оценить все возможные значения c для a = 2 ^ 0 и b = 3 ^ 0, и только когда он закончится, он перейдет к a = 2 ^ 0 и b = 3 ^ 1 . Таким образом, вы можете построить только конечный список, например так:
[ a * b * c | a <- map (2^) [0..n], b <- map (3^) [0..n], c <- map (5^) [0..n] ]
для данного n.

4
Kacper Korban

Моя первая идея исходила из списков степеней 2, 3 и 5 соответственно:

p2 = iterate (2 *) 1
p3 = iterate (3 *) 1
p5 = iterate (5 *) 1

Также легко объединить два отсортированных потока:

Fuse [] ys = ys
Fuse xs [] = xs
Fuse [email protected](x : xs') [email protected](y : ys')
    | x <= y    = x : Fuse xs' ys
    | otherwise = y : Fuse xs ys'

Но потом я застрял, потому что Fuse p2 (Fuse p3 p5) не делает ничего полезного. Он производит только кратные 2, 3 или 5, никогда не смешивая факторы.

Я не мог найти чисто генеративное решение, поэтому добавил немного фильтрации в виде набора аккумуляторов. Алгоритм (который является весьма обязательным):

  1. Инициализируйте аккумулятор в {1}.
  2. Найти и удалить наименьший элемент из аккумулятора; назовите это n.
  3. Emit n.
  4. Добавьте {2n, 3n, 5n} к аккумулятору.
  5. Перейдите к # 2, если вам нужно больше элементов.

Аккумулятор - это набор, потому что он позволяет мне легко находить и извлекать наименьший элемент (в основном я использую его в качестве очереди с приоритетами). Он также обрабатывает дубликаты, которые возникают, например, вычисление и 2 * 3 и 3 * 2.

Реализация на Haskell:

import qualified Data.Set as S

numbers :: [Integer]
numbers = go (S.singleton 1)
    where
    go acc = case S.deleteFindMin acc of
        (n, ns) -> n : go (ns `S.union` S.fromDistinctAscList (map (n *) [2, 3, 5]))

Это работает, но есть вещи, которые мне не нравятся:

  • Для каждого элемента, который мы излучаем (n : ...), мы добавляем до трех новых элементов в накопитель (ns `S.union` ... [2, 3, 5]). («До трех», потому что некоторые из них могут быть дубликатами, которые будут отфильтрованы.)
  • Это означает, что numbers поддерживает постоянно растущую структуру данных; чем больше элементов мы потребляем из numbers, тем больше растет аккумулятор.
  • В этом смысле это не чисто потоковый алгоритм. Даже если мы игнорируем постоянно растущие числа, нам нужно больше памяти и выполнять больше вычислений по мере углубления в последовательность.
3
melpomene

Из вашего кода:

[ a * b * c | a <- map (2^) [0..], b <- map (3^) [0..], c <- map (5^) [0..] ] 

Поскольку map (5^) [0..] является бесконечным списком, при первых итерациях a и b он повторяется по указанному бесконечному списку, который не останавливается. Вот почему он застрял на уровне 5.

Вот решение кроме арифметики. Обратите внимание, что map (2^) [0..], map (3^) [0..] и map (5^) [0..] - это списки, отсортированные по возрастанию. Это означает, что применима обычная операция слияния:

merge []     ys     = ys
merge xs     []     = xs
merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys) else y : merge (x:xs) ys

Для удобства let xs = map (2^) [0..]; let ys = map (3^) [0..]; let zs = map (5^) [0..].

Чтобы получить кратные 2 и 3, рассмотрите следующую организацию указанных чисел:

1, 2, 4, 8, 16, ...
3, 6, 12, 24, 48, ...
9, 18, 36, 72, 144, ...
...

Судя по этому, можно надеяться на следующие работы:

let xys = foldr (merge . flip fmap xs . (*)) [] ys

Но это не работает, потому что из вышеприведенной организации merge не знает, какая строка содержит результирующий элемент head, бесконечно оставляя его без оценки. Мы знаем, что в верхнем ряду содержится указанный элемент head, поэтому с помощью небольшого твика это наконец-то работает:

let xys = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xs . (*)) [] ys

Сделайте то же самое с zs, и вот вам нужный список:

let xyzs = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xys . (*)) [] zs

Полный код в резюме:

merge []     ys     = ys
merge xs     []     = xs
merge (x:xs) (y:ys) = if x <= y then x : merge xs (y:ys) else y : merge (x:xs) ys

xyzs = let
    xs = map (2^) [0..]
    ys = map (3^) [0..]
    zs = map (5^) [0..]
    xys = foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xs . (*)) [] ys
    in foldr ((\(m:ms) ns -> m : merge ms ns) . flip fmap xys . (*)) [] zs
2
Dannyu NDos

но он перечисляет только степени 5 и никогда не переходит к 2 или 3.

Обращаясь только к этому биту . Чтобы вычислить числа 2^a*3^0b*5^c, вы попытались сгенерировать тройки (a,b,c), но застряли, производя числа вида (0,0,c). Вот почему все ваши номера имеют форму 2^0*3^0*5^c, то есть только степени 5.

Это проще, если вы начнете с пар. Для производства всех пар (a,b) вы можете работать по диагонали вида, 

a+b = k

за каждое положительное значениеk. Каждую диагональ легко определить, 

diagonal k = [(k-x,x) | x <- [0..k]]

Таким образом, чтобы получить все пары, вы просто сгенерируете все диагонали для k<-[1..]. Вы хотите тройки (a,b,c), хотя, но это похоже, просто работать вдоль самолетов, 

a+b+c = k

Чтобы создавать такие самолеты, просто работайте вдоль их диагоналей, 

triagonal k = [(k-x,b,c) | x <- [0..k], (b,c) <- diagonal x]

И вот, пожалуйста. Теперь просто сгенерируйте все «триагоналы», чтобы получить все возможные тройки,

triples = [triagonal k | k <- [0..]]
2
Jorge Adriano

Другой способ взглянуть на это - вы хотели получить числа, которые делятся только на 2,3 или 5. Поэтому проверьте, удовлетворяет ли каждое число, начинающееся с 1, этому условию. Если да, то это часть списка. 

someList = [x| x<- [1..], isIncluded x]

где isIncluded - это функция, которая решает, удовлетворяет ли x вышеуказанному условию. Для этого isIncluded делит число сначала на 2, пока оно не может быть разделено дальше на 2. Затем то же самое происходит с новым разделенным числом для 3 и 5. Если в конце есть 1, то мы знаем, что это число делится только на 2 , 3 или 5 и ничего больше. 

Это может быть не самый быстрый способ, но все же самый простой способ. 

isIncluded :: Int -> Bool  
isIncluded n = if (powRemainder n 2 == 1) then True 
                                          else let q = powRemainder n 2 
                                           in if (powRemainder q 3 == 1) then True 
                                                                          else let p = powRemainder q 3 
                                                                               in if (powRemainder p 5 == 1) then True else False;

powRemainder - это функция, которая принимает число и основание и возвращает число, которое нельзя разделить на основание. 

powRemainder :: Int -> Int -> Int
powRemainder 1 b = 1
powRemainder n b = if (n `mod` b) == 0 then powRemainder (n `div` b) b else n

с этим, когда я запускаю take 20 someList, он возвращает [1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36]

1
Manoj R

Как уже отмечали другие, ваше ядро ​​не работает, потому что оно аналогично следующему обязательному псевдокоду:

for x in 0..infinity:
   for y in 0..infinity:
      for z in 0..infinity:
         print (2^x * 3^y * 5^x)

Для выполнения самой внутренней for требуется бесконечное время, поэтому два других цикла никогда не пройдут свою первую итерацию. Следовательно, x и y оба привязаны к значению 0.

Это классическая проблема ласточкин хвоста : если мы настаиваем на том, чтобы попробовать все значения z, прежде чем брать следующую y (или x), мы застряли в подмножестве предполагаемых выходных данных. Нам нужен более «справедливый» способ выбора значений x,y,z, чтобы мы не застревали таким образом: такие методы известны как «ласточкин хвост».

Другие показали некоторые методы ласточкин хвост. Здесь я упомяну только пакет control-monad-omega , который реализует простую в использовании монаду "ласточкин хвост". Полученный код очень похож на тот, который выложен в ОП.

import Control.Monad.Omega

powersOf235 :: [Integer]
powersOf235 = runOmega $ do
   x <- each [0..]
   y <- each [0..]
   z <- each [0..]
   return $ 2^x * 3^y * 5^z
1
chi