yotamDvir / elm-pivot / Pivot

A pivot is a list upgraded with a center and sides. However, a pivot can never be empty, so it is better to think of it an upgraded cons list.

In this library, we suffix functions with letters to denote their context, as follows.

This way you can guess a function's name easily. See the different map* functions.

For example, getL gets the left side of a pivot.

Type


type alias Pivot a =
Types.Pivot a

Pivot is an opaque data type. A Pivot represents a list with a pointer to the center.

A pivot of the list [1, 2, 3, 4] with 3 as the center shall be represented by [1 2 *3* 4].

To & Fro

So you want to use a pivot? Better know how to create one, and get stuff back!

Create

fromList : List a -> Maybe (Pivot a)

Make a pivot from a list with empty left side.

Fails if and only if the list given is empty.

fromList [] == Nothing
fromList [1, 2, 3, 4] == Just [*1* 2 3 4]

fromCons : a -> List a -> Pivot a

Like fromList, but by specifying the center explicitly, it cannot fail.

fromCons 1 [2, 3, 4] == [*1* 2 3 4]
Just (fromCons h t) == fromList (h :: t)

singleton : a -> Pivot a

Like fromCons, but without the list. That is, we specify only the center.

singleton 3 == [*3*]
singleton x == fromCons x []

Get

getC : Pivot a -> a

Get the center member.

getC [ 1 2 * 3 * 4 ] == 3

singleton >> getC == identity

getL : Pivot a -> List a

Get the left side list.

getL [ 1 2 * 3 * 4 ] == [ 1, 2 ]

getR : Pivot a -> List a

Get the right side list.

getR [ 1 2 * 3 * 4 ] == [ 4 ]

getA : Pivot a -> List a

Make the pivot into a list.

getA [ 1 2 * 3 * 4 ] == [ 1, 2, 3, 4 ]

toList : Pivot a -> List a

Alias for getA

hasL : Pivot a -> Basics.Bool

Check if the left side is not empty.

hasR : Pivot a -> Basics.Bool

Check if the right side is not empty.

Movement

Handle the position of the center. These functions do not mutate the underlying list. That is, if you apply functions from here and then apply getA, you'd get the same thing you would by applying getA beforehand.

Position

lengthL : Pivot a -> Basics.Int

Position from the left side. Starts with 0.

lengthR : Pivot a -> Basics.Int

Position from the right side. Starts with 0.

lengthA : Pivot a -> Basics.Int

Length of the pivot.

lengthA pvt == lengthL pvt + 1 + lengthR pvt

Momentum

goR : Pivot a -> Maybe (Pivot a)

Move one step right.

Fails if and only if the right side is empty.

Tip: withRollback replaces failures with no-ops (see Utilities).

goR [1 2 3 *4*] == Nothing
goR [1 *2* 3 4] == Just [1 2 *3* 4]

goL : Pivot a -> Maybe (Pivot a)

Move one step left.

Fails if and only if the left side is empty.

goRelative : Basics.Int -> Pivot a -> Maybe (Pivot a)

Move right by some number of steps. Negative numbers move left instead.

Fails if and only if the movement goes out of bounds.

goBy : Basics.Int -> Pivot a -> Maybe (Pivot a)

Alias for goRelative.

goAbsolute : Basics.Int -> Pivot a -> Maybe (Pivot a)

Go to a specific position from the left. Starts with 0.

Fails if and only if the position given doesn't exist.

goTo : Basics.Int -> Pivot a -> Maybe (Pivot a)

Alias for goAbsolute

goToStart : Pivot a -> Pivot a

Go to starting position.

goToStart >> lengthL == always 0

goToEnd : Pivot a -> Pivot a

Go to starting position.

goToEnd >> lengthR == always 0

Find

firstWith : (a -> Basics.Bool) -> Pivot a -> Maybe (Pivot a)

Find the first member of a pivot satisfying some predicate.

Fails if and only if there are no such members.

lastWith : (a -> Basics.Bool) -> Pivot a -> Maybe (Pivot a)

Find the last member of a pivot satisfying some predicate.

Fails if and only if there are no such members.

findR : (a -> Basics.Bool) -> Pivot a -> Maybe (Pivot a)

Find the first member to the center's right satisfying some predicate.

Fails if and only if there are no such members.

findR ((==) 3) [ 1 2 * 3 * 4 ] == Nothing

findL : (a -> Basics.Bool) -> Pivot a -> Maybe (Pivot a)

Find the first member to the center's left satisfying some predicate.

Fails if and only if there are no such members.

findL ((==) 2) [ 1 2 * 3 * 4 ] == Just [ 1 * 2 * 3 4 ]

findCR : (a -> Basics.Bool) -> Pivot a -> Maybe (Pivot a)

Like findR, but checks the center first as well.

Fails if and only if there are no such members.

findCR ((==) 3) [ 1 2 * 3 * 4 ] == Just [ 1 2 * 3 * 4 ]

firstWith pred == goToStart >> findCR pred

findCL : (a -> Basics.Bool) -> Pivot a -> Maybe (Pivot a)

Like findL, but checks the center first as well.

Fails if and only if there are no such members.

Modify

Now we start seeing functions that can actually change the underlying list.

Set

setC : a -> Pivot a -> Pivot a

Replace the center.

setL : List a -> Pivot a -> Pivot a

Replace the left.

setR : List a -> Pivot a -> Pivot a

Replace the right.

Append

appendL : a -> Pivot a -> Pivot a

Add a member to the left of the center

appendR : a -> Pivot a -> Pivot a

Add a member to the right of the center

appendGoL : a -> Pivot a -> Pivot a

Add a member to the left of the center and immediately move left. We know that appendL >> goL cannot really fail, but it still results in a Maybe type. This avoids this issue.

appendGoL >> Just == appendL >> goL

appendGoR : a -> Pivot a -> Pivot a

Add a member to the right of the center and immediately move right.

appendListL : List a -> Pivot a -> Pivot a

Like List.append, but the right side is a pivot.

appendListL [ 8, 9 ] [ 1 2 * 3 * 4 ] == [ 8 9 1 2 * 3 * 4 ]

appendListR : List a -> Pivot a -> Pivot a

Like List.append, but the left side is a pivot.

appendListR [ 8, 9 ] [ 1 2 * 3 * 4 ] == [ 1 2 * 3 * 4 8 9 ]

Remove

Removing is not guaranteed to work, for the simple reason that a pivot cannot be empty.

removeGoL : Pivot a -> Maybe (Pivot a)

Replace center with member nearest to the left.

Fails if and only if left side is empty.

removeGoR : Pivot a -> Maybe (Pivot a)

Replace center with member nearest to the right.

Fails if and only if right side is empty.

Switch

Switch places with other members.

switchL : Pivot a -> Maybe (Pivot a)

Switch places with member nearest to the left

Fails if and only if left side is empty

switchR : Pivot a -> Maybe (Pivot a)

Switch places with member nearest to the right

Fails if and only if right side is empty

Sort

sort : Pivot comparable -> Pivot comparable

Sort a pivot while keeping the center as center.

It does not simply sort each side separately!

sort >> getA == getA >> List.sort

getC == sort >> getC

sortWith : (a -> a -> Basics.Order) -> Pivot a -> Pivot a

Like sort, but with a costum comparator.

sort == sortWith compare

Maps

Lists can be mapped over, and so can pivots. However, since a pivot is made up of three distinct objects at any time, you may want to apply different transformations to the different objects.

As individuals

mapCLR : (a -> b) -> (a -> b) -> (a -> b) -> Pivot a -> Pivot b

Provide functions that control what happens to the center, the left members and the right members separately, and get a function that acts on pivots.

mapCRL : (a -> b) -> (a -> b) -> (a -> b) -> Pivot a -> Pivot b

Like mapCLR, but provide the function for the right before the left.

mapCS : (a -> b) -> (a -> b) -> Pivot a -> Pivot b

Like mapCLR, but you provide one function for both sides.

mapA : (a -> b) -> Pivot a -> Pivot b

Like mapCS, but you provide one function for all members. This is exactly like List.map for the underlying list.

mapA ((==) 3) [ 1 * 2 * 3 4 ] == [ False * False * True False ]

mapC : (a -> a) -> Pivot a -> Pivot a

Like mapA, but only the center is affected.

mapS : (a -> a) -> Pivot a -> Pivot a

Like mapA, but the center is not affected.

mapL : (a -> a) -> Pivot a -> Pivot a

Like mapA, but only the left is affected.

mapR : (a -> a) -> Pivot a -> Pivot a

Like mapA, but only the right is affected.

As a whole

Some List a -> List b functions cannot be made from a -> b functions. This is why these maps may be of importance. Just replace map* with mapWhole* to use functions on whole lists instead of values.

mapWholeCLR : (a -> b) -> (List a -> List b) -> (List a -> List b) -> Pivot a -> Pivot b

Like mapCLR, but the functions for the left and right act on the lists as a whole, and not on each member separately. The lists are ordered from the center out.

mapWholeCLR ((*) 3) (List.drop 1) (List.drop 1) [ 1 2 * 3 * 4 5 ] == [ 1 * 9 * 5 ]

mapWholeCRL : (a -> b) -> (List a -> List b) -> (List a -> List b) -> Pivot a -> Pivot b

See mapWholeCLR.

mapWholeCS : (a -> b) -> (List a -> List b) -> Pivot a -> Pivot b

See mapWholeCLR.

mapWholeS : (List a -> List a) -> Pivot a -> Pivot a

See mapWholeCLR.

mapWholeL : (List a -> List a) -> Pivot a -> Pivot a

See mapWholeCLR.

mapWholeR : (List a -> List a) -> Pivot a -> Pivot a

See mapWholeCLR.

Special

indexAbsolute : Pivot a -> Pivot ( Basics.Int, a )

Adds indices to all values, from left to right. Based internally on List.indexedMap.

indexAbsolute [ 1 2 * 3 * 4 ] == [ ( 0, 1 ) ( 1, 2 ) * ( 2, 3 ) * ( 3, 4 ) ]

indexRelative : Pivot a -> Pivot ( Basics.Int, a )

Like indexAbsolute, but relative to the center (that gets the index 0).

indexAbsolute [ 1 2 * 3 * 4 ] == [ ( -2, 1 ) ( -1, 2 ) * ( 0, 3 ) * ( 1, 4 ) ]

apply : Pivot (a -> b) -> Pivot a -> Pivot b

Apply functions in a pivot on values in another Pivot. The center gets applied to the center, and each side gets applied to each side. But how does a list of functions get applied on a list of values? Well, each function maps over the complete list of values, and then all the lists created from these applications are concatinated.

mapCLR onC onL onR == apply [ onL * onC * onR ]

Utilities

reverse : Pivot a -> Pivot a

Reverse a pivot, like a list. You could also think of it as mirroring left and right.

mirror : (Pivot a -> Pivot b) -> Pivot a -> Pivot b

Reverse a function's notion of left and right. Used in many of this library's functions under the hood

mirrorM : (Pivot a -> Maybe (Pivot b)) -> Pivot a -> Maybe (Pivot b)

Reverse a possibly-failing-function's notion of left and right. Used in many of this library's functions under the hood

assert : Pivot (Maybe a) -> Maybe (Pivot a)

Takes a pivot full of possible values, and realizes it only if all the values are real. That is, if all the values are Just a, then we get Just (Pivot a). Otherwise, we get Nothing. This is great for composing with the different map functions. For example, you could define

mapAM : (a -> Maybe b) -> Pivot a -> Maybe (Pivot b)
mapAM f =
    mapA f >> assert

withRollback : (a -> Maybe a) -> a -> a

Replace a possibly-failing-function with a possibly-does-nothing-function. For example, if you try to goR a pivot, you may fail since there is nothing to the right. But if you withRollback goR a pivot, the worst that could happen is that nothing happens.

Use it, don't abuse it. That is, only use it when it makes sense to ignore a failure, or when you are certain a possibly-failing-function cannot really fail. For example,

appendGoR == appendR >> withRollback goR