【问题标题】:Haskell: Splitting a list into 2 at index kHaskell:在索引 k 处将列表拆分为 2
【发布时间】:2012-09-14 10:33:26
【问题描述】:

我对 Haskell 很陌生,但遇到了一些麻烦。我正在尝试实现一个带有列表和 int 的函数。 int 应该是列表被分成一对列表的索引 k。第一个包含列表的前 k 个元素,第二个包含 k+1 到最后一个元素。到目前为止,这是我所拥有的:

split :: [a] -> Int -> ([a], [a])
split [] k = error "Empty list!"
split (x:[]) k = ([x],[])
split xs k | k >= (length xs) = error "Number out of range!"
           | k < 0 = error "Number out of range!"

我实际上无法弄清楚如何进行拆分。任何帮助将不胜感激。

【问题讨论】:

标签: list haskell split


【解决方案1】:

首先,请注意您尝试构建的函数已经在标准库中,在Prelude 中——它被称为splitAt。现在,直接看它的定义是令人困惑的,因为有两种算法,一种根本不使用标准递归结构 -splitAt n xs = (take n xs, drop n xs) - 一种是手动优化的,使它变得丑陋。前者更直观,因为您只需将前缀和后缀放在一对中。然而,后者教的更多,并且具有这样的整体结构:

splitAt :: Int -> [a] -> ([a], [a])
splitAt 0 xs     = ([], xs)
splitAt _ []     = ([], [])
splitAt n (x:xs) = (x:xs', xs'')
  where
    (xs', xs'') = splitAt (n - 1) xs

基本思想是,如果一个列表由一个头和一个尾组成(它的形式为x:xs),那么从索引 k+1 开始的列表将与从索引开始的列表相同删除第一个元素后 k 开始 - drop (k + 1) (x : xs) == drop k xs。要构造前缀,您同样删除第一个元素,取一个较小的前缀,然后将元素重新粘贴到 - take (k + 1) (x : xs) == x : take k xs

【讨论】:

    【解决方案2】:

    这个呢:

    splitAt' = \n -> \xs -> (take n xs, drop n xs)
    

    一些测试:

    > splitAt' 3 [1..10]
    > ([1,2,3],[4,5,6,7,8,9,10])
    
    > splitAt' 0 [1..10]
    > ([],[1,2,3,4,5,6,7,8,9,10])
    
    > splitAt' 3 []
    > ([],[])
    
    > splitAt' 11 [1..10]
    > ([1,2,3,4,5,6,7,8,9,10],[])
    
    > splitAt' 2 "haskell"
    > ("ha","skell")
    

    【讨论】:

    【解决方案3】:

    基本上,您需要某种方式在递归遍历列表时传递部分进度。我使用了第二个接受累加器参数的函数;它从 split 中调用,然后递归调用自身。几乎肯定有更好的方法..

    编辑: 删除了所有长度检查。但我相信使用 ++ 意味着它仍然是 O(n^2)。

    split xs k | k < 0 = error "Number out of range!"
    split xs k = ssplit [] xs k
    
    ssplit p xs 0 = (p, xs)
    ssplit p (x:xs) k = ssplit (p++[x]) xs (k-1)
    ssplit p [] k = error "Number out of range!"
    

    获取原始帖子中的行为或

    ssplit p [] k = (p,[])
    

    为了获得标准 splitAt 函数的更宽容的行为。

    【讨论】:

    • 我认为检查每个递归调用的长度并不好。您有效地使算法及时成为 O(n^2)。
    【解决方案4】:

    在构建列表时摆脱二次行为的一个常见技巧是反向构建,然后反转它,修改 Mark Reed 的解决方案:

    split xs k | k < 0 = error "Number out of range!"
    split xs k = (reverse a, b)
      where
        (a,b) = ssplit [] xs k
    
    ssplit p xs 0 = (p, xs)
    ssplit p (x:xs) k = ssplit (x:p) xs (k-1)
    ssplit p [] k = error "Number out of range!"
    

    ssplit 中的错误检查很好,因为除非有实际错误,否则不会检查(早期模式之一将匹配)。

    在实践中,您可能希望向 ssplit 添加一些严格性注释以管理堆栈增长,但这是进一步的改进。

    【讨论】:

      【解决方案5】:

      见前奏中的splitAt

      ghci> :t flip splitAt
      flip splitAt :: [a] -> Int -> ([a], [a])
      ghci> flip splitAt  ['a'..'j'] 5
      ("abcde","fghij")
      

      【讨论】:

        猜你喜欢
        • 2014-04-14
        • 2019-06-05
        • 2013-10-09
        • 1970-01-01
        • 2016-09-01
        • 1970-01-01
        • 2021-10-02
        • 2012-02-09
        • 1970-01-01
        相关资源
        最近更新 更多