├── .gitignore ├── tests └── elm-verify-examples.json ├── package.json ├── elm.json ├── README.md ├── LICENSE ├── CHANGELOG.md └── src └── Dict └── Extra.elm /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | node_modules/ 4 | elm-stuff/ 5 | *~ 6 | -------------------------------------------------------------------------------- /tests/elm-verify-examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "../src", 3 | "tests": [ "Dict.Extra" ] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "test": "elm-verify-examples" 4 | }, 5 | "devDependencies": { 6 | "elm-test": "^0.19.0-rev6", 7 | "elm-verify-examples": "^3.1.0" 8 | }, 9 | "dependencies": {} 10 | } 11 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "elm-community/dict-extra", 4 | "summary": "Convenience functions for working with Dict", 5 | "license": "MIT", 6 | "version": "2.4.0", 7 | "exposed-modules": [ 8 | "Dict.Extra" 9 | ], 10 | "elm-version": "0.19.0 <= v < 0.20.0", 11 | "dependencies": { 12 | "elm/core": "1.0.0 <= v < 2.0.0" 13 | }, 14 | "test-dependencies": { 15 | "elm-explorations/test": "1.2.1 <= v < 2.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dict Extra 2 | 3 | Convenience functions for working with Dict. You can treat the functions in this library as an extension to the core module, by using the following import statement: 4 | 5 | ``` 6 | import Dict.Extra as Dict 7 | ``` 8 | 9 | Feedback and contributions are very welcome. 10 | 11 | ## Writing tests 12 | 13 | Tests are created using [`elm-verify-examples`](https://github.com/stoeffel/elm-verify-examples). 14 | 15 | ## Running the tests 16 | 17 | - `$ npm install` 18 | - `$ npm test` 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 CircuitHub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2.4 4 | 5 | * Added `any` function. 6 | 7 | ## 2.3 8 | 9 | * `frequencies` counts number of elements in a `List Comparable`. 10 | 11 | ## 2.2 12 | 13 | * `filterGroupBy` is a version of `groupBy` that works similar to `List.filterMap`. 14 | 15 | ## 2.1 16 | 17 | * `insertDedupe` works like `insert`, but handles the case of duplicate keys. 18 | * `fromListDedupe` works like `fromList`, but handles the case of duplicate keys. 19 | * `fromListDedupeBy` works like `fromListBy`, but handles the case of duplicate keys. 20 | 21 | ## 2.0 22 | 23 | * `mapKeys` now has a more liberal type signature. Apparently, this is a mayor change. 24 | 25 | ## 1.5 26 | 27 | * Added `find` function. 28 | 29 | ## 1.4 30 | 31 | * Added `filterMap` function. 32 | * Added `invert` function. 33 | * Improved documentation. 34 | 35 | ## 1.3.2 36 | 37 | * `groupBy` now groups elements by order in list. 38 | 39 | ## 1.3.1 40 | 41 | * Support for Elm 0.18 42 | 43 | ## 1.3.0 44 | 45 | * Add `mapKeys` function. 46 | 47 | ## 1.2.0 48 | 49 | * Add `fromListBy` function. 50 | * Improved documentation. 51 | 52 | ## 1.1.0 53 | 54 | * Add `keepOnly` function. 55 | * Add `removeMany` function. 56 | 57 | ## 1.0.1 58 | 59 | * Optimized `groupBy` function. 60 | 61 | ## 1.0.0 62 | 63 | * Initial release. 64 | -------------------------------------------------------------------------------- /src/Dict/Extra.elm: -------------------------------------------------------------------------------- 1 | module Dict.Extra exposing 2 | ( groupBy, filterGroupBy, fromListBy, fromListDedupe, fromListDedupeBy, frequencies 3 | , removeWhen, removeMany, keepOnly, insertDedupe, mapKeys, filterMap, invert 4 | , any, find 5 | ) 6 | 7 | {-| Convenience functions for working with `Dict` 8 | 9 | 10 | # List operations 11 | 12 | @docs groupBy, filterGroupBy, fromListBy, fromListDedupe, fromListDedupeBy, frequencies 13 | 14 | 15 | # Manipulation 16 | 17 | @docs removeWhen, removeMany, keepOnly, insertDedupe, mapKeys, filterMap, invert 18 | 19 | 20 | # Utilities 21 | 22 | @docs any, find 23 | 24 | -} 25 | 26 | import Dict exposing (Dict) 27 | import Set exposing (Set) 28 | 29 | 30 | {-| Takes a key-fn and a list. 31 | Creates a `Dict` which maps the key to a list of matching elements. 32 | 33 | import Dict 34 | 35 | groupBy String.length [ "tree" , "apple" , "leaf" ] 36 | --> Dict.fromList [ ( 4, [ "tree", "leaf" ] ), ( 5, [ "apple" ] ) ] 37 | 38 | -} 39 | groupBy : (a -> comparable) -> List a -> Dict comparable (List a) 40 | groupBy keyfn list = 41 | List.foldr 42 | (\x acc -> 43 | Dict.update (keyfn x) (Maybe.map ((::) x) >> Maybe.withDefault [ x ] >> Just) acc 44 | ) 45 | Dict.empty 46 | list 47 | 48 | 49 | {-| Takes a key-fn and a list. 50 | Creates a `Dict` which maps the key to a list of matching elements, skipping elements 51 | where key-fn returns `Nothing` 52 | 53 | import Dict 54 | 55 | filterGroupBy (String.uncons >> Maybe.map Tuple.first) [ "tree" , "", "tweet", "apple" , "leaf", "" ] 56 | --> Dict.fromList [ ( 't', [ "tree", "tweet" ] ), ( 'a', [ "apple" ] ), ( 'l', [ "leaf" ] ) ] 57 | 58 | filterGroupBy 59 | .car 60 | [ { name = "Mary" 61 | , car = Just "Ford" 62 | } 63 | , { name = "Jack" 64 | , car = Nothing 65 | } 66 | , { name = "Jill" 67 | , car = Just "Tesla" 68 | } 69 | , { name = "John" 70 | , car = Just "Tesla" 71 | } 72 | ] 73 | --> Dict.fromList 74 | --> [ ( "Ford" 75 | --> , [ { name = "Mary" , car = Just "Ford" } ] 76 | --> ) 77 | --> , ( "Tesla" 78 | --> , [ { name = "Jill" , car = Just "Tesla" } 79 | --> , { name = "John" , car = Just "Tesla" } 80 | --> ] 81 | --> ) 82 | --> ] 83 | 84 | -} 85 | filterGroupBy : (a -> Maybe comparable) -> List a -> Dict comparable (List a) 86 | filterGroupBy keyfn list = 87 | List.foldr 88 | (\x acc -> 89 | case keyfn x of 90 | Just key -> 91 | Dict.update key (Maybe.map ((::) x) >> Maybe.withDefault [ x ] >> Just) acc 92 | 93 | Nothing -> 94 | acc 95 | ) 96 | Dict.empty 97 | list 98 | 99 | 100 | {-| Create a dictionary from a list of values, by passing a function that can get a key from any such value. 101 | If the function does not return unique keys, earlier values are discarded. 102 | 103 | import Dict 104 | 105 | fromListBy String.length [ "tree" , "apple" , "leaf" ] 106 | --> Dict.fromList [ ( 4, "leaf" ), ( 5, "apple" ) ] 107 | 108 | -} 109 | fromListBy : (a -> comparable) -> List a -> Dict comparable a 110 | fromListBy keyfn xs = 111 | List.foldl 112 | (\x acc -> Dict.insert (keyfn x) x acc) 113 | Dict.empty 114 | xs 115 | 116 | 117 | {-| Like `Dict.fromList`, but you provide a way to deal with 118 | duplicate keys. Create a dictionary from a list of pairs of keys and 119 | values, providing a function that is used to combine multiple values 120 | paired with the same key. 121 | 122 | import Dict 123 | 124 | fromListDedupe 125 | (\a b -> a ++ " " ++ b) 126 | [ ( "class", "menu" ), ( "width", "100%" ), ( "class", "big" ) ] 127 | --> Dict.fromList [ ( "class", "menu big" ), ( "width", "100%" ) ] 128 | 129 | -} 130 | fromListDedupe : (a -> a -> a) -> List ( comparable, a ) -> Dict comparable a 131 | fromListDedupe combine xs = 132 | List.foldl 133 | (\( key, value ) acc -> insertDedupe combine key value acc) 134 | Dict.empty 135 | xs 136 | 137 | 138 | {-| `fromListBy` and `fromListDedupe` rolled into one. 139 | 140 | import Dict 141 | 142 | fromListDedupeBy (\first second -> first) String.length [ "tree" , "apple" , "leaf" ] 143 | --> Dict.fromList [ ( 4, "tree" ), ( 5, "apple" ) ] 144 | 145 | -} 146 | fromListDedupeBy : (a -> a -> a) -> (a -> comparable) -> List a -> Dict comparable a 147 | fromListDedupeBy combine keyfn xs = 148 | List.foldl 149 | (\x acc -> insertDedupe combine (keyfn x) x acc) 150 | Dict.empty 151 | xs 152 | 153 | 154 | {-| Count the number of occurences for each of the elements in the list. 155 | 156 | import Dict 157 | 158 | frequencies [ "A", "B", "C", "B", "C", "B" ] 159 | --> Dict.fromList [ ( "A", 1 ), ( "B", 3 ), ( "C", 2 ) ] 160 | 161 | -} 162 | frequencies : List comparable -> Dict comparable Int 163 | frequencies list = 164 | list 165 | |> List.foldl 166 | (\el counter -> 167 | Dict.get el counter 168 | |> Maybe.withDefault 0 169 | |> (\count -> count + 1) 170 | |> (\count -> Dict.insert el count counter) 171 | ) 172 | Dict.empty 173 | 174 | 175 | {-| Remove elements which satisfies the predicate. 176 | 177 | import Dict 178 | 179 | Dict.fromList [ ( "Mary", 1 ), ( "Jack", 2 ), ( "Jill", 1 ) ] 180 | |> removeWhen (\_ value -> value == 1 ) 181 | --> Dict.fromList [ ( "Jack", 2 ) ] 182 | 183 | -} 184 | removeWhen : (comparable -> v -> Bool) -> Dict comparable v -> Dict comparable v 185 | removeWhen pred dict = 186 | Dict.filter (\k v -> not (pred k v)) dict 187 | 188 | 189 | {-| Remove a key-value pair if its key appears in the set. 190 | 191 | import Dict 192 | import Set 193 | 194 | Dict.fromList [ ( "Mary", 1 ), ( "Jack", 2 ), ( "Jill", 1 ) ] 195 | |> removeMany (Set.fromList [ "Mary", "Jill" ]) 196 | --> Dict.fromList [ ( "Jack", 2 ) ] 197 | 198 | -} 199 | removeMany : Set comparable -> Dict comparable v -> Dict comparable v 200 | removeMany set dict = 201 | Set.foldl Dict.remove dict set 202 | 203 | 204 | {-| Insert an element at the given key, providing a combining 205 | function that used in the case that there is already an 206 | element at that key. The combining function is called with 207 | original element and the new element as arguments and 208 | returns the element to be inserted. 209 | 210 | import Dict 211 | 212 | Dict.fromList [ ( "expenses", 38.25 ), ( "assets", 100.85 ) ] 213 | |> insertDedupe (+) "expenses" 2.50 214 | |> insertDedupe (+) "liabilities" -2.50 215 | --> Dict.fromList [ ( "expenses", 40.75 ), ( "assets", 100.85 ), ( "liabilities", -2.50 ) ] 216 | 217 | -} 218 | insertDedupe : (v -> v -> v) -> comparable -> v -> Dict comparable v -> Dict comparable v 219 | insertDedupe combine key value dict = 220 | let 221 | with mbValue = 222 | case mbValue of 223 | Just oldValue -> 224 | Just <| combine oldValue value 225 | 226 | Nothing -> 227 | Just value 228 | in 229 | Dict.update key with dict 230 | 231 | 232 | {-| Keep a key-value pair if its key appears in the set. 233 | 234 | import Dict 235 | import Set 236 | 237 | Dict.fromList [ ( "Mary", 1 ), ( "Jack", 2 ), ( "Jill", 1 ) ] 238 | |> keepOnly (Set.fromList [ "Jack", "Jill" ]) 239 | --> Dict.fromList [ ( "Jack", 2 ), ( "Jill", 1 ) ] 240 | 241 | -} 242 | keepOnly : Set comparable -> Dict comparable v -> Dict comparable v 243 | keepOnly set dict = 244 | Set.foldl 245 | (\k acc -> 246 | Maybe.withDefault acc <| Maybe.map (\v -> Dict.insert k v acc) (Dict.get k dict) 247 | ) 248 | Dict.empty 249 | set 250 | 251 | 252 | {-| Apply a function to all keys in a dictionary. 253 | 254 | import Dict 255 | 256 | Dict.fromList [ ( 5, "Jack" ), ( 10, "Jill" ) ] 257 | |> mapKeys (\x -> x + 1) 258 | --> Dict.fromList [ ( 6, "Jack" ), ( 11, "Jill" ) ] 259 | 260 | Dict.fromList [ ( 5, "Jack" ), ( 10, "Jill" ) ] 261 | |> mapKeys String.fromInt 262 | --> Dict.fromList [ ( "5", "Jack" ), ( "10", "Jill" ) ] 263 | 264 | -} 265 | mapKeys : (comparable -> comparable1) -> Dict comparable v -> Dict comparable1 v 266 | mapKeys keyMapper dict = 267 | Dict.foldl 268 | (\k v acc -> 269 | Dict.insert (keyMapper k) v acc 270 | ) 271 | Dict.empty 272 | dict 273 | 274 | 275 | {-| Apply a function that may or may not succeed to all entries in a dictionary, 276 | but only keep the successes. 277 | 278 | import Dict 279 | 280 | let 281 | isTeen n a = 282 | if 13 <= n && n <= 19 then 283 | Just <| String.toUpper a 284 | else 285 | Nothing 286 | in 287 | Dict.fromList [ ( 5, "Jack" ), ( 15, "Jill" ), ( 20, "Jones" ) ] 288 | |> filterMap isTeen 289 | --> Dict.fromList [ ( 15, "JILL" ) ] 290 | 291 | -} 292 | filterMap : (comparable -> a -> Maybe b) -> Dict comparable a -> Dict comparable b 293 | filterMap f dict = 294 | Dict.foldl 295 | (\k v acc -> 296 | case f k v of 297 | Just newVal -> 298 | Dict.insert k newVal acc 299 | 300 | Nothing -> 301 | acc 302 | ) 303 | Dict.empty 304 | dict 305 | 306 | 307 | {-| Inverts the keys and values of an array. 308 | 309 | import Dict 310 | 311 | Dict.fromList [ ("key", "value") ] 312 | |> invert 313 | --> Dict.fromList [ ( "value", "key" ) ] 314 | 315 | -} 316 | invert : Dict comparable1 comparable2 -> Dict comparable2 comparable1 317 | invert dict = 318 | Dict.foldl 319 | (\k v acc -> 320 | Dict.insert v k acc 321 | ) 322 | Dict.empty 323 | dict 324 | 325 | 326 | {-| Determine if any key/value pair satisfies some test. 327 | 328 | import Dict 329 | 330 | Dict.fromList [ ( 9, "Jill" ), ( 7, "Jill" ) ] 331 | |> any (\_ value -> value == "Jill") 332 | --> True 333 | 334 | Dict.fromList [ ( 9, "Jill" ), ( 7, "Jill" ) ] 335 | |> any (\key _ -> key == 5) 336 | --> False 337 | 338 | -} 339 | any : (comparable -> a -> Bool) -> Dict comparable a -> Bool 340 | any predicate dict = 341 | Dict.foldl 342 | (\k v acc -> 343 | if acc then 344 | acc 345 | 346 | else if predicate k v then 347 | True 348 | 349 | else 350 | False 351 | ) 352 | False 353 | dict 354 | 355 | 356 | {-| Find the first key/value pair that matches a predicate. 357 | 358 | import Dict 359 | 360 | Dict.fromList [ ( 9, "Jill" ), ( 7, "Jill" ) ] 361 | |> find (\_ value -> value == "Jill") 362 | --> Just ( 7, "Jill" ) 363 | 364 | Dict.fromList [ ( 9, "Jill" ), ( 7, "Jill" ) ] 365 | |> find (\key _ -> key == 5) 366 | --> Nothing 367 | 368 | -} 369 | find : (comparable -> a -> Bool) -> Dict comparable a -> Maybe ( comparable, a ) 370 | find predicate dict = 371 | Dict.foldl 372 | (\k v acc -> 373 | case acc of 374 | Just _ -> 375 | acc 376 | 377 | Nothing -> 378 | if predicate k v then 379 | Just ( k, v ) 380 | 381 | else 382 | Nothing 383 | ) 384 | Nothing 385 | dict 386 | --------------------------------------------------------------------------------