我是Haskell的新手,我正在尝试编写一个优雅的函数将任意数量的排序列表合并到一个排序列表中......任何人都可以提供优雅高效的参考实现吗?
谢谢!
答案 0 :(得分:8)
这样的事情应该有效:
merge2 pred xs [] = xs
merge2 pred [] ys = ys
merge2 pred (x:xs) (y:ys) =
case pred x y of
True -> x: merge2 pred xs (y:ys)
False -> y: merge2 pred (x:xs) ys
merge pred [] = []
merge pred (x:[]) = x
merge pred (x:xs) = merge2 pred x (merge pred xs)
此处,函数 merge2 合并了2个列表。函数合并合并列表列表。 pred 是您用于排序的谓词。
示例:
merge (<) [[1, 3, 9], [2, 3, 4], [7, 11, 15, 22]]
应该返回
[1,2,3,3,4,7,9,11,15,22]
答案 1 :(得分:2)
由于我喜欢利用中缀运算符和高阶函数,我会写,
infixr 5 @@
(@@) :: (Ord a) => [a] -> [a] -> [a]
-- if one side is empty, the merges can only possibly go one way
[] @@ ys = ys
xs @@ [] = xs
-- otherwise, take the smaller of the two heads out, and continue with the rest
(x:xs) @@ (y:ys) = case x `compare` y of
LT -> x : xs @@ (y:ys)
EQ -> x : xs @@ ys
GT -> y : (x:xs) @@ ys
-- a n-way merge can be implemented by a repeated 2-way merge
merge :: (Ord a) => [[a]] -> [a]
merge = foldr1 (@@)
在这里,xs @@ ys
按照自然顺序合并两个列表(并删除重复项),而merge [xs, ys, zs..]
合并任意数量的列表。
这导致Hamming numbers的非常自然的定义:
hamming :: (Num a, Ord a) => [a]
hamming = 1 : map (2*) hamming @@ map (3*) hamming @@ map (5*) hamming
hamming = 1 : merge [map (n*) hamming | n <- [2, 3, 5]] -- alternative
-- this generates, in order, all numbers of the form 2^i * 3^j * 5^k
-- hamming = [1,2,3,4,5,6,8,9,10,12,15,16,18,20,24,25,27,30,32,36,40,45,48,50,..]
{-# LANGUAGE ViewPatterns #-}
import qualified Data.Map as M
import Data.List (foldl', unfoldr)
import Data.Maybe (mapMaybe)
-- merge any number of ordered lists, dropping duplicate elements
merge :: (Ord a) => [[a]] -> [a]
-- create a map of {n: [tails of lists starting with n]}; then
-- repeatedly take the least n and re-insert the tails
merge = unfoldr ((=<<) step . M.minViewWithKey) . foldl' add M.empty where
add m (x:xs) = M.insertWith' (++) x [xs] m; add m _ = m
step ((x, xss), m) = Just (x, foldl' add m xss)
-- merge any number of ordered lists, preserving duplicate elements
mergeDup :: (Ord a) => [[a]] -> [a]
-- create a map of {(n, i): tail of list number i (which starts with n)}; then
-- repeatedly take the least n and re-insert the tail
-- the index i <- [0..] is used to prevent map from losing duplicates
mergeDup = unfoldr step . M.fromList . mapMaybe swap . zip [0..] where
swap (n, (x:xs)) = Just ((x, n), xs); swap _ = Nothing
step (M.minViewWithKey -> Just (((x, n), xs), m)) =
Just (x, case xs of y:ys -> M.insert (y, n) ys m; _ -> m)
step _ = Nothing
答案 2 :(得分:1)
如果效率不是我关注的问题
merge = sort . concat
否则:
merge :: Ord a => [[a]] -> [a]
merge [] = []
merge lists =
minVal : merge nextLists
where
heads = map head lists
(minVal, minIdx) = minimum $ zip heads [0..]
(pre, ((_:nextOfMin):post)) = splitAt minIdx lists
nextLists =
if null nextOfMin
then pre ++ post
else pre ++ nextOfMin : post
但请注意,此实现始终线性搜索最小值(而对于大量列表,可能希望维护堆等)。
答案 3 :(得分:1)
与其他帖子不同,我会merge :: [a] -> [a] -> [a]
type SortedList a = [a]
merge :: (Ord a) => SortedList a -> SortedList a -> SortedList a
merge [] ys = ys
merge xs [] = xs
merge (x:xs) (y:ys)
| x < y = x : merge xs (y : ys)
| otherwise = y : merge (x : xs) ys
mergeAll :: (Ord a) => [SortedList a] -> SortedList a
mergeAll = foldr merge []
答案 4 :(得分:1)
快速说明一下:如果你想在合并多个列表时(例如你得到一个优先级队列)拥有最佳的log n行为,你可以通过调整上面的Igor的漂亮解决方案来轻松完成。 (我会把它作为对上述答案的评论,但我没有足够的声誉。)特别是,你这样做:
merge2 pred xs [] = xs
merge2 pred [] ys = ys
merge2 pred (x:xs) (y:ys) =
case pred x y of
True -> x: merge2 pred xs (y:ys)
False -> y: merge2 pred (x:xs) ys
everyother [] = []
everyother e0:[] = e0:[]
everyother (e0:e1:es) = e0:everyother es
merge pred [] = []
merge pred (x:[]) = x
merge pred xs = merge2 pred (merge pred . everyother $ xs)
(merge pred . everyother . tail $ xs)
请注意,一个真正的优先级队列会更快/更节省空间,但是这个解决方案渐渐地同样好,而且正如我所说的那样,它的优势在于它对Igor上面非常清晰的解决方案进行了非常小的调整。
评论