在Haskell中从右侧加倍列表中的每个其他元素

时间:2013-11-08 19:55:13

标签: haskell

我有一个列表,我希望从右侧加倍此列表中的所有其他元素。

还有另一个相关的问题可以解决这个问题,但它从左边开始翻倍,而不是右边:Haskell: Double every 2nd element in list

例如,在我的情景中,[1,2,3,4]将成为[2,2,6,4],在那个问题中,[1,2,3,4]将成为[1, 4,3,8]。

我该如何实现?

15 个答案:

答案 0 :(得分:13)

我认为最佳答案误解了这个问题。标题清楚地表明OP希望将列表右侧的第二,第四等元素加倍。 ØrjanJohansen的回答是正确的,但速度很慢。这是我更有效的解决方案:

doubleFromRight :: [Integer] -> [Integer]
doubleFromRight xs = fst $ foldr (\x (acc, bool) ->
                                  ((if bool then 2 * x else x) : acc,
                                   not bool)) ([], False) xs

它从右侧折叠列表。初始值是一个包含空列表和布尔值的元组。布尔值从false开始,每次都翻转。仅当布尔值为真时,该值才乘以2。

答案 1 :(得分:10)

好的,正如@TomEllis所提到的,其他人似乎都将你的问题解释为左边的奇数元素,而不是右边的偶数,正如你的标题所暗示的那样。

由于您从右侧开始检查位置,因此无法知道在找到列表末尾之前要加倍的内容。所以解决方案不能是懒惰的,需要在返回任何内容之前将整个列表临时存储(即使只是在执行堆栈上)。

鉴于此,最简单的解决方案可能是在左 - 左解决方案之前和之后应用反向:

doubleFromRight = reverse . doubleFromLeft . reverse

答案 2 :(得分:9)

想一想。

double = zipWith ($) (cycle [(*2),id])

编辑我应该注意,这不是我的解决方案,它是(*2)id翻转的关联帖子的解决方案。这就是我说的原因,因为它是如此微不足道的修复。

答案 3 :(得分:3)

直接实施将是:

doubleOddElements :: [Int] -> [Int]
doubleOddElements [] = []
doubleOddElements [x] = [2 * x]
doubleOddElements (x:y:xs) = (2*x):y:(doubleOddElements xs)

答案 4 :(得分:3)

好吧,所以不像其他答案一样优雅或高效,但我从初学者的角度(我是一个)在可读性和基本功能方面写了这个。

开始,每隔一个数字增加一倍。

使用此脚本:doubleEveryOther [1,3,6,9,12,15,18]生成[1,6,6,18,12,30,18]doubleEveryOther [1,3,6,9,12,15]生成[2,3,12,9,24,15]

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther [] = []
doubleEveryOther (x:[]) = [x]
doubleEveryOther (x:y:zs)
  |  (length (x:y:zs)) `mod` 2    /= 0    =    x : y*2 : doubleEveryOther zs
  |  otherwise                            =    x*2 : y : doubleEveryOther zs

答案 5 :(得分:1)

我的第一个想法是:

doubleOdd (x:xs) = (2*x):(doubleEven xs)
doubleOdd [] = []
doubleEven (x:xs) = x:(doubleOdd xs)
doubleEven [] = []

DiegoNolan的解决方案更优雅,因为功能和序列长度更容易改变,但我花了一点时间才能理解。

添加从右侧操作的要求使其更复杂一些。 foldr是从右边做某事的巧妙起点,所以让我试试:

doubleOddFromRight = third . foldr builder (id,double,[])
    where third (_,_,x) = x
          builder x (fx,fy,xs) = (fy, fx, fx x : xs)
          double x = 2 * x

这为每个条目交换了两个函数fxfy。要查找任何条目的值,需要遍历列表的末尾,查找长度是奇数还是偶数。

答案 6 :(得分:1)

尝试稍微概括一下问题:因为我们想要从结尾加倍每个第二个元素,我们无法提前知道它是否是每个奇数或甚至从一开始。所以最简单的方法是构造两者,如果整体大小是偶数或奇数,则计算,然后决定。

让我们定义一个捕获:{/ p>的Applicative数据结构

  • 有两个值的变体,
  • 保持长度的奇偶校验(奇数/偶数)和
  • 当两个这样的值组合时交替两者,

如下:

import Control.Applicative
import Data.Monoid
import qualified Data.Traversable as T

data Switching m = Switching !Bool m m
  deriving (Eq, Ord, Show)

instance Functor Switching where
    fmap f (Switching b x y) = Switching b (f x) (f y)

instance Applicative Switching where
    pure x = Switching False x x
    (Switching False f g) <*> (Switching b2 x y) = Switching b2 (f x) (g y)
    (Switching True  f g) <*> (Switching b2 x y) = Switching (not b2) (f y) (g x)

因此,遍历列表将产生两个如下所示的列表:

x1 y2 x3 y4 ...
y1 x2 y3 x4 ...

两个zig-zag-ing副本。现在我们可以计算

double2 :: (Num m) => m -> Switching m
double2 x = Switching True (2 * x) x

double2ndRight :: (Num m, T.Traversable f) => f m -> f m
double2ndRight k = case T.traverse double2 k of
                    Switching True _ y -> y
                    Switching False x _ -> x

答案 7 :(得分:1)

我只是在学习 Haskell ,所以请找到以下初学者解决方案。我尝试使用有限的函数,例如zipWithcyclereverse

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther [] = []
doubleEveryOther s@(x:xs)
  | (length s) `mod` 2 == 0 = (x * 2) : (doubleEveryOther xs)
  | otherwise =  x : (doubleEveryOther xs)

需要注意的是,当从右侧加倍每个元素时,你可以将加倍分为两种情况:

  1. 如果列表是偶数长度,您最终将最终加倍列表的第一个元素。
  2. 如果列表长度为奇数,则不会将列表的第一个元素加倍。
  3. 我在CS194

    作为家庭作业的一部分回答了这个问题

答案 8 :(得分:1)

这是我的两个解决方案,请注意我在Haskell中完全是初学者。

首先使用列表函数, head tail lenght

doubleSecondFromEnd :: [Integer] -> [Integer]
doubleSecondFromEnd [] = []  -- Do nothing on empty list
doubleSecondFromEnd n
  | length n `mod` 2 == 0 = head n * 2 : doubleSecondFromEnd (tail n)
  | otherwise      = head n : doubleSecondFromEnd (tail n)

第二个,类似但使用不同方法仅使用长度功能:

doubleSecondFromEnd2 :: [Integer] -> [Integer]
doubleSecondFromEnd2 [] = []  -- Do nothing on empty list
doubleSecondFromEnd2 (x:y)
  | length y `mod` 2 /= 0 = x * 2 : doubleSecondFromEnd2 y
  | otherwise      = x : doubleSecondFromEnd2 y

答案 9 :(得分:1)

这是我对此CIS 194家庭作业的答案。它仅使用第1讲+ reverse中介绍的内容来实现。

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

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther xs = reverse (doubleEveryOtherLeftToRight (reverse xs))

答案 10 :(得分:0)

有些答案似乎没有涉及列表的奇数/偶数长度。

doubleEveryOtherEvenList = zipWith ($) (cycle [(*2),id])

doubleEveryOther :: [Int] -> [Int]
doubleEveryOther n
    | length n `mod` 2 == 0 = doubleEveryOtherEvenList n
    | otherwise = (head n) : doubleEveryOtherEvenList (tail n)

答案 11 :(得分:0)

参加haskell的edx课程,这是我的noob解决方案。

doubleSecondR :: [Integer] -> [Integer]

doubleSecondR xs = reverse(zipWith (*) (reverse xs) ys)

where ys = repeat' [1,2]

repeat' :: [a] -> [a]

repeat' xs = xs ++ repeat' xs

答案 12 :(得分:0)

我也从CIS 194课程来到这个问题。

我这样做了两种方式。首先,我认为问题的关键点应该只依赖于列出的3个可能来源中提到的函数或编程方式。 course lecture 1Real World Haskell ch. 1,2Learn You a Haskell ch. 2

好的:

  • 递归,条件
  • reverse,基本功能,如maxminoddeven
  • 列出函数,例如headtail,...

不行:

  • foldrfoldlmap
  • 高阶函数
  • 除此之外的任何事情

第一个解决方案,只使用带计数器的递归:

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther xs = loopDoubles xs 1

loopDoubles :: [Integer] -> Integer -> [Integer]
loopDoubles [] _ =  []
loopDoubles xs n =  loopDoubles (init xs) (n + 1) ++ [doubleEven (last xs) n]

doubleEven :: Integer -> Integer -> Integer
doubleEven x n = if even n then x * 2 else x

此方法使用递归,但避免计算递归的每个级别的长度。

违反我的上述规则的第二种方法:

doubleEveryOther' :: [Integer] -> [Integer]
doubleEveryOther' xs =  map (\x -> if even (fst x) then (snd x) * 2 else snd x) $ zip (reverse [1..n]) xs
                        where n = length(xs)

第二个工作方法是建立一组反向索引,然后映射这些索引。这确实计算了长度,但只计算一次。

e.g。 [1,1,1,1] -> [(4,1),(3,1),(2,1),(1,1)]

这两项都要求将所有其他元素从右侧加倍

> doubleEveryOther [1,2,3,4]
[2,2,6,4]
> doubleEveryOther [1,2,3]
[1,4,3]
> doubleEveryOther' [1,2,3,4]
[2,2,6,4]
> doubleEveryOther' [1,2,3]
[1,4,3]

答案 13 :(得分:0)

为简单起见,这是怎么回事?

doubleEveryOtherRev :: [Integer] -> [Integer]

doubleEveryOtherRev l = doubleRev l []
  where
    doubleRev [] a = a
    doubleRev (x:[]) a = (x:a)
    doubleRev (x:y:zs) a = doubleRev zs (2*y:x:a)

如果您遵循该课程的推荐,则必须提供反向的数字列表,因为它会在每次反转时将其他所有元素加倍。我认为这与使用两次反向函数不同,另一种是在其他数字之间加倍,因为你不需要第二次知道列表的完整范围。换句话说,它解决了这个课程的问题,但如果我错了,有人会纠正我。

答案 14 :(得分:0)

我猜想OP在研究Haskell CIS194 Course对作业1作业的答案时提出了这个问题。在该课程的那个阶段,几乎没有将Haskell传授给学生,因此尽管上面的答案是正确的,但它们超出了学习学生的理解范围,因为诸如lambda,函数组成(。)甚至库例程等元素尚未介绍长度和反向。这是与课程教学阶段相匹配的答案:

doubleEveryOtherEven :: [Integer] -> [Integer]
doubleEveryOtherEven []         = []
doubleEveryOtherEven (x:y:xs)   = x*2 : y : doubleEveryOtherEven xs

doubleEveryOtherOdd :: [Integer] -> [Integer]
doubleEveryOtherOdd (x:[])      = [x] 
doubleEveryOtherOdd (x:y:xs)    = x : y*2 : doubleEveryOtherOdd xs

integerListLen :: [Integer] -> Integer
integerListLen []       = 0 
integerListLen (x:xs)   = 1 + integerListLen xs

doubleEveryOther :: [Integer] -> [Integer]
doubleEveryOther xs
    | integerListLen xs `mod` 2 == 0    = doubleEveryOtherEven xs   -- also handles empty list case
    | otherwise                         = doubleEveryOtherOdd xs

该计算需要预先知道列表中的元素是偶数还是奇数,以确定应将每对数字中的哪一个数字加倍。但是,基本的Haskell模式匹配仅允许从左到右匹配列表元素(例如:x:xs),这意味着直到到达末尾,您才能确定元素是奇数还是偶数列表中的所有元素,但是到那时为止为时已晚,因为您需要在遍历列表以到达末尾时对每个左侧的元素对进行计算。

解决方案是将加倍逻辑拆分为两个函数-一个处理偶数长度的列表,另一个处理奇数长度的列表。需要第三个函数来确定要调用给定列表的这两个函数中的哪一个,而这又需要一个可以计算列表长度的附加函数,以便我们可以确定列表中的元素是奇数还是偶数(再次,因为在课程的此阶段尚未引入 length 库功能。

此解决方案还与第1周的课程中的建议相符,该课程指出:“ 通过结合许多简单的功能来构建更复杂的功能是一种很好的Haskell风格。