Skip to content

Commit

Permalink
Adds Dict.Extra.{invertAll,upsert,updateIfExists} (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
gampleman authored Jul 26, 2024
1 parent ec5c3ce commit feeafd3
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 3 deletions.
2 changes: 1 addition & 1 deletion docs.json

Large diffs are not rendered by default.

75 changes: 73 additions & 2 deletions src/Dict/Extra.elm
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Dict.Extra exposing
( groupBy, filterGroupBy, fromListBy, fromListCombining, fromListByCombining, frequencies
, removeWhen, removeMany, keepOnly, insertCombining, mapKeys, filterMap, invert
, removeWhen, removeMany, keepOnly, insertCombining, updateIfExists, upsert, invert, invertAll
, mapKeys, filterMap
, any, all
, find
, unionWith
Expand All @@ -16,7 +17,12 @@ module Dict.Extra exposing
# Manipulation
@docs removeWhen, removeMany, keepOnly, insertCombining, mapKeys, filterMap, invert
@docs removeWhen, removeMany, keepOnly, insertCombining, updateIfExists, upsert, invert, invertAll
# Maps
@docs mapKeys, filterMap
# Predicates
Expand Down Expand Up @@ -243,6 +249,46 @@ insertCombining combine key value dict =
Dict.update key with dict


{-| Updates a value if the key is present in the dictionary, leaves the dictionary untouched otherwise.
import Dict
Dict.fromList [ ( "expenses", 38.25 ), ( "assets", 100.85 ) ]
|> updateIfExists "expenses" (\amount -> amount + 2.50)
|> updateIfExists "liabilities" (\amount -> amount - 2.50)
--> Dict.fromList [ ( "expenses", 40.75 ), ( "assets", 100.85 ) ]
-}
updateIfExists : comparable -> (a -> a) -> Dict comparable a -> Dict comparable a
updateIfExists key f dict =
case Dict.get key dict of
Just value ->
Dict.insert key (f value) dict

Nothing ->
dict


{-| Updates a value if the key is present in the dictionary, inserts a new key-value pair otherwise.
import Dict
Dict.fromList [ ( "expenses", 38.25 ), ( "assets", 100.85 ) ]
|> upsert "expenses" 4.50 (\amount -> amount + 2.50)
|> upsert "liabilities" 2.50 (\amount -> amount - 2.50)
--> Dict.fromList [ ( "expenses", 40.75 ), ( "assets", 100.85 ), ( "liabilities", 2.50 ) ]
-}
upsert : comparable -> a -> (a -> a) -> Dict comparable a -> Dict comparable a
upsert key value f dict =
case Dict.get key dict of
Just oldValue ->
Dict.insert key (f oldValue) dict

Nothing ->
Dict.insert key value dict


{-| Keep a key-value pair if its key appears in the set.
import Dict
Expand Down Expand Up @@ -337,6 +383,31 @@ invert dict =
dict


{-| Like `invert`, it changes the keys and values. However, if one value maps to multiple keys, then all of the keys will be retained.
import Dict
import Set
Dict.fromList [ ( 1, "Jill" ), ( 2, "Jill" ), ( 3, "Jack" ) ]
|> invertAll
--> Dict.fromList [ ( "Jill", Set.fromList [ 1, 2 ] ), ( "Jack", Set.singleton 3 ) ]
-}
invertAll : Dict comparable1 comparable2 -> Dict comparable2 (Set comparable1)
invertAll dict =
Dict.foldl
(\k v acc ->
case Dict.get v acc of
Just set ->
Dict.insert v (Set.insert k set) acc

Nothing ->
Dict.insert v (Set.singleton k) acc
)
Dict.empty
dict


{-| Determine if any key/value pair satisfies some test.
import Dict
Expand Down
44 changes: 44 additions & 0 deletions tests/DictTests.elm
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
module DictTests exposing (suite)

import Dict
import Dict.Extra
import Expect
import Fuzz exposing (Fuzzer)
import Set
import Set.Extra
import Test exposing (Test, describe)


dictFuzzer : Fuzzer comparable -> Fuzzer v -> Fuzzer (Dict.Dict comparable v)
dictFuzzer keyFuzzer valueFuzzer =
Fuzz.map Dict.fromList (Fuzz.list (Fuzz.pair keyFuzzer valueFuzzer))


suite : Test
suite =
describe "Dict.Extra"
[ describe "invertAll"
[ Test.fuzz (dictFuzzer Fuzz.string Fuzz.int) "does not loose information" <|
\dict ->
dict
|> Dict.Extra.invertAll
|> Dict.map (always Set.size)
|> Dict.values
|> List.sum
|> Expect.equal (Dict.size dict)
, Test.fuzz (dictFuzzer Fuzz.string Fuzz.int) "the values are keys into the original dictionary" <|
\dict ->
dict
|> Dict.Extra.invertAll
|> Dict.Extra.all
(\key vals ->
Set.Extra.all (\val -> Dict.get val dict == Just key) vals
)
|> Expect.equal True
, Test.fuzz (Fuzz.map Dict.Extra.invert (dictFuzzer Fuzz.string Fuzz.int)) "behaves the same as invert on an already inverted dictionary" <|
\dict ->
dict
|> Dict.Extra.invertAll
|> Expect.equal (dict |> Dict.Extra.invert |> Dict.map (always Set.singleton))
]
]

0 comments on commit feeafd3

Please sign in to comment.