【问题标题】:Haskell. Keeping track of indices in order to generate a new list哈斯克尔。跟踪索引以生成新列表
【发布时间】:2013-05-31 21:00:46
【问题描述】:

我决定学习 Haskell 并学习以更实用的方式思考,所以我尝试解决非常简单的练习,尝试在这个范例中使用一种好的方法。

我正在尝试在 Haskell 中完成这个简单的练习:

Input: [2, 4, 1, 1, 2]
Output: [True, True, False, False, False, False, True, False, True, True]

因此,Input 列表中的元素将落入False Output 列表,奇数元素为True;每一个人 重复次数与Input 列表中的值相同。

遍历Input列表,如果i ᵗʰ项是成对的 位置,追加到输出True i 次到Output;如果 i ᵗʰ 项目在奇数位置,追加False i 次 到Output 列表。

这似乎是一个非常简单的问题,确实如此。但对我来说,没有任何 函数式编程背景,不知道怎么表达 在 Haskell 中。

我尝试使用 λ 函数来跟踪当前索引 在列表理解中。

    row :: [Integer] -> [Bool]
    row xs = [ (last $ zipWith (\i x -> x) [1..] [0..i]) `mod` 2 == 0
                | j <- xs, i <- [0..j-1] ]

但我不了解它的行为,所以我结束了使用findIndices 作为一个快速的替代方案:

    row :: [Integer] -> [Bool]
    row xs = [ (head $ findIndices (==j) (xs)) `mod` 2 == 0
                | j <- xs, i <- [0..j-1] ]

使用最后一种方法似乎没问题:

    > let xs = [ 1, 4, 3, 2 ]
    > print $ row xs
    [True,False,False,False,False,True,True,True,False,False]

但问题并没有解决,因为项目不一定是唯一的:

    > let xs = [ 2, 2, 4, 3]
    > print $ row xs
    [True,True,True,True,True,True,True,True,False,False,False]

因为head findIndices 只给出了第一次出现的情况。 (虽然我认为,如果有效,那不是一种非常有效的方法 解决这个问题。)

我怎样才能以 Haskellian 的方式实现我正在寻找的结果?

【问题讨论】:

  • 我认为您的示例输出应该更改。你错过了 4 个错误。
  • 你没有附加真/假 i 次,对吧?您将其附加列表 [i] 次。

标签: list haskell functional-programming list-comprehension


【解决方案1】:

您希望将输入列表中的每个元素转换为与元素所说的相同数量的Bools 序列,并且如果输入列表中数字的索引,您希望BoolTrue是偶数,如果索引是奇数则False

为此,您不需要索引,最好避免使用它 - 这样可以提供更简单且通常更高效的代码。关键是值是交替的,它有一个周期性的模式。为了构建这样的周期性模式,Prelude 提供了有用的

cycle :: [a] -> [a]

Prelude> take 10 $ cycle [1,2,3]
[1,2,3,1,2,3,1,2,3,1]
Prelude> take 10 $ cycle [True,False]
[True,False,True,False,True,False,True,False,True,False]

整洁,这正是我们所需要的。

现在,我们可以将输入列表的每个元素与对应的Bool 配对:

[  2,    2,   4,   3]
[True,False,True,False,...

我们可以使用zip 来生成对,[(2,True), (2,False), ...],然后使用一个函数将该对转换为Bools 的适当序列。

但这种模式非常普遍,我们为此提供了一个特殊的高阶函数zipWith

所以如果列表元素的类型是Int,我们可以这样写

row :: [Int] -> [Bool]
row xs = concat $ zipWith replicate xs (cycle [True,False])

对于Integer类型,我们不能使用replicate,但可以使用Data.List中的genericReplicate

【讨论】:

  • 使用cycle 提供了一种更自然的方法。我过于关注指数的平价。定义这样的函数就不需要在代码中添加任何cmets了。
【解决方案2】:

您似乎已经发现可以使用zip 将元素与其索引配对。对于每个索引i 和相应的元素n,您希望生成一个布尔值的n 副本(使用replicate),这个布尔值取决于i 是奇数还是偶数。这意味着mapping 每个元组(i, n) 到一个布尔列表,所以你得到一个列表列表([[Bool]])。最后一步是将这些列表与concat 合并(可以与map 合并为concatMap)。

row = concatMap (\(i, n) -> replicate n (odd i)) . zip [1..]

或者如果你不喜欢无点风格:

row xs = concatMap (\(i, n) -> replicate n (odd i)) (zip [1..] xs)

【讨论】:

  • 解释得很好。我想这要容易得多,你的解释给出了很多澄清。当然,我还没有考虑使用 Haskell...。
  • concat . zipWith (flip replicate) (cycle [True, False])
【解决方案3】:

另一种解决方案

row :: [Integer] -> [Bool]
row ns = r' True ns
    where
        r' :: Bool -> [Integer] -> [Bool]
        r' b (n:ns) = replicate b n : r' (not b) ns
        r' b   []   = []

(未测试)

【讨论】:

  • 这个差不多好了。我能看到的唯一问题是: 应该替换为++
  • 你是对的。我看到您在帖子中添加了更正版本。
【解决方案4】:
row :: [Integer] -> [Bool]
row xs = row' xs True

row' [] _ = []
row' (x:xs) b = (replicate x b) ++ (row' xs (not b))

【讨论】:

    【解决方案5】:

    我会这样做。

    row [] = []
    row xs = row' xs 0
       where row' xs i
                | i >= length xs    = []
                | otherwise         = (take (xs !! i) (repeat (isOdd i))) ++ (row' xs (i+1))
    
    
    isOdd n = n `rem` 2 == 1
    

    但是我这台电脑上没有 ghc 来测试它。

    【讨论】:

    • 似乎工作正常,您只需要输入concat。但是,这是 IMO 一项不应该通过显式累加器递归来完成的任务,OP 已经走上了更好的道路。
    • 另外,没有必要定义你自己的isOdd,因为odd已经在Prelude中了。
    【解决方案6】:

    我理解

    遍历Input列表,如果i ᵗʰ项在pair位置, 附加到输出 True i 次到输出;如果 i ᵗʰ 项目是奇数 位置,将 False i 次附加到输出列表中。

    我看到了两种解释方式。 “对于所有元素,如果第 i 个元素是偶数,则返回 True i 次。否则,返回 false i 次”或“对于所有元素,包含 (v :: Int) 的第 i 个元素应返回 True v 次,如果如果 i 是奇数,则 i 是偶数或 False v 次”。第二个已经有一个令人满意的答案,所以我会给第一个。

    有些人喜欢参考指数,但在这种情况下,您无需担心指数。你可以确定有多少Bools,而不用计算你遍历了多少元素。

    accum f (x:[]) = [x]
    accum f (x:xs) = (x):(map f (accum f xs))
    

    这个函数接受一个函数f 和一个列表。它将f 应用于除第一个元素之外的每个元素,然后对列表的尾部进行递归调用,再次将f 应用于每个剩余元素,等等......结果是这样的:

    accum (+1) [1,1,1,1,1]
    [2,3,4,5,6]
    

    函数+1 两次应用于第二个元素,三次应用于第二个,等等。 现在你问这对我们有什么帮助?好吧,我们可以这样做:

    accum (\x -> [head x] ++ x) $ map (\x -> [x]) [2, 4, 1, 1, 2]
    [[2],[4,4],[1,1,1],[1,1,1,1],[2,2,2,2,2]]
    

    我们首先将每个元素转换为一个包含单个项目的列表。我们accum 与您在上面看到的 lambda,它将头部连接到列表。我们现在可以直接转换为Bools,无需进一步操作。

    (map . map) odd $ accum (\x -> [head x] ++ x) $ map (\x -> [x]) [2, 4, 1, 1, 2]
    [[False],[False,False],[True,True,True],[True,True,True,True],[False,False,False,False,False]]
    

    您想要[Bool] 而不是[[Bool]],所以只需concat。请注意,您可以先concat,然后使用map 而不是map . map

    map odd $ concat $ accum (\x -> [head x] ++ x) $ map (\x -> [x]) [2, 4, 1, 1, 2]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多