lue-bird / elm-linear-direction / List.Linear

List operations that can be applied in either Direction

scan

element : ( Linear.Direction, Basics.Int ) -> List element -> Maybe element

Just the element at the given index in a Direction

import Linear exposing (Direction(..))

[ 0, 1, 2, 3 ]
    |> List.Linear.element ( Down, 0 )
--> Just 3

[ 0, 1, 2, 3 ]
    |> List.Linear.element ( Up, 2 )
--> Just 2

Nothing if the index is out of range:

import Linear exposing (Direction(..))

[ 0, 1, 2, 3 ]
    |> List.Linear.element ( Up, 5 )
--> Nothing

[ 0, 1, 2, 3 ]
    |> List.Linear.element ( Up, -1 )
--> Nothing

If you're using at-operations often, consider using an Array instead of a List to get O(log n) vs. O(n) random access performance

alter

elementAlter : ( Linear.Direction, Basics.Int ) -> (element -> element) -> List element -> List element

Alter the element at the given index in a Direction

import Linear exposing (Direction(..))

[ 1, 2, 2 ]
    |> List.Linear.elementAlter ( Down, 0 )
        (\n -> n + 1)
--> [ 1, 2, 3 ]

Do nothing if the index is out of range

import Linear exposing (Direction(..))

[ 0, 1, 2, 3 ]
    |> List.Linear.elementAlter ( Up, 4 )
        (\n -> n + 1)
--> [ 0, 1, 2, 3 ]

[ 0, 1, 2, 3 ]
    |> List.Linear.elementAlter ( Up, -1 )
        (\n -> n + 1)
--> [ 0, 1, 2, 3 ]

If you're using at-operations often, consider using an Array instead of a List to get O(log n) vs. O(n) random access performance

padToAtLeast : Linear.Direction -> Basics.Int -> (Basics.Int -> List element) -> List element -> List element

Pad in a Direction based on how much it takes to reach at a given length

import Linear exposing (Direction(..))

[ 1, 2 ]
    |> List.Linear.padToAtLeast Up
        4
        (\l -> List.repeat l 0)
--> [ 1, 2, 0, 0 ]

[ 1, 2 ]
    |> List.Linear.padToAtLeast Down
        4
        (\l -> List.repeat l 0)
--> [ 0, 0, 1, 2 ]

To achieve "resize" behavior, use padToAtLeast direction length followed by take direction length

[ 1, 2, 3 ]
    |> List.Linear.padToAtLeast Up
        2
        (\l -> List.repeat l 0)
    |> List.Linear.take Up 2
--> [ 1, 2 ]

[ 1, 2, 3 ]
    |> List.Linear.padToAtLeast Down
        2
        (\l -> List.repeat l 0)
    |> List.Linear.take Down 2
--> [ 2, 3 ]

transform

foldFrom : accumulationValue -> Linear.Direction -> (element -> accumulationValue -> accumulationValue) -> List element -> accumulationValue

Reduce in a Direction from a given initial accumulated thing

import Linear exposing (Direction(..))

[ 'l', 'i', 'v', 'e' ]
    |> List.Linear.foldFrom "" Up String.cons
--> "evil"

[ 'l', 'i', 'v', 'e' ]
    |> List.Linear.foldFrom "" Down String.cons
--> "live"

foldUntilCompleteFrom : foldedPartial -> Linear.Direction -> (element -> foldedPartial -> PartialOrComplete foldedPartial foldedComplete) -> List element -> PartialOrComplete foldedPartial foldedComplete

A fold that can stop → (Complete the fold) early instead of traversing the whole List

-- from lue-bird/elm-partial-or-complete
import PartialOrComplete exposing (PartialOrComplete(..))
import Linear exposing (Direction(..))

[ 2, -1, 4, 8 ]
    -- take from the right while not negative
    |> List.Linear.foldUntilCompleteFrom []
        Down
        (\n soFar ->
            if n < 0 then
                Complete soFar -- stop the fold
            else
                Partial (n :: soFar)
        )
    -- even if none are negative
    |> PartialOrComplete.value
--> [ 4, 8 ]

If negative elements exist, our foldUntilCompleteFrom will return Complete with the list up to that point exclusive.

If the list only contains non-negative numbers, the result would be Partial with the whole list.

To treat both cases the same way, we use value, one of a few helpers from PartialOrComplete

Compared to List.Extra.stoppableFoldl you'll have the extra information about whether the fold completed or not, as well as the ability to have different Complete and Partial types more in the spirit of "parse, don't validate" (plus both Directions available).

mapFoldFrom : accumulationValue -> Linear.Direction -> ({ element : element, folded : accumulationValue } -> { element : mappedElement, folded : accumulationValue }) -> List element -> { mapped : List mappedElement, folded : accumulationValue }

Map each element using information collected from previous steps, folding in a given Direction from given initial information.

Both the mapped Array and the folded information will be returned

You'll often find this under the name "mapAccum"

import Linear exposing (Direction(..))

[ 1, 2, 3 ]
    |> List.Linear.mapFoldFrom 0
        Down
        (\state ->
            { element = state.folded
            , folded = state.folded + state.element
            }
        )
--> { mapped = [ 5, 3, 0 ], folded = 6 }

mapIndexed : Direction -> (Int -> a -> b) -> (List a -> List b)
mapIndexed indexDirection mapAtIndex =
    List.Linear.mapFoldFrom 0
        indexDirection
        (\state ->
            { element = state.element |> mapAtIndex state.folded
            , folded = state.folded + 1
            }
        )
        >> .mapped

[ 'h', 'i', 'y', 'o' ]
    |> mapIndexed Up Tuple.pair
--> [ ( 0, 'h' ), ( 1, 'i' ), ( 2, 'y' ), ( 3, 'o' ) ]

[ 'h', 'i', 'y', 'o' ]
    |> mapIndexed Down Tuple.pair
--> [ ( 3, 'h' ), ( 2, 'i' ), ( 1, 'y' ), ( 0, 'o' ) ]

part

take : Linear.Direction -> Basics.Int -> List element -> List element

A given number of elements from one side in a Direction

import Linear exposing (Direction(..))

[ 1, 2, 3, 4 ]
    |> List.Linear.take Up 2
--> [ 1, 2 ]

[ 1, 2, 3, 4 ]
    |> List.Linear.take Down 2
--> [ 3, 4 ]

[] if the amount of elements to take is negative

import Linear exposing (Direction(..))

[ 1, 2, 3 ]
    |> List.Linear.take Up -100
--> []

drop : Linear.Direction -> Basics.Int -> List element -> List element

Remove a given number of elements from one side in a Direction

import Linear exposing (Direction(..))

removeFirst =
    List.Linear.drop Up 0

removeLast =
    List.Linear.drop Down 0

Nothing is dropped if the amount of elements to drop is negative

import Linear exposing (Direction(..))

[ 1, 2, 3 ]
    |> List.Linear.drop Up -1
--> [ 1, 2, 3 ]

toChunksOf : Linear.Direction -> Basics.Int -> List element -> { chunks : List (List element), remainder : List element }

Split into equal-sized chunks of a given length in a given Direction The left over elements to one side are in remainder

import Linear exposing (Direction(..))

[ 1, 2, 3, 4, 5, 6, 7 ]
    |> List.Linear.toChunksOf Up 3
--> { chunks = [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
--> , remainder = [ 7 ]
--> }

[ 1, 2, 3, 4, 5, 6, 7 ]
    |> List.Linear.toChunksOf Down 3
--> { remainder = [ 1 ]
--> , chunks = [ [ 2, 3, 4 ], [ 5, 6, 7 ] ]
--> }