【问题标题】:Implementing concatenation by hand in haskell在haskell中手动实现连接
【发布时间】:2018-05-09 20:33:28
【问题描述】:

如何在 Haskell 中手动实现连接运算符? 这是我到目前为止所拥有的,但我在最后的递归中苦苦挣扎:

data MyList a = Empty | Element a (MyList a)
 deriving Show

concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = Empty
concatenation a Empty = a
concatenation Empty a = a
concatenation x y = ???

【问题讨论】:

  • 提示:模式匹配 Element 构造函数。
  • concatenation (Element x xs) y = ?(第一个基本情况不是必需的;如果a 匹配Emptyconcatenation a Empty = a 就可以正常工作。)

标签: list haskell concatenation


【解决方案1】:

我们可以枚举构造函数,而不是直接使用变量。因此,我们需要涵盖四种情况:

concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = -- ...
concatenation Empty (Element b bs) = -- ...
concatenation (Element a as) Empty = -- ...
concatenation (Element a as) (Element b bs) = -- ...

现在我们可以着手解决这些问题。连接两个Empty 列表将返回一个空列表,因此Empty

concatenation Empty Empty = Empty

如果第一个列表是Empty,而后者不是,那么我们返回第二个列表。

concatenation Empty (Element b bs) = Element b bs

如果第一个元素是一个列表,而后者不是,那么我们可以返回第一个列表:

concatenation (Element a as) Empty = Element a as

最后,如果两个列表都不为空,则结果列表从第一个列表的第一个元素开始,然后是第一个列表的尾部和第二个列表的串联:

concatenation (Element a as) (Element b bs) = Element a (concatenation as (Element b bs))

这给了我们以下代码:

concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = Empty
concatenation Empty (Element b bs) = Element b bs
concatenation (Element a as) Empty = Element a as
concatenation (Element a as) (Element b bs) = Element a (concatenation as (Element b bs))

我们可以用 as patterns 来缩短实现:

concatenation:: MyList a -> MyList a -> MyList a
concatenation Empty Empty = Empty
concatenation Empty bl@(Element b bs) = bl
concatenation al@(Element a as) Empty = al
concatenation (Element a as) bl@(Element b bs) = Element a (concatenation as bl)

但这相当冗长,而且它在两个列表上执行模式匹配,这可能很昂贵:例如,如果第二个列表首先需要昂贵的计算,并且第一个列表足够长(甚至无限),那么我们将无论如何都不要对第二个列表感兴趣。

因此,我们可以查看是否可以压缩子句的数量。前两行可以组合在一起,来自:

concatenation Empty Empty = Empty
concatenation Empty (Element b bs) = Element b bs

到:

concatenation Empty bl = bl

因为不管第二个列表的值是多少,我们无论如何都会返回它。它还为我们节省了潜在的评估,这可以产生更有效的代码。

我们可以对最后两行做类似的事情吗?

concatenation (Element a as) Empty = Element a as
concatenation (Element a as) bl@(Element b bs) = Element a (concatenation as bl)

因此,两者都有一个Element 构造函数,并且a 作为第一个元素。由于asEmpty 的串联最终是concatenation as Empty = as,因此我们也可以在前一种情况下执行递归调用,并使用:

concatenation (Element a as) bl = Element a (concatenation as bl)

这会导致:

concatenation :: MyList a -> MyList a -> MyList a
concatenation Empty bl = bl
concatenation (Element a as) bl = Element a (concatenation as bl)

我们可以在这里省略第二个参数:

concatenation :: MyList a -> MyList a -> MyList a
concatenation Empty = id
concatenation (Element a as) = Element a . concatenation as

请注意,最后一个归约本身并不是更有效:这可能导致将(可能很大)列表的整个副本构建到内存中,并且如果第二个列表结果为空,则需要进行大量计算。另一方面,如果计算第二个列表会导致陷入无限循环或昂贵的计算,而我们对此部分不感兴趣,那么我们可能会节省大量 CPU 周期。

【讨论】:

    【解决方案2】:

    只需使用标准递归:

    concatenation:: MyList a -> MyList a -> MyList a
    concatenation Empty l = l
    concatenation (Element e l1) l2 = Element e (concatenation l1 l2)
    

    为什么会停止?第二条规则将第一个列表的每个元素一个接一个地嵌套在Empty 和第二个列表的连接之前,即第二个列表本身(第一条规则)。

      concatenation Element 1 (... Element n (Empty)) l2
    = Element 1 (concatenation Element 2 (... Element n (Empty))) l2 -- second rule
    = ...
    = Element 1 (Element 2 (... Element n (concatenation Empty l2))) -- second rule
    = Element 1 (Element 2 (... Element n (l2))) -- first rule
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-11-05
      • 2018-12-27
      • 2011-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-04
      • 1970-01-01
      相关资源
      最近更新 更多