【问题标题】:Double every other element of list from right in Haskell在 Haskell 中从右侧开始将列表的所有其他元素加倍
【发布时间】:2013-11-08 19:55:12
【问题描述】:

我有一个列表,我想将该列表中的所有其他元素从右侧翻倍。

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

例如,在我的场景中,[1,2,3,4] 将变为 [2,2,6,4],而在该问题中,[1,2,3,4] 将变为 [1, 4,3,8]。

我将如何实现这个?

【问题讨论】:

  • 写一个基于元素索引的map函数。
  • 我对这个问题的理解与迄今为止提供答案的人不同。似乎侯赛因想要[1,2,3] -> [1,4,3] 而不是[2,2,6]
  • @TomEllis 我同意。我认为我的回答有效地解决了这个问题:stackoverflow.com/a/24178579/803801
  • 看起来你正在尝试解决 Brent Yorgey 的 Haskell 课程中的一个练习:seas.upenn.edu/~cis194/hw/01-intro.pdf

标签: haskell


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

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

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

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

    doubleFromRight = reverse . doubleFromLeft . reverse
    

    【讨论】:

    • 他的头衔与他的解释不符。他连接的那个是第 2 和第 4 的两倍。他说在我的场景中将第一个和第三个加倍,在那个视图中将第二个和第四个加倍。
    • @DiegoNolan 他没有使用第一和第三这两个词,他只是提供了示例列表。由于他只给出了偶数长度的示例,因此与我们的解释中他的意思是模棱两可的,除了标题明确表示“列表中的第二个元素 right”。
    • 这不是模棱两可的。他链接了一个将第二、第四、第六等翻倍的帖子。并说那不是他想要的。只有另一种方式。他展示的清单正是我所说的。
    • @DiegoNolan 你认为他想要 left 中的 first、third 等元素。我认为他想要中的第二、第四等元素。因为这个问题的标题是“在haskell中双列表的第二个元素”。对于他给出的偶数长度的例子,这些给出了相同的结果,但对于像 TomEllis 所建议的那样的奇数长度的例子,结果不同。
    • 我相信您对问题的解释是正确的@ØrjanJohansen。但是,我认为我的解决方案更有效:stackoverflow.com/a/24178579/803801
    【解决方案3】:

    想一想。

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

    编辑我应该注意,这不是我的解决方案,它是链接帖子的解决方案,其中 (*2)id 翻转。这就是为什么我说考虑一下,因为这是一个微不足道的解决方案。

    【讨论】:

    • 毫无疑问,double 是一个坏名字。但是很好的解决方案!
    • @shang 可能是,但我不知道 ghc 是否会将(*1) 优化为id。它不应该用于浮点数,因为这没有任何意义。
    • @DiegoNolan:我不确定 GHC 的优化,但据我了解,即使是浮点数,1.0 也是乘法恒等式。
    • OP 明确要求将 从右侧开始的每个第二个元素加倍。这个答案是从左边开始的,所以如果输入列表的长度是奇数,结果就会出错。
    • @PetrPudlák 你是对的 - 尽管在使用类似的东西之前列表中的reverse 似乎是根据来自的课程处理问题的预期方式
    【解决方案4】:

    直接实现是:

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

    【讨论】:

    • 这会将左边的每个元素加倍,而不是右边。
    • 我用这个解决方案夹在reverse:doubleOddFromRight = reverse . doubleOddElements . reverse
    【解决方案5】:

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

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

    使用此脚本: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
    

    【讨论】:

      【解决方案6】:

      这是我的两个解决方案,注意我是 Haskell 的初学者。

      第一个使用列表函数,headtaillenght

      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)
      

      第二个,类似但不同的方法只使用length函数:

      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
      

      【讨论】:

      • 请注意,Prelude 函数 evenodd 会返回与元素奇偶校验的布尔值
      【解决方案7】:

      尝试将问题概括一下:由于我们希望将每个第二个元素从末尾开始加倍,因此我们无法提前知道它是从一开始就是奇数还是偶数。所以最简单的方法是两者都构建,计算整体大小是偶数还是奇数,然后再决定。

      让我们定义一个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 ...
      

      两个曲折的副本。现在我们可以计算了

      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
      

      【讨论】:

        【解决方案8】:

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

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

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

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

        我在 CS194 的家庭作业中回答了这个问题

        【讨论】:

          【解决方案9】:

          我的第一个想法是:

          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。要找到任何条目的值,都需要遍历列表的末尾,找出长度是奇数还是偶数。

          【讨论】:

            【解决方案10】:

            这是我对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))
            

            【讨论】:

              【解决方案11】:

              为了简单起见,这个怎么样?

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

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

              【讨论】:

              • 您缺少一个案例:列表中恰好有两个元素的案例,即doubleRev (x:y:[]) a = (y:2*x:a)。此外,您的结果必须颠倒才能实现 OP 想要的。
              • 真的有必要吗?我在问,因为您不希望该列表中的最后一位数字加倍。只有倒数第二个等等。另外,我的解释是你必须已经提供了一个反转的数字列表,但我可能因此不得不写一些新的东西:numbers to digits in order
              • 不,您没有遗漏任何案例。
              • @WillNess,我正在考虑以这种方式在编辑之前使用该功能:(doubleEveryOtherRev . reverse) l。我没有明确展示它,但一如既往地欢迎您。
              【解决方案12】:

              我们也可以这样做:

              doubleEveryOther = reverse . zipWith (*) value . reverse
                  where
                  value = 1 : 2 : value
              

              【讨论】:

              • 您的答案可以通过额外的支持信息得到改进。请edit 添加更多详细信息,例如引用或文档,以便其他人可以确认您的答案是正确的。你可以找到更多关于如何写好答案的信息in the help center
              【解决方案13】:

              有些答案似乎与列表的奇数/偶数长度无关。

              doubleEveryOtherEvenList = zipWith ($) (cycle [(*2),id])
              
              doubleEveryOther :: [Int] -> [Int]
              doubleEveryOther n
                  | length n `mod` 2 == 0 = doubleEveryOtherEvenList n
                  | otherwise = (head n) : doubleEveryOtherEvenList (tail n)
              

              【讨论】:

                【解决方案14】:

                在 haskell 中参加 edx 课程,这是我的菜鸟解决方案。

                doubleSecondR :: [Integer] -> [Integer]
                
                doubleSecondR xs = reverse(zipWith (*) (reverse xs) ys)
                
                where ys = repeat' [1,2]
                
                repeat' :: [a] -> [a]
                
                repeat' xs = xs ++ repeat' xs
                

                【讨论】:

                  【解决方案15】:

                  我也是从CIS 194 课程来回答这个问题的。

                  我做了这两种方式。首先,我认为问题的重点应该仅取决于列出的 3 个可能来源中的任何一个中提到的功能或编程方式。 course lecture 1Real World Haskell ch. 1,2Learn You a Haskell ch. 2

                  好吧:

                  • 递归,条件
                  • reversemaxminoddeven等基本函数
                  • 列出函数,例如head, tail, ...

                  不行:

                  • foldr, foldl, map
                  • 高阶函数
                  • 除此之外的任何东西

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

                  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)
                  

                  第二个通过建立一组反向索引然后映射这些索引来工作。这会计算长度,但只计算一次。

                  例如[1,1,1,1] -&gt; [(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]
                  

                  【讨论】:

                    【解决方案16】:

                    我猜 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 风格。

                    【讨论】:

                      猜你喜欢
                      • 2015-11-18
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 2015-10-01
                      • 2021-05-19
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多