How to edit nth element in a Haskell list?

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

Because Haskell is a functional language, you cannot 'edit' elements in lists, because everything is immutable. Instead, you can create a new list with something like:

take n xs ++ [newElement] ++ drop (n + 1) xs

However, it is not recommended in Haskell. For some more information you can see this post: Haskell replace element in list


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.)

Tags:

List

Haskell

Edit