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

时间:2017-04-09 09:59:09

标签: elm

我目前正试图找出遍历List的最佳方法。 遍历是什么意思?

示例:

我有一个用户列表:

userList : List User 
userList =
    [user, user, user, user]

我有一个currentUser,它必须是userList

之外的用户

所以我想要实现的目标是: 我想要像 List.getNext 那样获取userList和当前用户并返回列表中的下一个用户,相对于currentUser

这是我的实施。我觉得这很复杂 - 所以有人知道如何以更好的方式做到这一点吗?

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)并返回该元素。否则我什么都不回。

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

结果是一个可能(也许是用户)......这不太好......或者?

感谢任何想法以更好的功能方式做这样的事情。

我真的想在功能编程方面做得更好......

5 个答案:

答案 0 :(得分:4)

这是一个非常幼稚的递归解决方案:

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

这里的想法是查看列表的前两个元素,如果第一个是当前元素,则选择第二个元素。如果没有,请再次使用列表的尾部。

必须处理极端情况(您可以为此功能编写至少4个单元测试):

  • 目前尚未找到当前元素
  • 当前元素是列表中的最后一个
  • 列表可能为空

也许有一个更优雅的解决方案,但递归是函数式编程中非常常见的技术,所以我想分享这种方法。

答案 1 :(得分:4)

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

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

答案 2 :(得分:1)

我最喜欢farmio基于图书馆的方法,这也是我将来会使用的方法。

那就是说,对于榆树的新手而不是查看它的库,我以前的方法如下:即索引列表,查找索引(如果搜索到的任何项目),获取下一个索引的项目(如果有的话)

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

使用折叠的另一种方法就是使用折叠。

也许只使用List代替Array... @foreach($photos as $photo) <tr data-sortable="{{ $photo->pivot->position }}" data-id="{{ $restaurant->id }}" data-photo-id="{{ $photo->pivot->photo_id }}"> <td> <i class="fa fa-sort" aria-hidden="true"></i> </td> ... </tr> @endforeach <script type="text/javascript"> $("#sortable-ui tbody").sortable({ helper: fixHelper, update: function(event, ui) { $("#sortable-ui tbody tr").each(function(index){ console.log($(this).data('id')+', '+(index+1)); $.ajax({ url: '{{ route('owner.photo.update.position') }}', type: 'POST', data: 'restaurant_id='+$(this).data('id')+'&photo_id='+$(this).data('photo-id')+'&position='+(index+1) }) .done(function (response) { console.log(response); }) .fail(function (jqXhr) { console.log(jqXhr); }); }); } }).disableSelection(); </script> 提供基于索引的访问。

答案 3 :(得分:1)

另一种可能的解决方案:

<form id="form" onSubmit="return validate();" action="" method="post">

我们删除列表的第一个元素,然后将其与原始列表一起压缩以获取元组列表。元组的每个元素都包含当前元素和下一个元素。然后我们过滤该列表,然后选择&#39; next&#39;来自元组的元素并占据最终列表的头部。

答案 4 :(得分:0)

具体问题有一些好的答案。我想提出一种替代模型,因为这种方法有一些缺点:

  • 您依靠检查用户是否平等。这会限制用户类型,例如其中没有功能。
  • 在不能保证所有用户都不同的列表中查找用户是脆弱的:如果列表恰巧以[user1, user2, user1, user3]结尾,则轮换将永远不会达到user3
  • 没有任何东西可以保证当前用户在列表中,因此,其他答案的返回类型中的Maybe很难正确处理。

因此,假设您当前的模型是

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]
            }