在Elm中获取列表的下一个元素的最佳方法

在Elm中获取列表的下一个元素的最佳方法,elm,Elm,我目前正试图找出遍历列表的最佳方法。 我所说的穿越是什么意思 示例: 我有一个用户列表: userList : List User userList = [user, user, user, user] 我有一个currentUser,它必须是userList之外的用户 因此,我想要实现的是: 我想要类似List.getNext的东西,它获取userList和当前用户,并返回列表中相对于当前用户的下一个用户 这是我的实现。我认为这是非常复杂的-那么有没有人知道如何以更好的方式做这件事

我目前正试图找出遍历列表的最佳方法。 我所说的穿越是什么意思

示例:

我有一个用户列表:

userList : List User 
userList =
    [user, user, user, user]
我有一个currentUser,它必须是userList之外的用户

因此,我想要实现的是: 我想要类似List.getNext的东西,它获取userList和当前用户,并返回列表中相对于当前用户的下一个用户

这是我的实现。我认为这是非常复杂的-那么有没有人知道如何以更好的方式做这件事

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
说明:

我的方法是获取列表,为给定的列表创建一个索引列表(看起来像[-1,-1,-1,3,-1,-1])

然后我得到这个列表的最大值,因为这会给出当前用户在list.indexedMap中的位置

然后我将原始元素作为List.indexedMap进行迭代,找出下一个元素(在第4个示例中)并返回该元素。否则我什么也不退

然后我过滤这个空列表,只过滤一个用户,并使用List.head从列表中提取用户

结果是可能(可能用户)。。。那不太好。。。还是

感谢您提出的以更好的功能方式执行类似操作的想法


我真的想在函数式编程方面做得更好。

这里有一个非常简单的递归解决方案:

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
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 (,)
这里的想法是查看列表的前两个元素,如果第一个元素是当前元素,则使用第二个元素。如果没有,请使用列表的尾部重试

必须处理紧急情况(您可以为此函数编写至少4个单元测试):

  • 根本找不到当前元素
  • 当前元素是列表中的最后一个元素
  • 列表可能为空

也许有更优雅的解决方案,但递归是函数式编程中非常常见的技术,因此我想与大家分享这种方法。

如果您愿意导入,可以使用

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)

如果找到的元素是列表中的最后一个元素,它将返回Nothing

我最喜欢farmio基于库的方法,这也是我将来将使用的方法

也就是说,对于elm来说是新手,不需要查看它的库,我以前的方法如下:即为列表编制索引,如果搜索到任何项,则查找索引,如果有,则在下一个索引处获取项

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
另一种尽可能多地使用小提琴的方法是使用折叠


也许不必使用
列表
,只需使用
数组
,它提供基于索引的访问。

另一种可能的解决方案:

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
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 (,)

我们删除列表的第一个元素,然后将其与原始列表压缩,以获得元组列表。元组的每个元素都包含当前元素和下一个元素。然后我们过滤该列表,从元组中提取“next”元素,并获取最终列表的开头。

对于具体问题,有一些很好的答案。我想提出另一种模式,因为这种方法有一些缺点:

  • 您依赖于检查用户的平等性。这限制了用户类型,例如,其中不能有函数
  • 在不能保证所有用户都不同的列表中查找用户是脆弱的:如果列表恰好以
    [user1,user2,user1,user3]
    结束,则循环将永远不会到达
    user3
  • 没有任何东西可以保证当前用户在列表中,因此其他答案的返回类型中的
    可能
    ,很难正确处理
因此,假设您当前的模型是

type alias Model =
    { currentUser : User
    , users : [User]
    }
你要的是

rotateUser : Model -> Model
rotateUser model =
  let
    nextUser = Maybe.withDefault
        model.currentUser
        (getNext model.users model.currentUser)
  in
    { model | currentUser = nextUser }

getNext : List a -> a -> Maybe a
...
与其实施
getNext
,不如改变模型,让生活更轻松:

type alias Model =
    { currentUser : User
    , otherUsers : [User]
    }

rotateUser : Model -> Model
rotateUser model =
    case model.otherUsers of
        [] -> model
        (nextUser::rest) ->
            { model
            | currentUser = nextUser
            , otherUsers = rest ++ [model.currentUser]
            }

如果当前用户是列表中的最后一个用户,那么应该返回什么?没有,或者列表的第一个?当前在这个实现中,它返回第一个。对于我的用例,这是可以的。=>应该返回第一个元素。。。哈哈。。但我想知道为什么它返回第一个…)我不知道…啊好的。。。现在我明白了。因为-1列表的最大值是-1。后来我只是得到了头。。。所以它返回第一个元素。。。好啊这是好的,但不是有意的;-)这个很短很漂亮。谢谢你的建议。我不能标记多个解决方案,所以我更喜欢@stholzm的概念,它为我理解如何处理这些事情提供了良好的基础。谢谢太优雅了!我想你可以简单地添加“模列表长度”使其在所有情况下都能工作。我喜欢这种方法:)@marschro谢谢,但我承认它不是很简洁。我的代码中有五个case/if,很难一目了然。我自己也不喜欢,我已经怀疑我错过了什么。法米奥有一个更好的方法。显式递归有时仍然有用,但通常更倾向于使用高阶函数和组合。所以我希望你学到了一些关于递归的知识,并把这个答案看作是一个更具教育意义的答案。是的。确切地我把它作为一个学习的例子。我完全理解递归和列表解构