简体   繁体   English

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

[英]Getting even and odd position of elements in list - Haskell Mutual Recursion

I recently started learning Haskell. 我最近开始学习Haskell。

I found this code online which returns the elements at all even/odd positions of a list. 我在网上找到了这个代码,它返回列表所有偶数/奇数位置的元素。

It makes use of mutual recursion, but I cannot seem to understand how it works internally. 它利用了相互递归,但我似乎无法理解它是如何在内部工作的。

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

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

In particular, I don't understand how the list is moving forward and evaluating all elements. 特别是,我不明白该清单如何向前推进并评估所有要素。 How does it check for even positions even though there is no explicit index checking 即使没有明确的索引检查,它如何检查偶数位置

Would someone be able to provide insight? 有人能够提供洞察力吗?

First, let's agree on something: we use 0-based indices most of the time, right? 首先,让我们就某事达成一致:我们大多数时候都使用0指数,对吧? So, if I were to ask you what the element in position 2 is in the list 所以,如果我问你列表中第2位的元素是什么

a : b : c : d : []

you would answer c . 你会回答c EDIT : if you're not familiar with Haskell notation, : is the list constructor and a : b denotes the list made by prepending a in front of list b . 编辑 :如果您不熟悉Haskell表示法,则:列表构造函数和a : b表示通过在列表b前面添加前缀而a的列表。

Moving on to elements in even position, a and c would be the obvious answer, whereas b and d would be in odd position. 继续到偶数位置的元素, ac将是明显的答案,而bd将处于奇数位置。 Let's look at the definition of even . 让我们来看看even的定义。

evens (x:xs) = x:odds xs
evens [] = []
  • The base case is trivial: there is no element in even position in the empty list 基本情况很简单:空列表中的偶数位置没有元素

  • the inductive case says that the element in position 0 ( x ) is in even position -- which it is -- and it also says that all the other elements in even position in the list (x:xs) are in odd position in the list xs . 归纳案例说明位置0( x )中的元素处于偶数位置 - 它是 - 并且它还表示列表中偶数位置中的所有其他元素(x:xs)位于奇数位置列表xs Indeed, element in position 2 in list (x:xs) is in position 1 in list xs , the one in position 4 in position 3, and so on and so forth; 实际上,列表中位置2中的元素(x:xs)位于列表xs中的位置1,位置3中位置3中的元素,依此类推; does that make sense? 那有意义吗?

Using the same reasoning, it follows that elements in odd position in the list (x:xs) are elements in even position in the list xs , which is exactly the definition of odds here above. 使用相同的推理,下面列表中奇数位置的元素(x:xs)是列表xs中偶数位置的元素,这正是上面odds的定义。

Use Debug.Trace to see how the indices change with each recursive call. 使用Debug.Trace查看索引如何随每次递归调用而变化。 The trace function prints its first argument to standard error before returning its second argument. 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")

Standard error will show 标准错误将显示

[(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')]
[]

Each time you make a recursive call, the positions of each original item "shifts" by one place. 每次进行递归调用时,每个原始项目的位置会“移动”一个位置。 g , for instance, is in an even position in the original list, but alternates from even to odd positions in each recursive call. 例如, g在原始列表中处于偶数位置,但在每个递归调用中从偶数位置交替到奇数位置。

Let's look at a sample evaluation of evens on an input list. 让我们看一下输入列表上的evens样本评估。 I'll use "abcde" —recall that String is an alias for a list of characters [Char] , so this is equivalent to ['a', 'b', 'c', 'd', 'e'] or 'a' : 'b' : 'c' : 'd' : 'e' : [] . 我将使用"abcde" -recall, String是字符列表[Char]的别名,所以这相当于['a', 'b', 'c', 'd', 'e']'a' : 'b' : 'c' : 'd' : 'e' : []

We start with the initial input: 我们从最初的输入开始:

evens "abcde"

Match the first pattern of evens , adding 'a' to the beginning of the result and proceeding on the remainder of the list: 匹配第一种evens模式,在结果的开头添加'a'并继续执行列表的其余部分:

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

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

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

Match the first pattern of odds , ignoring 'b' and proceeding: 匹配第一种odds模式,忽略'b'并继续:

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

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

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

First pattern of evens , adding 'c' : 第一种evens模式,添加'c'

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

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

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

First pattern of odds , ignoring 'd' : 第一种odds模式,忽略'd'

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

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

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

First pattern of evens , adding 'e' : 第一种evens模式,添加'e'

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

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

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

Now, finally, the first pattern of odds doesn't match, because an empty list [] doesn't match the list constructor _ : _ , so we fall through to the second (default) pattern: 现在,最后,第一个odds模式不匹配,因为空列表[]与列表构造函数_ : _不匹配,所以我们落到第二个(默认)模式:

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

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

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

Giving the final result: 给出最终结果:

"ace"

Basically these functions are treating the input as a “stream” of values and producing a stream as a result: evens consumes one element and outputs it to the result, proceeding to take the odds of the remainder; 基本上这些函数将输入视为值的“流”并产生流作为结果: evens消耗一个元素并将其输出到结果,继续获取余数的odds ; while odds consumes one element and discards it, taking the evens of the remainder. 虽然odds消耗了一个元素并将其丢弃,但剩下的则是evens

This doesn't do any calculation on the indices because it doesn't have to—it's just following the structure of the list. 这不会对索引进行任何计算,因为它没有 - 它只是遵循列表的结构。 By definition, the first value in a list is at an even index (when counting from 0), so evens keeps it and takes the odd indices of the remainder, while odds discards it and takes the even indices of the remainder. 根据定义,列表中的第一个值是偶数索引(当从0开始计数时),因此evens保留它并获取余数的奇数索引,而odds丢弃它并获取余数的偶数索引。 Removing each element shifts all the indices down by one—that is, the element that was at index 1 in the input list is at index 0 in the tail of the input: 删除每个元素会将所有索引向下移动一个 - 也就是说,输入列表中索引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')]

You could also implement these functions explicitly using indices instead of mutual recursion. 您还可以使用索引而不是相互递归来显式实现这些函数。 With list comprehensions: 使用列表推导:

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

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

Or with explicit map and filter : 或者使用显式mapfilter

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

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

Then you could even turn the predicate on the indices into a parameter: 然后你甚至可以将索引上的谓词变成一个参数:

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

evens = indices even

odds = indices odd

Our own language is quite powerful. 我们自己的语言非常强大。 I was having difficulty with limits when teaching myself calculus until I read one sentence in one paragraph of Newton. 在我自学微积分时,我遇到了极限,直到我读了一段牛顿中的一句话。 It was, of course, in English. 当然,这是用英语写的。

First off, you are correct about the indexing not being used or needed. 首先,您对未使用或不需要的索引是正确的。

Secondly, the code does not know the difference between even or odd and you are again right to question it. 其次,代码不知道偶数或奇数之间的区别,你再次对它提出质疑。

Finally, I modified these slightly to work properly on my implementation. 最后,我稍微修改了这些,以便在我的实现上正常工作。

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

How they work is evens does the work. 他们的工作方式是平均的。 It builds the output list. 它构建输出列表。 It takes the first item of a list fed it and makes it the first or next item of the output list. 它接受列表的第一项并使其成为输出列表的第一项或下一项。 It calls odds with the remainder of the list. 它用列表的其余部分调用赔率。 odds simply returns the tail of what it received back to evens. 赔率只是将收到的东西的尾巴返回到均衡。

Like tail, it discards the first item of what it receives. 像尾巴一样,它会丢弃它收到的第一项。

evens produces a list with about half of the items discarded. evens生成一个列表,其中大约一半的项目被丢弃。 odds produces nothing but does the critical discarding. 赔率几乎不会产生任何影响,但是关键的丢弃。

If you give evens the list [0,1,2,3,4] it returns every other item starting with 0 , that is, [0,2,4] . 如果给evens列表[0,1,2,3,4]则返回从0开始的所有其他项,即[0,2,4] If you give evens the list [1,2,3,4] it returns [1,3] because the list started with and odd number. 如果你给evens列表[1,2,3,4]它返回[1,3]因为列表以奇数开头。 Wherever evens starts, that's what it produces. 无论在哪里开始,这就是它产生的东西。

Try either with [1,1,1,1,1] or "bcdefg" 尝试使用[1,1,1,1,1]或“bcdefg”

The modifications made to the functions reflect what each does respectively with the remaining element. 对函数所做的修改分别反映了每个函数对剩余元素的作用。 odds discards it, evens attaches it to the end of the output list. 赔率丢弃它,evens将它附加到输出列表的末尾。

Both functions only work properly if given a list starting with an even number. 如果给出以偶数开头的列表,则这两个函数仅能正常工作。 If given a list with an odd number they work in reverse. 如果给出具有奇数的列表,则它们反向工作。

Here is a function that will produce an even or odd list depending on a specified starting number and ending in the specified ending number. 这是一个函数,它将根据指定的起始编号生成偶数或奇数列表,并以指定的结束编号结束。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM