获得列表中元素的偶数和奇数位置 - Haskell Mutual Recursion

时间:2018-04-15 15:35:09

标签: haskell recursion functional-programming mutual-recursion

我最近开始学习Haskell。

我在网上找到了这个代码,它返回列表所有偶数/奇数位置的元素。

它使用相互递归,但我似乎无法理解它是如何在内部工作的。

evens (x:xs) = x:odds xs
evens _ = []

odds (_:xs) = evens xs
odds _ = []

特别是,我不明白该列表是如何向前发展并评估所有元素的。即使没有明确的索引检查,它如何检查偶数位置

有人能够提供见解吗?

4 个答案:

答案 0 :(得分:6)

首先,让我们就某事达成一致:我们大多数时候都使用0指数,对吧?所以,如果我问你列表中的第2位元素是什么

a : b : c : d : []

你会回答c编辑:如果您不熟悉Haskell表示法,:是列表构造函数,a : b表示通过在列表前添加a而生成的列表{ {1}}。

转到偶数位置的元素,ba将是明显的答案,而cb将处于奇数位置。我们来看看d的定义。

even
  • 基本情况很简单:空列表中的偶数位置没有元素

  • 归纳案例说明位置0(evens (x:xs) = x:odds xs evens [] = [] )中的元素处于偶数位置 - 它是 - 并且它还表示列表中偶数位置的所有其他元素{ {1}}位于列表x中的奇数位置。实际上,列表(x:xs)中位置2中的元素位于列表xs中的位置1,位置3中位置3中的元素,依此类推;这有意义吗?

使用相同的推理,列表(x:xs)中奇数位置的元素是列表xs中偶数位置的元素,这正是上面(x:xs)的定义

答案 1 :(得分:1)

使用Debug.Trace查看索引如何随每次递归调用而变化。 trace函数在返回第二个参数之前将其第一个参数打印到标准错误。

import Debug.Trace

evens (x:xs) = x : odds (trace (show (zip [0..] xs)) xs)
evens [] = []
odds (_:xs) = evens (trace (show (zip [0..] xs)) xs)
odds [] = []

main = print (evens "abcdefg")

标准错误将显示

[(0,'b'),(1,'c'),(2,'d'),(3,'e'),(4,'f'),(5,'g')]
[(0,'c'),(1,'d'),(2,'e'),(3,'f'),(4,'g')]
[(0,'d'),(1,'e'),(2,'f'),(3,'g')]
[(0,'e'),(1,'f'),(2,'g')]
[(0,'f'),(1,'g')]
[(0,'g')]
[]

每次进行递归通话时,每个原始项目的位置都会转移"一个地方。例如,g在原始列表中处于偶数位置,但在每个递归调用中从偶数位置交替到奇数位置。

答案 2 :(得分:0)

让我们看一下输入列表中evens的示例评估。我将使用"abcde" - 请注意String是字符列表[Char]的别名,因此这相当于['a', 'b', 'c', 'd', 'e']'a' : 'b' : 'c' : 'd' : 'e' : []

我们从初始输入开始:

evens "abcde"

匹配evens的第一个模式,将'a'添加到结果的开头并继续执行列表的其余部分:

evens "abcde"
-------------

-- evens (x : xs) = x : odds xs
-- x = 'a'
-- xs = "bcde"
-- evens "abcde" = 'a' : odds "bcde"

'a' : odds "bcde"
-----------------

匹配odds的第一个模式,忽略'b'并继续:

'a' : odds "bcde"
      -----------

-- odds (_ : xs) = evens xs
-- xs = "cde"
-- odds "bcde" = evens "cde"

'a' : evens "cde"
      -----------

evens的第一种模式,添加了'c'

'a' : evens "cde"
      -----------

-- evens (x : xs) = x : odds xs
-- x = 'c'
-- xs = "de"
-- evens "cde" = 'c' : odds "de"

'a' : 'c' : odds "de"
      ---------------

odds的第一种模式,忽略'd'

'a' : 'c' : odds "de"
            ---------

-- odds (_ : xs) = evens xs
-- xs = "e"
-- odds "de" = evens "e"

'a' : 'c' : evens "e"
            ---------

evens的第一种模式,添加了'e'

'a' : 'c' : evens "e"
            ---------

-- evens (x : xs) = x : odds xs
-- x = 'e'
-- xs = "" = []
-- evens "e" = 'e' : odds ""

'a' : 'c' : 'e' : odds ""
            -------------

现在,最后,odds的第一个模式不匹配,因为空列表[]与列表构造函数_ : _不匹配,所以我们落到第二个(默认)模式:

'a' : 'c' : 'e' : odds ""
                  -------

-- odds _ = []
-- odds "" = []

'a' : 'c' : 'e' : []
                  --

给出最终结果:

"ace"

基本上这些函数将输入视为值的“流”并生成流:evens消耗一个元素并将其输出到结果,继续采用odds的剩下的;虽然odds消耗了一个元素并丢弃了它,但取其余的evens

这不会对索引进行任何计算,因为它没有 - 它只是遵循列表的结构。根据定义,列表中的第一个值位于偶数索引(从0开始计数时),因此evens保留它并获取奇数索引余数,而odds丢弃它,并取余数的偶数索引。删除每个元素会将所有索引向下移动一个 - 也就是说,输入列表中索引为1的元素位于输入尾部的索引0处:

zip [0..] "abcde" == [(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]

'a' 'b' 'c' 'd' 'e'
 0   1   2   3   4
 |   |   |   |   |
 x   /   /   /   /
    /   /   /   /
   /   /   /   /
  /   /   /   /
 |   |   |   |
'b' 'c' 'd' 'e'
 0   1   2   3

zip [0..] "bcde" == [(0, 'b'), (1, 'c'), (2, 'd'), (3, 'e')]

您还可以使用索引而不是相互递归来显式实现这些函数。使用列表推导:

evens xs = [x | (i, x) <- zip [0..] xs, even i]

odds xs = [x | (i, x) <- zip [0..] xs, odd i]

或使用明确的mapfilter

evens = map snd . filter (even . fst) . zip [0..]

odds = map snd . filter (odd . fst) . zip [0..]

然后你甚至可以将索引上的谓词变成参数:

indices f = map snd . filter (f . fst) . zip [0..]

evens = indices even

odds = indices odd

答案 3 :(得分:0)

我们自己的语言非常强大。在我自学微积分时,我遇到了极限,直到我读了一段牛顿中的一句话。当然,这是英文版。

首先,您对未使用或不需要的索引是正确的。

其次,代码不知道偶数或奇数之间的区别,你再次对它提出质疑。

最后,我稍微修改了这些,以便在我的实现上正常工作。

 evens [x] = [x];    evens (x:xs) = x:odds xs
 odds  [x] = [];      odds (_:xs) =  evens xs

他们的工作方式是平均的。它构建输出列表。它接受列表的第一项并使其成为输出列表的第一项或下一项。它用列表的其余部分调用赔率。赔率只是将收到的东西的尾巴归还给均匀。

与尾巴一样,它会丢弃它收到的第一项。

evens生成一个列表,其中约有一半的项目被丢弃。赔率几乎不会产生任何影响。但

如果您为列表[0,1,2,3,4]提供evens,则会返回以0开头的所有其他项目,即[0,2,4]。如果您为列表[1,2,3,4]提供evens,则返回[1,3],因为列表以奇数开头。无论在哪里开始,这就是它产生的东西。

尝试使用[1,1,1,1,1]或“bcdefg”

对函数所做的修改分别反映了每个函数对剩余元素的作用。赔率丢弃它,evens将它附加到输出列表的末尾。

如果给出以偶数开头的列表,这两个函数只能正常工作。如果给出一个带有奇数的列表,它们就会反向运行。

这是一个函数,它将根据指定的起始编号生成偶数或奇数列表,并以指定的结束编号结束。

eo s e = [s,s+2..e]