我决定学习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 的方式实现我正在寻找的结果?
答案 0 :(得分:11)
您希望将输入列表中的每个元素转换为与元素所表示的数量相等Bool
的序列,并且如果索引为Bool
,则希望True
为False
输入列表中的数字是偶数,如果索引是奇数,则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]