Haskell:在索引k处将列表拆分为2

时间:2012-09-22 01:58:23

标签: list haskell split

我对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!"

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

5 个答案:

答案 0 :(得分:18)

首先,请注意您尝试构建的函数已经在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上。

答案 1 :(得分:3)

这个怎么样:

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")

答案 2 :(得分:1)

基本上,当您通过列表递归时,您需要一些传递部分进度的方法。我使用了第二个带累加器参数的函数;它是从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函数的更宽容行为。

答案 3 :(得分:1)

在构建列表时摆脱二次行为的一个常见技巧是向后构建它,然后反转它,修改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添加一些严格注释来管理堆栈增长,但这是一个进一步的改进。

答案 4 :(得分:0)

请参阅前奏中的splitAt

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