哈斯克尔。跟踪索引以生成新列表

时间:2013-05-31 21:00:47

标签: list haskell functional-programming list-comprehension

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

我正在尝试在Haskell中实现这个简单的练习:

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

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

如果 i ᵗʰ项目在一对中,则遍历Input列表 位置,附加到输出True i 次到Output;如果 ᵗʰ项目处于奇数位置,追加False i 次 到Output列表。

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

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

    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 的方式实现我正在寻找的结果?

6 个答案:

答案 0 :(得分:11)

您希望将输入列表中的每个元素转换为与元素所表示的数量相等Bool的序列,并且如果索引为Bool,则希望TrueFalse输入列表中的数字是偶数,如果索引是奇数,则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]

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

Bool

干净,这正是我们所需要的。

现在,我们可以将输入列表的每个元素与相应的[ 2, 2, 4, 3] [True,False,True,False,... 配对:

zip

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

但是这种模式非常普遍,我们有一个特殊的高阶函数,Int

因此,如果列表元素的类型为row :: [Int] -> [Bool] row xs = concat $ zipWith replicate xs (cycle [True,False]) ,我们可以编写

Integer

对于replicate类型,我们无法使用genericReplicate,但我们可以使用Data.List中的{{1}}。

答案 1 :(得分:2)

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

concatMap

或者,如果您不喜欢无点样式:

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

答案 2 :(得分:1)

另一种解决方案

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   []   = []

(未经测试)

答案 3 :(得分:1)

row :: [Integer] -> [Bool]
row xs = row' xs True

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

答案 4 :(得分:0)

以下是我将如何做到这一点。

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来测试它。

答案 5 :(得分:0)

我介意

  

如果iᵗʰ项目处于成对位置,则遍历输入列表,   追加输出True i次到输出;如果iᵗʰ项目是奇数   位置,在输出列表中附加False i次。

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

有些人喜欢引用索引,但在这种情况下,您无需担心指标。您可以在不计算已遍历的元素数量的情况下确定Bool的数量。

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两次应用到第二个elem,三次到第二个等等。 现在,这对你有什么帮助?好吧,我们可以这样做:

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,它将头部连接到列表。我们现在可以直接转换为Bool而无需进一步操作。

(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]