【问题标题】:How to split a list into a list of lists by removing a specific separation(Haskell)如何通过删除特定分隔符将列表拆分为列表列表(Haskell)
【发布时间】:2013-08-25 20:30:10
【问题描述】:

我是 Haskell 的新手,我有一个问题。我需要编写一个函数,在出现“分隔”的任何地方将列表拆分为列表列表。

【问题讨论】:

  • 您对算法的第一次尝试是什么?您可以先尝试显式递归或先解决更简单的问题(分隔符是单个字符)。
  • 如果您可以将分隔符替换为空格,那么您可以将分隔符替换为:。到目前为止你有什么?

标签: list haskell split declarative


【解决方案1】:

我将尝试帮助您了解如何通过递归开发适用于列表的函数。首先学习如何以“低级”方式进行操作会很有帮助,这样您就可以更好地了解在实际代码中更常见的“高级”方式中发生的事情。

首先,您必须考虑要使用的数据类型的性质。列表在某种意义上是 Haskell 中递归定义类型的典型示例:列表要么是空列表 [],要么是某个列表元素 a 与通过 a : list 的列表组合。这是仅有的两种可能性。我们称空列表为基本情况,因为它在定义中不引用自身。如果没有基本情况,递归将永远不会“触底”并且会无限期地继续下去!

列表的定义中有两种情况意味着您必须在使用列表的函数的定义中考虑两种情况。在 Haskell 中考虑多种情​​况的规范方法是模式匹配。 Haskell 语法提供了许多进行模式匹配的方法,但我现在只使用基本的case 表达式:

case xs of
  []    ->  ...
  x:xs' ->  ...

这是列表必须考虑的两种情况。第一个匹配文字空列表构造函数;第二个匹配添加元素的构造函数:,并将两个变量xxs' 绑定到列表中的第一个元素和包含其余元素的子列表。

如果您的函数传递了一个与第一种情况匹配的列表,那么您就知道初始列表是空的,或者您已经完成了对列表的递归,直到它的最后一个元素。无论哪种方式,都没有要处理的列表;您要么完成(如果您的调用是尾递归的),要么您需要将答案构造的基本元素传递回调用它的函数(通过返回它)。如果您的答案是一个列表,那么基本元素通常又是一个空列表[]

如果您的函数传递了一个与第二种情况匹配的列表,那么您就知道它传递了一个非空列表,而且您还有一些绑定到有用值的新变量。根据这些变量,您需要决定两件事:

  1. 假设我在列表的 rest 上执行算法得到了正确答案,我该如何在那个元素上执行我的算法的一步?
  2. 如何将这一步的结果与对列表其余部分执行的结果结合起来?

一旦你想出了这些问题的答案,你就需要构建一个组合它们的表达式;获得列表其余部分的答案只需对列表的其余部分调用递归调用,然后您需要对第一个元素执行该步骤并进行组合。

这是一个查找列表长度的简单示例

listLength :: [a] -> Int
listLength as =
  case as of
    []    -> 0                  -- The empty list has a length of 0
    a:as' -> 1 + listlength as' -- If not empty, the length is one more than the
                                -- length of the rest of the list

这是另一个从列表中删除匹配元素的示例

listFilter :: Int -> [Int] -> Int
listFilter x ns =
  case ns of
    []    -> []                            -- base element to build the answer on
    n:ns' -> if n == x
               then listFilter x ns'       -- don't include n in the result list
               else n : (listFilter x ns') -- include n in the result list

现在,您提出的问题有点困难,因为它涉及辅助“列表匹配”递归,以识别列表上基本递归中的分隔符。有时向递归函数添加额外参数会很有帮助,以便保存有关您在问题中所处位置的额外信息。也可以通过将它们放在一个元组中同时对两个参数进行模式匹配:

case (xs, ys) of
  ([]   , []   ) -> ...
  (x:xs', []   ) -> ...
  ([]   , y:ys') -> ...
  (x:xs', y:ys') -> ...

希望这些提示能帮助您解决问题!

【讨论】:

  • 很好的解释,对于像我这样的新手来说很容易理解
【解决方案2】:

让我们看看是否可以通过明显的方式减少问题。

假设调用 splitList 时,使用 xs 进行拆分,使用 ys 作为分隔符。如果 xs 为空,则问题最小,那么该问题的答案是什么?这里有正确的答案很重要,因为归纳解决方案取决于这个决定。但我们可以稍后再做决定。

好的,所以要简化问题,列表 xs 不为空。所以,它至少有一个头元素 h 和较小的问题 t,列表的尾部:你可以匹配 xs@(h:t)。如何获得较小问题的解决方案?好吧, splitList 可以通过函数的定义来解决这个问题。所以现在的诀窍是弄清楚如何为更大的问题 (h:t) 构建解决方案,当我们知道较小问题的解决方案 zs=splitList t ys 时。这里我们知道 zs 是列表的列表 [[a]],并且因为 t 可能是最小的问题,所以 zs 很可能是最小问题的解决方案。所以,无论你用 zs 做什么,它必须是有效的,即使是解决最小的问题。

  splitList [] ys = ... -- some constant is the solution to the smallest problem
  splitList xs@(h:t) ys = let zs = splitList t ys
                          in ... -- build a solution to (h:t) from solution to t

【讨论】:

  • tks,所以这是首先考虑如何编写递归函数的方式
【解决方案3】:

我不知道如何测试它。有人告诉我如何将函数写入 .hs 文件并使用 winGHCi 运行此函数?

WinGHCi 自动与 .hs 文件关联,因此只需双击该文件,即可启动 ghci。使用您喜欢的编辑器对文件进行一些更改后,您可以编写使用 ghci 中的:r 命令重新加载文件。

要在修正拼写错误、类型错误并确保正确缩进后测试程序,请尝试使用不同的输入调用您定义的函数(或使用 QuickCheck)。注意Maybe 定义为Just xNothing。您可以使用fromMaybe 提取x(并为Nothing 情况提供默认值)。

还要尝试确保模式匹配是详尽无遗的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-27
    • 2019-04-08
    • 2019-06-05
    • 2022-10-07
    • 2018-03-17
    • 1970-01-01
    • 2011-10-20
    相关资源
    最近更新 更多