简单的Haskell函数中的中间值

时间:2019-02-20 15:08:55

标签: 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。

5 个答案:

答案 0 :(得分:7)

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

doubleEveryOtherFromRight = reverseList . doubleEveryOther . reverseList

请注意,您的reverseList实际上已经在标准前奏中定义为reverse。因此您不需要自己定义它。

我知道上述解决方案不是很有效,因为reverse的两种用法都需要遍历整个列表。我将它留给其他人来建议更有效的版本,但希望这可以说明函数组合的功能,以从较简单的函数构建更复杂的计算。

答案 1 :(得分:5)

正如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) end 结尾,因此起始项由ls的长度确定。奇数长度要求我们以(*2)开始;偶数id

答案 2 :(得分:2)

每当您需要处理列表中的连续术语时,使用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开始的每个元素进行计数,并且如果索引与列表中的最后一个元素(奇数或偶数均为偶数)具有相同的奇偶校验,则将该元素加倍,否则将其保留不变。 / p>

但是,如果有人可以在评论中指出这一点,那我不是100%肯定会更有效

答案 3 :(得分:2)

大多数其他解决方案向您展示如何使用已有的构建块或标准库中可用的构建块来构建函数。我认为了解如何从头开始构建它也很有启发性,因此在此答案中,我将讨论一个想法。

这是计划:我们将一直走到列表的最后,然后再走到最前面。从头到尾,我们将建立新清单。我们回溯时构建它的方法是在(乘)因子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_隐藏在where中的doubleFromRight块内;并且由于略微修改的名称实际上并没有告诉您任何新内容,因此我们将在内部使用社区标准名称。这两个变化可能会把您放在这里:

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,[])

但是我认为在这种情况下,最好通过显式递归提前停止一步;在这种情况下甚至可能更具可读性!

答案 4 :(得分:1)

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

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

这会将输入列表与循环的 infinite 函数列表[(*2), id, (*2), id, .... ]配对。然后两者都跳过。当第一个列表完成时,第二个处于适当的状态-再次-成对应用-在第二个列表上!这次是真的。

因此实际上它确实测量了长度(当然),没有测量整数,而是说列表元素。

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

> doubleFromRight [1..4]
[2,2,6,4]

> doubleFromRight [1..5]
[1,4,3,8,5]

foldl'函数从左到右处理列表。它的类型是

foldl' :: (b -> a -> b) -> b -> [a] -> b
--        reducer_func    acc   xs    result