我最近开始学习Haskell。
我在网上找到了这个代码,它返回列表所有偶数/奇数位置的元素。
它使用相互递归,但我似乎无法理解它是如何在内部工作的。
evens (x:xs) = x:odds xs
evens _ = []
odds (_:xs) = evens xs
odds _ = []
特别是,我不明白该列表是如何向前发展并评估所有元素的。即使没有明确的索引检查,它如何检查偶数位置
有人能够提供见解吗?
答案 0 :(得分:6)
首先,让我们就某事达成一致:我们大多数时候都使用0指数,对吧?所以,如果我问你列表中的第2位元素是什么
a : b : c : d : []
你会回答c
。 编辑:如果您不熟悉Haskell表示法,:
是列表构造函数,a : b
表示通过在列表前添加a
而生成的列表{ {1}}。
转到偶数位置的元素,b
和a
将是明显的答案,而c
和b
将处于奇数位置。我们来看看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]
或使用明确的map
和filter
:
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]