'How to set value in nth element in a Haskell list?
I know that xs !! n gives me nth element in a list, but I don't know how to edit nth element in that list. Can you tell me how can I edit nth element in a list or give a hint at least?
For example how can I make the second element 'a' an 'e' in this: ['s','t','a','c','k']?
Solution 1:[1]
Changing the nth element
A common operation in many languages is to assign to an indexed position in an array. In python you might:
>>> a = [1,2,3,4,5]
>>> a[3] = 9
>>> a
[1, 2, 3, 9, 5]
The
lens package gives this functionality with the (.~) operator. Though unlike in python the original list is not mutated, rather a new list is returned.
> 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 is just a function and the (&) operator, part of the
lens package, is just reverse function application. Here it is with more common function application.
> (element 3 .~ 9) [1,2,3,4,5]
[1,2,3,9,5]
Assignment again works perfectly fine with arbitrary nesting of Traversables.
> [[1,2,3],[4,5,6]] & element 0 . element 1 .~ 9
[[1,9,3],[4,5,6]]
or
> set (element 3) 9 [1,2,3,4,5,6,7]
Or if you want to effect multiple elements you can use:
> over (elements (>3)) (const 99) [1,2,3,4,5,6,7]
> [1,2,3,4,99,99,99]
Working with types other then lists
This is not just limited to lists however, it will work with any datatype that is an instance of the Traversable typeclass.
Take for example the same technique works on trees form the standard containers package.
> 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
Solution 2:[2]
You can't edit the nth element of a list, values are immutable. You have to create a new list. But due to the immutability, it can share the part after the changed element with the original list.
So if you want to apply a transformation to the nth element of a list (and have the parts before and after identical), you have three parts
- the front of the list before the element in question, say
front - the element in question, say
element - the back of the list after the element in question, say
back.
Then you'd assemble the parts
front ++ transform element : back
so it remains to get a hold on the interesting parts in a nice way.
splitAt :: Int -> [a] -> ([a],[a])
does that, splitAt idx list gives back the first part of the list, before the index idx as the first component of the pair, and the rest as the second, so
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
(Note: I have started counting elements at 0, if you want to start counting at 1, you need to adjust and use idx-1.)
Solution 3:[3]
I' surprised the following method was not yet mentioned, so I will add it for further reference:
replace index elem = map (\(index', elem') -> if index' == index then elem else elem') . zip [0..]
> replace 2 'e' "stack"
"steck"
It handle the casse of out of range index.
> replace (-1) 'z' "abc"
"abc"
> replace 0 'z' "abc"
"zbc"
> replace 2 'z' "abc"
"abz"
> replace 3 'z' "abc"
"abc"
It is not slower than the splitAt method ( O(2N) ).
Solution 4:[4]
There is also the possibility of writing a simple recursive solution.
The basic idea is that, in order to substitute element #5 in a list, you just have to substitute element #4 in the tail of that list.
Using the notations from the answer of @DanielFisher, this gives the following code:
changeNthElement :: Int -> (a -> a) -> [a] -> [a]
changeNthElement n fn [] = [] -- nothing to change
changeNthElement n fn (x:xs)
| (n < 0) = x:xs -- no change for a negative index
| (n == 0) = (fn x) : xs -- easy case
| otherwise = x : (changeNthElement (n-1) fn xs) -- recursion
If the new value does not depend on the old one, one can specialize the above function:
setNthElement :: Int -> a -> [a] -> [a]
setNthElement n v xs = changeNthElement n (const v) xs
Testing:
$ ghci
GHCi, version 8.8.4: https://www.haskell.org/ghc/ :? for help
...
?>
?> :load q15530511.hs
[1 of 1] Compiling Main ( q15530511.hs, interpreted )
Ok, one module loaded.
?>
?> xs = replicate 10 7
?>
?> xs
[7,7,7,7,7,7,7,7,7,7]
?>
?> changeNthElement 3 (+2) xs
[7,7,7,9,7,7,7,7,7,7]
?>
?> setNthElement 2 42 xs
[7,7,42,7,7,7,7,7,7,7]
?>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | Daniel Fischer |
| Solution 3 | Philip Gaudreau |
| Solution 4 | jpmarinier |
