如何制作多个数字的倍数排序列表?

时间:2019-01-09 10:13:52

标签: list haskell

我在处理Haskell类的作业时遇到麻烦。我已经解决了此任务的部分问题:我必须编写一个函数,该函数接受一个I​​nt并使用该Int的倍数创建一个无限列表。

function :: Int -> [Int]
function d = [d*x | x <- [1..]]

控制台:

ghci> take 10 (function 3)

给予

[3,6,9,12,15,18,21,24,27,30]

在第二个任务中,我必须扩展该函数,以便它接受一个数字列表,并将该列表的每个值用作因子(以前是d)。例如:

ghci> take 10 (function [3, 5])

应提供

[3,5,6,9,10,12,15,18,20,21]

已经尝试过类似列表理解

function d = [y*x | y <- [1..], x <- d]

但是该函数以未排序的形式返回列表:

[3,5,6,10,9,15,12,20,15,25]

我们得到了应该使用Haskell的模函数的提示,但是我不知道如何准确地进行操作。你对我有个好建议吗?

5 个答案:

答案 0 :(得分:3)

如果您认为d不是一个因素

y = x * d 

但是

y `mod` d == 0,

然后,您可以从列表[1..]中获取列表推导,并添加谓词函数,例如:

function ds 
    | null ds   = [1..]
    | otherwise = [ x | x <- [1..], qualifies x ]
    where
      qualifies x = any (==0) $ (flip mod) <$> ds <*> [x]

更具表现力的版本,一开始可能更容易理解:

function' ds   
    | null ds   = [1..]
    | otherwise = [ x | x <- [1..], divByAnyIn ds x ]
    where
      divByAnyIn ds x = 
          case ds of
            (d:ds') -> if x `mod` d == 0 then True 
                                         else divByAnyIn ds' x
            _       -> False

答案 1 :(得分:2)

我只有一支班轮。

import Data.List (nub)

f xs = nub [x|x<-[1..], d<-xs, x `mod` d == 0]

take 10 $ f [3,5] -- [3,5,6,9,10,12,15,18,20,21]
从结果列表中,

运行时应为O(n²+ n * d)。 nub 以O(n²)运行。摆脱它会很好。

g xs = [x |x<-[1..], let ys = map (mod x) xs in 0 `elem` ys]

这很好。它应该以O(n * d)运行。我也有这个版本,我认为它的性能至少和 g 一样好,但是显然它的性能比 f 好,但比 g 差。 / p>

h xs = [x |x<-[1..], or [x `mod` d == 0 |d<-xs] ]

我不确定为什么是懒惰的,据我所知,它运行速度较慢的任何原因都没有。当您增加输入列表的长度时,它尤其不能很好地缩放。

i xs = foldr1 combine [[x, x+x ..] |x<- sort xs]
    where
      combine l [] = l
      combine [] r = r
      combine l@(x:xs) r@(y:ys)
        | x < y = (x: combine xs r)
        | x > y = (y: combine l ys)
        | otherwise = (x: combine xs ys)

不再是一个班轮,但我能想到的最快的。我不能百分百地确定,如果您左右折叠并且预先对输入列表进行排序,为什么它对运行时有如此大的影响?但是,由于以下原因,它不会对结果产生影响:

commutative a b = combine [a] [b] == combine [b] [a]

我认为在将递归函数折叠到输入系数的倍数的无穷列表上时,完全想到这个问题是很疯狂的。

在我的系统上,它仍然比这里使用Data.List.Ordered提供的另一种解决方案慢大约10倍。

答案 2 :(得分:1)

如果要使用模函数,可以定义一个简单的单线

foo ds = filter (\x -> any (== 0) [mod x d | d <- ds]) [1..]

或更容易阅读的形式

foo ds = filter p [1..]
  where
  p x = any id [ mod x d == 0 | d <- ds]
      = any (== 0) [ mod x d | d <- ds]
      = not $ null [ () | d <- ds, mod x d == 0]
      = null [ () | d <- ds, mod x d /= 0]
      = null [ () | d <- ds, rem x d > 0]

有了这个,我们得到

> take 20 $ foo [3,5]
[3,5,6,9,10,12,15,18,20,21,24,25,27,30,33,35,36,39,40,42]

但是,它效率低下:last $ take 20 $ foo [300,500] == 4200,因此要生成这20个数字,此代码将测试4200。数字越大,情况就越糟。

相反,我们应该及时产生n个与n大致成比例的数字。

为此,首先将每个数字的倍数写在自己的列表中:

[ [d*x | x <- [1..]] | d <- ds ] ==
[ [d, d+d ..] | d <- ds ] 

然后合并这些有序的递增数字列表,以一种有序的方式生成一个有序的非递减数字列表。软件包data-ordlist具有许多functions来处理这种列表:

import qualified Data.List.Ordered as O
import           Data.List               (sort)

bar :: (Ord a, Num a, Enum a) => [a] -> [a]
bar ds = foldr    O.merge [] [ [d, d+d ..] | d <- ds ]
       = O.foldt' O.merge [] [ [d, d+d ..] | d <- ds ]   -- more efficient, 
       = O.mergeAll [ [d, d+d ..] | d <- sort ds ]       -- tree-shaped folding

如果我们希望生成的列表不包含任何重复项,即创建一个递增列表,则可以将其更改为

baz ds = O.nub $ foldr O.merge [] [ [d, d+d ..] | d <- ds ]
       = foldr    O.union [] [ [d, d+d ..] | d <- ds ]
       = O.foldt' O.union [] [ [d, d+d ..] | d <- ds ]
       = O.unionAll [ [d, d+d ..] | d <- sort ds ]
       = (O.unionAll . map (iterate =<< (+)) . sort)  ds

哦,与二次Data.List.nub不同,Data.List.Ordered.nub是线性的,在输入列表的每个元素上花费了O(1)时间。

答案 3 :(得分:1)

这里的答案仅显示了这个想法,它不是一个优化的解决方案,可能存在许多实现它的方法。

首先,从输入的列表中计算每个因子的所有值:

map (\d->[d*x|x<-[1..]]) xs

例如:xs = [3, 5]给出

[[3, 6, 9, ...], [5, 10, 15, ...]]

然后,找到每个列表的第一个元素的最小值:

findMinValueIndex::[(Int, [Int])]->Int
findMinValueIndex xss = minimum $ 
                        map fst $ 
                        filter (\p-> (head $ snd p) == minValue) xss
    where minValue = minimum $ map (head . snd) xss

一旦发现列表包含最小值,则将其返回并从列表中删除最小值,如下所示:

sortMulti xss = 
            let idx = findMinValueIndex $ zip [0..] xss
            in  head (xss!!idx):sortMulti (updateList idx (tail $ xss!!idx) xss

例如,在找到结果的第一个值(即3)之后,查找下一个值的列表为:

[[6, 9, ...], [5, 10, 15, ...]]

重复以上步骤,我们可以构建所需的列表。最后,删除重复的值。这是完整的编码:

import Data.Sequence (update, fromList)
import Data.Foldable (toList)

function :: [Int] -> [Int]
function xs = removeDup $ sortMulti $ map (\d->[d*x|x<-[1..]]) xs
    where sortMulti xss = 
            let idx = findMinValueIndex $ zip [0..] xss
            in  head (xss!!idx):sortMulti (updateList idx (tail $ xss!!idx) xss)

removeDup::[Int]->[Int]
removeDup [] = []
removeDup [a] = [a]
removeDup (x:xs) | x == head xs = removeDup xs
                 | otherwise = x:removeDup xs

findMinValueIndex::[(Int, [Int])]->Int
findMinValueIndex xss = minimum $ 
                        map fst $ 
                        filter (\p-> (head $ snd p) == minValue) xss
    where minValue = minimum $ map (head . snd) xss

updateList::Int->[Int]->[[Int]]->[[Int]]
updateList n xs xss = toList $ update n xs $ fromList xss

答案 4 :(得分:1)

有一个很好的递归解决方案

function' :: Int -> [Int]
function' d = [d * x | x <- [1..]]

braid :: [Int] -> [Int] -> [Int]
braid []        bs = bs
braid as        [] = as
braid aa@(a:as) bb@(b:bs) 
  | a < b     = a:braid as bb
  | a == b    = a:braid as bs # avoid duplicates
  | otherwise = b:braid aa bs

function :: [Int] -> [Int]
function ds = foldr braid [] (map function' ds)

braid函数仅使用输入的标题和惰性即可“即时”构建所需列表