【问题标题】:How to create Haskell function that returns every third element from a list of ints如何创建从整数列表中返回每三个元素的 Haskell 函数
【发布时间】:2018-02-01 02:33:38
【问题描述】:

我想创建一个函数,它在不使用任何预定义函数的情况下从整数列表中每隔三个整数返回一次。例如everyThird [1,2,3,4,5] --> [1,4]

everyThird:: [a] -> [a]

我是否可以继续使用 tail 遍历列表并每隔三次调用附加到一个新列表?我是 Haskell 的新手,对这一切感到非常困惑

【问题讨论】:

  • 作为一个经验法则,你应该避免head,tail,并且更喜欢模式匹配,如下面的答案所示。
  • 我会尽量记住这一点,谢谢。这是我第一次在 Haskell 工作,所以我现在正在学习其中一些是如何工作的。

标签: haskell functional-programming


【解决方案1】:

另一种方法是处理三种不同的基本情况,在所有这些情况下,我们都位于列表的末尾并且列表的长度小于三个元素,以及一种递归情况,其中列表位于至少三个元素长:

everyThird :: [a] -> [a]
everyThird []         = []
everyThird [x]        = [x]
everyThird [x, _]     = [x]
everyThird (x:_:_:xs) = x:everyThird xs

【讨论】:

    【解决方案2】:

    您想完全按照您所说的去做:遍历列表并仅在每第三次调用时包含该元素。但是,有一个问题。 Haskell 是一种有趣的语言,其中“更改”变量的想法没有意义,因此“有一个计数器变量 i 告诉我们是否在第三个元素上”的常用方法不会以通常的方式工作。相反,我们将创建一个递归辅助函数来为我们维护计数。

    everyThird :: [Int] -> [Int]
    everyThird xs = helper 0 xs
      where helper _ [] = []
            helper 0 (x : xs) = x : helper 2 xs
            helper n (_ : xs) = helper (n - 1) xs
    

    我们在帮助器中有三个案例。

    1. 如果列表为空,则停止并返回空列表。
    2. 如果计数器为 0(也就是说,如果我们在第三个元素上),则创建一个从当前元素开始到剩余计算结束的列表。
    3. 如果计数器不为零,请倒计时并继续迭代。

    由于模式匹配的工作方式,它会依次尝试这三个语句。

    请注意我们如何使用附加参数作为计数器变量,因为我们不能像在命令式语言中那样改变变量。另外,请注意我们如何递归地构造列表;我们从不“追加”到现有列表,因为这意味着我们正在改变列表。我们只是从头开始构建列表,并在第一轮得到正确的结果。

    【讨论】:

    • 那么,当你调用everyThird xs = helper 0 xs 时,你是在调用一个辅助函数(helper)来检查这三种情况?然后返回全部检查完后剩下的列表?
    • 没错。请注意,helper 会调用自己来获取列表的其余部分。像这样递归思考是 Haskell 的工作原理,它通过一次只处理一个步骤来帮助您划分问题。
    • 非常感谢,感谢您的帮助!
    • 有没有在不使用 where 的情况下重写这个?或者其他的比如让?
    • 您可以用相当复杂的fix 调用来替换大多数递归函数,但这只是有点矫枉过正。为什么要避免声明局部变量/函数?
    【解决方案3】:

    Haskell 没有经典迭代(即没有循环),至少没有 monad,但您可以使用与 for 循环中类似的逻辑,方法是使用索引 [0..] 压缩列表并应用来自 Data 的适当函数.列表。

    例如你需要做的是过滤每三个元素:

    everyThirdWithIndexes list = filter (\x -> snd x `mod` 3 == 0) $ zip list [0..]
    

     

    当然你必须摆脱索引,有两种优雅的方法可以做到这一点:

    everyThird list = map (fst) . everyThirdWithIndexes list
    -- or:
    everyThird list = fst . unzip . everyThirdWithIndexes list
    

     

    如果您不熟悉过滤器和映射,您可以定义一个简单的递归,从列表的每个第一个元素构建一个列表,删除接下来的两个,然后通过新的函数调用添加另一个:

    everyThird [] = []  -- both in case if the list is empty and the end case
    everyThird (x:xs) = x : everyThird (drop 2 xs)
    

     

    编辑:如果您对这些解决方案有任何疑问(例如某些您不熟悉的语法),请随时在 cmets 中提问。 :)

    【讨论】:

    • 所以参考你帖子的最后一部分,调用 everyThird[] = [] 会检查列表是否为空,如果是则返回一个空列表?然后下一个调用 (everyThird (x:xs) = x :everyThird (drop 2xs) 将获取列表,从中删除前两个整数,然后附加下一个?我是 Haskell 的新手,这一切似乎都是魔法我
    • 是的,也许举个例子会有所帮助。假设我们有[1,2,3,4,5]。该列表不为空,因此它匹配第二个表达式并构建1:everyThird(drop 2 [2,3,4,5]),其计算结果为1:everyThird([4,5])。好的,太好了,我们有了第一个元素,在第二次调用中,我们构建了一个类似的表达式并将其附加到第一个元素,因此生成了类似 1:4:everyThird(drop 2 [5]) 的内容。从[5] 中删除两个会生成一个与第一个函数定义匹配的空列表,因此最终表达式为1:4:[],与[1,4] 相同(后者只是语法糖)
    • 这个例子帮了很多忙,一开始我不太确定它是如何工作的,现在它更清楚了。非常感谢!
    • 没问题,祝你学习 Haskell 好运,别担心 - Haskell 中的很多事情都需要一些时间来理解 :)
    【解决方案4】:

    一种经典方法:

    everyThird xs = [x | (1,x) <- zip (cycle [1..3]) xs]
    

    【讨论】:

      【解决方案5】:

      您也可以使用 Data.List.Split 中的 chunksOf 将列表分成 3 个块,然后只映射每个块的第一个元素:

      import Data.List.Split
      
      everyThird :: [a] -> [a]
      everyThird xs = map head $ chunksOf 3 xs
      

      其工作原理如下:

      *Main> everyThird [1,2,3,4,5]
      [1,4]
      

      注意:您可能需要运行cabal install split 才能使用chunksOf

      【讨论】:

      • 他不想使用预定义的函数。除此之外,这是一个很好的答案。
      猜你喜欢
      • 2012-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-27
      相关资源
      最近更新 更多