├── tests ├── elm-verify-examples.json └── Test │ ├── UniqueList.elm │ └── AssocList.elm ├── .travis.yml ├── .gitignore ├── package.json ├── elm.json ├── examples ├── elm.json ├── Disney.elm └── AllFuncs.elm ├── Performance.md ├── LICENSE ├── README.md ├── src ├── UniqueList.elm └── AssocList.elm ├── docs.json └── assoc-list.optimized.js /tests/elm-verify-examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": "../src", 3 | "tests": ["AssocList", "UniqueList", "./README.md"] 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: "12" 3 | 4 | cache: 5 | npm: true 6 | directories: 7 | - $HOME/.elm 8 | - elm-stuff 9 | - tests/elm-stuff 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # elm-package generated files 2 | elm-stuff 3 | # elm-repl generated files 4 | repl-temp-* 5 | # stoeffel/elm-verify-examples 6 | tests/VerifyExamples 7 | # CI dependency directories 8 | node_modules/ 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "assoc-list", 3 | "version": "1.0.0", 4 | "scripts": { 5 | "test": "elm-test && elm-verify-examples --run-tests && elm-format --validate ." 6 | }, 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/pzp1997/assoc-list.git" 10 | }, 11 | "license": "BSD-3-Clause", 12 | "dependencies": { 13 | "elm": "^0.19.1-3", 14 | "elm-format": "^0.8.2", 15 | "elm-test": "^0.19.1-revision2", 16 | "elm-verify-examples": "^5.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "package", 3 | "name": "pzp1997/assoc-list", 4 | "summary": "Dictionary with custom keys implemented using association lists", 5 | "license": "BSD-3-Clause", 6 | "version": "1.0.0", 7 | "exposed-modules": [ 8 | "AssocList" 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.0.0 <= v < 2.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/elm.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "application", 3 | "source-directories": [ 4 | ".", 5 | "../src/" 6 | ], 7 | "elm-version": "0.19.0", 8 | "dependencies": { 9 | "direct": { 10 | "elm/browser": "1.0.1", 11 | "elm/core": "1.0.2", 12 | "elm/html": "1.0.0" 13 | }, 14 | "indirect": { 15 | "elm/json": "1.1.2", 16 | "elm/time": "1.0.0", 17 | "elm/url": "1.0.0", 18 | "elm/virtual-dom": "1.0.2" 19 | } 20 | }, 21 | "test-dependencies": { 22 | "direct": {}, 23 | "indirect": {} 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /examples/Disney.elm: -------------------------------------------------------------------------------- 1 | module Main exposing (main) 2 | 3 | import AssocList as Dict exposing (Dict) 4 | import Html exposing (Html) 5 | 6 | 7 | type Character 8 | = Ariel 9 | | Simba 10 | | Mufasa 11 | | Woody 12 | 13 | 14 | type Movie 15 | = LittleMermaid 16 | | LionKing 17 | | ToyStory 18 | 19 | 20 | characterToMovie : Dict Character Movie 21 | characterToMovie = 22 | Dict.fromList 23 | [ ( Ariel, LittleMermaid ) 24 | , ( Simba, LionKing ) 25 | , ( Mufasa, LionKing ) 26 | , ( Woody, ToyStory ) 27 | ] 28 | 29 | 30 | main : Html msg 31 | main = 32 | Html.text <| 33 | case Dict.get Simba characterToMovie of 34 | Just LionKing -> 35 | "Simba was in The Lion King" 36 | 37 | Just _ -> 38 | "Simba was not in The Lion King" 39 | 40 | Nothing -> 41 | "I am not sure who Simba is" 42 | -------------------------------------------------------------------------------- /Performance.md: -------------------------------------------------------------------------------- 1 | | | Function | AssocList | elm/core | 2 | |-----------|-----------|----------------|------------------| 3 | | Build | empty | O(1) | O(1) | 4 | | | singleton | O(1) | O(1) | 5 | | | insert | O(n) | O(log n) | 6 | | | update | O(n) | O(log n) | 7 | | | remove | O(n) | O(log n) | 8 | | Query | isEmpty | O(1) | O(1) | 9 | | | member | O(n) | O(log n) | 10 | | | get | O(n) | O(log n) | 11 | | | size | O(n) | O(n) | 12 | | Lists | keys | O(n) | O(n) | 13 | | | values | O(n) | O(n) | 14 | | | toList | O(1) | O(n) | 15 | | | fromList | O(n * n) | O(n log n) | 16 | | Transform | map | O(n) | O(n) | 17 | | | foldl | O(n) | O(n) | 18 | | | foldr | O(n) | O(n) | 19 | | | filter | O(n) | O(n) | 20 | | | partition | O(n) | O(n) | 21 | | Combine | union | O(n * (n + m)) | O(n log (n + m)) | 22 | | | intersect | O(n * m) | O(n log m) | 23 | | | diff | O(n * m) | O(m log n) | 24 | | | merge | O(n * m) | O(n + m) | 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Palmer Paul 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /examples/AllFuncs.elm: -------------------------------------------------------------------------------- 1 | module Basic exposing (main, testCondition, testDict, testEquality) 2 | 3 | import AssocList as Dict exposing (Dict) 4 | 5 | 6 | testDict : Dict String Int 7 | testDict = 8 | let 9 | leftDict = 10 | Dict.update "a" (always (Just 4)) Dict.empty 11 | 12 | rightDict = 13 | Dict.remove "a" (Dict.insert "b" 2 (Dict.singleton "a" 1)) 14 | in 15 | Dict.map (\_ v -> v + 1) 16 | (Dict.union 17 | (Dict.intersect leftDict rightDict) 18 | (Dict.diff leftDict rightDict) 19 | ) 20 | 21 | 22 | testCondition : Int 23 | testCondition = 24 | if Dict.member "b" testDict then 25 | case Dict.get "b" testDict of 26 | Just x -> 27 | x 28 | 29 | Nothing -> 30 | 0 31 | 32 | else 33 | Dict.size (Tuple.second (Dict.partition (\_ _ -> False) testDict)) 34 | 35 | 36 | testEquality : List String 37 | testEquality = 38 | if Dict.eq (Dict.fromList []) testDict then 39 | Dict.keys (Dict.filter (\_ _ -> True) testDict) 40 | 41 | else if testCondition == 0 then 42 | List.map Tuple.first (Dict.toList testDict) 43 | 44 | else if Dict.isEmpty testDict then 45 | Dict.foldl (\k _ _ -> [ k ]) [] testDict 46 | 47 | else 48 | Dict.foldr (\k _ _ -> [ k ]) [] testDict 49 | 50 | 51 | main = 52 | Platform.worker 53 | { init = \() -> ( testEquality, Cmd.none ) 54 | , update = 55 | \_ _ -> 56 | ( List.map String.fromInt <| 57 | Dict.values 58 | (Dict.merge 59 | Dict.insert 60 | (\k v _ result -> Dict.insert k v result) 61 | Dict.insert 62 | Dict.empty 63 | testDict 64 | testDict 65 | ) 66 | , Cmd.none 67 | ) 68 | , subscriptions = \_ -> Sub.none 69 | } 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Association lists 2 | 3 | [![Build Status](https://travis-ci.com/pzp1997/assoc-list.svg?branch=master)](https://travis-ci.com/pzp1997/assoc-list) 4 | 5 | An [association list](https://en.wikipedia.org/wiki/Association_list) is a list of tuples that map unique keys to values. The keys can be of any type (so long as it has a reasonable definition for equality). This includes pretty much everything except for functions and things that contain functions. 6 | 7 | ## Usage 8 | 9 | This library is intended to be used as a drop-in replacement for the `Dict` module in elm/core. You might use it like so, 10 | 11 | ```elm 12 | import AssocList as Dict exposing (Dict) 13 | 14 | type Character 15 | = Ariel 16 | | Simba 17 | | Mufasa 18 | | Woody 19 | 20 | type Movie 21 | = LittleMermaid 22 | | LionKing 23 | | ToyStory 24 | 25 | characterToMovie : Dict Character Movie 26 | characterToMovie = 27 | Dict.fromList 28 | [ ( Ariel, LittleMermaid ) 29 | , ( Simba, LionKing ) 30 | , ( Mufasa, LionKing ) 31 | , ( Woody, ToyStory ) 32 | ] 33 | 34 | Dict.get Simba characterToMovie --> Just LionKing 35 | ``` 36 | 37 | (Note the use of a custom type as the dictionary key, which is not possible with the `Dict` module in elm/core!) 38 | 39 | ## Performance 40 | 41 | Since this library does not require your keys to be `comparable`, some operations are asymptotically slower than those in the `Dict` module in elm/core. The good news is that if you are working with small-ish dictionaries, this is likely not a problem. Furthermore, the bottleneck point in most Elm programs is DOM manipulation, so slower data structure operations are unlikely to cause a noticeable difference in how your app performs. For a detailed comparison of the performance characteristics of the two implementations, see [Performance.md](https://github.com/pzp1997/assoc-list/blob/master/Performance.md). 42 | 43 | ## Comparison to existing work 44 | 45 | ### Dictionary with non-comparable keys 46 | 47 | All the existing libraries that I have found that attempt to solve the dictionary with non-comparable keys problem suffer from at least one of the following issues: 48 | 49 | 1. stores a function for converting keys to `comparable` within the data structure itself 50 | 51 | - can cause runtime errors should you ever use the `==` operator to compare the structures 52 | - makes serialization trickier (for this reason, conventional wisdom states that you should "never put functions in your `Model` or `Msg` types") 53 | - see this [Discourse post](https://discourse.elm-lang.org/t/consequences-of-functions-in-the-model-with-0-19) and this [Elm Discuss thread](https://groups.google.com/forum/#!topic/elm-discuss/bOAHwSnklLc) for more information 54 | 55 | 2. does not provide full type-level safety against operating on two dictionaries with different comparators, e.g. `union (singleton identity 0 'a') (singleton (\x -> x + 1) 1 'b')` 56 | 57 | Here is a detailed analysis of all the relevant libraries I could find: 58 | 59 | turboMaCk/any-dict 60 | 61 | - suffers from problems (1) and (2) 62 | 63 | rtfeldman/elm-sorter-experiment 64 | 65 | - suffers from problem (1) 66 | 67 | jjant/elm-dict 68 | 69 | - similar to problem (1), the data structure itself is actually a function 70 | - does not support the entire `Dict` API from elm/core 71 | 72 | eeue56/elm-all-dict 73 | 74 | - suffers from problems (1) and (2) 75 | - some parts of the library rely on Kernel code, making it non-trivial to update to 0.19 76 | 77 | robertjlooby/elm-generic-dict 78 | 79 | - suffers from problem (1) 80 | - has not been updated to 0.19 as of time of writing 81 | 82 | ### Ordered dictionary 83 | 84 | Although not the primary problem that this library aims to solve, assoc-list can also be used as an ordered dictionary, i.e. a dictionary that keeps track of the order in which entries were inserted. This functionality is similar to the following libraries: 85 | 86 | y0hy0h/ordered-containers 87 | 88 | - requires the keys to be `comparable` 89 | 90 | wittjosiah/elm-ordered-dict 91 | 92 | - requires the keys to be `comparable` 93 | - has not been updated to 0.19 as of time of writing 94 | 95 | rnons/ordered-containers 96 | 97 | - requires the keys to be `comparable` 98 | - has not been updated to 0.19 as of time of writing 99 | 100 | eliaslfox/orderedmap 101 | 102 | - requires the keys to be `comparable` 103 | - has not been updated to 0.19 as of time of writing 104 | -------------------------------------------------------------------------------- /src/UniqueList.elm: -------------------------------------------------------------------------------- 1 | module UniqueList exposing 2 | ( Set 3 | , empty, singleton, insert, remove 4 | , isEmpty, member, size 5 | , union, intersect, diff 6 | , toList, fromList 7 | , map, foldl, foldr, filter, partition 8 | ) 9 | 10 | {-| A unique list is a list of values with the guarantee that no value will be 11 | duplicated. The values can be of any type (so long as it has a reasonable 12 | definition for equality). This includes pretty much everything except for 13 | functions and things that contain functions. 14 | 15 | All functions in this module are "stack safe," which means that your program 16 | won't crash from recursing over large association lists. You can read 17 | Evan Czaplicki's 18 | [document on tail-call elimination](https://github.com/evancz/functional-programming-in-elm/blob/master/recursion/tail-call-elimination.md) 19 | for more information about this topic. 20 | 21 | 22 | # Sets 23 | 24 | @docs Set 25 | 26 | 27 | # Build 28 | 29 | @docs empty, singleton, insert, remove 30 | 31 | 32 | # Query 33 | 34 | @docs isEmpty, member, size 35 | 36 | 37 | # Combine 38 | 39 | @docs union, intersect, diff 40 | 41 | 42 | # Lists 43 | 44 | @docs toList, fromList 45 | 46 | 47 | # Transform 48 | 49 | @docs map, foldl, foldr, filter, partition 50 | 51 | -} 52 | 53 | import Basics exposing (Bool, Int) 54 | import Dict 55 | import List exposing ((::)) 56 | import Maybe exposing (Maybe(..)) 57 | 58 | 59 | {-| Represents a set of unique values. So `(Set Int)` is a set of integers and 60 | `(Set String)` is a set of strings. 61 | -} 62 | type Set t 63 | = S (List t) 64 | 65 | 66 | {-| Create an empty set. 67 | -} 68 | empty : Set a 69 | empty = 70 | S [] 71 | 72 | 73 | {-| Create a set with one value. 74 | -} 75 | singleton : a -> Set a 76 | singleton key = 77 | S [ key ] 78 | 79 | 80 | {-| Insert a value into a set. 81 | -} 82 | insert : a -> Set a -> Set a 83 | insert key set = 84 | let 85 | (S alteredList) = 86 | remove key set 87 | in 88 | S (key :: alteredList) 89 | 90 | 91 | {-| Remove a value from a set. If the value is not found, no changes are made. 92 | -} 93 | remove : a -> Set a -> Set a 94 | remove targetKey (S uniqueList) = 95 | S (List.filter (\key -> key /= targetKey) uniqueList) 96 | 97 | 98 | {-| Determine if a set is empty. 99 | -} 100 | isEmpty : Set a -> Bool 101 | isEmpty set = 102 | set == S [] 103 | 104 | 105 | {-| Determine if a value is in a set. 106 | -} 107 | member : a -> Set a -> Bool 108 | member key (S uniqueList) = 109 | List.member key uniqueList 110 | 111 | 112 | {-| Determine the number of elements in a set. 113 | -} 114 | size : Set a -> Int 115 | size (S uniqueList) = 116 | List.length uniqueList 117 | 118 | 119 | {-| Get the union of two sets. Keep all values. 120 | 121 | If you are using this module as an ordered set, the ordering of the output set 122 | will be all the entries of the first set (from most recently inserted to least 123 | recently inserted) followed by all the entries of the second set that are not 124 | in the first set (from most recently inserted to least recently inserted). 125 | 126 | toList (union (fromList [ "Bob" ]) (fromList [ "Alice", "Bob" ])) 127 | --> [ "Bob", "Alice" ] 128 | 129 | -} 130 | union : Set a -> Set a -> Set a 131 | union (S uniqueList) set = 132 | List.foldr insert set uniqueList 133 | 134 | 135 | {-| Get the intersection of two sets. Keeps values that appear in both sets. 136 | 137 | If you are using this module as an ordered set, the output set will have the 138 | same relative order as the first set. 139 | 140 | -} 141 | intersect : Set a -> Set a -> Set a 142 | intersect (S leftList) rightSet = 143 | S (List.filter (\key -> member key rightSet) leftList) 144 | 145 | 146 | {-| Get the difference between the first set and the second. Keeps values 147 | that do not appear in the second set. 148 | 149 | If you are using this module as an ordered set, the output set will have the 150 | same relative order as the first set. 151 | 152 | -} 153 | diff : Set a -> Set a -> Set a 154 | diff (S leftList) rightSet = 155 | S (List.filter (\key -> not (member key rightSet)) leftList) 156 | 157 | 158 | {-| Convert a set into a list, in the order the values were inserted with the 159 | most recently inserted value at the head of the list. 160 | -} 161 | toList : Set a -> List a 162 | toList (S uniqueList) = 163 | uniqueList 164 | 165 | 166 | {-| Convert a list into a set, removing any duplicates. 167 | 168 | If you are using this module as an ordered set, please note that the elements 169 | are inserted from right to left. (If you want to insert the elements from left 170 | to right, you can simply call `List.reverse` on the input before passing it to 171 | `fromList`.) 172 | 173 | -} 174 | fromList : List a -> Set a 175 | fromList list = 176 | List.foldr insert (S []) list 177 | 178 | 179 | {-| Fold over the values in a set from most recently inserted to least recently 180 | inserted. 181 | -} 182 | foldl : (a -> b -> b) -> b -> Set a -> b 183 | foldl func initialState (S uniqueList) = 184 | List.foldl func initialState uniqueList 185 | 186 | 187 | {-| Fold over the values in a set from least recently inserted to most recently 188 | inserted. 189 | -} 190 | foldr : (a -> b -> b) -> b -> Set a -> b 191 | foldr func initialState (S uniqueList) = 192 | List.foldr func initialState uniqueList 193 | 194 | 195 | {-| Map a function onto a set, creating a new set with no duplicates. 196 | -} 197 | map : (a -> b) -> Set a -> Set b 198 | map func (S uniqueList) = 199 | fromList (List.map func uniqueList) 200 | 201 | 202 | {-| Only keep elements that pass the given test. 203 | 204 | 205 | positives : Set Int 206 | positives = 207 | filter (\x -> x > 0) (fromList [ -2, -1, 0, 1, 2 ]) 208 | 209 | --> positives == fromList [ 0, 1, 2 ] 210 | 211 | -} 212 | filter : (a -> Bool) -> Set a -> Set a 213 | filter isGood (S uniqueList) = 214 | S (List.filter isGood uniqueList) 215 | 216 | 217 | {-| Create two new sets. The first contains all the elements that passed the 218 | given test, and the second contains all the elements that did not. 219 | -} 220 | partition : (a -> Bool) -> Set a -> ( Set a, Set a ) 221 | partition isGood (S uniqueList) = 222 | let 223 | ( uniqueList1, uniqueList2 ) = 224 | List.partition isGood uniqueList 225 | in 226 | ( S uniqueList1, S uniqueList2 ) 227 | -------------------------------------------------------------------------------- /docs.json: -------------------------------------------------------------------------------- 1 | [{"name":"AssocList","comment":" An [association list](https://en.wikipedia.org/wiki/Association_list) is a\nlist of tuples that map unique keys to values. The keys can be of any type (so\nlong as it has a reasonable definition for equality). This includes pretty\nmuch everything except for functions and things that contain functions.\n\nAll functions in this module are \"stack safe,\" which means that your program\nwon't crash from recursing over large association lists. You can read\nEvan Czaplicki's\n[document on tail-call elimination](https://github.com/evancz/functional-programming-in-elm/blob/master/recursion/tail-call-elimination.md)\nfor more information about this topic.\n\n\n# Dictionaries\n\n@docs Dict\n\n\n# Build\n\n@docs empty, singleton, insert, update, remove\n\n\n# Query\n\n@docs isEmpty, member, get, size, eq\n\n\n# Lists\n\n@docs keys, values, toList, fromList\n\n\n# Transform\n\n@docs map, foldl, foldr, filter, partition\n\n\n# Combine\n\n@docs union, intersect, diff, merge\n\n","unions":[{"name":"Dict","comment":" A dictionary of keys and values. So a `Dict String User` is a dictionary\nthat lets you look up a `String` (such as user names) and find the associated\n`User`.\n\n import AssocList as Dict exposing (Dict)\n\n users : Dict String User\n users =\n Dict.fromList\n [ ( \"Alice\", User \"Alice\" 28 1.65 )\n , ( \"Bob\", User \"Bob\" 19 1.82 )\n , ( \"Chuck\", User \"Chuck\" 33 1.75 )\n ]\n\n type alias User =\n { name : String\n , age : Int\n , height : Float\n }\n\n","args":["a","b"],"cases":[]}],"aliases":[],"values":[{"name":"diff","comment":" Keep a key-value pair when its key does not appear in the second dictionary.\n","type":"AssocList.Dict k a -> AssocList.Dict k b -> AssocList.Dict k a"},{"name":"empty","comment":" Create an empty dictionary.\n","type":"AssocList.Dict k v"},{"name":"eq","comment":" Compare two dictionaries for equality, ignoring insertion order.\nDictionaries are defined to be equal when they have identical key-value pairs\nwhere keys and values are compared using the built-in equality operator.\n\nYou should almost never use the built-in equality operator to compare\ndictionaries from this module since association lists have no canonical form.\n\n eq\n (fromList [ ( \"a\", 1 ), ( \"b\", 2 ) ])\n (fromList [ ( \"b\", 2 ), ( \"a\", 1 ) ])\n --> True\n\n","type":"AssocList.Dict k v -> AssocList.Dict k v -> Basics.Bool"},{"name":"filter","comment":" Keep only the key-value pairs that pass the given test.\n","type":"(k -> v -> Basics.Bool) -> AssocList.Dict k v -> AssocList.Dict k v"},{"name":"foldl","comment":" Fold over the key-value pairs in a dictionary from most recently inserted\nto least recently inserted.\n\n users : Dict String Int\n users =\n empty\n |> insert \"Alice\" 28\n |> insert \"Bob\" 19\n |> insert \"Chuck\" 33\n\n foldl (\\name age result -> age :: result) [] users\n --> [28,19,33]\n\n","type":"(k -> v -> b -> b) -> b -> AssocList.Dict k v -> b"},{"name":"foldr","comment":" Fold over the key-value pairs in a dictionary from least recently inserted\nto most recently insered.\n\n users : Dict String Int\n users =\n empty\n |> insert \"Alice\" 28\n |> insert \"Bob\" 19\n |> insert \"Chuck\" 33\n\n foldr (\\name age result -> age :: result) [] users\n --> [33,19,28]\n\n","type":"(k -> v -> b -> b) -> b -> AssocList.Dict k v -> b"},{"name":"fromList","comment":" Convert an association list into a dictionary. The elements are inserted\nfrom left to right. (If you want to insert the elements from right to left, you\ncan simply call `List.reverse` on the input before passing it to `fromList`.)\n","type":"List.List ( k, v ) -> AssocList.Dict k v"},{"name":"get","comment":" Get the value associated with a key. If the key is not found, return\n`Nothing`. This is useful when you are not sure if a key will be in the\ndictionary.\n\n type Animal\n = Cat\n | Mouse\n\n animals : Dict String Animal\n animals = fromList [ (\"Tom\", Cat), (\"Jerry\", Mouse) ]\n\n get \"Tom\" animals\n --> Just Cat\n\n get \"Jerry\" animals\n --> Just Mouse\n\n get \"Spike\" animals\n --> Nothing\n\n","type":"k -> AssocList.Dict k v -> Maybe.Maybe v"},{"name":"insert","comment":" Insert a key-value pair into a dictionary. Replaces value when there is\na collision.\n","type":"k -> v -> AssocList.Dict k v -> AssocList.Dict k v"},{"name":"intersect","comment":" Keep a key-value pair when its key appears in the second dictionary.\nPreference is given to values in the first dictionary.\n","type":"AssocList.Dict k v -> AssocList.Dict k v -> AssocList.Dict k v"},{"name":"isEmpty","comment":" Determine if a dictionary is empty.\n\n isEmpty empty\n --> True\n\n","type":"AssocList.Dict k v -> Basics.Bool"},{"name":"keys","comment":" Get all of the keys in a dictionary, in the order that they were inserted\nwith the most recently inserted key at the head of the list.\n\n keys (fromList [ ( 0, \"Alice\" ), ( 1, \"Bob\" ) ])\n --> [ 1, 0 ]\n\n","type":"AssocList.Dict k v -> List.List k"},{"name":"map","comment":" Apply a function to all values in a dictionary.\n","type":"(k -> a -> b) -> AssocList.Dict k a -> AssocList.Dict k b"},{"name":"member","comment":" Determine if a key is in a dictionary.\n","type":"k -> AssocList.Dict k v -> Basics.Bool"},{"name":"merge","comment":" The most general way of combining two dictionaries. You provide three\naccumulators for when a given key appears:\n\n1. Only in the left dictionary.\n2. In both dictionaries.\n3. Only in the right dictionary.\n\nYou then traverse all the keys in the following order, building up whatever\nyou want:\n\n1. All the keys that appear only in the right dictionary from least\n recently inserted to most recently inserted.\n2. All the keys in the left dictionary from least recently inserted to most\n recently inserted (without regard to whether they appear only in the left\n dictionary or in both dictionaries).\n\n","type":"(k -> a -> result -> result) -> (k -> a -> b -> result -> result) -> (k -> b -> result -> result) -> AssocList.Dict k a -> AssocList.Dict k b -> result -> result"},{"name":"partition","comment":" Partition a dictionary according to some test. The first dictionary\ncontains all key-value pairs which passed the test, and the second contains\nthe pairs that did not.\n","type":"(k -> v -> Basics.Bool) -> AssocList.Dict k v -> ( AssocList.Dict k v, AssocList.Dict k v )"},{"name":"remove","comment":" Remove a key-value pair from a dictionary. If the key is not found,\nno changes are made.\n","type":"k -> AssocList.Dict k v -> AssocList.Dict k v"},{"name":"singleton","comment":" Create a dictionary with one key-value pair.\n","type":"k -> v -> AssocList.Dict k v"},{"name":"size","comment":" Determine the number of key-value pairs in the dictionary.\n\n size (fromList [ ( \"a\", 1 ), ( \"b\", 2 ), ( \"c\", 3 ) ])\n --> 3\n\n size (insert 1 \"b\" (singleton 1 \"a\"))\n --> 1\n\n","type":"AssocList.Dict k v -> Basics.Int"},{"name":"toList","comment":" Convert a dictionary into an association list of key-value pairs, in the\norder that they were inserted with the most recently inserted entry at the\nhead of the list.\n","type":"AssocList.Dict k v -> List.List ( k, v )"},{"name":"union","comment":" Combine two dictionaries. If there is a collision, preference is given\nto the first dictionary.\n\nIf you are using this module as an ordered dictionary, the ordering of the\noutput dictionary will be all the entries of the first dictionary (from most\nrecently inserted to least recently inserted) followed by all the entries of\nthe second dictionary (from most recently inserted to least recently inserted).\n\n","type":"AssocList.Dict k v -> AssocList.Dict k v -> AssocList.Dict k v"},{"name":"update","comment":" Update the value of a dictionary for a specific key with a given function.\n\nIf you are using this module as an ordered dictionary, please note that if you\nare replacing the value of an existing entry, the entry will remain where it\nis in the insertion order. (If you do want to change the insertion order,\nconsider using `get` in conjunction with `insert` instead.)\n\n","type":"k -> (Maybe.Maybe v -> Maybe.Maybe v) -> AssocList.Dict k v -> AssocList.Dict k v"},{"name":"values","comment":" Get all of the values in a dictionary, in the order that they were inserted\nwith the most recently inserted value at the head of the list.\n\n values (fromList [ ( 0, \"Alice\" ), ( 1, \"Bob\" ) ])\n --> [ \"Bob\", \"Alice\" ]\n\n","type":"AssocList.Dict k v -> List.List v"}],"binops":[]}] -------------------------------------------------------------------------------- /assoc-list.optimized.js: -------------------------------------------------------------------------------- 1 | var pzp1997$assoc_list$AssocList$D = elm$core$Basics$identity; 2 | 3 | var pzp1997$assoc_list$AssocList$empty = _List_Nil; 4 | 5 | var pzp1997$assoc_list$AssocList$get = F2( 6 | function (targetKey, _n0) { 7 | get: 8 | while (true) { 9 | var alist = _n0; 10 | if (!alist.b) { 11 | return elm$core$Maybe$Nothing; 12 | } else { 13 | var _n2 = alist.a; 14 | var key = _n2.a; 15 | var value = _n2.b; 16 | var rest = alist.b; 17 | if (_Utils_eq(key, targetKey)) { 18 | return elm$core$Maybe$Just(value); 19 | } else { 20 | var $temp$targetKey = targetKey, 21 | $temp$_n0 = rest; 22 | targetKey = $temp$targetKey; 23 | _n0 = $temp$_n0; 24 | continue get; 25 | } 26 | } 27 | } 28 | }); 29 | 30 | var pzp1997$assoc_list$AssocList$member = F2( 31 | function (targetKey, dict) { 32 | var _n0 = A2(pzp1997$assoc_list$AssocList$get, targetKey, dict); 33 | if (!_n0.$) { 34 | return true; 35 | } else { 36 | return false; 37 | } 38 | }); 39 | 40 | var pzp1997$assoc_list$AssocList$size = function (_n0) { 41 | var alist = _n0; 42 | return elm$core$List$length(alist); 43 | }; 44 | 45 | var pzp1997$assoc_list$AssocList$isEmpty = function (dict) { 46 | return _Utils_eq(dict, _List_Nil); 47 | }; 48 | 49 | var pzp1997$assoc_list$AssocList$eq = F2( 50 | function (leftDict, rightDict) { 51 | return A6( 52 | pzp1997$assoc_list$AssocList$merge, 53 | F3( 54 | function (_n0, _n1, _n2) { 55 | return false; 56 | }), 57 | F4( 58 | function (_n3, a, b, result) { 59 | return result && _Utils_eq(a, b); 60 | }), 61 | F3( 62 | function (_n4, _n5, _n6) { 63 | return false; 64 | }), 65 | leftDict, 66 | rightDict, 67 | true); 68 | }); 69 | 70 | var pzp1997$assoc_list$AssocList$insert = F3( 71 | function (key, value, dict) { 72 | var _n0 = A2(pzp1997$assoc_list$AssocList$remove, key, dict); 73 | var alteredAlist = _n0; 74 | return A2( 75 | elm$core$List$cons, 76 | _Utils_Tuple2(key, value), 77 | alteredAlist); 78 | }); 79 | 80 | var pzp1997$assoc_list$AssocList$remove = F2( 81 | function (targetKey, _n0) { 82 | var alist = _n0; 83 | return A2( 84 | elm$core$List$filter, 85 | function (_n1) { 86 | var key = _n1.a; 87 | return !_Utils_eq(key, targetKey); 88 | }, 89 | alist); 90 | }); 91 | 92 | var pzp1997$assoc_list$AssocList$update = F3( 93 | function (targetKey, alter, dict) { 94 | var alist = dict; 95 | var maybeValue = A2(pzp1997$assoc_list$AssocList$get, targetKey, dict); 96 | if (!maybeValue.$) { 97 | var _n1 = alter(maybeValue); 98 | if (!_n1.$) { 99 | var alteredValue = _n1.a; 100 | return A2( 101 | elm$core$List$map, 102 | function (entry) { 103 | var key = entry.a; 104 | return _Utils_eq(key, targetKey) ? _Utils_Tuple2(targetKey, alteredValue) : entry; 105 | }, 106 | alist); 107 | } else { 108 | return A2(pzp1997$assoc_list$AssocList$remove, targetKey, dict); 109 | } 110 | } else { 111 | var _n2 = alter(elm$core$Maybe$Nothing); 112 | if (!_n2.$) { 113 | var alteredValue = _n2.a; 114 | return A2( 115 | elm$core$List$cons, 116 | _Utils_Tuple2(targetKey, alteredValue), 117 | alist); 118 | } else { 119 | return dict; 120 | } 121 | } 122 | }); 123 | 124 | var pzp1997$assoc_list$AssocList$singleton = F2( 125 | function (key, value) { 126 | return _List_fromArray( 127 | [ 128 | _Utils_Tuple2(key, value) 129 | ]); 130 | }); 131 | 132 | var pzp1997$assoc_list$AssocList$union = F2( 133 | function (_n0, rightDict) { 134 | var leftAlist = _n0; 135 | return A3( 136 | elm$core$List$foldr, 137 | F2( 138 | function (_n1, result) { 139 | var lKey = _n1.a; 140 | var lValue = _n1.b; 141 | return A3(pzp1997$assoc_list$AssocList$insert, lKey, lValue, result); 142 | }), 143 | rightDict, 144 | leftAlist); 145 | }); 146 | 147 | var pzp1997$assoc_list$AssocList$intersect = F2( 148 | function (_n0, rightDict) { 149 | var leftAlist = _n0; 150 | return A2( 151 | elm$core$List$filter, 152 | function (_n1) { 153 | var key = _n1.a; 154 | return A2(pzp1997$assoc_list$AssocList$member, key, rightDict); 155 | }, 156 | leftAlist); 157 | }); 158 | 159 | var pzp1997$assoc_list$AssocList$diff = F2( 160 | function (_n0, rightDict) { 161 | var leftAlist = _n0; 162 | return A2( 163 | elm$core$List$filter, 164 | function (_n1) { 165 | var key = _n1.a; 166 | return !A2(pzp1997$assoc_list$AssocList$member, key, rightDict); 167 | }, 168 | leftAlist); 169 | }); 170 | 171 | var pzp1997$assoc_list$AssocList$merge = F6( 172 | function (leftStep, bothStep, rightStep, leftDict, _n0, initialResult) { 173 | var leftAlist = leftDict; 174 | var rightAlist = _n0; 175 | var _n1 = A2( 176 | elm$core$List$partition, 177 | function (_n2) { 178 | var key = _n2.a; 179 | return A2(pzp1997$assoc_list$AssocList$member, key, leftDict); 180 | }, 181 | rightAlist); 182 | var inBothAlist = _n1.a; 183 | var inRightOnlyAlist = _n1.b; 184 | var intermediateResult = A3( 185 | elm$core$List$foldr, 186 | F2( 187 | function (_n5, result) { 188 | var rKey = _n5.a; 189 | var rValue = _n5.b; 190 | return A3(rightStep, rKey, rValue, result); 191 | }), 192 | initialResult, 193 | inRightOnlyAlist); 194 | return A3( 195 | elm$core$List$foldr, 196 | F2( 197 | function (_n3, result) { 198 | var lKey = _n3.a; 199 | var lValue = _n3.b; 200 | var _n4 = A2(pzp1997$assoc_list$AssocList$get, lKey, inBothAlist); 201 | if (!_n4.$) { 202 | var rValue = _n4.a; 203 | return A4(bothStep, lKey, lValue, rValue, result); 204 | } else { 205 | return A3(leftStep, lKey, lValue, result); 206 | } 207 | }), 208 | intermediateResult, 209 | leftAlist); 210 | }); 211 | 212 | var pzp1997$assoc_list$AssocList$map = F2( 213 | function (alter, _n0) { 214 | var alist = _n0; 215 | return A2( 216 | elm$core$List$map, 217 | function (_n1) { 218 | var key = _n1.a; 219 | var value = _n1.b; 220 | return _Utils_Tuple2( 221 | key, 222 | A2(alter, key, value)); 223 | }, 224 | alist); 225 | }); 226 | 227 | var pzp1997$assoc_list$AssocList$foldl = F3( 228 | function (func, initialResult, _n0) { 229 | var alist = _n0; 230 | return A3( 231 | elm$core$List$foldl, 232 | F2( 233 | function (_n1, result) { 234 | var key = _n1.a; 235 | var value = _n1.b; 236 | return A3(func, key, value, result); 237 | }), 238 | initialResult, 239 | alist); 240 | }); 241 | 242 | var pzp1997$assoc_list$AssocList$foldr = F3( 243 | function (func, initialResult, _n0) { 244 | var alist = _n0; 245 | return A3( 246 | elm$core$List$foldr, 247 | F2( 248 | function (_n1, result) { 249 | var key = _n1.a; 250 | var value = _n1.b; 251 | return A3(func, key, value, result); 252 | }), 253 | initialResult, 254 | alist); 255 | }); 256 | 257 | var pzp1997$assoc_list$AssocList$filter = F2( 258 | function (isGood, _n0) { 259 | var alist = _n0; 260 | return A2( 261 | elm$core$List$filter, 262 | function (_n1) { 263 | var key = _n1.a; 264 | var value = _n1.b; 265 | return A2(isGood, key, value); 266 | }, 267 | alist); 268 | }); 269 | 270 | var pzp1997$assoc_list$AssocList$partition = F2( 271 | function (isGood, _n0) { 272 | var alist = _n0; 273 | var _n1 = A2( 274 | elm$core$List$partition, 275 | function (_n2) { 276 | var key = _n2.a; 277 | var value = _n2.b; 278 | return A2(isGood, key, value); 279 | }, 280 | alist); 281 | var good = _n1.a; 282 | var bad = _n1.b; 283 | return _Utils_Tuple2(good, bad); 284 | }); 285 | 286 | var pzp1997$assoc_list$AssocList$keys = function (_n0) { 287 | var alist = _n0; 288 | return A2(elm$core$List$map, elm$core$Tuple$first, alist); 289 | }; 290 | 291 | var pzp1997$assoc_list$AssocList$values = function (_n0) { 292 | var alist = _n0; 293 | return A2(elm$core$List$map, elm$core$Tuple$second, alist); 294 | }; 295 | 296 | var pzp1997$assoc_list$AssocList$toList = function (_n0) { 297 | var alist = _n0; 298 | return alist; 299 | }; 300 | 301 | var pzp1997$assoc_list$AssocList$fromList = function (alist) { 302 | return A3( 303 | elm$core$List$foldl, 304 | F2( 305 | function (_n0, result) { 306 | var key = _n0.a; 307 | var value = _n0.b; 308 | return A3(pzp1997$assoc_list$AssocList$insert, key, value, result); 309 | }), 310 | _List_Nil, 311 | alist); 312 | }; 313 | -------------------------------------------------------------------------------- /tests/Test/UniqueList.elm: -------------------------------------------------------------------------------- 1 | module Test.UniqueList exposing (tests) 2 | 3 | import Basics exposing (..) 4 | import Expect 5 | import List exposing ((::)) 6 | import Test exposing (..) 7 | import UniqueList as Set exposing (Set) 8 | 9 | 10 | tests : Test 11 | tests = 12 | describe "Set Tests" 13 | [ describe "empty" emptyTests 14 | , describe "singleton" singletonTests 15 | , describe "insert" insertTests 16 | , describe "remove" removeTests 17 | , describe "isEmpty" isEmptyTests 18 | , describe "member" memberTests 19 | , describe "size" sizeTests 20 | , describe "foldl" foldlTests 21 | , describe "foldr" foldrTests 22 | , describe "map" mapTests 23 | , describe "filter" filterTests 24 | , describe "partition" partitionTests 25 | , describe "union" unionTests 26 | , describe "intersect" intersectTests 27 | , describe "diff" diffTests 28 | , describe "toList" toListTests 29 | , describe "fromList" fromListTests 30 | ] 31 | 32 | 33 | 34 | -- HELPERS 35 | 36 | 37 | set42 : Set Int 38 | set42 = 39 | Set.singleton 42 40 | 41 | 42 | set1To100 : Set Int 43 | set1To100 = 44 | Set.fromList (List.range 1 100) 45 | 46 | 47 | set1To50 : Set Int 48 | set1To50 = 49 | Set.fromList (List.range 1 50) 50 | 51 | 52 | set51To100 : Set Int 53 | set51To100 = 54 | Set.fromList (List.range 51 100) 55 | 56 | 57 | set51To150 : Set Int 58 | set51To150 = 59 | Set.fromList (List.range 51 150) 60 | 61 | 62 | isLessThan51 : Int -> Bool 63 | isLessThan51 n = 64 | n < 51 65 | 66 | 67 | 68 | -- TESTS 69 | 70 | 71 | emptyTests : List Test 72 | emptyTests = 73 | [ test "returns an empty set" <| 74 | \() -> Expect.equal 0 (Set.size Set.empty) 75 | ] 76 | 77 | 78 | singletonTests : List Test 79 | singletonTests = 80 | [ test "returns set with one element" <| 81 | \() -> Expect.equal 1 (Set.size (Set.singleton 1)) 82 | , test "contains given element" <| 83 | \() -> Expect.equal True (Set.member 1 (Set.singleton 1)) 84 | ] 85 | 86 | 87 | insertTests : List Test 88 | insertTests = 89 | [ test "adds new element to empty set" <| 90 | \() -> Expect.equal set42 (Set.insert 42 Set.empty) 91 | , test "adds new element to a set of 100" <| 92 | \() -> Expect.equal (Set.fromList (101 :: List.range 1 100)) (Set.insert 101 set1To100) 93 | , test "leaves singleton set intact if it contains given element" <| 94 | \() -> Expect.equal set42 (Set.insert 42 set42) 95 | , test "leaves set of 100 intact if it contains given element" <| 96 | \() -> 97 | Expect.equal 98 | (Set.fromList 99 | (42 :: List.range 1 41 ++ List.range 43 100) 100 | ) 101 | (Set.insert 42 set1To100) 102 | ] 103 | 104 | 105 | removeTests : List Test 106 | removeTests = 107 | [ test "removes element from singleton set" <| 108 | \() -> Expect.equal Set.empty (Set.remove 42 set42) 109 | , test "removes element from set of 100" <| 110 | \() -> Expect.equal (Set.fromList (List.range 1 99)) (Set.remove 100 set1To100) 111 | , test "leaves singleton set intact if it doesn't contain given element" <| 112 | \() -> Expect.equal set42 (Set.remove -1 set42) 113 | , test "leaves set of 100 intact if it doesn't contain given element" <| 114 | \() -> Expect.equal set1To100 (Set.remove -1 set1To100) 115 | ] 116 | 117 | 118 | isEmptyTests : List Test 119 | isEmptyTests = 120 | [ test "returns True for empty set" <| 121 | \() -> Expect.equal True (Set.isEmpty Set.empty) 122 | , test "returns False for singleton set" <| 123 | \() -> Expect.equal False (Set.isEmpty set42) 124 | , test "returns False for set of 100" <| 125 | \() -> Expect.equal False (Set.isEmpty set1To100) 126 | ] 127 | 128 | 129 | memberTests : List Test 130 | memberTests = 131 | [ test "returns True when given element inside singleton set" <| 132 | \() -> Expect.equal True (Set.member 42 set42) 133 | , test "returns True when given element inside set of 100" <| 134 | \() -> Expect.equal True (Set.member 42 set1To100) 135 | , test "returns False for element not in singleton" <| 136 | \() -> Expect.equal False (Set.member -1 set42) 137 | , test "returns False for element not in set of 100" <| 138 | \() -> Expect.equal False (Set.member -1 set1To100) 139 | ] 140 | 141 | 142 | sizeTests : List Test 143 | sizeTests = 144 | [ test "returns 0 for empty set" <| 145 | \() -> Expect.equal 0 (Set.size Set.empty) 146 | , test "returns 1 for singleton set" <| 147 | \() -> Expect.equal 1 (Set.size set42) 148 | , test "returns 100 for set of 100" <| 149 | \() -> Expect.equal 100 (Set.size set1To100) 150 | ] 151 | 152 | 153 | foldlTests : List Test 154 | foldlTests = 155 | [ test "with insert and empty set acts as identity function" <| 156 | \() -> 157 | Expect.equal (Set.fromList (List.reverse (List.range 1 100))) 158 | (Set.foldl Set.insert Set.empty set1To100) 159 | , test "with counter and zero acts as size function" <| 160 | \() -> Expect.equal 100 (Set.foldl (\_ count -> count + 1) 0 set1To100) 161 | , test "folds set elements in reverse insertion order" <| 162 | \() -> Expect.equal [ 3, 1, 2 ] (Set.foldl (\n ns -> n :: ns) [] (Set.fromList [ 2, 1, 3 ])) 163 | ] 164 | 165 | 166 | foldrTests : List Test 167 | foldrTests = 168 | [ test "with insert and empty set acts as identity function" <| 169 | \() -> Expect.equal set1To100 (Set.foldr Set.insert Set.empty set1To100) 170 | , test "with counter and zero acts as size function" <| 171 | \() -> Expect.equal 100 (Set.foldr (\_ count -> count + 1) 0 set1To100) 172 | , test "folds set elements in insertion order" <| 173 | \() -> Expect.equal [ 2, 1, 3 ] (Set.foldr (\n ns -> n :: ns) [] (Set.fromList [ 2, 1, 3 ])) 174 | ] 175 | 176 | 177 | mapTests : List Test 178 | mapTests = 179 | [ test "applies given function to singleton element" <| 180 | \() -> Expect.equal (Set.singleton 43) (Set.map ((+) 1) set42) 181 | , test "applies given function to each element" <| 182 | \() -> 183 | Expect.equal (Set.fromList (List.reverse (List.range -100 -1))) 184 | (Set.map negate set1To100) 185 | , test "maps to duplicate keys" <| 186 | \() -> 187 | Expect.equal 188 | (Set.singleton 1) 189 | (Set.map (\_ -> 1) set1To50) 190 | ] 191 | 192 | 193 | filterTests : List Test 194 | filterTests = 195 | [ test "with always True doesn't change anything" <| 196 | \() -> Expect.equal set1To100 (Set.filter (always True) set1To100) 197 | , test "with always False returns empty set" <| 198 | \() -> Expect.equal Set.empty (Set.filter (always False) set1To100) 199 | , test "simple filter" <| 200 | \() -> Expect.equal set1To50 (Set.filter isLessThan51 set1To100) 201 | ] 202 | 203 | 204 | partitionTests : List Test 205 | partitionTests = 206 | [ test "of empty set returns two empty sets" <| 207 | \() -> Expect.equal ( Set.empty, Set.empty ) (Set.partition isLessThan51 Set.empty) 208 | , test "simple partition" <| 209 | \() -> Expect.equal ( set1To50, set51To100 ) (Set.partition isLessThan51 set1To100) 210 | ] 211 | 212 | 213 | unionTests : List Test 214 | unionTests = 215 | [ test "with empty set doesn't change anything" <| 216 | \() -> Expect.equal set42 (Set.union set42 Set.empty) 217 | , test "with itself doesn't change anything" <| 218 | \() -> Expect.equal set1To100 (Set.union set1To100 set1To100) 219 | , test "with subset doesn't change anything" <| 220 | \() -> Expect.equal set1To100 (Set.union set1To100 set42) 221 | , test "with superset returns superset" <| 222 | \() -> 223 | Expect.equal 224 | (Set.fromList 225 | (42 :: List.range 1 41 ++ List.range 43 100) 226 | ) 227 | (Set.union set42 set1To100) 228 | , test "contains elements of both singletons" <| 229 | \() -> Expect.equal (Set.insert 1 set42) (Set.union (Set.singleton 1) set42) 230 | , test "consists of elements from either set" <| 231 | \() -> 232 | Set.union set1To100 set51To150 233 | |> Expect.equal (Set.fromList (List.range 1 150)) 234 | ] 235 | 236 | 237 | intersectTests : List Test 238 | intersectTests = 239 | [ test "with empty set returns empty set" <| 240 | \() -> Expect.equal Set.empty (Set.intersect set42 Set.empty) 241 | , test "with itself doesn't change anything" <| 242 | \() -> Expect.equal set1To100 (Set.intersect set1To100 set1To100) 243 | , test "with subset returns subset" <| 244 | \() -> Expect.equal set42 (Set.intersect set1To100 set42) 245 | , test "with superset doesn't change anything" <| 246 | \() -> Expect.equal set42 (Set.intersect set42 set1To100) 247 | , test "returns empty set given disjunctive sets" <| 248 | \() -> Expect.equal Set.empty (Set.intersect set42 (Set.singleton 1)) 249 | , test "consists of common elements only" <| 250 | \() -> 251 | Set.intersect set1To100 set51To150 252 | |> Expect.equal set51To100 253 | ] 254 | 255 | 256 | diffTests : List Test 257 | diffTests = 258 | [ test "with empty set doesn't change anything" <| 259 | \() -> Expect.equal set42 (Set.diff set42 Set.empty) 260 | , test "with itself returns empty set" <| 261 | \() -> Expect.equal Set.empty (Set.diff set1To100 set1To100) 262 | , test "with subset returns set without subset elements" <| 263 | \() -> Expect.equal (Set.remove 42 set1To100) (Set.diff set1To100 set42) 264 | , test "with superset returns empty set" <| 265 | \() -> Expect.equal Set.empty (Set.diff set42 set1To100) 266 | , test "doesn't change anything given disjunctive sets" <| 267 | \() -> Expect.equal set42 (Set.diff set42 (Set.singleton 1)) 268 | , test "only keeps values that don't appear in the second set" <| 269 | \() -> 270 | Set.diff set1To100 set51To150 271 | |> Expect.equal set1To50 272 | ] 273 | 274 | 275 | toListTests : List Test 276 | toListTests = 277 | [ test "returns empty list for empty set" <| 278 | \() -> Expect.equal [] (Set.toList Set.empty) 279 | , test "returns singleton list for singleton set" <| 280 | \() -> Expect.equal [ 42 ] (Set.toList set42) 281 | , test "returns sorted list of set elements" <| 282 | \() -> Expect.equal (List.range 1 100) (Set.toList set1To100) 283 | ] 284 | 285 | 286 | fromListTests : List Test 287 | fromListTests = 288 | [ test "returns empty set for empty list" <| 289 | \() -> Expect.equal Set.empty (Set.fromList []) 290 | , test "returns singleton set for singleton list" <| 291 | \() -> Expect.equal set42 (Set.fromList [ 42 ]) 292 | , test "returns set with unique list elements" <| 293 | \() -> Expect.equal set1To100 (Set.fromList (1 :: List.range 1 100)) 294 | ] 295 | -------------------------------------------------------------------------------- /tests/Test/AssocList.elm: -------------------------------------------------------------------------------- 1 | module Test.AssocList exposing (tests) 2 | 3 | import AssocList as Dict 4 | import Expect 5 | import Test exposing (..) 6 | 7 | 8 | animals : Dict.Dict String String 9 | animals = 10 | Dict.fromList [ ( "Jerry", "mouse" ), ( "Tom", "cat" ) ] 11 | 12 | 13 | tests : Test 14 | tests = 15 | let 16 | buildTests = 17 | describe "build Tests" 18 | [ test "empty" <| 19 | \() -> 20 | Expect.equal (Dict.fromList []) Dict.empty 21 | , test "singleton" <| 22 | \() -> 23 | Expect.equal 24 | (Dict.fromList [ ( "k", "v" ) ]) 25 | (Dict.singleton "k" "v") 26 | , test "insert" <| 27 | \() -> 28 | Expect.equal 29 | (Dict.fromList [ ( "k", "v" ) ]) 30 | (Dict.insert "k" "v" Dict.empty) 31 | , test "insert replace" <| 32 | \() -> 33 | Expect.equal 34 | (Dict.fromList [ ( "k", "vv" ) ]) 35 | (Dict.insert "k" "vv" (Dict.singleton "k" "v")) 36 | , test "update" <| 37 | \() -> 38 | Expect.equal 39 | (Dict.fromList [ ( "k", "vv" ) ]) 40 | (Dict.update "k" (\v -> Just "vv") (Dict.singleton "k" "v")) 41 | , test "update Nothing" <| 42 | \() -> 43 | Expect.equal 44 | Dict.empty 45 | (Dict.update "k" (\v -> Nothing) (Dict.singleton "k" "v")) 46 | , test "update does not change order" <| 47 | \() -> 48 | Expect.equal 49 | (Dict.insert "b" 2 (Dict.singleton "a" 0)) 50 | (Dict.update "a" 51 | (\v -> Just 0) 52 | (Dict.insert "b" 2 (Dict.singleton "a" 1)) 53 | ) 54 | , test "remove" <| 55 | \() -> 56 | Expect.equal 57 | Dict.empty 58 | (Dict.remove "k" (Dict.singleton "k" "v")) 59 | , test "remove not found" <| 60 | \() -> 61 | Expect.equal 62 | (Dict.singleton "k" "v") 63 | (Dict.remove "kk" (Dict.singleton "k" "v")) 64 | ] 65 | 66 | queryTests = 67 | describe "query Tests" 68 | [ test "member 1" <| \() -> Expect.equal True (Dict.member "Tom" animals) 69 | , test "member 2" <| \() -> Expect.equal False (Dict.member "Spike" animals) 70 | , test "get 1" <| \() -> Expect.equal (Just "cat") (Dict.get "Tom" animals) 71 | , test "get 2" <| \() -> Expect.equal Nothing (Dict.get "Spike" animals) 72 | , test "get with duplicate key" <| 73 | \() -> 74 | Expect.equal (Just 2) 75 | (Dict.get "a" (Dict.insert "a" 2 (Dict.singleton "a" 1))) 76 | , test "size of empty dictionary" <| \() -> Expect.equal 0 (Dict.size Dict.empty) 77 | , test "size of example dictionary" <| \() -> Expect.equal 2 (Dict.size animals) 78 | , test "size with duplicate key" <| 79 | \() -> 80 | Expect.equal 1 81 | (Dict.size (Dict.insert "a" 2 (Dict.singleton "a" 1))) 82 | ] 83 | 84 | equalityTests = 85 | describe "equality Tests" 86 | [ test "eq empty" <| 87 | \() -> 88 | Expect.equal True 89 | (Dict.eq (Dict.fromList []) Dict.empty) 90 | , test "eq in order" <| 91 | \() -> 92 | Expect.equal True 93 | (Dict.eq 94 | (Dict.fromList [ ( 'a', 1 ), ( 'b', 2 ) ]) 95 | (Dict.fromList [ ( 'a', 1 ), ( 'b', 2 ) ]) 96 | ) 97 | , test "eq out of order" <| 98 | \() -> 99 | Expect.equal True 100 | (Dict.eq 101 | (Dict.fromList [ ( 'a', 1 ), ( 'b', 2 ) ]) 102 | (Dict.fromList [ ( 'b', 2 ), ( 'a', 1 ) ]) 103 | ) 104 | , test "eq left bigger" <| 105 | \() -> 106 | Expect.equal False 107 | (Dict.eq 108 | (Dict.fromList [ ( 'a', 1 ), ( 'b', 2 ) ]) 109 | (Dict.fromList [ ( 'a', 1 ) ]) 110 | ) 111 | , test "eq right bigger" <| 112 | \() -> 113 | Expect.equal False 114 | (Dict.eq 115 | (Dict.singleton 'a' 1) 116 | (Dict.fromList [ ( 'a', 1 ), ( 'b', 2 ) ]) 117 | ) 118 | , test "eq different values" <| 119 | \() -> 120 | Expect.equal False 121 | (Dict.eq 122 | (Dict.singleton 'a' 1) 123 | (Dict.singleton 'a' 2) 124 | ) 125 | ] 126 | 127 | combineTests = 128 | describe "combine Tests" 129 | [ test "union" <| 130 | \() -> 131 | Expect.equal 132 | animals 133 | (Dict.union 134 | (Dict.singleton "Jerry" "mouse") 135 | (Dict.singleton "Tom" "cat") 136 | ) 137 | , test "union collison" <| 138 | \() -> 139 | Expect.equal 140 | (Dict.singleton "Tom" "cat") 141 | (Dict.union 142 | (Dict.singleton "Tom" "cat") 143 | (Dict.singleton "Tom" "mouse") 144 | ) 145 | , test "intersect" <| 146 | \() -> 147 | Expect.equal 148 | (Dict.singleton "Tom" "cat") 149 | (Dict.intersect 150 | animals 151 | (Dict.singleton "Tom" "cat") 152 | ) 153 | , test "diff" <| 154 | \() -> 155 | Expect.equal 156 | (Dict.singleton "Jerry" "mouse") 157 | (Dict.diff animals (Dict.singleton "Tom" "cat")) 158 | ] 159 | 160 | transformTests = 161 | describe "transform Tests" 162 | [ test "filter" <| 163 | \() -> 164 | Expect.equal 165 | (Dict.singleton "Tom" "cat") 166 | (Dict.filter (\k v -> k == "Tom") animals) 167 | , test "partition fst" <| 168 | \() -> 169 | Expect.equal 170 | (Dict.singleton "Tom" "cat") 171 | (Tuple.first (Dict.partition (\k v -> k == "Tom") animals)) 172 | , test "partition snd" <| 173 | \() -> 174 | Expect.equal 175 | (Dict.singleton "Jerry" "mouse") 176 | (Tuple.second (Dict.partition (\k v -> k == "Tom") animals)) 177 | ] 178 | 179 | mergeTests = 180 | let 181 | insertBoth key leftVal rightVal dict = 182 | Dict.insert key (leftVal ++ rightVal) dict 183 | 184 | s1 = 185 | Dict.empty |> Dict.insert "u1" [ 1 ] 186 | 187 | s2 = 188 | Dict.empty |> Dict.insert "u2" [ 2 ] 189 | 190 | s23 = 191 | Dict.empty |> Dict.insert "u2" [ 3 ] 192 | 193 | b1 = 194 | List.map (\i -> ( i, [ i ] )) (List.range 1 10) 195 | |> Dict.fromList 196 | 197 | b2 = 198 | List.map (\i -> ( i, [ i ] )) (List.range 5 15) 199 | |> Dict.fromList 200 | 201 | bExpected = 202 | [ ( 1, [ 1 ] ), ( 2, [ 2 ] ), ( 3, [ 3 ] ), ( 4, [ 4 ] ), ( 5, [ 5, 5 ] ), ( 6, [ 6, 6 ] ), ( 7, [ 7, 7 ] ), ( 8, [ 8, 8 ] ), ( 9, [ 9, 9 ] ), ( 10, [ 10, 10 ] ), ( 11, [ 11 ] ), ( 12, [ 12 ] ), ( 13, [ 13 ] ), ( 14, [ 14 ] ), ( 15, [ 15 ] ) ] 203 | in 204 | describe "merge Tests" 205 | [ test "merge empties" <| 206 | \() -> 207 | Expect.equal Dict.empty 208 | (Dict.merge 209 | Dict.insert 210 | insertBoth 211 | Dict.insert 212 | Dict.empty 213 | Dict.empty 214 | Dict.empty 215 | ) 216 | , test "merge singletons in order" <| 217 | \() -> 218 | Expect.equal [ ( "u1", [ 1 ] ), ( "u2", [ 2 ] ) ] 219 | (Dict.merge 220 | Dict.insert 221 | insertBoth 222 | Dict.insert 223 | s1 224 | s2 225 | Dict.empty 226 | |> Dict.toList 227 | ) 228 | , test "merge singletons out of order" <| 229 | \() -> 230 | Expect.equal [ ( "u2", [ 2 ] ), ( "u1", [ 1 ] ) ] 231 | (Dict.merge 232 | Dict.insert 233 | insertBoth 234 | Dict.insert 235 | s2 236 | s1 237 | Dict.empty 238 | |> Dict.toList 239 | ) 240 | , test "merge with duplicate key" <| 241 | \() -> 242 | Expect.equal [ ( "u2", [ 2, 3 ] ) ] 243 | (Dict.merge 244 | Dict.insert 245 | insertBoth 246 | Dict.insert 247 | s2 248 | s23 249 | Dict.empty 250 | |> Dict.toList 251 | ) 252 | , test "partially overlapping" <| 253 | \() -> 254 | Expect.equal bExpected 255 | (Dict.merge 256 | Dict.insert 257 | insertBoth 258 | Dict.insert 259 | b1 260 | b2 261 | Dict.empty 262 | |> Dict.toList 263 | ) 264 | ] 265 | in 266 | describe "Dict Tests" 267 | [ buildTests 268 | , queryTests 269 | , equalityTests 270 | , combineTests 271 | , transformTests 272 | , mergeTests 273 | ] 274 | -------------------------------------------------------------------------------- /src/AssocList.elm: -------------------------------------------------------------------------------- 1 | module AssocList exposing 2 | ( Dict 3 | , empty, singleton, insert, update, remove 4 | , isEmpty, member, get, size, eq 5 | , keys, values, toList, fromList 6 | , map, foldl, foldr, filter, partition 7 | , union, intersect, diff, merge 8 | ) 9 | 10 | {-| An [association list](https://en.wikipedia.org/wiki/Association_list) is a 11 | list of tuples that map unique keys to values. The keys can be of any type (so 12 | long as it has a reasonable definition for equality). This includes pretty 13 | much everything except for functions and things that contain functions. 14 | 15 | All functions in this module are "stack safe," which means that your program 16 | won't crash from recursing over large association lists. You can read 17 | Evan Czaplicki's 18 | [document on tail-call elimination](https://github.com/evancz/functional-programming-in-elm/blob/master/recursion/tail-call-elimination.md) 19 | for more information about this topic. 20 | 21 | 22 | # Dictionaries 23 | 24 | @docs Dict 25 | 26 | 27 | # Build 28 | 29 | @docs empty, singleton, insert, update, remove 30 | 31 | 32 | # Query 33 | 34 | @docs isEmpty, member, get, size, eq 35 | 36 | 37 | # Lists 38 | 39 | @docs keys, values, toList, fromList 40 | 41 | 42 | # Transform 43 | 44 | @docs map, foldl, foldr, filter, partition 45 | 46 | 47 | # Combine 48 | 49 | @docs union, intersect, diff, merge 50 | 51 | -} 52 | 53 | 54 | {-| A dictionary of keys and values. So a `Dict String User` is a dictionary 55 | that lets you look up a `String` (such as user names) and find the associated 56 | `User`. 57 | 58 | import AssocList as Dict exposing (Dict) 59 | 60 | users : Dict String User 61 | users = 62 | Dict.fromList 63 | [ ( "Alice", User "Alice" 28 1.65 ) 64 | , ( "Bob", User "Bob" 19 1.82 ) 65 | , ( "Chuck", User "Chuck" 33 1.75 ) 66 | ] 67 | 68 | type alias User = 69 | { name : String 70 | , age : Int 71 | , height : Float 72 | } 73 | 74 | -} 75 | type Dict a b 76 | = D (List ( a, b )) 77 | 78 | 79 | {-| Create an empty dictionary. 80 | -} 81 | empty : Dict k v 82 | empty = 83 | D [] 84 | 85 | 86 | {-| Get the value associated with a key. If the key is not found, return 87 | `Nothing`. This is useful when you are not sure if a key will be in the 88 | dictionary. 89 | 90 | type Animal 91 | = Cat 92 | | Mouse 93 | 94 | animals : Dict String Animal 95 | animals = fromList [ ("Tom", Cat), ("Jerry", Mouse) ] 96 | 97 | get "Tom" animals 98 | --> Just Cat 99 | 100 | get "Jerry" animals 101 | --> Just Mouse 102 | 103 | get "Spike" animals 104 | --> Nothing 105 | 106 | -} 107 | get : k -> Dict k v -> Maybe v 108 | get targetKey (D alist) = 109 | case alist of 110 | [] -> 111 | Nothing 112 | 113 | ( key, value ) :: rest -> 114 | if key == targetKey then 115 | Just value 116 | 117 | else 118 | get targetKey (D rest) 119 | 120 | 121 | {-| Determine if a key is in a dictionary. 122 | -} 123 | member : k -> Dict k v -> Bool 124 | member targetKey dict = 125 | case get targetKey dict of 126 | Just _ -> 127 | True 128 | 129 | Nothing -> 130 | False 131 | 132 | 133 | {-| Determine the number of key-value pairs in the dictionary. 134 | 135 | size (fromList [ ( "a", 1 ), ( "b", 2 ), ( "c", 3 ) ]) 136 | --> 3 137 | 138 | size (insert 1 "b" (singleton 1 "a")) 139 | --> 1 140 | 141 | -} 142 | size : Dict k v -> Int 143 | size (D alist) = 144 | List.length alist 145 | 146 | 147 | {-| Determine if a dictionary is empty. 148 | 149 | isEmpty empty 150 | --> True 151 | 152 | -} 153 | isEmpty : Dict k v -> Bool 154 | isEmpty dict = 155 | dict == D [] 156 | 157 | 158 | {-| Compare two dictionaries for equality, ignoring insertion order. 159 | Dictionaries are defined to be equal when they have identical key-value pairs 160 | where keys and values are compared using the built-in equality operator. 161 | 162 | You should almost never use the built-in equality operator to compare 163 | dictionaries from this module since association lists have no canonical form. 164 | 165 | eq 166 | (fromList [ ( "a", 1 ), ( "b", 2 ) ]) 167 | (fromList [ ( "b", 2 ), ( "a", 1 ) ]) 168 | --> True 169 | 170 | -} 171 | eq : Dict k v -> Dict k v -> Bool 172 | eq leftDict rightDict = 173 | merge 174 | (\_ _ _ -> False) 175 | (\_ a b result -> result && a == b) 176 | (\_ _ _ -> False) 177 | leftDict 178 | rightDict 179 | True 180 | 181 | 182 | {-| Insert a key-value pair into a dictionary. Replaces value when there is 183 | a collision. 184 | -} 185 | insert : k -> v -> Dict k v -> Dict k v 186 | insert key value dict = 187 | let 188 | (D alteredAlist) = 189 | remove key dict 190 | in 191 | D (( key, value ) :: alteredAlist) 192 | 193 | 194 | {-| Remove a key-value pair from a dictionary. If the key is not found, 195 | no changes are made. 196 | -} 197 | remove : k -> Dict k v -> Dict k v 198 | remove targetKey (D alist) = 199 | D (List.filter (\( key, _ ) -> key /= targetKey) alist) 200 | 201 | 202 | {-| Update the value of a dictionary for a specific key with a given function. 203 | 204 | If you are using this module as an ordered dictionary, please note that if you 205 | are replacing the value of an existing entry, the entry will remain where it 206 | is in the insertion order. (If you do want to change the insertion order, 207 | consider using `get` in conjunction with `insert` instead.) 208 | 209 | -} 210 | update : k -> (Maybe v -> Maybe v) -> Dict k v -> Dict k v 211 | update targetKey alter ((D alist) as dict) = 212 | let 213 | maybeValue = 214 | get targetKey dict 215 | in 216 | case maybeValue of 217 | Just _ -> 218 | case alter maybeValue of 219 | Just alteredValue -> 220 | D 221 | (List.map 222 | (\(( key, _ ) as entry) -> 223 | if key == targetKey then 224 | ( targetKey, alteredValue ) 225 | 226 | else 227 | entry 228 | ) 229 | alist 230 | ) 231 | 232 | Nothing -> 233 | remove targetKey dict 234 | 235 | Nothing -> 236 | case alter Nothing of 237 | Just alteredValue -> 238 | D (( targetKey, alteredValue ) :: alist) 239 | 240 | Nothing -> 241 | dict 242 | 243 | 244 | {-| Create a dictionary with one key-value pair. 245 | -} 246 | singleton : k -> v -> Dict k v 247 | singleton key value = 248 | D [ ( key, value ) ] 249 | 250 | 251 | 252 | -- COMBINE 253 | 254 | 255 | {-| Combine two dictionaries. If there is a collision, preference is given 256 | to the first dictionary. 257 | 258 | If you are using this module as an ordered dictionary, the ordering of the 259 | output dictionary will be all the entries of the first dictionary (from most 260 | recently inserted to least recently inserted) followed by all the entries of 261 | the second dictionary (from most recently inserted to least recently inserted). 262 | 263 | -} 264 | union : Dict k v -> Dict k v -> Dict k v 265 | union (D leftAlist) rightDict = 266 | List.foldr 267 | (\( lKey, lValue ) result -> 268 | insert lKey lValue result 269 | ) 270 | rightDict 271 | leftAlist 272 | 273 | 274 | {-| Keep a key-value pair when its key appears in the second dictionary. 275 | Preference is given to values in the first dictionary. 276 | -} 277 | intersect : Dict k v -> Dict k v -> Dict k v 278 | intersect (D leftAlist) rightDict = 279 | D (List.filter (\( key, _ ) -> member key rightDict) leftAlist) 280 | 281 | 282 | {-| Keep a key-value pair when its key does not appear in the second dictionary. 283 | -} 284 | diff : Dict k a -> Dict k b -> Dict k a 285 | diff (D leftAlist) rightDict = 286 | D (List.filter (\( key, _ ) -> not (member key rightDict)) leftAlist) 287 | 288 | 289 | {-| The most general way of combining two dictionaries. You provide three 290 | accumulators for when a given key appears: 291 | 292 | 1. Only in the left dictionary. 293 | 2. In both dictionaries. 294 | 3. Only in the right dictionary. 295 | 296 | You then traverse all the keys in the following order, building up whatever 297 | you want: 298 | 299 | 1. All the keys that appear only in the right dictionary from least 300 | recently inserted to most recently inserted. 301 | 2. All the keys in the left dictionary from least recently inserted to most 302 | recently inserted (without regard to whether they appear only in the left 303 | dictionary or in both dictionaries). 304 | 305 | -} 306 | merge : 307 | (k -> a -> result -> result) 308 | -> (k -> a -> b -> result -> result) 309 | -> (k -> b -> result -> result) 310 | -> Dict k a 311 | -> Dict k b 312 | -> result 313 | -> result 314 | merge leftStep bothStep rightStep ((D leftAlist) as leftDict) (D rightAlist) initialResult = 315 | let 316 | ( inBothAlist, inRightOnlyAlist ) = 317 | List.partition 318 | (\( key, _ ) -> 319 | member key leftDict 320 | ) 321 | rightAlist 322 | 323 | intermediateResult = 324 | List.foldr 325 | (\( rKey, rValue ) result -> 326 | rightStep rKey rValue result 327 | ) 328 | initialResult 329 | inRightOnlyAlist 330 | in 331 | List.foldr 332 | (\( lKey, lValue ) result -> 333 | case get lKey (D inBothAlist) of 334 | Just rValue -> 335 | bothStep lKey lValue rValue result 336 | 337 | Nothing -> 338 | leftStep lKey lValue result 339 | ) 340 | intermediateResult 341 | leftAlist 342 | 343 | 344 | 345 | -- TRANSFORM 346 | 347 | 348 | {-| Apply a function to all values in a dictionary. 349 | -} 350 | map : (k -> a -> b) -> Dict k a -> Dict k b 351 | map alter (D alist) = 352 | D (List.map (\( key, value ) -> ( key, alter key value )) alist) 353 | 354 | 355 | {-| Fold over the key-value pairs in a dictionary from most recently inserted 356 | to least recently inserted. 357 | 358 | users : Dict String Int 359 | users = 360 | empty 361 | |> insert "Alice" 28 362 | |> insert "Bob" 19 363 | |> insert "Chuck" 33 364 | 365 | foldl (\name age result -> age :: result) [] users 366 | --> [28,19,33] 367 | 368 | -} 369 | foldl : (k -> v -> b -> b) -> b -> Dict k v -> b 370 | foldl func initialResult (D alist) = 371 | List.foldl 372 | (\( key, value ) result -> 373 | func key value result 374 | ) 375 | initialResult 376 | alist 377 | 378 | 379 | {-| Fold over the key-value pairs in a dictionary from least recently inserted 380 | to most recently insered. 381 | 382 | users : Dict String Int 383 | users = 384 | empty 385 | |> insert "Alice" 28 386 | |> insert "Bob" 19 387 | |> insert "Chuck" 33 388 | 389 | foldr (\name age result -> age :: result) [] users 390 | --> [33,19,28] 391 | 392 | -} 393 | foldr : (k -> v -> b -> b) -> b -> Dict k v -> b 394 | foldr func initialResult (D alist) = 395 | List.foldr 396 | (\( key, value ) result -> 397 | func key value result 398 | ) 399 | initialResult 400 | alist 401 | 402 | 403 | {-| Keep only the key-value pairs that pass the given test. 404 | -} 405 | filter : (k -> v -> Bool) -> Dict k v -> Dict k v 406 | filter isGood (D alist) = 407 | D (List.filter (\( key, value ) -> isGood key value) alist) 408 | 409 | 410 | {-| Partition a dictionary according to some test. The first dictionary 411 | contains all key-value pairs which passed the test, and the second contains 412 | the pairs that did not. 413 | -} 414 | partition : (k -> v -> Bool) -> Dict k v -> ( Dict k v, Dict k v ) 415 | partition isGood (D alist) = 416 | let 417 | ( good, bad ) = 418 | List.partition (\( key, value ) -> isGood key value) alist 419 | in 420 | ( D good, D bad ) 421 | 422 | 423 | 424 | -- LISTS 425 | 426 | 427 | {-| Get all of the keys in a dictionary, in the order that they were inserted 428 | with the most recently inserted key at the head of the list. 429 | 430 | keys (fromList [ ( 0, "Alice" ), ( 1, "Bob" ) ]) 431 | --> [ 0, 1 ] 432 | 433 | -} 434 | keys : Dict k v -> List k 435 | keys (D alist) = 436 | List.map Tuple.first alist 437 | 438 | 439 | {-| Get all of the values in a dictionary, in the order that they were inserted 440 | with the most recently inserted value at the head of the list. 441 | 442 | values (fromList [ ( 0, "Alice" ), ( 1, "Bob" ) ]) 443 | --> [ "Alice", "Bob" ] 444 | 445 | -} 446 | values : Dict k v -> List v 447 | values (D alist) = 448 | List.map Tuple.second alist 449 | 450 | 451 | {-| Convert a dictionary into an association list of key-value pairs, in the 452 | order that they were inserted with the most recently inserted entry at the 453 | head of the list. 454 | -} 455 | toList : Dict k v -> List ( k, v ) 456 | toList (D alist) = 457 | alist 458 | 459 | 460 | {-| Convert an association list into a dictionary. 461 | 462 | If you are using this module as an ordered dictionary, please note that the 463 | elements are inserted from right to left. (If you want to insert the elements 464 | from left to right, you can simply call `List.reverse` on the input before 465 | passing it to `fromList`.) 466 | 467 | toList (fromList [ ( "Alice", 1 ), ( "Bob", 2 ), ( "Alice", 3 ) ]) 468 | --> [ ( "Alice", 1 ), ( "Bob", 2 ) ] 469 | 470 | -} 471 | fromList : List ( k, v ) -> Dict k v 472 | fromList alist = 473 | List.foldr 474 | (\( key, value ) result -> 475 | insert key value result 476 | ) 477 | (D []) 478 | alist 479 | --------------------------------------------------------------------------------