简单Haskell函数中的中间值

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简单Haskell函数中的中间值相关的知识,希望对你有一定的参考价值。

我需要一个函数来加倍列表中的每个其他数字。这样做的诀窍:

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther []         = []
doubleEveryOther (x:[])     = [x]
doubleEveryOther (x:(y:zs)) = x : 2 * y : doubleEveryOther zs

然而,问题是我需要从右边开始加倍每个其他数字 - 所以如果列表的长度是偶数,那么第一个将加倍,等等。

我理解在Haskell中向后操作列表很棘手,所以我的计划是反转列表,应用我的函数,然后再次输出反向。我有一个reverseList功能:

reverseList :: [Integer] -> [Integer]
reverseList  [] = []
reverseList  xs = last xs : reverseList (init xs) 

但是我不太确定如何将它植入我原来的功能中。我得到这样的事情:

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther []         = []
doubleEveryOther (x:[])     = [x]
doubleEveryOther (x:(y:zs)) =
 | rev_list = reverseList (x:(y:zs))
 |  rev_list = [2 * x, y] ++ doubleEveryOther zs

我不完全确定包含这样的中间值的函数的语法。

如果它是相关的,这是CIS 194 HW 1的练习2。

答案

这是您已经创建的两个函数的非常简单的组合:

doubleEveryOtherFromRight = reverseList . doubleEveryOther . reverseList

请注意,您的reverseList实际上已在标准Prelude中定义为reverse。所以你不需要自己定义它。

我知道上面的解决方案效率不高,因为reverse的两种用法都需要通过整个列表。我将把它留给其他人来建议更高效的版本,但希望这说明了函数组合的功能,以便用更简单的版本构建更复杂的计算。

另一答案

正如Lorenzo指出的那样,您可以进行一次传递以确定列表是否具有奇数或偶数长度,然后进行第二次传递以实际构建新列表。但是,将两个任务分开可能更简单。

doubleFromRight ls = zipWith ($) (cycle fs) ls -- [f0 ls0, f1 ls1, f2 ls2, ...]
   where fs = if odd (length ls)
              then [(*2), id]
              else [id, (*2)]

那么这是如何工作的呢?首先,我们观察到要创建最终结果,我们需要将两个函数(id(*2))中的一个应用于ls的每个元素。如果我们有适当的功能列表,zipWith可以做到这一点。其定义的有趣部分基本上是

zipWith f (x:xs) (y:ys) = f x y : zipWith f xs ys

f($)时,我们只是将一个列表中的函数应用到另一个列表中的相应元素。

我们想用lsid的无限交替列表来输入(*2)。问题是,该列表应该从哪个函数开始?它应该始终以(*2)结束,因此起始项目由ls的长度决定。奇怪的长度需要我们从(*2)开始;一个偶数,id

另一答案

大多数其他解决方案向您展示如何使用您已有的构建块或构建标准库中的构建块来构建您的函数。我认为看看如何从头开始构建它也是有益的,所以在这个答案中我讨论了一个想法。

这是计划:我们将一直走到列表的末尾,然后走回前面。我们将在从最后回来的过程中构建新的列表。我们回过头来构建它的方式是在(乘法)因子1和2之间交替,将当前元素乘以当前因子,然后交换下一步的因子。最后,我们将返回最终因素和新列表。所以:

doubleFromRight_ :: Num a => [a] -> (a, [a])
doubleFromRight_ [] = (1, [])
doubleFromRight_ (x:xs) =
    -- not at the end yet, keep walking
    let (factor, xs') = doubleFromRight_ xs
    -- on our way back to the front now
    in (3-factor, factor*x:xs')

如果你愿意,你可以编写一个小包装器,最后抛弃该因子。

doubleFromRight :: Num a => [a] -> [a]
doubleFromRight = snd . doubleFromRight_

在ghci:

> doubleFromRight [1..5]
[1,4,3,8,5]
> doubleFromRight [1..6]
[2,2,6,4,10,6]

现代的做法是将辅助函数doubleFromRight_隐藏在wheredoubleFromRight区块内;由于略微修改的名称实际上并没有告诉您任何新内容,因此我们将在内部使用社区标准名称。这两个变化可能会让你在这里:

doubleFromRight :: Num a => [a] -> [a]
doubleFromRight = snd . go where
    go [] = (1, [])
    go (x:xs) = let (factor, xs') = go xs in (3-factor, factor*x:xs')

一个先进的Haskeller可能会注意到go符合折叠的形状并写下:

doubleFromRight :: Num a => [a] -> [a]
doubleFromRight = snd . foldr (\x (factor, xs) -> (3-factor, factor*x:xs)) (1,[])

但是我认为在这种情况下使用显式递归停止前一步是完全正常的;在这种情况下它甚至可能更具可读性!

另一答案

每当你必须处理列表中的连续术语时,具有列表理解的zip是一种简单的方法。它需要两个列表并返回一个元组列表,因此您可以使用其尾部压缩列表或将其编入索引。我的意思是

doubleFromRight :: [Int] -> [Int]
doubleFromRight ls = [if (odd i == oddness) then 2*x else x | (i,x) <- zip [1..] ls]
    where
      oddness = odd . length $ ls

这样你就可以计算每个元素,从1开始,如果索引与列表中的最后一个元素具有相同的奇偶校验(奇数或偶数两个),那么你将元素加倍,否则你保持原样。

我不是百分百肯定这是更有效率,但是,如果任何人都可以在评论中指出它会很棒

另一答案

如果我们真的想避免计算长度,我们可以定义

doubleFromRight :: Num a => [a] -> [a]
doubleFromRight xs = zipWith ($)
                       (foldl' (\a _ -> drop 1 a) (cycle [(2*), id]) xs)
                       xs

这将输入列表与循环的无限函数列表q​​azxswpoi配对。然后它会同时跳过它们。当第一个列表完成时,第二个列表处于适当的状态 - 再次 - 应用,成对, - 在第二个列表上!这一次,真实的。

所以实际上它确实测量了长度(当然),它只是不计入整数,而是列表元素可以这么说。

如果列表的长度是偶数,则第一个元素将加倍,否则第二个元素将加倍,如您在问题中指定的那样:

[(*2), id, (*2), id, .... ]

> doubleFromRight [1..4] [2,2,6,4] > doubleFromRight [1..5] [1,4,3,8,5] 函数从左到右处理列表。它的类型是

foldl'

以上是关于简单Haskell函数中的中间值的主要内容,如果未能解决你的问题,请参考以下文章

Haskell - 简单的构造函数比较(?)函数

如何处理 Haskell 中的“可能 [值]”列表?

函数返回与函数参数中的 Haskell 模式匹配

如何编码 Haskell/函数式编程中的选择公理?

Haskell中函数调用的优化

haskell中的公平并发`map`函数?