indique / elm-pairdict / PairDict


type PairDict pair left right

Want to look up value-pairs from the left or the right?

You want the pair where 🗝️ is 1 and the pair where 🔑 is 0?

  → < 🔑= 0, 🗝️= 2 >
    < 🔑= 2, 🗝️= 0 >
    < 🔑= 1, 🗝️= 1 > ←

Going through while checking every pair, if 🗝️ is equal, then, if 🔑 is equal... Ah! Here they are:

    🔑 is 1 where 🗝️ is 1  and   🗝️ is 2 where 🔑 is 0

Like assoc-list, a PairDict allows for anything as values except for functions and things that contain functions.

Example: cased letters

type alias CasedLetter=
  { lowercase: Char
  , uppercase: Char
  }

casedLetters: PairDict CasedLetter Char Char
casedLetters=
  PairDict.empty .lowercase .uppercase
  |>PairDict.putIn { lowercase= 'a', uppercase= 'A' }
  |>PairDict.putIn { lowercase= 'b', uppercase= 'B' }
  |>PairDict.putIn { lowercase= 'c', uppercase= 'C' }

uppercase char=
  PairDict.access .lowercase char lowerUppercaseLetters
  |>Maybe.map .uppercase

create

empty : (pair -> left) -> (pair -> right) -> PairDict pair left right

A PairDict with no pairs inside

fromDict : (left -> right -> pair) -> (pair -> left) -> (pair -> right) -> AssocList.Dict left right -> PairDict pair left right

Create a PairDict from a association-Dict. left is the key, right is the value. If multiple equal keys or values are present, the value first in the Dict is prefered (see putIn).

lowerToUpperLetters=
  AssocList.empty
  |>AssocList.insert 'a' 'A'
  |>AssocList.insert 'b' 'B'

lowerUpperLetters=
  PairDict.fromDict
    (\k v-> { lowercase= k, uppercase= v })
    .lowercase .uppercase
    lowerToUpperLetters

fromList : (pair -> left) -> (pair -> right) -> List pair -> PairDict pair left right

Create a PairDict conveniently from pairs. If right or left values are given multiple times, the value first in the List is prefered (see putIn).

PairDict.fromList
  [ { lowercase= 'b', uppercase= 'B' } --put in
  , { lowercase= 'a', uppercase= 'A' } --put in
  , { lowercase= 'b', uppercase= 'C' }
      --ignored, as the left value already exists
  , { lowercase= 'c', uppercase= 'A' }
      --ignored, as the right value already exists
  , { lowercase= 'c', uppercase= 'C' } --put in
  ]

decode : (pair -> left) -> (pair -> right) -> Json.Decode.Decoder pair -> Json.Decode.Decoder (PairDict pair left right)

A Json.Decode.Decoder for PairDicts encoded by encodePair.

The order of insertion is not reconstructed (see equal)

type alias NamedNumber=
  { number: Int
  , name: String
  }

decodeNamedNumber=
  Decode.map NamedNumber
    (\{ number, name }->
      Decode.object
        [ ( "number", Decode.int number )
        , ( "name", Decode.string name )
        ]
    )

"""
[
 {
  \"left\": 2,
  \"right\": "two"
 },
 {
  \"left\": 1,
  \"right\": "one"
 }
]
"""
|>Decode.decodeString
    (PairDict.decode .number .name
      decodeNamedNumber
    )

Ok (Pairs [ { number= 1, name= "one" }, { number= 2, name= "two" } ]) = a PairDict

scan

equal : PairDict pair leftA rightA -> PairDict pair leftB rightB -> Basics.Bool

using built-in (==) equality is often not useful in the context of association-dicts.

Do these 2 PairDicts have the same size and identical pairs (ignoring insertion order)?

letterCodes=
  PairDict.fromList .letter .code 
    [ { letter= 'a', code= 97 }
    , { letter= 'b', code= 98 }
    ]
fancyCompetingLetterCodes=
  PairDict.empty .code .letter
  |>PairDict.putIn { code= 98, letter= 'b' }
  |>PairDict.putIn { code= 97, letter= 'a' }

PairDict.equal
  letterCodes
  fancyCompetingLetterCodes

True

access : (pair -> key) -> key -> PairDict pair left right -> Maybe pair

Just the pair in which key is present in the PairDict, if no pair with the key is found Nothing.

casedLetters=
  PairDict.empty .lowercase .uppercase
  |>PairDict.putIn { lowercase= 'a', uppercase= 'A' }
  |>PairDict.putIn { lowercase= 'b', uppercase= 'B' }

lowercase char=
  PairDict.access .uppercase char
    casedLetters
  |>Maybe.map .lowercase
uppercase char=
  PairDict.access .lowercase char
    casedLetters
  |>Maybe.map .uppercase

Note: If accessKey is neither accessLeft or accessRight (see empty, fromList, fromDict), access will find the most recently inserted value where key is equal in the pair.

PairDict.empty .lowercase .uppercase
|>PairDict.putIn { inAlphabet= 0, lowercase= 'a', uppercase= 'A' }
|>PairDict.putIn { inAlphabet= 1, lowercase= 'b', uppercase= 'B' }
|>PairDict.access .inAlphabet 1

{ inAlphabet= 1, lowercase= 'b', uppercase= 'B' }

emptyOrMore : { ifEmpty : result, ifMore : pair -> PairDict pair left right -> result } -> PairDict pair left right -> result

ifEmpty if the PairDict contains no pairs, else ifMore with the most recently putIned pair followed by a PairDict with the other pairs.

It has a very similar use case to a case .. of on a List.

isEmpty=
  PairDict.emptyOrMore
    { ifEmpty= True
    , ifMore= \_ _-> False
    }
mostRecent=
  PairDict.emptyOrMore
    { ifMore= \pair _-> Just pair
    , ifEmpty= Nothing
    }
removeMostRecent pairDict=
  pairDict
  |>PairDict.emptyOrMore
      { ifMore= \_ rest-> rest
      , ifEmpty= pairDict
      }

size : PairDict pair left right -> Basics.Int

How many pairs there are in a PairDict.

PairDict.fromList .number .following
  (List.map (\i-> { number= i, following= i+1 })
    (List.range 0 41)
  )
|>PairDict.size

42

in

putIn : pair -> PairDict pair left right -> PairDict pair left right

Put in a pair.

If either value is already present, the PairDict is unchanged.

specialCasedA=
  { lowercase= 'a', uppercase= 'A', inAphabet= 0 }

casedBadB=
  { lowercase= 'b', uppercase= 'B', inAlphabet= 0 }

PairDict.empty .lowercase .uppercase --lowercase and uppercase are unique across each pair
|>PairDict.putIn casedBadB --put in 
|>PairDict.putIn specialCasedA --put in, because inAlphabet isn't checked
|>PairDict.putIn { lowercase= 'b', uppercase= 'C' }
    --ignored, the left value already exists
|>PairDict.putIn { lowercase= 'c', uppercase= 'A' }
    --ignored, the right value already exists
|>PairDict.putIn { lowercase= 'c', uppercase= 'C' } --put in

union : PairDict pair left right -> PairDict pair left right -> PairDict pair left right

Combine 2 PairDicts, so that the pairs in toInsert are put into preferred. If a value on the left or right is present, prefer the last PairDict (see putIn).

numberNamedOperators=
  PairDict.fromList
    [ { operator= '+', name= "plus" }
    , { operator= '-', name= "minus" }
    ]
customNamedOperators=
  PairDict.fromList
    [ { operator= '∧', name= "and" }
    , { operator= '∨', name= "or" }
    , { operator= '-', name= "negate" }
    ]
validNamedOperators=
  PairDict.union
    custumNamedOperators --has a '-' left
    numberOperatorNames --preferred → its '-'-pair is put in

out

remove : (pair -> key) -> key -> PairDict pair left right -> PairDict pair left right

Remove the left-right pair at left. If the value does not exist, the PairDict is unchanged

openClosedBrackets=
  PairDict.empty .open .closed
  |>PairDict.putIn { open= "(", closed= ")" }

openClosedBrackets
|>PairDict.remove .open ")" 
    --unchanged, ")" is not a open value
|>PairDict.remove .open "("

PairDict.empty

openClosedBrackets
|>PairDict.remove .closed "("
    --unchanged, "(" is not a closed value
|>PairDict.remove .closed ")"

PairDict.empty

Notice: If you don't specify accessValue as left or right, it acts as a normal filter

PairDict.empty .open .closed
|>PairDict.putIn { open= "(", closed= ")", meaning= Nothing }
|>PairDict.putIn { open= "[", closed= "]", meaning= Just (List Element) }
|>PairDict.putIn { open= "<, closed= ">", meaning= Nothing }
|>PairDict.remove .meaning Nothing

shape

values : (pair -> value) -> PairDict pair left right -> List value

Values on the pairs.

brackets=
  PairDict.fromList .open .closed
    [ { open= '(', closed= ')' }
    , { open= '{', closed= '}' }
    ]

open= values .open brackets
closed= values .closed brackets

fold : (pair -> acc -> acc) -> acc -> PairDict pair left right -> acc

Reduce the left-right pairs from most recently putIned to least recently putIned.

A fold in the other direction doesn't exist, as association-Dicts should rarely rely on order (see equal).

brackets=
  PairDict.empty
  |>PairDict.putIn ( '(', ')' )
  |>PairDict.putIn ( '{', '}' )

openingAndClosing=
  brackets
  |>PairDict.fold
      (\( left, right ) acc->
        acc++[ String.fromList [ left, right ] ]
      )
      []

[ "{}", "()" ]

map : (pair -> resultPair) -> (resultPair -> resultLeft) -> (resultPair -> resultRight) -> PairDict pair left right -> PairDict resultPair resultLeft resultRight

Map pairs. Take a look at pair's map operations.

digitNames=
  PairDict.empty .number .name
  |>PairDict.putIn { number= 0, name= "zero" }
  |>PairDict.putIn { number= 1, name= "one" }

mathSymbolNames=
  digitNames
  |>PairDict.map .symbol .name
      (\{ number, name }->
        { symbol= String.fromInt number, name= name }
      )
  |>PairDict.putIn { symbol= "+", name= "plus" }

toDict : PairDict pair left right -> AssocList.Dict left right

Convert a PairDict to an association-Dict, which you can access only from the left.

casedLetters=
  PairDict.fromList .lowercase .uppercase
    [ { uppercase= 'A', lowercase= 'a' }
    , { uppercase= 'B', lowercase= 'b' }
    ]
lowerFromUpper=
  PairDict.toDict casedLetters

encode : (pair -> Json.Encode.Value) -> PairDict pair left right -> Json.Encode.Value

Convert a PairDict to a Json.Encode.Value.

somePairDict=
  PairDict.empty
  |>PairDict.putIn ( 1, 11 )
  |>PairDict.putIn ( 2, 22 )
Encode.encode 1
  (PairDict.encode
    Encode.int Encode.int
    somePairDict
  )

"""
[
 {
  \"left\": 2,
  \"right\": 22
 },
 {
  \"left\": 1,
  \"right\": 11
 }
]
"""