【问题标题】:All list rotations in Haskell [duplicate]Haskell中的所有列表轮换[重复]
【发布时间】:2012-08-05 22:02:21
【问题描述】:

我有一个旋转列表的功能:

rotate :: [a] -> [a]
rotate [] = []
rotate (x:xs) = xs ++ [x]

现在我想要一个函数,它给出一个有限列表的每个可能旋转的列表:

rotateAll :: [a] -> [[a]]

在命令式语言中,我会做类似(在伪代码中)

for i = 1 to length of list
  append list to rotateList
  list = rotate(list)

当然,命令式思考可能无法帮助我找到解决这个问题的有效方法。我正在寻找一些关于如何解决这个问题的提示。

其他想法:

为了解决这个问题,我有两个问题要解决。首先,我需要反复轮换一个列表并将每个结果收集到一个列表中。所以第一个解决方案需要做类似的事情

rotateAll xs = [xs (rotate xs) (rotate (rotate xs)) (rotate (rotate (rotate xs))) ...]

当然,我不知道要这样做多少次。我很乐意无限地执行此操作,然后使用take (length xs) 来获得我想要的有限数量的列表。这实际上说明了第二个问题:确定何时停止。我不知道使用 take 是否是解决问题的最有效或最优雅的方法,但在我输入此内容时我想到了它并且应该可以工作。

附录: 现在我已经找到了两种解决方案,或者有提示。我很乐意欢迎任何其他更快或使用不同方法的解决方案。谢谢!

【问题讨论】:

  • 看看iterate函数。
  • @TiloWiklund 这正是我想要的。谢谢!
  • 请注意,您将在每次迭代中遍历列表一次(追加为 O(n),对于 n 列表的长度)和一次用于计算 xs 的长度(即也是 O(n))
  • 有趣的是,您也可以为无限输入列表解决此问题 - 只要您旋转正确的方向。提出一个适用于无限和有限输入的解决方案是一个很好的进一步练习。
  • @TiloWiklund 我通常想先制定一个可行的解决方案,然后再进行调整以提高效率。在最初的解决方案中牢记这些问题仍然是件好事。所以感谢您指出!如果以后有问题,我会重新访问我的 rotate 函数。

标签: list haskell functional-programming


【解决方案1】:

使用Data.List中的预定义函数!您可以使用四个函数调用、没有递归和没有rotate 函数来获取所有旋转的列表。

您要求不要在此处发布完整的解决方案。想看的朋友可以在http://pastebin.com/atGiw1ig看到完整的解决方案(一行代码)。

【讨论】:

  • 有趣的是,这与@Jan 给出的链接中的解决方案相同!
  • 既然这么久了,我想你现在应该完全揭示你的解决方案了。确实是非常好的解决方案。
【解决方案2】:

除了iterate,您还可以编写一个函数来生成一个包含 n 个旋转的列表。 case n=0 只会将输入包装在一个列表中,而 case n=m+1 会将输入附加到 case m 的结果中。虽然通常首选使用标准函数,但有时编写自己的解决方案是健康的。

【讨论】:

  • 奇怪的是,我基本上使用了与我发布问题后想到的相同的想法。我不知道 iterate 函数,所以它将被添加到我的 Haskell aresenal 中。
【解决方案3】:

这是一个在轮换列表本身和列表中的每个单独条目中都完全惰性的版本。关键是,与其预先计算长度,不如将结果中的元素与列表中的元素进行匹配,当输入列表用完时停止。

rotations xs = map (takeSame xs) $ takeSame xs (tails (xs ++ xs)) where
    takeSame [] _ = [] 
    takeSame (_:xs) (y:ys) = y:takeSame xs ys

此外,由于它使用了尾部并且只有单一的连接,因此它在内存方面的表现比其他一些选择要好得多。当然,它也能正确处理无限列表。

【讨论】:

    【解决方案4】:

    您可能还想考虑这个回答相同问题的网站How to define a rotates function

    因评论而编辑: 基于rotate 的实现以及基于initstails 的实现在列表长度上应该是二次的。但是,基于initstails 的算法效率应该较低,因为它执行了多次二次遍历。尽管请注意,这些陈述仅在您完全评估结果时才成立。此外,编译器可能会改进代码,因此您必须谨慎对待这些语句。

    【讨论】:

    • 嗯...运行时效率如何比较?
    • 随着 inits 的最新改进,initstails 版本应该是渐近可选的。
    • 如果我没记错的话,复杂度仍然是参数长度的二次方,因为++ 应用于长度从零增加到参数长度的列表。
    【解决方案5】:
    rotations (x:xs) = (xs++[x]):(rotations (xs++[x]) ) 
    

    这会创建持续的惰性旋转 现在只取第一个唯一的,这将等于原始列表的长度

    take (length xs) (rotations xs)
    

    【讨论】:

      【解决方案6】:

      我想出了两个解决方案。第一个是我发布问题后收到的手工制作的:

      rotateAll :: [a] -> [[a]]
      rotateAll xs = rotateAll' xs (length xs)
          where rotateAll' xs 1 = [xs]
                rotateAll' xs n = xs : rotateAll' (rotate xs) (n - 1)
      

      其他人使用@Tilo的建议:

      rotateAll :: [a] -> [[a]]
      rotateAll xs = take (length xs) (iterate rotate xs)
      

      【讨论】:

        【解决方案7】:

        您也可以递归地生成它们。生成空或单个元素列表的所有旋转是微不足道的,生成x:xs 的旋转只需将x 插入xs 的所有旋转的正确位置即可。

        您可以通过生成要插入的索引来做到这一点(只需列表 [1, 2, ...] 假设先前的旋转按此顺序)并使用 zipWithx 插入正确的位置.

        或者,您可以使用initstails 的组合来split 围绕位置旋转,然后使用zipWith 将它们粘在一起。

        【讨论】:

          【解决方案8】:

          附录:现在我自己找到了两种解决方案,或者有提示。 我很乐意欢迎任何其他更快或使用的解决方案 不同的方法。谢谢!

          由于没有其他人指出使用cycle,我想我会为有限列表添加这个解决方案:

          rotations x = let n = length x in map (take n) $ take n (tails (cycle x))
          

          对于无限列表x,旋转只是tails x

          评估cycle x是O(n)时间和空间,tails的每个元素是O(1),take n是O(n)时间和空间但是两个take n是嵌套的所以评估整个结果是 O(n^2) 时间和空间。

          如果它在懒惰地生成下一个旋转之前垃圾收集每个旋转,那么空间理论上是 O(n)。

          如果你很清楚自己的消费量,那么你就不需要map (take n),只需走cycle xtake n (tails (cycle x)) 并保持空间O(n)。

          【讨论】:

            【解决方案9】:

            也许你可以使用Data.List

            import Data.List
            rotate x=[take (length x) $ drop i $ cycle x | i<-[0..length x-1]]
            

            【讨论】:

              【解决方案10】:

              从头开始:

              data [] a = [] | a : [a]
              
              end :: a -> [a] -> [a]
              end y []       = y : []
              end y (x : xs) = x : y `end` xs
              
              -- such that `end x xs  =  xs ++ [x]`
              
              rotating :: [a] -> [[a]]
              rotating [] = []
              rotating xs = rots xs
                 where
                    rots xs@(x : xs') = xs : rots (x `end` xs')
              
              -- An infinite list of rotations
              
              rotations :: [a] -> [[a]]
              rotations xs = rots xs (rotating xs)
                 where
                    rots []       _        = []
                    rots (_ : xs) (r : rs) = r : rots xs rs
              
              -- All unique rotations, `forall xs.` equivalent to `take
              -- (length xs) (rotating xs)`
              

              或者:

              {-# LANGUAGE BangPatters #-}
              
              rotate :: [a] -> [a]
              rotate []       = []
              rotate (x : xs) = x `end` xs
                 where
                    end y []       = y : []
                    end y (x : xs) = x : end y xs
              
              iterate :: Int -> (a -> a) -> a -> [a]
              iterate 0 _ _ = []
              iterate n f x = case f x of
                                 x' -> x' : iterate (n - 1) f x'
              
              length :: [a] -> Int
              length xs = len 0 xs
                 where
                    len !n []       = n
                    len !n (_ : xs) = len (n + 1) xs
              
              rotations :: [a] -> [[a]]
              rotations xs = iterate (length xs) rotate xs
              
              -- Starting with single rotation
              

              或者,集成:

              rotations :: [a] -> [[a]]
              rotations [] = [[]]
              rotations xs = rots xs xs
                 where
                    rots []       _            = []
                    rots (_ : xc) xs@(x : xs') = xs : rots xc (x `end` xs')
              
                    end y []       = y : []
                    end y (x : xs) = x : end y xs
              

              【讨论】:

                【解决方案11】:

                我使用以下rotations 函数作为permutations algorithm 的助手。它似乎是这里最快的。

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

                【讨论】:

                • 这与我自己的解决方案有何不同? stackoverflow.com/a/11870855/1440565
                • @Code-Apprentice 如果您的第二个 sn-p 使用 rotate 函数,是的,如果您引用的旋转函数实际上是 \(y:ys) -&gt; ys ++ [y],则应该基本相同。这(或这些)似乎是迄今为止最好的方法。
                猜你喜欢
                • 2020-08-19
                • 2021-12-29
                • 2012-03-16
                • 2021-04-22
                • 2016-03-24
                • 2017-02-27
                • 2021-05-19
                • 2013-02-15
                • 1970-01-01
                相关资源
                最近更新 更多