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.

+3


source to share


4 answers


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.

+4


source


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

+4


source


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.

+1


source


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.

+1


source







All Articles