elm-format Hashdict & Iddict

I should attempt to not forget this!
2-transfer-tools
Bram van den Heuvel 2023-12-14 20:43:23 +01:00
parent 41ede5bbff
commit fcc7699a44
2 changed files with 97 additions and 25 deletions

View File

@ -1,39 +1,54 @@
module Internal.Tools.Hashdict exposing (Hashdict, decoder, empty, encode, fromList, get, insert, isEmpty, keys, member, memberKey, rehash, remove, removeKey, singleton, size, softDecoder, toList, union, values) module Internal.Tools.Hashdict exposing
( Hashdict
, empty, singleton, insert, remove, removeKey
, isEmpty, member, memberKey, get, size
, keys, values, toList, fromList
, rehash, union
, encode, decoder, softDecoder
)
{-| This module abstracts the `Dict` type with one function that assigns a {-| This module abstracts the `Dict` type with one function that assigns a
unique identifier for each value based on a function that assigns each value. unique identifier for each value based on a function that assigns each value.
This allows you to store values based on an externally defined identifier. This allows you to store values based on an externally defined identifier.
## Dictionaries ## Dictionaries
@docs Hashdict @docs Hashdict
## Build ## Build
@docs empty, singleton, insert, remove, removeKey @docs empty, singleton, insert, remove, removeKey
## Query ## Query
@docs isEmpty, member, memberKey, get, size @docs isEmpty, member, memberKey, get, size
## Lists ## Lists
@docs keys, values, toList, fromList @docs keys, values, toList, fromList
## Transform ## Transform
@docs rehash, union @docs rehash, union
## JSON coders ## JSON coders
@docs encode, decoder, softDecoder @docs encode, decoder, softDecoder
-} -}
import FastDict as Dict exposing (Dict) import FastDict as Dict exposing (Dict)
import Json.Decode as D import Json.Decode as D
import Json.Encode as E import Json.Encode as E
{-| A dictionary of keys and values where each key is defined by its value. For {-| A dictionary of keys and values where each key is defined by its value. For
example, this can be useful when every user is identifiable by their username: example, this can be useful when every user is identifiable by their username:
@ -53,9 +68,11 @@ example, this can be useful when every user is identifiable by their username:
, height : Float , height : Float
} }
In the example listed above, the users are stored by their username, which means In the example listed above, the users are stored by their username, which means
that all you need to know is the value "Alice" to retrieve all the information that all you need to know is the value "Alice" to retrieve all the information
about them. Additionally, you do not need to specify a key to insert the values. about them. Additionally, you do not need to specify a key to insert the values.
-} -}
type Hashdict a type Hashdict a
= Hashdict = Hashdict
@ -63,6 +80,7 @@ type Hashdict a
, values : Dict String a , values : Dict String a
} }
{-| Decode a hashdict from a JSON value. To create a hashdict, you are expected {-| Decode a hashdict from a JSON value. To create a hashdict, you are expected
to insert a hash function. If the hash function doesn't properly hash the values to insert a hash function. If the hash function doesn't properly hash the values
as expected, the decoder will fail to decode the hashdict. as expected, the decoder will fail to decode the hashdict.
@ -72,22 +90,25 @@ decoder f xDecoder =
D.keyValuePairs xDecoder D.keyValuePairs xDecoder
|> D.andThen |> D.andThen
(\items -> (\items ->
if List.all (\(hash, value) -> f value == hash) items then if List.all (\( hash, value ) -> f value == hash) items then
items items
|> Dict.fromList |> Dict.fromList
|> (\d -> { hash = f, values = d }) |> (\d -> { hash = f, values = d })
|> Hashdict |> Hashdict
|> D.succeed |> D.succeed
else else
D.fail "Hash function fails to properly hash all values" D.fail "Hash function fails to properly hash all values"
) )
{-| Create an empty hashdict. {-| Create an empty hashdict.
-} -}
empty : (a -> String) -> Hashdict a empty : (a -> String) -> Hashdict a
empty hash = empty hash =
Hashdict { hash = hash, values = Dict.empty } Hashdict { hash = hash, values = Dict.empty }
{-| Encode a Hashdict into a JSON value. Keep in mind that an Elm function {-| Encode a Hashdict into a JSON value. Keep in mind that an Elm function
cannot be universally converted to JSON, so it is up to you to preserve that cannot be universally converted to JSON, so it is up to you to preserve that
hash function! hash function!
@ -99,6 +120,7 @@ encode encodeX (Hashdict h) =
|> List.map (Tuple.mapSecond encodeX) |> List.map (Tuple.mapSecond encodeX)
|> E.object |> E.object
{-| Convert an association list into a hashdict. {-| Convert an association list into a hashdict.
-} -}
fromList : (a -> String) -> List a -> Hashdict a fromList : (a -> String) -> List a -> Hashdict a
@ -111,6 +133,7 @@ fromList hash xs =
|> Dict.fromList |> Dict.fromList
} }
{-| Get the value associated with a hash. If the hash is not found, return {-| Get the value associated with a hash. If the hash is not found, return
`Nothing`. This is useful when you are not sure if a hash will be in the `Nothing`. This is useful when you are not sure if a hash will be in the
hashdict. hashdict.
@ -119,6 +142,7 @@ get : String -> Hashdict a -> Maybe a
get k (Hashdict h) = get k (Hashdict h) =
Dict.get k h.values Dict.get k h.values
{-| Insert a value into a hashdict. The key is automatically generated by the {-| Insert a value into a hashdict. The key is automatically generated by the
hash function. If the function generates a collision, it replaces the existing hash function. If the function generates a collision, it replaces the existing
value in the hashdict. value in the hashdict.
@ -127,30 +151,35 @@ insert : a -> Hashdict a -> Hashdict a
insert v (Hashdict h) = insert v (Hashdict h) =
Hashdict { h | values = Dict.insert (h.hash v) v h.values } Hashdict { h | values = Dict.insert (h.hash v) v h.values }
{-| Determine if a hashdict is empty. {-| Determine if a hashdict is empty.
-} -}
isEmpty : Hashdict a -> Bool isEmpty : Hashdict a -> Bool
isEmpty (Hashdict h) = isEmpty (Hashdict h) =
Dict.isEmpty h.values Dict.isEmpty h.values
{-| Get all of the hashes in a hashdict, sorted from lowest to highest. {-| Get all of the hashes in a hashdict, sorted from lowest to highest.
-} -}
keys : Hashdict a -> List String keys : Hashdict a -> List String
keys (Hashdict h) = keys (Hashdict h) =
Dict.keys h.values Dict.keys h.values
{-| Determine if a value's hash is in a hashdict. {-| Determine if a value's hash is in a hashdict.
-} -}
member : a -> Hashdict a -> Bool member : a -> Hashdict a -> Bool
member value (Hashdict h) = member value (Hashdict h) =
Dict.member (h.hash value) h.values Dict.member (h.hash value) h.values
{-| Determine if a hash is in a hashdict. {-| Determine if a hash is in a hashdict.
-} -}
memberKey : String -> Hashdict a -> Bool memberKey : String -> Hashdict a -> Bool
memberKey key (Hashdict h) = memberKey key (Hashdict h) =
Dict.member key h.values Dict.member key h.values
{-| Remap a hashdict using a new hashing algorithm. {-| Remap a hashdict using a new hashing algorithm.
-} -}
rehash : (a -> String) -> Hashdict a -> Hashdict a rehash : (a -> String) -> Hashdict a -> Hashdict a
@ -164,35 +193,42 @@ rehash f (Hashdict h) =
|> Dict.fromList |> Dict.fromList
} }
{-| Remove a value from a hashdict. If the value's hash is found, the key-value {-| Remove a value from a hashdict. If the value's hash is found, the key-value
pair is removed. If the value's hash is not found, no changes are made. pair is removed. If the value's hash is not found, no changes are made.
hdict |> Hashdict.remove (User "Alice" 19 1.82) hdict |> Hashdict.remove (User "Alice" 19 1.82)
-} -}
remove : a -> Hashdict a -> Hashdict a remove : a -> Hashdict a -> Hashdict a
remove v (Hashdict h) = remove v (Hashdict h) =
Hashdict { h | values = Dict.remove (h.hash v) h.values } Hashdict { h | values = Dict.remove (h.hash v) h.values }
{-| Remove a key from a hashdict. If the key is not found, no changes are made. {-| Remove a key from a hashdict. If the key is not found, no changes are made.
hdict |> Hashdict.removeKey "Alice" hdict |> Hashdict.removeKey "Alice"
-} -}
removeKey : String -> Hashdict a -> Hashdict a removeKey : String -> Hashdict a -> Hashdict a
removeKey k (Hashdict h) = removeKey k (Hashdict h) =
Hashdict { h | values = Dict.remove k h.values } Hashdict { h | values = Dict.remove k h.values }
{-| Create a hashdict with a single key-value pair. {-| Create a hashdict with a single key-value pair.
-} -}
singleton : (a -> String) -> a -> Hashdict a singleton : (a -> String) -> a -> Hashdict a
singleton f v = singleton f v =
empty f |> insert v empty f |> insert v
{-| Determine the number of values in a hashdict. {-| Determine the number of values in a hashdict.
-} -}
size : Hashdict a -> Int size : Hashdict a -> Int
size (Hashdict h) = size (Hashdict h) =
Dict.size h.values Dict.size h.values
{-| Decode a hashdict from a JSON value. If you cannot deduce the originally {-| Decode a hashdict from a JSON value. If you cannot deduce the originally
used hash function, (or if you simply do not care) you can use this function to used hash function, (or if you simply do not care) you can use this function to
decode and rehash the Hashdict using your new hash function. decode and rehash the Hashdict using your new hash function.
@ -202,13 +238,15 @@ softDecoder f xDecoder =
D.keyValuePairs xDecoder D.keyValuePairs xDecoder
|> D.map (List.map Tuple.second >> fromList f) |> D.map (List.map Tuple.second >> fromList f)
{-| Convert a hashdict into an association list of key-value pairs, sorted by {-| Convert a hashdict into an association list of key-value pairs, sorted by
keys. keys.
-} -}
toList : Hashdict a -> List (String, a) toList : Hashdict a -> List ( String, a )
toList (Hashdict h) = toList (Hashdict h) =
Dict.toList h.values Dict.toList h.values
{-| Combine two hashdicts under the hash function of the first. If there is a {-| Combine two hashdicts under the hash function of the first. If there is a
collision, preference is given to the first hashdict. collision, preference is given to the first hashdict.
-} -}
@ -221,6 +259,7 @@ union (Hashdict h1) hd2 =
, values = Dict.union h1.values h2.values , values = Dict.union h1.values h2.values
} }
{-| Get all values stored in the hashdict, in the order of their keys. {-| Get all values stored in the hashdict, in the order of their keys.
-} -}
values : Hashdict a -> List a values : Hashdict a -> List a

View File

@ -1,4 +1,11 @@
module Internal.Tools.Iddict exposing (Iddict, decoder, empty, encode, get, insert, isEmpty, keys, map, member, remove, singleton, size, values) module Internal.Tools.Iddict exposing
( Iddict
, empty, singleton, insert, map, remove
, isEmpty, member, get, size
, keys, values
, encode, decoder
)
{-| The id-dict is a data type that lets us store values in a dictionary using {-| The id-dict is a data type that lets us store values in a dictionary using
unique identifiers. This can be used as a dictionary where the keys do not unique identifiers. This can be used as a dictionary where the keys do not
matter. matter.
@ -6,31 +13,38 @@ matter.
The benefit of the iddict is that it generates the keys FOR you. This way, you The benefit of the iddict is that it generates the keys FOR you. This way, you
do not need to generate identifiers yourself. do not need to generate identifiers yourself.
## Id-dict ## Id-dict
@docs Iddict @docs Iddict
## Build ## Build
@docs empty, singleton, insert, map, remove @docs empty, singleton, insert, map, remove
## Query ## Query
@docs isEmpty, member, get, size @docs isEmpty, member, get, size
## Lists ## Lists
@docs keys, values @docs keys, values
## JSON coders ## JSON coders
@docs encode, decoder @docs encode, decoder
-} -}
import FastDict as Dict exposing (Dict) import FastDict as Dict exposing (Dict)
import Json.Decode as D import Json.Decode as D
import Json.Encode as E import Json.Encode as E
{-| The Iddict data type. {-| The Iddict data type.
-} -}
type Iddict a type Iddict a
@ -39,6 +53,7 @@ type Iddict a
, dict : Dict Int a , dict : Dict Int a
} }
{-| Decode an id-dict from a JSON value. {-| Decode an id-dict from a JSON value.
-} -}
decoder : D.Decoder a -> D.Decoder (Iddict a) decoder : D.Decoder a -> D.Decoder (Iddict a)
@ -50,27 +65,32 @@ decoder xDecoder =
dict = dict =
pairs pairs
|> List.filterMap |> List.filterMap
(\(k, v) -> (\( k, v ) ->
k k
|> String.toInt |> String.toInt
|> Maybe.map (\n -> (n, v)) |> Maybe.map (\n -> ( n, v ))
) )
|> Dict.fromList |> Dict.fromList
in in
Iddict Iddict
{ cursor = { cursor =
Dict.keys dict -- Larger than all values in the list Dict.keys dict
|> List.map ((+) 1) -- Larger than all values in the list
|> List.maximum |> List.map ((+) 1)
|> Maybe.withDefault 0 |> List.maximum
|> max (Dict.size dict) -- At least the dict size |> Maybe.withDefault 0
|> max c -- At least the given value |> max (Dict.size dict)
, dict = dict -- At least the dict size
} |> max c
-- At least the given value
, dict = dict
}
) )
(D.field "cursor" D.int) (D.field "cursor" D.int)
(D.field "dict" <| D.keyValuePairs xDecoder) (D.field "dict" <| D.keyValuePairs xDecoder)
{-| Create an empty id-dict. {-| Create an empty id-dict.
-} -}
empty : Iddict a empty : Iddict a
@ -80,25 +100,28 @@ empty =
, dict = Dict.empty , dict = Dict.empty
} }
{-| Encode an id-dict to a JSON value. {-| Encode an id-dict to a JSON value.
-} -}
encode : (a -> E.Value) -> Iddict a -> E.Value encode : (a -> E.Value) -> Iddict a -> E.Value
encode encodeX (Iddict d) = encode encodeX (Iddict d) =
E.object E.object
[ ( "cursor", E.int d.cursor ) [ ( "cursor", E.int d.cursor )
, ( "dict", , ( "dict"
d.dict , d.dict
|> Dict.toCoreDict |> Dict.toCoreDict
|> E.dict String.fromInt encodeX |> E.dict String.fromInt encodeX
) )
] ]
{-| Get a value from the id-dict using its key. {-| Get a value from the id-dict using its key.
-} -}
get : Int -> Iddict a -> Maybe a get : Int -> Iddict a -> Maybe a
get k (Iddict { dict }) = get k (Iddict { dict }) =
Dict.get k dict Dict.get k dict
{-| Insert a new value into the id-dict. Given that the id-dict generates its {-| Insert a new value into the id-dict. Given that the id-dict generates its
key, the function returns both the updated id-dict as the newly generated key. key, the function returns both the updated id-dict as the newly generated key.
@ -107,25 +130,29 @@ key, the function returns both the updated id-dict as the newly generated key.
case x of case x of
( _, iddict ) -> ( _, iddict ) ->
get 0 iddict -- Just "hello" get 0 iddict -- Just "hello"
-} -}
insert : a -> Iddict a -> (Int, Iddict a) insert : a -> Iddict a -> ( Int, Iddict a )
insert v (Iddict d) = insert v (Iddict d) =
( d.cursor ( d.cursor
, Iddict { cursor = d.cursor + 1, dict = Dict.insert d.cursor v d.dict } , Iddict { cursor = d.cursor + 1, dict = Dict.insert d.cursor v d.dict }
) )
{-| Determine if an id-dict is empty. {-| Determine if an id-dict is empty.
-} -}
isEmpty : Iddict a -> Bool isEmpty : Iddict a -> Bool
isEmpty (Iddict d) = isEmpty (Iddict d) =
Dict.isEmpty d.dict Dict.isEmpty d.dict
{-| Get all of the keys from the id-dict, sorted from lowest to highest. {-| Get all of the keys from the id-dict, sorted from lowest to highest.
-} -}
keys : Iddict a -> List Int keys : Iddict a -> List Int
keys (Iddict { dict }) = keys (Iddict { dict }) =
Dict.keys dict Dict.keys dict
{-| Map an existing value at a given key, if it exists. If it does not exist, {-| Map an existing value at a given key, if it exists. If it does not exist,
the operation does nothing. the operation does nothing.
-} -}
@ -133,12 +160,14 @@ map : Int -> (a -> a) -> Iddict a -> Iddict a
map k f (Iddict d) = map k f (Iddict d) =
Iddict { d | dict = Dict.update k (Maybe.map f) d.dict } Iddict { d | dict = Dict.update k (Maybe.map f) d.dict }
{-| Determine if a key is in an id-dict. {-| Determine if a key is in an id-dict.
-} -}
member : Int -> Iddict a -> Bool member : Int -> Iddict a -> Bool
member k (Iddict d) = member k (Iddict d) =
k < d.cursor && Dict.member k d.dict k < d.cursor && Dict.member k d.dict
{-| Remove a key-value pair from the id-dict. If the key is not found, no {-| Remove a key-value pair from the id-dict. If the key is not found, no
changes are made. changes are made.
-} -}
@ -146,10 +175,13 @@ remove : Int -> Iddict a -> Iddict a
remove k (Iddict d) = remove k (Iddict d) =
Iddict { d | dict = Dict.remove k d.dict } Iddict { d | dict = Dict.remove k d.dict }
{-| Create an id-dict with a single value. {-| Create an id-dict with a single value.
-} -}
singleton : a -> (Int, Iddict a) singleton : a -> ( Int, Iddict a )
singleton v = insert v empty singleton v =
insert v empty
{-| Determine the number of key-value pairs in the id-dict. {-| Determine the number of key-value pairs in the id-dict.
-} -}
@ -157,6 +189,7 @@ size : Iddict a -> Int
size (Iddict d) = size (Iddict d) =
Dict.size d.dict Dict.size d.dict
{-| Get all of the values from an id-dict, in the order of their keys. {-| Get all of the values from an id-dict, in the order of their keys.
-} -}
values : Iddict a -> List a values : Iddict a -> List a