Best way to get next list item in Elm
I am currently trying to find the best way to traverse the list. What can I say after going through?
Example:
I have a list of users:
userList : List User userList = [user, user, user, user]
and I have currentUser which should be the user from userList
So what I want to achieve: I want to have something like List.getNext that takes a userList and the current user and returns the next user in the list relative to currentUser
Here is my implementation. I think this is very difficult - so does anyone know how to do it in the best way?
traverseList : List a -> a -> Maybe (Maybe a)
traverseList list currentElement =
let
indexList =
List.indexedMap
(\index element ->
if element == currentElement then
index
else
-1
)
list
currentAsIndex =
let
mayBeIndex =
List.maximum indexList
in
case mayBeIndex of
Just index ->
index
Nothing ->
0
getWanted =
List.map
(\( id, element ) ->
if id == (currentAsIndex + 1) then
Just element
else
Nothing
)
(List.indexedMap (,) list)
|> List.filter
(\element ->
element /= Nothing
)
|> List.head
in
getWanted
Explanation:
My approach is to get a list, make an index list of the given list (looks like [-1, -1, -1, 3, -1, -1])
Then I get the maximum of this list - since this gives me the position of the current user in the List.indexedMap.
Then I iterate over the original as List.indexedMap and calculate the next one (in our case # 4) and return that item. Otherwise, I will not return anything.
Then I filter this list by Nothings and only one user and retrieve the user from the list using List.head.
The result is perhaps (maybe the user) ... it's not that nice ... or?
Thanks for any ideas to make something like this in a more functional way.
I am really trying to get better at functional programming.
source to share
Here's a rather naive, recursive solution:
getWanted : List a -> a -> Maybe a
getWanted list currentElement =
let findNextInList l = case l of
[] -> Nothing
x :: [] -> if x == currentElement
then List.head list
else Nothing
x :: y :: rest -> if x == currentElement
then Just y
else findNextInList (y :: rest)
in
findNextInList list
The idea here is to look at the first two items in the list, and if the first is the current item, take the second. If not, try again with the tail of the list.
Root cases need to be handled (you can write at least 4 unit tests for this function):
- the current element was not found at all
- the current item is the last in the list
- the list can be empty
There may be a more elegant solution, but recursion is a fairly common technique in functional programming, so I wanted to share this approach.
source to share
If you want to import list-extra you can use
import List.Extra as LE exposing ((!!))
getNext : a -> List a -> Maybe a
getNext item list =
list
|> LE.elemIndex item
|> Maybe.map ((+) 1)
|> Maybe.andThen ((!!) list)
If the found element is the last in the list, it returns Nothing
source to share
I like the best farm-based approach and will use it in the future.
However, in order to be new to elm and not browse its libraries, my previous approach was as follows: i.e. index the list, find the index if any of the search item, get the item at the next index if there is one.
nextUser : List User -> User -> Maybe User
nextUser lst usr =
let
numberedUsers =
List.map2 (\idx u -> ( idx, u )) (List.range 1 (List.length lst)) lst
usrIdx =
List.filter (\( idx, u ) -> u.name == usr.name) numberedUsers
|> List.head
|> Maybe.map Tuple.first
|> Maybe.withDefault -1
in
List.filter (\( idx, u ) -> idx == usrIdx + 1) numberedUsers
|> List.head
|> Maybe.map Tuple.second
Another way with so many fiddlery is to use folds.
Maybe instead List
just use Array
which provides index based access.
source to share
Another possible solution:
getNext : List a -> a -> Maybe a
getNext list element =
list
|> List.drop 1
|> zip list
|> List.filter (\(current, next) -> current == element)
|> List.map (\(current, next) -> next)
|> List.head
zip : List a -> List b -> List (a,b)
zip = List.map2 (,)
We discard the first element of the list and then put it into the original list to get a list of tuples. Each element of the tuple contains the current element and the next element. Then we filter this list, take the "next" element from the tuple, and take the head of the final list.
source to share