获取Haskell中列表的所有排列

时间:2016-10-17 23:16:59

标签: haskell

我试图从头开始,不使用标准库之外的库。继承我的代码:

permutations :: [a] -> [[a]]
permutations (x:xs) = [x] : permutations' xs
    where permutations' (x:xs) = (:) <$> [x] <*> split xs
            split l = [[x] | x <- l]

问题是这只产生一个非确定性计算的分支。理想情况下我想要

(:) <$> [x] <*> ((:) <$> [x] <*> ((:) <$> [x] <*> ((:) <$> [x] <*> xs)))

但我找不到干净利落的方法。我想要的结果是这样的:

permutations "abc" -> ["abc", "acb", "bac", "bca", "cab", "cba"]

我该怎么做?

8 个答案:

答案 0 :(得分:19)

也许您应该使用现有代码:

import Data.List
permutations [1,2,3,4]

答案 1 :(得分:4)

对于一个简单的实现,不考虑输入中的重复

permutations :: Eq a => [a] -> [[a]]
permutations [] = [[]]
permutations as = do a <- as
                     let l = delete a as
                     ls <- permutations l
                     return $ a : ls

测试:

λ> permutations [1,2,3]
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
λ> permutations "abc"
["abc","acb","bac","bca","cab","cba"]
λ> 

Algorithm Reference

答案 2 :(得分:1)

我这样做:

select :: [a] -> [(a,[a])]
select = select' id where
  select' _ [] = []
  select' acc (a:r) = (a, acc r) : select' (acc . (a:)) r

permutations [] = [[]]
permutations l = do
  (a,r1) <- select l
  r2 <- permutations r1
  return (a: r2)

答案 3 :(得分:1)

我对Haskell比较新,但I had developed a very efficient permutations algorithm for JS。它几乎击败了堆算法,但在JS中,与列表上的惰性Haskell iterate函数相比,旋转数组的成本更高。因此,与上述所有提供的答案不同,这一点似乎更有效率。

内置Data.List.permutations仍然比今天快2倍,因为我根本不知道Haskell的性能限制。可能是这里的某个人可以帮我推进这个代码。

所以我有一个辅助函数,它返回所提供列表的所有旋转列表。如

rotations [1,2,3]会产生[[1,2,3],[2,3,1],[3,1,2]]

因此perms函数是;

rotations :: [a] -> [[a]]
rotations xs = take (length xs) (iterate (\(y:ys) -> ys ++ [y]) xs)

perms :: [a] -> [[a]]
perms []     = [[]]
perms (x:xs) = concatMap (rotations.(x:)) (perms xs)

编辑:所以我一直在考虑如何提高上述代码的效率。确定Haskell中的列表是链接列表,与JavaScript不同,长度不是您可以在O(1)时间访问但O(n)的属性。它是遍历整个该死的列表的函数,基本上计算列表中的所有项目。因此反复使用非常昂贵。这恰好是我们在每次调用rotate函数时通过take (length xs)指令所做的事情。如果您的输入列表长度为10-11项或更长,我们实际上会调用数百万次。削减它将产生巨大的节省。然后我们不要让它计算相同长度列表的长度,而是让它简单地提供它;

rotations :: Int -> [a] -> [[a]]
rotations len xs = take len (iterate (\(y:ys) -> ys ++ [y]) xs)

美丽。那么,现在我们必须稍微修改我们的perms函数,如;

perms :: [a] -> [[a]]
perms []        = [[]]
perms il@(x:xs) = concatMap ((rotations len).(x:)) (perms xs)
                  where len = length il

所以显然il现已分配给 i 输入 l ist,len缓存它的长度。现在这很漂亮,非常有趣的是它比默认的Data.List.permutations运行 更快

到目前为止一直很好......但是现在是时候作弊了一点,让它变得更快。我知道的一件事,正如我刚刚证明的那样,在CS中,算法的最佳燃料是缓存。那么我们在这里继续缓存什么呢?我们可以轻松缓存[x][x,y][x,y,z]等输入的返回值,并保存一些计算。你想要更快的速度..?然后无耻地缓存[w,x,y,z]。让我们看一下整个新代码;

rotations :: Int -> [a] -> [[a]]
rotations len xs = take len (iterate (\(y:ys) -> ys ++ [y]) xs)

perms :: [a] -> [[a]]
perms []        = [[]]
perms [x]       = [[x]]
perms [x,y]     = [[x,y],[y,x]]
perms [x,y,z]   = [[x,y,z],[y,z,x],[z,x,y],[x,z,y],[z,y,x],[y,x,z]]
perms il@(x:xs) = concatMap ((rotations len).(x:)) (perms xs)
                  where len = length il

欢迎任何想法......

答案 4 :(得分:1)

我认为对于其他人的建议,这是更短更优雅的变体:

 <Person>
    <ttId>1408</ttId>
    <FirstName>RAJ</FirstName>
    <NationalityValue>INDIAN</NationalityValue>
    <Sex>Male</Sex>
    </Person>

答案 5 :(得分:0)

它已经在标准的base库中了,所以不需要挣扎。如果你真的想看看如何做,你可以查看该库的来源。

答案 6 :(得分:0)

monads的一切都更好:

perm :: [a] -> [[a]]
perm []     = return []
perm (x:xs) = (perm xs) >>= (ins x)
    where
    ins :: a -> [a] -> [[a]]
    ins x []     = [[x]]
    ins x (y:ys) = [x:y:ys] ++ ( map (y:) (ins x ys) )

所以:你有一个函数,它在一个单词中插入字母,但是它会生成多个单词,那么如何递归地应用它呢? >>=有帮助!

答案 7 :(得分:0)

我解决了这个问题,然后找到了讨论。这是使用递归的简短解决方案。 doPerm的第一个参数包含适合于排列中任何位置的元素,第二个参数元素仅适合于除第一个位置以外的其他位置。

permutations :: [a] -> [[a]]
permutations xs = doPerm xs []
  where
    doPerm [] _ = [[]]
    doPerm [y] ys = (y:) <$> doPerm ys []
    doPerm (y : ys) zs = doPerm [y] (ys ++ zs) ++ doPerm ys (y : zs)

这是一个示例运行:

λ> permutations "abc"
["abc","acb","bca","bac","cba","cab"]