我有一个元素列表,我希望更新它们:
来自:["Off","Off","Off","Off"]
:["Off","Off","On","Off"]
由于我对Haskell不熟悉,我一直使用(x:xs)!!y
使用函数提取和更新单个组件:
replace y z [] = []
replace y z (x:xs)
| x==y = z:replace y z xs
| otherwise = x:replace y z xs
然后在ghci中输入以下内容:(replace "Off" "On" ["Off",'Off","Off","Off"]) !! 2
我得到以下内容:"On"
我似乎能够提取和转换列表的元素,但我似乎无法获得转换单个元素的列表。
有关此事的任何帮助将不胜感激。
答案 0 :(得分:48)
通常,您可以通过拆分列表,替换元素并将其重新连接来修改列表的元素。
要在索引处拆分列表,我们有:
splitAt :: Int -> [a] -> ([a], [a])
可以用来分解列表,如下所示:
> splitAt 2 ["Off","Off","Off","Off"]
(["Off","Off"],["Off","Off"])
现在你只需要弹出列表的snd
组件的head元素。这可以通过模式匹配轻松完成:
> let (x,_:ys) = splitAt 2 ["Off","Off","Off","Off"]
> x
["Off","Off"]
> ys
["Off"]
您现在可以一起加入列表,并使用“开启”:
> x ++ "On" : ys
["Off","Off","On","Off"]
我会留给你把这些碎片组合成一个单独的功能。
作为样式注释,我建议您使用新的自定义数据类型,而不是String
来进行切换:
data Toggle = On | Off deriving Show
答案 1 :(得分:27)
许多语言的常见操作是分配给数组中的索引位置。在python中你可以:
>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]
在
lens包为(.~)
运算符提供此功能。虽然与python不同,原始列表不会发生变异,而是返回一个新列表。
> let a = [1,2,3,4,5]
> a & element 3 .~ 9
[1,2,3,9,5]
> a
[1,2,3,4,5]
element 3 .~ 9
只是一个函数而(&)
运算符是其中的一部分
lens包,只是反向功能应用程序。这里有更常见的功能应用。
> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]
使用Traversable
s的任意嵌套再次分配工作完全正常。
> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
或
> set (element 3) 9 [1,2,3,4,5,6,7]
或者,如果您想要影响多个元素,可以使用:
> over (elements (>3)) (const 99) [1,2,3,4,5,6,7]
> [1,2,3,4,99,99,99]
这不仅限于列表,但它适用于作为Traversable类型类实例的任何数据类型。
例如,相同的技术适用于标准trees containers包。
> import Data.Tree
> :{
let
tree = Node 1 [
Node 2 [Node 4[], Node 5 []]
, Node 3 [Node 6 [], Node 7 []]
]
:}
> putStrLn . drawTree . fmap show $ tree
1
|
+- 2
| |
| +- 4
| |
| `- 5
|
`- 3
|
+- 6
|
`- 7
> putStrLn . drawTree . fmap show $ tree & element 1 .~ 99
1
|
+- 99
| |
| +- 4
| |
| `- 5
|
`- 3
|
+- 6
|
`- 7
> putStrLn . drawTree . fmap show $ tree & element 3 .~ 99
1
|
+- 2
| |
| +- 4
| |
| `- 99
|
`- 3
|
+- 6
|
`- 7
> putStrLn . drawTree . fmap show $ over (elements (>3)) (const 99) tree
1
|
+- 2
| |
| +- 4
| |
| `- 5
|
`- 99
|
+- 99
|
`- 99
答案 2 :(得分:15)
我不确定你要做什么。如果您只需要生成[“关闭”,“关闭”,“开启”,“关闭”],您可以明确地执行此操作。一般来说,应该避免在haskell中修改状态。
也许您想要的是一个“修改”(生成具有不同值的新元素)列表的第n个元素的函数?唐对这类问题给出了非常一般的解决方法。 您还可以使用显式递归:
replaceNth :: Int -> a -> [a] -> [a]
replaceNth _ _ [] = []
replaceNth n newVal (x:xs)
| n == 0 = newVal:xs
| otherwise = x:replaceNth (n-1) newVal xs
Haskell为列表操作提供了出色的功能。如果您不了解它们filter
,map
和foldr
/ foldl
都值得关注,列表推导也是如此。
答案 3 :(得分:5)
以下是我一直在使用的一些代码:
-- | Replaces an element in a list with a new element, if that element exists.
safeReplaceElement
-- | The list
:: [a]
-- | Index of the element to replace.
-> Int
-- | The new element.
-> a
-- | The updated list.
-> [a]
safeReplaceElement xs i x =
if i >= 0 && i < length xs
then replaceElement xs i x
else xs
-- | Replaces an element in a list with a new element.
replaceElement
-- | The list
:: [a]
-- | Index of the element to replace.
-> Int
-- | The new element.
-> a
-- | The updated list.
-> [a]
replaceElement xs i x = fore ++ (x : aft)
where fore = take i xs
aft = drop (i+1) xs
答案 4 :(得分:5)
这是一个完美运作的单线程
replace pos newVal list = take pos list ++ newVal : drop (pos+1) list
在哈斯克尔做这种事情似乎并不高效。
答案 5 :(得分:2)
实际上,对于您使用List的许多情况(并非总是如此),Data.Vector是更好的选择。
它附带了一个更新功能,请参阅Hackage,它完全符合您的需要。
答案 6 :(得分:1)
我认为您应该考虑使用List以外的数据结构。例如,如果您只想拥有四个开/关开关状态,那么:
data State = St { sw1, sw2, sw3, sw4 :: Bool }
对于动态数量的交换机,请考虑从switch name
到Bool
的映射。
答案 7 :(得分:0)
我相信这是替换单个元素的更优雅方式:
setelt:: Int -> [a] -> a -> [a]
setelt i list newValue =
let (ys,zs) = splitAt i-1 list in ys ++ newValue ++ tail zs
有输入错误处理。 因此,如果索引 i 超出边界,则haskell将显示错误的输出。 (注意:在Haskell索引中从1开始)
拥抱的行为如下:
Main> setelt 1 [1,2,3] 9
[9,2,3]
Main> setelt 3 [1,2,3] 9
[1,2,9]
Main> setelt 0 [1,2,3] 9
[9,2,3]
Main> setelt 4 [1,2,3] 9
[1,2,3,9]
Program error: pattern match failure: tail []
您服务时的错误处理:
setelt i y newValue =
if and [i>0, i<= length y]
then let (ys,zs) = splitAt (i-1) y in ys ++ [newValue] ++ tail zs
else y
如果您提供的索引错误,则返回原始列表。
答案 8 :(得分:0)
这个答案很晚才到,但我想我会分享我认为在Haskell中替换列表中nth
元素的高效方式。我是Haskell的新手并且认为我会投入。
set
函数将列表中的第n个元素设置为给定值:
set' :: Int -> Int -> a -> [a] -> [a]
set' _ _ _ [] = []
set' ind curr new arr@(x:xs)
| curr > ind = arr
| ind == curr = new:(set' ind (curr+1) new xs)
| otherwise = x:(set' ind (curr+1) new xs)
set :: Int -> a -> [a] -> [a]
set ind new arr = (set' ind 0 new arr)
当set
遍历列表时,它会将列表分开,如果当前索引是给定的n
,它将前一个元素与给定的新值组合在一起,否则,它将前一个元素与该索引列表中的旧值。