views:

410

answers:

9

How would I print the multiples of a list of given numbers in a merged, sorted list?

I.e.

take 10 (multiples [4,5])

gives

4,5,8,10,12,15,16,20,24,25

I've got it working for lists of size 2 or 1 but I need a more general solution :)

A: 

Perhaps:

let howmany = 10 in take howmany (nub (sort [ x * y | x <- [4, 5, 6, 7], y <- [1..howmany] ]))

Gives:

[4,5,6,7,8,10,12,14,15,16]

Haskell isn't my strong point and it's rather inefficient for larger lists, but it works (I think!).

You will need the List module :l List at hugs/ghci.

Tim Green
You need to specify "howmany" beforehand, so this was not what was intended.
ShreevatsaR
+1  A: 
multi xs = [x*y | y <- [1..], x <- xs ]

That should do. The main problem is that it is a little hard to control how many numbers you should take.

To avoid multiple equal numbers in the result apply Data.List.nub on the resulting list. This isn't terribly sophisticated and can be done faster but does the job.

pmr
This nearly works for what I need apart from:take 10(multi [2,3])2,3,4,6,6,9,8,12,10,15
handheldpenguin
@handheldpenguin: I updated the answer to hopefully reflect your requirement. You should state what is wrong with the result instead of just showing a result and hope someone will figure out what is wrong with it. Makes life a lot easier.
pmr
Cheers, that's done the trick.
handheldpenguin
This works only if no number in the list is more than twice the other. Try `take 10 $ multi [4,9]` and you get [4,9,8,18,12,27,16,36,20,45], which is all out of order.
ShreevatsaR
This doesn't work insofar as the resulting list isn't sorted, so take n won't get the n lowest multiples.
MtnViewMark
I tried putting sort (nub...) but that just hung. Any ideas?
handheldpenguin
@MtnViewMark: Thanks for pointing it out. Unfortunately I can't delete an accepted answer and another answer should be accepted.
pmr
Even though it was downvoted, this works: http://stackoverflow.com/questions/2692152/haskell-multiples-of-numbers-in-a-list/2692278#2692278 - I tried a lot and I couldn't get it right with just a list comprehension, they don't offer enough flexibilty :/
LukeN
@handheldpenguin: It's the sorting that's not playing well with infinite lists. You have to call sort after `take`ing!
LukeN
@sadface: You cannot sort *after* `take`ing, because with this (or your) approach, you don't know how many multiples of each number you need to take; that depends on the relative magnitudes of the numbers in the input.
ShreevatsaR
A: 

This does work, but not for infinite lists:

sort [ x * y | x <- [4, 5], y <- [1..10] ]

Thus you have to specify how many multiples you want inside the [1..10] part. Bad thing is, it won't respect [5,4] for example, just sorting it to the same thing.


Okay, a better one:

multiples :: (Num a, Ord a) => [a] -> [a] 
multiples nums = sort $ multiply 1
   where multiply n = map (*n) nums ++ (multiply $ n + 1)

take 10 $ multiples [4,5]

[4,5,8,10,12,15,16,20,20,25]

You might want to add a "nub" there, to remove double numbers

multiples :: (Num a, Ord a) => [a] -> [a] 
multiples nums = sort . nub $ multiply 1
   where multiply n = map (*n) nums ++ (multiply $ n + 1)
LukeN
Why the downvote? It does exactly what handheldpenguin wanted... at least say what's wrong instead of making an unreasonable downvote.
LukeN
I didn't downvote this, but I can guess: your original code required specifying the "10" beforehand, which is not what the question wanted. (Even your new code isn't perfect: try `take 10 $ multiples [4,5,9]`; the output isn't sorted.)
ShreevatsaR
Oh well, sorting, just a sec... edited - and failed. Sorting just does not play well with infinite lists - sorry for acting up, my solution truly is invalid for the sorting part.
LukeN
+8  A: 

Here are two efficient solutions that produce sorted, infinite lists without duplicates, which you can take from. Suppose your input to multiples has n elements.

O(n) per element

First, for each number in the input make an infinite list of its multiples. Then, merge these lists carefully, keeping them sorted and avoiding duplicates. (This is the harder part of the problem.)

multiples xs = merge [map (*x) [1..] | x<-xs]

merge xss
    | all null xss = []
    | otherwise    = m : merge (map (avoid m) xss)
    where
      m = minimum [head xs | xs<-xss, xs/=[]]
      avoid m (x:xs) | m==x  = xs
      avoid m   xs           = xs

(Code cleaned up from original version, thanks to MtnViewMark's comments.) This works:

*Main> take 20 $ multiples [4,6,9]
[4,6,8,9,12,16,18,20,24,27,28,30,32,36,40,42,44,45,48,52]

This implementation of merge is more efficient than merging the lists two at a time, and it takes only O(n) time to produce each element of output.

O(log n) per element

A more (and AFAICT, most) efficient algorithm is to generate the multiples as you need them, by keeping candidates in a heap. This takes only O(log n) time for each output element.

import Data.Heap as Heap (MinHeap, insert, empty, view)

multiplesH xs = uniq $ tail $ map fst $ iterate (next . snd) (0, prep xs)
    where
      prep :: Ord a => [a] -> MinHeap (a,a)
      prep = foldr (\x -> insert (x,x)) empty
      next h = case view h of Just ((x,n),hh) -> (x, insert (x+n,n) hh)
      uniq (x:y:ys) | x==y  = uniq (y:ys)
      uniq (x:xs)           = x: (uniq xs)
      uniq []               = []

When you have only a few numbers they're not much different, but for large n the heap version is much faster:

*Main> :set +s
*Main> multiples [1000..2000] !! 10000
20088
(21.70 secs, 2108213464 bytes)
*Main> multiplesH [1000..2000] !! 10000
20088
(0.08 secs, 15348784 bytes)
ShreevatsaR
As per your request: Learn to use 'map' rather than list comprehensions. Instead of "[notm m xs | xs<-xss]" you should write "map (notm m) xss", "allEmpty xss = and [xs==[] | xs<-xss]" becomes "allEmpty xss = and $ map (==[]) xss". Then learn 'all' and 'null', and this becomes just "allEmpty = all null"! Use 'filter', and now you can write "m = minimum $ map head $ filter (not.null) xss". Finally, you can drop the first clause for multiples by writing 'multiples xs = merge $ map (\n -> map (*n) [1..]) xs".
MtnViewMark
@MtnViewMark: Thanks very much for your comments, the code looks much better. I didn't realise the first clause was redundant! And I knew of `null`, but had forgotten that `all = (and . map)`. I decided to keep two of the list comprehensions because I felt they were more readable, but maybe with more familiarity with reading others' Haskell code, this feeling will change. :-)
ShreevatsaR
Actually, `all` is `(and.) . map` (because `all f x = and (map f x)`)
sdcvvc
+4  A: 

Each number in the argument becomes an infinite list of multiples

multiLists :: [Integer] -> [[Integer]]
multiLists = map (\x -> iterate (+x) x)

Then you need to merge the resulting lists. Since each list is guaranteed to be in ascending order you can just use a merge function like the one at the end of this page.

Finally, you may want to eliminate duplicates. The way to do this with a sorted list is:

sortedNub :: [Integer] -> [Integer]
sortedNub = map head . group
Paul Johnson
Right. That's the same as my answer, except that I implemented merge from scratch too (and which I think is the harder part of the problem). The `merge` on that page is for only two lists... while it is clear that you could merge a list of lists two-at-a-time, that would be quite inefficient, wouldn't it?
ShreevatsaR
You could fold the merge function over the list of lists, which would be O(n) on the number of lists, or you could be clever and divide them into a binary tree, then merge branches on that tree.
Paul Johnson
Uh, this code does not actually work as required. You cannot sort unless you know beforehand how many elements of each list you're going to need. What is your actual multiples function which for `take 10 $ multiples [4, 6, 11]` would give `[4,6,8,11,12,16,18,20,22,24]`? (Note that for 10 elements, you need to take 6 multiples of 4, 4 multiples of 6, and 2 of 11, and avoid the 2 duplicates 12 and 24.)
ShreevatsaR
+2  A: 

Here's a version that always produces sorted results, removes duplicates, produces an infinite list (which you can take from), and is relatively efficient (should be constant memory!):

multiples :: (Num a, Ord a) => [a] -> [a]
multiples = map (fst.head) . iterate step . prep
    where prep                    = map (\i -> (i,i))
          next (m,i)              = (m+i,i)

          step (p:ps)             = uniq $ insert (next p) ps

          insert q  []            = [q]
          insert q (p:ps) | q > p = p : insert q ps
          insert q  ps            = q : ps

          uniq p@((ma,_):(mb,_):_) | ma == mb = step p
          uniq p                              = p

Example:

> take 20 $ multiples [4,9]
[4,8,9,12,16,18,20,24,27,28,32,36,40,44,45,48,52,54,56,60]

> take 20 $ multiples [4,8,10]
[4,8,10,12,16,20,24,28,30,32,36,40,44,48,50,52,56,60,64,68]

> take 20 $ multiples [4, 9, 20]
[4,8,9,12,16,18,20,24,27,28,32,36,40,44,45,48,52,54,56,60]

Note: assumes the input list is sorted. Add . sort after . prep to remove this constraint.

MtnViewMark
+1, clever if involved idea. It's frustrating that *correct-looking* answers are often voted more highly on StackOverflow than correct ones like this. It's not constant memory, though, and in fact the memory usage seems to grow super-linearly (why is this?). With :set +s in ghci, try `(multiples [4,6,9]) !! 300000` (or similar large number that will cause a stackoverflow)... my code seems to have slightly longer running time, but with memory growing (apparently) linearly. BTW could you look at my code and tell me what would be the Haskell way of writing it? :-)
ShreevatsaR
+1  A: 

I see this as a filter on the list of integers.

All you need is a predicate that determines whether an integer is a multiple of an item in your list.

And then filter [1..] by that predicate.

multiples xs = filter (isDividedByAny xs) [1..]
       where isDividedByAny xs int =  any (divides int) xs
                      where divides int elem  = int `mod` elem == 0
jakebman
If the input list is expected to be a short list of relatively small integers, then this is a small, compact, elegant solution. However, if the input list is either a long list, or contains large integers, then this version does a large amount of work per generated result, compared to the others.
MtnViewMark
+1  A: 

I'm surprised to see that "Hamming problem" is not mentioned: the Hamming problem is one of the classical examples of lazy functional programming that David Turner presented for his FP, the first Haskell-like language, Miranda.

The Hamming problem is the same as is similar to multiples [2,3,5], and Turner's solution is (see comments below):

ham = 1 : foldr1 merge [mult 2 ham, mult 3 ham, mult 5 ham]
      where
      mult n x = [n*a|a<-x]
      merge (a:x) (b:y) = a : merge x y,     if a=b
                        = a : merge x (b:y), if a<b
                        = b : merge (a:x) y, if a>b

(From Turner's Example Miranda scripts)

This directly generalises to (assuming all elements passed to multiples are greater than 1 and, contrary to the question, that the parameter list is increasing):

multiples ms = drop 1 mms
      where mms = 1: foldr1 merge (map (mult mms) ms))
            mult x n = [n*a|a<-x]
            merge (a:x) (b:y) = a : merge x y,     if a=b
                              = a : merge x (b:y), if a<b
                              = b : merge (a:x) y, if a>b

There was a discussion of four kinds of solution to the Hamming problem on LtU: expressivity of "idiomatic C++".

Charles Stewart
I upvoted this, but this solves a different problem. Hamming's problem is to generate numbers which are multiples of *only* 2, 3, and 5 (excluding numbers like 14), but in the question here, `multiples [4,5]` includes e.g. 12 and 15. Also, I doubt that this is faster than MtnViewMark's (or my) solution (in fact the idea is similar to the former). It takes constant time per element only in the original problem where the size of [2,3,5] was constant. What *would* be really cool would be to see a solution that uses heaps and gets the O(log N) per element which is (AFAIK) the best one can do.
ShreevatsaR
@Shreevatsa: Ah, I misunderstood the question! It's a piece of historical culture then. Changing the function mult to the analogous function based on addition is then an answer, with no claims made about efficiency.
Charles Stewart
Actually, changing multiplication to addition (naïvely) would probably introduce things like 7(=2+5) in `multiples [2, 3, 5]`, but you're right it can be done cleverly. This is what MtnViewMark has done (AFAICT).
ShreevatsaR
+1  A: 

Another answer? Well, one way to construe this problem is as a generalized merge. I became a little obsessed with finding both a relatively clean and efficient method of doing a multi-way merge.

This merge function takes any finite number of arbitrary lists as input and produces their merge. The only precondition is that the lists are sorted. The lists can be empty or infinite:

merge :: (Ord a) => [[a]] -> [a]
merge rs =
    case foldr minToFront [] rs of
        []          -> []
        ([]:rs)     ->     merge rs
        ((a:as):rs) -> a : merge (as:rs)
    where
        minToFront a (b:rs) | a `after` b = b:a:rs
        minToFront a  qs                  = a:qs

        []    `after` _     = False
        _     `after` []    = True
        (a:_) `after` (b:_) = a > b

This code makes just one pass through the heads of the input lists for each element produced.

Once you have this, defining the original function is easy:

multiples :: (Num a, Ord a) => [a] -> [a]
multiples = uniq . merge . map (\n -> iterate (+n) n)

You need another nice generalized utility function to strip out repeated answers. Named for the unix utility, here it is:

uniq :: (Eq a) => [a] -> [a]
uniq :: (Eq a) => [a] -> [a]
uniq []                       = []
uniq (a:bs@(b:_)) | a == b    =     uniq bs
uniq (a:bs)                   = a : uniq bs

You can actually turn that little snippit into a fully workable equivalent to the uniq command line utility (well, ignoring command line options) with just this simple code:

main :: IO ()
main = interact (unlines . uniq . lines)

Haskell makes me smile!

MtnViewMark
What's wrong with the merge I wrote? :-) It also takes any finite number of arbitrary lists as input and produces their merge, without duplicates. It takes two passes, but doing the minimum in a separate step won't hurt the complexity, since it's still O(number of lists). Is there a difference I'm not noticing?
ShreevatsaR
(+1, BTW.) I also just wrote a `uniq` by hand (see updated answer, I implemented the O(log n) algorithm I had in mind), but as Paul Johnson's answer points out, we can also just write `uniq = map head . group`. If you have time, it would be good to compare the different implementations of "unique merge" we have so far (your `uniq.merge`, and my `merge`), and learn how they differ in running time and memory usage (I expect mine is slower, but not by much).
ShreevatsaR
@ShreevatsaR - Your original merge does three passes per step: "all null", "map (avoid m)" and "minimum [...]". And, given they way they are written, they won't be collapsed into a single loop by the optimizer. The thing I didn't like in your merge was that it will spends both time and code repeatedly handling exhausted inputs, since they are never removed from the input. That said, your version is perfectly workable. -- It would be nice to benchmark the various versions given here... I might do that tonight!
MtnViewMark
Good point... it would be great to benchmark them!
ShreevatsaR
Benchmarked: http://bitbucket.org/mtnviewmark/haskell-playground/src/tip/StackOverflow/Multiples.hs -- enjoy!
MtnViewMark
Awesome, thank you! [Among other observations: the merge that makes 3 passes instead of one takes about thrice as long; and an algorithmic improvement can make a difference sometimes.]
ShreevatsaR