【问题标题】:Understanding lists in list in recursive function - haskell了解递归函数列表中的列表 - haskell
【发布时间】:2018-11-01 19:19:00
【问题描述】:

我正在编写一个函数来按元组或三元组对给定列表进行排序。它应该按列表的两个元素之间的数字对其进行排序。

例如,如果给定列表是[1,2,3,6,7],它应该返回[[1,2,3], [6,7]] 因为 1,22,3 之间以及 6,7

之间的数字为零

这是我的代码

import Data.List 

check :: [Int] -> [[Int]]
check listCopy@(x:xs) = 
    let sorted = sort (listCopy) 
        in if (length sorted > 1) 
              then if ((sorted !! 1 ) - (sorted !! 0)) == 1 || ((sorted !! 1 ) - (sorted !! 0)) == 0 
                      then [[x]  ++ check(xs) !! 0] 
                      else [[x]] ++ check(xs)
              else [[x]]
check [] = [[]]

if ((sorted !! 1 ) - (sorted !! 0)) == 1 || ((sorted !! 1 ) - (sorted !! 0)) == 0 正在检查列表的两个元素之间是否有 0 个数字

如果上述语句为 True,则 [[x] ++ check(xs) !! 0] 将把该元素添加到列表中并再次调用该函数并获取它返回的第一个元素。示例:[1,2,3,6,7] -> [[1 ++ check [2,3,6,7]]] -> [[1,2 ++ check[3,6,7]]] 等等...

但是,当if ((sorted !! 1 ) - (sorted !! 0)) == 1 || ((sorted !! 1 ) - (sorted !! 0)) == 0 为 False 时,它​​应该 else [[x]] ++ check(xs) 将元素设置在列表中的列表中并再次调用函数并在列表中创建另一个新列表。示例:[[1,2 ++ check[3,6,7]]] -> 是 6-3 == 0 或 1(False) 返回 [[1,2,3]] + check[6,7] 这应该会导致 [[1,2,3], [6,7]]

调用check[1,2,3,6,7] 但返回[[1,2,3]]。我没有错误, 但据我所知[[1,2]] ++ [[3,4]] 应该导致[[1,2], [3,4]] 这正是我在else [[x]] ++ check(xs) 中所做的事情,不知何故我的功能就在那里结束了。我在哪里犯了错误,或者它做了我错过的事情?

【问题讨论】:

  • 您的代码绝不会计算[[1,2]] ++ [[3,4]]。您的输入列表中甚至没有4
  • 尽量远离!!。需要考虑三类列表:空列表[]、单例列表x:[](又名[x])和一般情况(x:y:ys)。另外,用check' :: [Int] -> [[Int]] 解决排序列表的主要问题;然后check = check' . sort。无需在每次递归调用中对列表进行(重新)排序。
  • 提示:递归分组(y:ys)后,x要么自己进入一个组,要么被添加到包含y的组的开头。
  • 不要使用length, !!, head, tail,除非你真的必须——通常你不需要。而不是检查if length sorted > 1 then ... 使用case sorted of (x1:x2:rest) -> ... ; _ -> ...,这样您就可以在没有!! 的情况下访问第一个和第二个元素。

标签: haskell


【解决方案1】:

这里的一个问题是你只附加了第一个子列表

then [[x]  ++ check(xs) !! 0]

因此,您进行了一个递归调用,该调用将返回一个子列表列表,但是您“丢弃”了除第一个列表之外的所有列表,然后将第一个列表连接起来。其余子列表将被忽略。

您可以通过以下方式解决此问题:

then [[x]  ++ check(xs) !! 0] ++ safeTail check

我们将safeTail 实现为:

safeTail :: [a] -> [a]
safeTail (x:xs) = xs
safeTail [] = []

或者,像@melpomene says

safeTail :: [a] -> [a]
safeTail = drop 1

稍后会发现我们可以使用tail,但使用上面的代码,很难看到。

但实现不是很“Haskellish”。您的代码使用了大量的(!!)s 和lengths。由于(!!)O(k) 中运行,而 k 是我们想要获取元素的索引,而lengthO(n ) 加上 n 列表的长度,这样也会相当低效。

在进一步处理列表之前先对列表进行排序是有意义的。接下来我们只需要查找当前元素x,下一个元素n,以及其余元素xs,所以:

go :: (Ord n, Num n) => [n] -> [[n]]
go (x:n:xs) = ...
go other = other

如果是n <= x+1,那么我们知道这两个数字之间的差是零或一,所以在这种情况下,递归调用检查的head(第一个元素)应该是前面加上x,所以我们可以这样写:

go :: (Ord n, Num n) => [n] -> [[n]]
go (x:n:xs) | n <= x+1 = (x:r) : rs
            | otherwise = ...
   where (r:rs) = go (n:xs)
go [x] = [[x]]
go [] = []

否则我们可以只构造一个单例列表,然后是列表的其余部分:

go :: (Ord n, Num n) => [n] -> [[n]]
go (x:n:xs) | n <= x+1 = (x:r) : rs
            | otherwise = [x]:(r:rs)
    where (r:rs) = go (n:xs)
go [x] = [[x]]
go [] = []

我们知道go (n:xs) 至少有一个元素,因为我们用一个元素递归调用列表,并且在列表非空的所有情况下,我们返回一个非空列表。

通过使用 as-pattern,我们可以让它更优雅一点:

go :: (Ord n, Num n) => [n] -> [[n]]
go (x:na@(n:xs)) | n <= x+1 = (x:r) : rs
                 | otherwise = [x]: ra
    where ra@(~(r:rs)) = go na
go [x] = [[x]]
go [] = []

我们可以将上面的内容,如@chepner says,概括为只需要Eq aOrd a

go :: (Ord n, Enum n) => [n] -> [[n]]
go (x:na@(n:xs)) | succ x >= n = (x:r) : rs
                 | otherwise = [x]: ra
    where ra@(~(r:rs)) = go na
go [x] = [[x]]
go [] = []

所以现在我们只需要用go来表达check,用:

import Data.List(sort)

check :: (Ord n, Enum n) => [n] -> [[n]]
check = go . sort
    where go (x:na@(n:xs)) | succ x >= n = (x:r) : rs
                           | otherwise = [x]: ra
              where ra@(~(r:rs)) = go na
          go [x] = [[x]]
          go [] = []

或者我们可以让check 函数对(Eq n, Enum n) 类型进行操作:

import Data.List(sortBy)
import Data.Ord(comparing)

check :: (Ord n, Enum n) => [n] -> [[n]]
check = go . sortBy (comparing fromEnum)
    where go (x:na@(n:xs)) | succ x == n || x == n = (x:r) : rs
                           | otherwise = [x]: ra
              where ra@(~(r:rs)) = go na
          go [x] = [[x]]
          go [] = []

【讨论】:

  • safeTail = drop 1
  • 泛化为(Eq a, Enum a) =&gt; [a] -&gt; [[a]],你只需要检查n == succ x
  • @chepner: 有点遗憾的是,sort 步骤对于“外部”check 函数是必需的 :(
  • 您可以使用map toEnum . sort . map fromEnum 来回避订购要求:)
  • @cheshire:我认为你最好从一些更简单的列表函数开始,比如mapfilter等。有关列表数据结构的一些详细信息,请参见此处:learnyouahaskell.com/starting-out#an-intro-to-lists
【解决方案2】:

这是一个有趣的问题,这是一个应用风格的实验方法。是的,它看起来有点复杂,但实际上非常简单。我唯一不喜欢的是last 函数的使用。也许我们可以找到一种方法来以某种方式放弃它。

splitConsequtives :: Integral a => [a] -> [[a]]
splitConsequtives xs = foldr id [[last xs]] $ zipWith f <*> tail $ xs
                       where f x y | y-x == 1  = (:) <$> (x:) . head <*> tail
                                   | otherwise = ([x]:)

*Main> splitConsequtives [1,2,3,6,7]
[[1,2,3],[6,7]]
*Main> splitConsequtives [-1,2,3,6,8,9]
[[-1],[2,3],[6],[8,9]]

我们的想法是将构造函数放在一个列表中,当它们通过折叠链接起来时,最终将构建我们的整个结果列表。列表构造函数(:) 是右关联的,这就是我使用foldr 的原因。

这一切都始于zipWithing 我们的xs 列表,它是tailf 函数像zipWith f xs (tail xs)f :: a -&gt; a -&gt; [[a]] -&gt; [[a]] 函数是我们获取应用程序作为结果列表元素的地方。

好的..!现在让我们关注以xy 作为参数的f 函数。

  • 如果yx 的后续,那么我们的[[a]] -&gt; [[a]] 类型函数 是(:) &lt;$&gt; (x:) . head &lt;*&gt; tail,它需要一个列表列表 numbers 然后在head 处获取子列表,将x 附加到它并 将所有内容重新组合在一起。所以如果我们的结果正在构建 (foldr 的累加器参数)是[[7], [9,10]]x6 然后我们会得到[[6,7],[9,10]]
  • 如果y不是x的后续,那么我们的[[a]] -&gt; [[a]]类型 函数是([x]:)。因此,如果我们的结果正在构建( foldr) 的累加器参数是 [[6,7], [9,10]]x3 然后我们会得到[[3],[6,7],[9,10]]

需要注意的一个有趣点是我们用于foldr :: Foldable t =&gt; (a -&gt; b -&gt; b) -&gt; b -&gt; t a -&gt; b 的迭代函数的类型。您希望它是 a -&gt; b -&gt; b 类型,但 id :: a -&gt; a 看起来不同。这怎么可能..?我相信这是因为a ~ [[a]] -&gt; [[a]]b ~ [[a]](a -&gt; b -&gt; b) 中的foldr

【讨论】:

  • 对我来说,splitConsequtives [] = [] ; splitConsequtives xs = foldr ($) [[last xs]] $ zipWith f xs $ tail xs where f x y ~(g:gs) | x+1 == y = (x : g) : gs | otherwise = [x] : g : gs 更具可读性和清晰性。 :)
  • @WillNess 尊重..!老实说,我认为,使用 Haskell 编码的人应该牢记应用程序是他们思维定势的一种反射,这样我的代码就应该易于阅读。我相信只有这样,人们才能对连续传递等技巧感到满意(就像我们在最近的答案中所做的那样)。我也不喜欢列表推导和 do 表示法,但这是另一回事 :)
  • 我实际上非常喜欢 LC 和 do。甚至 MC,MonadComprehensions。口味问题...
  • (在我学会喜欢它之前,我曾经讨厌do。:) 但我喜欢我的dos 带有明确的分隔符——大括号和分号。我不喜欢的“裸体”do 代码。也许那是我的老 lisper,但你的代码因为一个意外的空间而中断?那是什么? )
猜你喜欢
  • 1970-01-01
  • 2015-03-10
  • 2017-06-22
  • 1970-01-01
  • 2016-04-09
  • 2019-07-21
  • 2011-07-16
  • 1970-01-01
  • 2020-06-16
相关资源
最近更新 更多