【问题标题】:List to lists of lists in recursive calling列表到递归调用中的列表列表
【发布时间】:2018-04-01 17:20:19
【问题描述】:

这是我的代码:

findY mnozP a = 
    if null(Set.toList $ mnozP)==False
        then (
            if (((null(intersect ( Set.toList $ head $ Set.toList $ mnozP) (a))==False) && (null(( Set.toList $ head $ Set.toList $ mnozP) \\ (a))==False)))
                then (((intersect ( Set.toList $ head $ Set.toList $ mnozP) (a)) ++ (( Set.toList $ head $ Set.toList $ mnozP) \\ (a))) ++ findY ( Set.fromList $ tail $ Set.toList $ mnozP) (a))
                else findY ( Set.fromList $ tail $ Set.toList $ mnozP) (a)
            )
        else []
findY _ a = []
findY _ _ = []

该函数检查列表mnozP是否为空,如果不是,则取第一项并检查其与列表a的交集是否为空and其差值不为空。在条件为真的情况下,实现这两个操作,并为列表b的尾部再次递归调用。 我实际上有两个问题:

  1. 让我们假设以下列表:a = ["3","4"]mnozP = [["1","6"],["2","4","3","5"]]。输出为["3","4","2","5"]是否有任何函数可以根据每次迭代将该列表拆分为列表列表? 预期输出应类似于 [["3","4"],["2","5"]]
  2. 编译 GHCi 时说:

    Warning:
    Pattern match(es) are overlapped
    In an equation for `findY':
        findY _ a = ...
        findY _ _ = ...
    

    我不确定我的模式有什么问题。我试图涵盖所有可能发生的情况。 有什么想法吗?

编辑:(简短摘要:) 函数 findY 应该返回一个列表列表。每个内部列表都与一个具体的递归迭代相关(如果条件为真)。

【问题讨论】:

  • 我认为您在这里使代码变得复杂。您最好首先使用where 子句、守卫等来使语法更干净。
  • 首先指定您的函数的目标可能也很有用?
  • 编译器还说最后两行实际上是重复的,因此没有理由同时写这两行。所以你可以删除两者之一。
  • @WillemVanOnsem 这不是这个问题的主题。首先,我需要通过测试 mnozP 的每个子集(子列表)来获得该列表列表。如果mnozP的实际子集(让调用是P)的条件匹配,我实现Pa的交集和差异,将这两个创建的集合(列表)插入最终列表。而这份清单就是我的目标。
  • @WillemVanOnsem 我想这就是 OP 想要的。

标签: list haskell recursion


【解决方案1】:

您要查找的函数是列表构造函数。在你的归纳案例中,你有

(((intersect ( Set.toList $ head $ Set.toList $ mnozP) (a)) ++ (( Set.toList $ head $ Set.toList $ mnozP) \\ (a))) ++ findY ( Set.fromList $ tail $ Set.toList $ mnozP) (a))

这只是

as ++ bs ++ cs

其中cs 是递归调用的结果。如果findY 返回一个列表列表,那么整个表达式也必须如此。根据归纳假设,cs 是一个列表列表。换句话说,exp ++ cs 左侧的表达式(即as ++ bs)也必须是列表列表。但事实并非如此! intersect\\ 返回平面列表,您将它们连接在一起,得到一个新的平面列表。

要解决这个问题,您只需将左表达式包装在一个列表中

[as ++ bs] ++ cs

或使用列表构造函数

(as ++ bs) : cs

如果您可以从中获得任何其他信息,那就是如果代码以更惯用的方式编写,您可以自己发现这一点。我将尝试向您介绍我是如何“解包”您的代码的。

首先,我添加了一个与您的描述相匹配的类型签名。

findY :: Eq a => [[a]] -> [a] -> [[a]]

有趣的是,仅此更改就会指出归纳案例中的表达是不正确的。

然后,我摆脱了Set <-> [] 转换。你可以要求你的输入是集合,或者只是简单的列表,并确保没有重复。我选择了后一种方法:

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY mnozP a = 
if null(mnozP)==False
    then (
        if (((null(intersect (head mnozP) (a))==False) && (null(( head mnozP) \\ (a))==False)))
            then (((intersect ( head mnozP) (a)) ++ (( head mnozP) \\ (a))) ++ findY ( tail mnozP) (a))
            else findY ( tail mnozP) (a)
        )
    else []
findY _ a = []
findY _ _ = []

已经越来越清楚了。您也可以摆脱大量()。在 Haskell 中,函数应用只是空格字符。因此,不要像在许多语言中那样写 f(x),而是写 f x 并用括号括起来只是为了消除表达式的歧义:

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY mnozP a = 
if (null mnozP) == False
    then
        if (null (intersect (head mnozP) a) == False) && (null (head mnozP \\ a) == False)
            then (intersect (head mnozP) a) ++ (head mnozP \\ a) ++ findY (tail mnozP) a
            else findY (tail mnozP) a
    else []
findY _ a = []
findY _ _ = []

接下来,注意headtail 的多次重复。您可以使用let 表达式将它们绑定到变量,但有更好的方法。 Haskell 允许您对参数进行模式匹配,以便根据输入选择应评估的分支。在这里,我们只需要知道列表是否有头和尾,这恰好是您模式匹配非空列表的方式:

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY (xs : xss) a = 
    if (null (intersect xs a) == False) && (null (xs \\ a) == False)
        then (intersect xs a) ++ (xs \\ a) ++ findY xss a
        else findY xss a
findY _ _ = []

看看这样做是如何让我摆脱第一个if 语句的? (我还删除了额外的模式匹配)。还是有一些冗余的,我们去掉吧:

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY (xs : xss) a =
  let as = intersect xs a
      bs = xs \\ a
  in
    if (null as == False) && (null bs == False)
        then as ++ bs ++ findY xss a
        else findY xss a
findY _ _ = []

到那时,问题就很突出了。

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY (xs : xss) a =
  let as = intersect xs a
      bs = xs \\ a
  in
    if (null as == False) && (null bs == False)
        then (as ++ bs) : findY xss a
        else findY xss a
findY _ _ = []

现在是风格问题,但我个人发现大多数时候使用模式匹配比 if 语句更优雅。不过这里没那么多:

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY (xs : xss) a =
  let as = intersect xs a
      bs = xs \\ a
  in case (as, bs) of
    ((_ : _), (_ : _)) -> (as ++ bs) : findY xss a
    _                  -> findY xss a
findY _ _ = []

但在 Haskell 2010 中,您可以使用模式保护,这是一种更强大的模式匹配版本。它使您的代码更加清晰:

findY :: Eq a => [[a]] -> [a] -> [[a]]
findY (xs : xss) a 
  | as@(_ : _) <- intersect xs a
  , bs@(_ : _) <- xs \\ a = (as ++ bs) : findY xss a
  | otherwise             = findY xss a
findY _ _ = []

最后,您的程序的形式是经典的结构递归。这通常是您使用foldr 的目的。

findY :: Eq a => [a] -> [[a]] -> [[a]]
findY a = foldr build []
  where 
    build xs yss
      | as@(_ : _) <- intersect xs a
      , bs@(_ : _) <- xs \\ a = (as ++ bs) : yss
      | otherwise             = yss

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-06-24
    • 1970-01-01
    • 1970-01-01
    • 2021-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-15
    相关资源
    最近更新 更多