【问题标题】:Process Haskell list from right to left keeping origin order从右到左处理 Haskell 列表,保持原点顺序
【发布时间】:2015-12-24 20:21:07
【问题描述】:

需要从 Haskell 列表的右侧开始每隔一个项目增加一次,但要保持原始顺序(例如 reverse 不是这种情况)。例如:

 f [1, 2, 3] --  [1, 3, 3]

 f [1, 2, 3, 4] -- [2, 2, 4, 4]

我尝试过类似以下的方法:

fc ([]) = []
fc (x:[]) = [x]
fc (x:[y]) = [x+1,y]
fc( x:xs ) = fc [x] : ( fc xs ) -- this line is wrong

附言显然,我可以两次反转(但更愿意理解原始任务)列表并应用如下内容:

helper (x:y:tail) = [x, y+1] ++ tail
fc x = reverse (helper (reverse x) )

【问题讨论】:

  • 您的第二个示例是更改第 1 项和第 3 项。你想做什么?
  • @MASL 任务从右数每增加 2 个。所以从右边看列表:右边第二个是3->(3+1)=4,现在跳过2,之后是第四个1->(1+1)=2。所以结果[2, 2, 4, 4]
  • 好的。监督那个细节。
  • 反转列表,每隔一个元素加 1 并反转以恢复原始顺序。
  • @raymonad 是的,这是可能的,但我想了解如何从右到左处理列表,请参阅我的“附注”更新

标签: list haskell iteration reverse


【解决方案1】:

从右到左处理 Haskell 列表的典型方法是反转它。由于您希望获得结果的原始顺序,您只需再次反转:

f1 = reverse . zipWith (+) (cycle [0,1]) . reverse

但是,如果您真的愿意,您可以让每个递归调用都返回更新后的尾部和一个标志,该标志指示该位置是否从末尾开始计数,以便您知道是否增加该位置的元素:

f2 = snd . g
  where
  g []     = (False, [])
  g (x:xs) = let (addOne, xs') = g xs
                 x' = if addOne then x + 1 else x
             in (not addOne, x':xs')

我们基本上是在列表上映射一个函数,但是这个函数需要一个额外的参数,该参数从列表的右端开始计算。我们可以使用一个标准函数:

import Data.List (mapAccumR)
f2' = snd . mapAccumR g False
  where
  g addOne x = (not addOne, if addOne then x + 1 else x)

【讨论】:

  • 我真的很喜欢你的第二个回答。请注意,这两种方法的执行方式可能非常不同。双反转通常会分配更多的内存,但只有基准测试才能确定哪个会更好。
【解决方案2】:

我认为你想要的更清晰的规范是,如果长度为偶数,则增加偶数索引,如果长度为奇数,则增加奇数索引。例如,从零开始索引时,长度为 3 的列表导致索引 1 递增。一种方法是使用明显的两遍解决方案:

f xs = zipWith (+) (cycle sol) xs
 where sol = map fromEnum [even len, odd len] 
       len = length xs

这可以通过“打结”一次性完成(不依赖编译器融合规则)。例如(使用手动递归样式作为通信方式)。

f2 xs = let (isEven, result) = go isEven xs in result
 where
  go _ []     = (True,     [])
  go e (x:xs) = let (ne,rest) = go (not e) xs
                in (not ne, x+fromEnum e : rest)

【讨论】:

  • 不错的尝试!确实,它给出了正确的结果。我的起源问题的灵感来自试图在 Prolog 和 Haskell 中找到共同部分。 Prolog 为您提供了两种方式“迭代”和“递归”来处理任何方向的列表。所以看着 Haskell 我只能看到 f (x:xs) - 递归方式
  • 我看不出 raymonad 的回答本身是如何打结的。
  • @dfeuer 糟糕,我看得太快了。我的错。
【解决方案3】:

这可以使用左折叠来有效地完成

inc :: Num a => [a] -> [a]
inc xs = foldl go (\_ _ acc -> acc) xs id (+ 1) []
    where go run x f g acc = run g f (f x: acc)

请注意,即使这是左折叠,列表也是使用 cons (:) 运算符构建的;并且它将以线性方式执行而不是二次执行(类似于difference lists 中的构造)。

\> inc [1, 2, 3]
[1,3,3]
\> inc [1, 2, 3, 4]
[2,2,4,4]

它也可以推广到id(+ 1)以外的交替函数。

【讨论】:

    【解决方案4】:

    我喜欢 Thomas 的解决方案。不过,我认为这里简单的foldr 就足够了。

    process = snd . foldr (\x (b,xs) -> (not b, x + fromEnum b:xs)) (False,[])
    

    【讨论】:

    • 这与 raymonad 的第二个答案相同,只是您已内联 mapAccumR
    • 它不必在概念上有所不同才能算作答案。另外,我发现foldr 对于初学者来说比mapAccumR 更容易理解,因为它在函数式语言中的使用更为广泛。
    • 我同意这一点,但整个情况有点不寻常。 mapAccumL 比较常见,很少有初学者会理解它的翻译成foldr
    猜你喜欢
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 2011-06-09
    • 2015-07-04
    • 1970-01-01
    • 1970-01-01
    • 2023-01-04
    • 1970-01-01
    相关资源
    最近更新 更多