【问题标题】:Getting even and odd position of elements in list - Haskell Mutual Recursion获取列表中元素的偶数和奇数位置 - Haskell Mutual Recursion
【发布时间】:2018-09-25 09:19:41
【问题描述】:

我最近开始学习 Haskell。

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

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

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

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

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

有人能提供见解吗?

【问题讨论】:

  • 您是否尝试过手动评估简单的表达式,例如evens ('a':'b':'c':[])?什么时候你的理解失败了?
  • @Bergi 更多的是我不明白列表是如何向前推进和评估所有元素的。即使没有明确的索引检查,它如何检查偶数位置
  • evens中,x是第0个位置的元素,xs是第一个位置以后的元素。在对odds xs 的调用中,我们将一个元素向前移动。除了两个函数,您还可以编写一个带有“布尔索引”的函数 - 切换您当前是在原始数组中的奇数还是偶数位置。
  • 你见过其他递归列表的例子吗(比如lengthsumtake)?
  • 我明白了,也许你应该从比(我推荐 lengthtake 以及返回第 0、第 1 或第 2 个元素的函数:head、@987654333)更简单的东西开始@,second)。您可能需要了解有关模式匹配的更多信息。

标签: haskell recursion functional-programming mutual-recursion


【解决方案1】:

首先,让我们达成一致意见:我们大部分时间都使用从 0 开始的索引,对吧?所以,如果我要问你列表中位置 2 的元素是什么

a : b : c : d : []

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

移动到偶数位置的元素,ac 将是显而易见的答案,而 bd 将处于奇数位置。我们来看看even的定义。

evens (x:xs) = x:odds xs
evens [] = []
  • 基本情况很简单:空列表中的偶数位置没有元素

  • 1234563在列表xs 中处于奇数位置。实际上,列表(x:xs) 中位置2 的元素在列表xs 中的位置1,位置3 中位置4 的元素,依此类推;这有意义吗?

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

【讨论】:

    【解决方案2】:

    使用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 在原始列表中的偶数位置,但在每个递归调用中从偶数位置交替到奇数位置。

    【讨论】:

      【解决方案3】:

      让我们看一下输入列表中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

      这不会对索引进行任何计算,因为它没有必要——它只是遵循列表的结构。根据定义,列表中的第一个值位于 even 索引处(从 0 开始计数),因此 evens 保留它并获取余数的 odd 索引,而odds 将其丢弃并采用余数的 even 索引。删除每个元素会将所有索引向下移动 1 — 也就是说,输入列表中位于索引 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
      

      【讨论】:

        【解决方案4】:

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

        首先,关于未使用或不需要索引的说法是正确的。

        其次,代码不知道偶数或奇数之间的区别,您再次质疑它是正确的。

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

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

        他们的工作方式是 evens 工作。它构建输出列表。它获取列表中的第一项并使其成为输出列表的第一项或下一项。它用列表的其余部分调用赔率。赔率只是将它收到的尾部返回到偶数。

        像tail一样,它丢弃它收到的第一项。

        evens 生成一个列表,其中大约有一半的项目被丢弃。赔率只会产生关键的弃牌。

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

        尝试使用 [1,1,1,1,1] 或 "bcdefg"

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

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

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-12-22
          • 2021-07-18
          • 1970-01-01
          • 2021-08-27
          • 1970-01-01
          • 2012-09-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多