【发布时间】:2013-03-09 23:00:14
【问题描述】:
我知道 xs!!n 给了我列表中的第 n 个元素,但我不知道如何编辑该列表中的第 n 个元素。您能告诉我如何编辑列表中的第 n 个元素或至少给出提示吗?例如,如何使第二个元素 'a' 成为 'e' : ['s','t','a','c','k'] ? 谢谢。
【问题讨论】:
我知道 xs!!n 给了我列表中的第 n 个元素,但我不知道如何编辑该列表中的第 n 个元素。您能告诉我如何编辑列表中的第 n 个元素或至少给出提示吗?例如,如何使第二个元素 'a' 成为 'e' : ['s','t','a','c','k'] ? 谢谢。
【问题讨论】:
您不能编辑列表的第 n 个元素,值是不可变的。您必须创建一个新列表。但由于不可变性,它可以与原始列表共享更改元素之后的部分。
因此,如果您想对列表的第 n 个元素应用转换(并且前后部分相同),则需要三个部分
front
element
back。然后你会组装零件
front ++ transform element : back
所以仍然需要以一种很好的方式抓住有趣的部分。
splitAt :: Int -> [a] -> ([a],[a])
这样做,splitAt idx list 返回列表的第一部分,在索引 idx 之前作为该对的第一个组成部分,其余部分作为第二个组成部分,所以
changeNthElement :: Int -> (a -> a) -> [a] -> [a]
changeNthElement idx transform list
| idx < 0 = list
| otherwise = case spliAt idx list of
(front, element:back) -> front ++ transform element : back
_ -> list -- if the list doesn't have an element at index idx
(注:我已经从0开始计数元素,如果要从1开始计数,需要调整并使用idx-1。)
【讨论】:
transform 是您用来从旧元素中获取新元素的函数。我比仅仅用给定的元素替换一个元素更通用,所以你可以做例如changeNthElement 3 (*2) list 将索引 3 处的元素加倍。
因为 Haskell 是一种函数式语言,您不能“编辑”列表中的元素,因为一切都是不可变的。相反,您可以使用以下内容创建一个新列表:
take n xs ++ [newElement] ++ drop (n + 1) xs
但是,在 Haskell 中不建议这样做。有关更多信息,您可以查看此帖子:Haskell replace element in list
【讨论】:
许多语言中的一个常见操作是分配给数组中的索引位置。在 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]
在Traversables 的任意嵌套中,赋值再次完美运行。
> [[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
【讨论】:
我很惊讶下面的方法还没有提到,所以我会添加它以供进一步参考:
replace index elem = map (\(index', elem') -> if index' == index then elem else elem') . zip [0..]
> replace 2 'e' "stack"
"steck"
它处理超出范围索引的情况。
> replace (-1) 'z' "abc"
"abc"
> replace 0 'z' "abc"
"zbc"
> replace 2 'z' "abc"
"abz"
> replace 3 'z' "abc"
"abc"
它并不比 splitAt 方法慢( O(2N) )。
【讨论】: