【问题标题】:Haskell: List Created Evaluating List ElementsHaskell:创建列表评估列表元素
【发布时间】:2011-08-06 10:23:28
【问题描述】:

我正在尝试编写一个列表函数,它接受一个简单的列表并反馈一个列表列表,后者的所有元素都与前一个具有相同的关系。

更具体地说,函数应该这样做:

  1. 获取列表; let xs = [1,2,3,4,5,6,8,9,10]
  2. 查看头部的两个元素,如果第二个元素等于第一个元素加一(即xs!!0 = xs!!1 - 1),则从它们中创建一个列表中的列表。
  3. 列表采用元素,而最后一个元素与元素具有相同的关系,是从主列表中新馈入的。当出现中断时,子列表会关闭,但该函数应该根据自始至终的相同条件创建一个新的子列表。
  4. 所以,最终结果应该是,[[1,2,3,4,5,6],[8,9,10]] 缺席的 7 将主列表分为两个子列表。两者都是等差数列,共同差为 1。

在阅读 Learn You a Haskell for Great Good 到第 7 章之后,我认为我真的有一个很棒的想法,并且尝试过但可耻地失败了。欢迎提供帮助!

ghci> filter (\x y -> x + 1 == y) xs

"<"interactive">":1:8:
    The lambda expression `\ x y -> x + 1 == y' has two arguments, 
    but its type `a -> Bool' has only one 
    In the first argument of `filter', namely `(\ x y -> x + 1 == y)' 
    In the expression: filter (\ x y -> x + 1 == y) xs 
    In the definition of `it': it = filter (\ x y -> x + 1 == y) xs

【问题讨论】:

  • 注意:ghci&gt; let xs = [1,2,3,4,8,5,10] ghci&gt; [x | x &lt;- myGroupBy (\x y -&gt; x*2==y) xs, length x &gt; 1] [[1,2],[4,8],[5,10]] 没关系。

标签: list haskell


【解决方案1】:

这是我对这个问题的思考过程......我们想将一个列表分成“链”(即一个列表的列表),并进行测试以查看两个元素是否链接。

chains :: (x -> x -> Bool) -> [x] -> [[x]]

我不记得图书馆里有这样的东西,所以我决定自己动手。我想确定一个合适的递归策略来处理列表。

我可以只考虑元素吗?不:我很快排除了mapfoldMap,因为在这个问题中元素似乎没有被相互独立地处理。

接下来,我问“输出类型是否有列表代数?”。以这种方式表达,这听起来可能不是一件显而易见的事情,但它揭示了以下明智的问题。是否存在构建输出(链列表)而不是输入(列表)的“nil”和“cons”操作?如果是这样,我可以使用foldr 将输入 nil-and-cons 转换为输出 nil-and-cons,如下所示。

chains :: (x -> x -> Bool) -> [x] -> [[x]]
chains link = foldr chCons chNil where
  -- chNil :: [[x]]
  -- chCons :: x -> [[x]] -> [[x]]

很清楚chNil 必须是什么,因为我正在对原始元素进行分组。有空吗?清空!

chains :: (x -> x -> Bool) -> [x] -> [[x]]
chains link = foldr chCons [] where
  -- chCons :: x -> [[x]] -> [[x]]

我可以写chCons吗?假设我得到一个链列表:如何添加新元素?好吧,如果有一条我可以链接到的前链,那么我应该增长这条链,否则我应该开始一个新的链。所以我在非空链列表的开头有一个非空链的特殊情况,并且默认为 cons 单例。

chains :: (x -> x -> Bool) -> [x] -> [[x]]
chains link = foldr chCons [] where
  chCons y (xs@(x : _) : xss) | link y x  = (y : xs) : xss
  chCons y xss                            = [y] : xss

我们到家了!

> chains (\ x y -> x + 1 == y) [1,2,3,4,5,6,8,9,10]
[[1,2,3,4,5,6],[8,9,10]]

如果您可以为该类型的值实现这些运算符,则一堆运算符对于给定类型具有 代数。数据类型的构造函数只是一个代数,一组运算符的一个实现,在该数据类型中构建值。使用来自数据类型的输入进行计算的一种好方法是为所需的输出类型实现其代数。 foldr 的重点是捕捉这种“寻找代数”模式,而且它对这个问题来说是正确的。

【讨论】:

  • 嗯,我得到了一个不同的结果集:ghci&gt; chains (\ x y -&gt; x + 1 == y) [1,2,3,4,5,6,8,9,10] [[1],[2],[3],[4],[5],[6],[8],[9],[10]] 以防万一它是由于一个过时的版本,我的是 GHCi,版本 6.8.2。
  • 我的错:我把 x 和 y 倒过来,复制代码;现在编辑。
  • 现在该功能可以正常工作了。我仍在为此苦苦挣扎,因为: 的左侧元素在三个地方似乎是String[y]:xss 等)。不是像a:[a]那样使用cons,是用于模式匹配还是用于构造列表?
  • 啊,但是在这里,我们正在处理事物列表。因此,在(y : xs) : xss 中,内部:x[x] 相结合以生成[x],然后外部:[x][[x]] 相结合以生成[[x]]。两个:s 用于a:[a] 模式,但用于a 的不同实例。类似地,[y] : xss 将事物与事物列表相结合(事物本身就是列表)。根据:的类型,将[[x]]这样的类型推入(xs@(x : _) : xss)这样的模式,并找出xxsxss的类型,这是一个很好的练习。
  • 当我意识到 Haskell 列表是 foldr 的一个专门应用程序时点击了一些东西,这就是我定义一组新规则来创建 [[1,2,3,4,5,6],[8,9,10]] 时所需要的,这是另一种的列表。谢谢!
【解决方案2】:

遗憾的是,按照 pmr 的建议使用 groupBy 不起作用,因为它会与每个组的第一个元素进行比较,而不是比较相邻的元素。这是因为groupBy 假定相等谓词是可传递的。您可以通过使用此更改后的版本来获得所需的行为:

groupBy rel []          =  []
groupBy rel (x:xs)      =  (x:ys) : groupBy rel zs
      where (ys,zs) = groupByAux x xs
            groupByAux x0 (x:xs) | rel x0 x = (x:ys, zs)
              where (ys,zs) = groupByAux x xs
            groupByAux y xs = ([], xs)

另请参阅:Data.List.groupBy with non-transitive equality predicate

【讨论】:

  • 我记得那个帖子;我认为这个改变已经在 H2010 的基础上进行了。它可能应该回到图书馆@。
  • 谢谢,哈马尔。工作得很好! [还是不知道怎么在评论区返回] 类型推断是:groupByMe :: (a -> a -> Bool) -> [a] -> [[a]] 所以,这是一个函数检查二元谓词上的列表以产生列表列表。我也将开始阅读您的链接。 【还不知道这里有没有私信系统】。感谢 dave 修复了我上面的 very 第一篇文章的格式。
  • |x0 是什么?除了它们,你的代码在我看来是最简单的,所以我会先解决这个问题。谢谢。
  • | 启动一个守卫,这意味着只有当右侧的守卫评估为 True 时,左侧的模式才会匹配。 x0 只是一个变量名,和其他变量名一样。
  • 那么,groupByAux x0 (x:xs) | rel x0 x = (x:ys, zs) 是这个意思吗? “如果谓词 'rel x0 x' 返回 True,则 groupByAux 的两种模式中的第一种返回二进制元组。”有时函数是函数,但有时它是输出......
【解决方案3】:

好的,把你的比较函数放在 GHCi 中,看看它给你的类型:

Prelude> :t (\x y -> x + 1 == y)
(\x y -> x + 1 == y) :: Num a => a -> a -> Bool

但是filter 的谓词类型为(a -&gt; Bool);这是行不通的,因为它一次只查看一个元素。你需要像过滤器这样的东西,但类型 (a -&gt; a -&gt; Bool) -&gt; [a] -&gt; [[a]]

Hoogle 时间。 (或Hayoo,如果您愿意)

Hoogle 对该类型签名的第一个匹配项是 Data.List.groupBy,这正是您要查找的函数。

很遗憾,groupBy 并不完全正确,正如您所发现的那样。问题在于它将每个新元素与它正在测试的第一个元素进行比较,因此输入[1,2,3],测试函数在比较1和2时返回True,但在比较1和3时返回False。但是,如果你看看实现可能会稍微修改一下。

groupBy                 :: (a -> a -> Bool) -> [a] -> [[a]]
groupBy _  []           =  []
groupBy eq (x:xs)       =  (x:ys) : groupBy eq zs
                           where (ys,zs) = span (eq x) xs

嗯。这使用span 来划分列表,这在此处不起作用,因为span 一次只查看一个参数。你可以按照 hammar 的建议修改 groupBy,但你也可以用其他方式编写它,比如这个带有 foldr 的版本:

   myGroupBy _  []     = []
   myGroupBy eq (x:xs) = (\(_,t1,t2) -> init $ t1:t2) $ foldr (\el (el2,t1,t2) ->
     if eq el el2
       then (el,el:t1,t2)
       else (el,[el],t1:t2))
     (x,[],[]) (x:xs)

【讨论】:

    【解决方案4】:

    对于您的错误:filterS 类型是 (a -&gt; Bool) -&gt; [a] -&gt; [a]。你的 lambda 有两个参数,filter 需要一个一元函数。

    你想要的功能(据我所知)是groupBy :: (a -&gt; a -&gt; Bool) -&gt; [a] -&gt; [[a]]

    【讨论】:

    • 嘿,我在发帖几分钟后就意识到了这一点。再说一次,ghci&gt; groupBy (\x y -&gt; x + 1 == y) xs [[1,2],[3,4],[5,6],[8,9],[10]]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-26
    • 1970-01-01
    • 1970-01-01
    • 2022-10-09
    • 1970-01-01
    • 2021-03-02
    • 2019-07-21
    相关资源
    最近更新 更多