roc/crates/compiler/builtins/roc/Set.roc
2023-05-24 14:13:40 -05:00

381 lines
8.5 KiB
Text

interface Set
exposes [
Set,
empty,
single,
walk,
walkUntil,
insert,
len,
remove,
contains,
toList,
fromList,
union,
intersection,
difference,
]
imports [
List,
Bool.{ Bool, Eq },
Dict.{ Dict },
Num.{ Nat },
Hash.{ Hash, Hasher },
]
## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
## type which stores a collection of unique values, without any ordering
Set k := Dict.Dict k {} | k has Hash & Eq
has [
Eq {
isEq,
},
Hash {
hash: hashSet,
},
]
isEq : Set k, Set k -> Bool | k has Hash & Eq
isEq = \xs, ys ->
if len xs != len ys then
Bool.false
else
walkUntil xs Bool.true \_, elem ->
if contains ys elem then
Continue Bool.true
else
Break Bool.false
hashSet : hasher, Set k -> hasher | k has Hash & Eq, hasher has Hasher
hashSet = \hasher, @Set inner -> Hash.hash hasher inner
## Creates a new empty `Set`.
## ```
## emptySet = Set.empty {}
## countValues = Set.len emptySet
##
## expect countValues == 0
## ```
empty : {} -> Set k | k has Hash & Eq
empty = \{} -> @Set (Dict.empty {})
## Creates a new `Set` with a single value.
## ```
## singleItemSet = Set.single "Apple"
## countValues = Set.len singleItemSet
##
## expect countValues == 1
## ```
single : k -> Set k | k has Hash & Eq
single = \key ->
Dict.single key {} |> @Set
## Insert a value into a `Set`.
## ```
## fewItemSet =
## Set.empty {}
## |> Set.insert "Apple"
## |> Set.insert "Pear"
## |> Set.insert "Banana"
##
## countValues = Set.len fewItemSet
##
## expect countValues == 3
## ```
insert : Set k, k -> Set k | k has Hash & Eq
insert = \@Set dict, key ->
Dict.insert dict key {} |> @Set
# Inserting a duplicate key has no effect.
expect
actual =
empty {}
|> insert "foo"
|> insert "bar"
|> insert "foo"
|> insert "baz"
expected =
empty {}
|> insert "foo"
|> insert "bar"
|> insert "baz"
expected == actual
## Counts the number of values in a given `Set`.
## ```
## fewItemSet =
## Set.empty {}
## |> Set.insert "Apple"
## |> Set.insert "Pear"
## |> Set.insert "Banana"
##
## countValues = Set.len fewItemSet
##
## expect countValues == 3
## ```
len : Set k -> Nat | k has Hash & Eq
len = \@Set dict ->
Dict.len dict
# Inserting a duplicate key has no effect on length.
expect
actual =
empty {}
|> insert "foo"
|> insert "bar"
|> insert "foo"
|> insert "baz"
|> len
actual == 3
## Removes the value from the given `Set`.
## ```
## numbers =
## Set.empty {}
## |> Set.insert 10
## |> Set.insert 20
## |> Set.remove 10
##
## has10 = Set.contains numbers 10
## has20 = Set.contains numbers 20
##
## expect has10 == Bool.false
## expect has20 == Bool.true
## ```
remove : Set k, k -> Set k | k has Hash & Eq
remove = \@Set dict, key ->
Dict.remove dict key |> @Set
## Test if a value is in the `Set`.
## ```
## Fruit : [Apple, Pear, Banana]
##
## fruit : Set Fruit
## fruit =
## Set.single Apple
## |> Set.insert Pear
##
## hasApple = Set.contains fruit Apple
## hasBanana = Set.contains fruit Banana
##
## expect hasApple == Bool.true
## expect hasBanana == Bool.false
## ```
contains : Set k, k -> Bool | k has Hash & Eq
contains = \@Set dict, key ->
Dict.contains dict key
## Retrieve the values in a `Set` as a `List`.
## ```
## numbers : Set U64
## numbers = Set.fromList [1,2,3,4,5]
##
## values = [1,2,3,4,5]
##
## expect Set.toList numbers == values
## ```
toList : Set k -> List k | k has Hash & Eq
toList = \@Set dict ->
Dict.keys dict
## Create a `Set` from a `List` of values.
## ```
## values =
## Set.empty {}
## |> Set.insert Banana
## |> Set.insert Apple
## |> Set.insert Pear
##
## expect Set.fromList [Pear, Apple, Banana] == values
## ```
fromList : List k -> Set k | k has Hash & Eq
fromList = \list ->
initial = @Set (Dict.withCapacity (List.len list))
List.walk list initial insert
## Combine two `Set` collection by keeping the
## [union](https://en.wikipedia.org/wiki/Union_(set_theory))
## of all the values pairs. This means that all of the values in both `Set`s
## will be combined.
## ```
## set1 = Set.single Left
## set2 = Set.single Right
##
## expect Set.union set1 set2 == Set.fromList [Left, Right]
## ```
union : Set k, Set k -> Set k | k has Hash & Eq
union = \@Set dict1, @Set dict2 ->
Dict.insertAll dict1 dict2 |> @Set
## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
## of all the values pairs. This means that we keep only those values that are
## in both `Set`s.
## ```
## set1 = Set.fromList [Left, Other]
## set2 = Set.fromList [Left, Right]
##
## expect Set.intersection set1 set2 == Set.single Left
## ```
intersection : Set k, Set k -> Set k | k has Hash & Eq
intersection = \@Set dict1, @Set dict2 ->
Dict.keepShared dict1 dict2 |> @Set
## Remove the values in the first `Set` that are also in the second `Set`
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
## of the values. This means that we will be left with only those values that
## are in the first and not in the second.
## ```
## first = Set.fromList [Left, Right, Up, Down]
## second = Set.fromList [Left, Right]
##
## expect Set.difference first second == Set.fromList [Up, Down]
## ```
difference : Set k, Set k -> Set k | k has Hash & Eq
difference = \@Set dict1, @Set dict2 ->
Dict.removeAll dict1 dict2 |> @Set
## Iterate through the values of a given `Set` and build a value.
## ```
## values = Set.fromList ["March", "April", "May"]
##
## startsWithLetterM = \month ->
## when Str.toUtf8 month is
## ['M', ..] -> Bool.true
## _ -> Bool.false
##
## reduce = \state, k ->
## if startsWithLetterM k then
## state + 1
## else
## state
##
## result = Set.walk values 0 reduce
##
## expect result == 2
## ```
walk : Set k, state, (state, k -> state) -> state | k has Hash & Eq
walk = \@Set dict, state, step ->
Dict.walk dict state (\s, k, _ -> step s k)
## Iterate through the values of a given `Set` and build a value, can stop
## iterating part way through the collection.
## ```
## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10]
##
## find42 = \state, k ->
## if k == 42 then
## Break FoundTheAnswer
## else
## Continue state
##
## result = Set.walkUntil numbers NotFound find42
##
## expect result == FoundTheAnswer
## ```
walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state | k has Hash & Eq
walkUntil = \@Set dict, state, step ->
Dict.walkUntil dict state (\s, k, _ -> step s k)
expect
first =
single "Keep Me"
|> insert "And Me"
|> insert "Remove Me"
second =
single "Remove Me"
|> insert "I do nothing..."
expected =
single "Keep Me"
|> insert "And Me"
difference first second == expected
expect
first =
single "Keep Me"
|> insert "And Me"
|> insert "Remove Me"
second =
single "Remove Me"
|> insert "I do nothing..."
expected =
single "Keep Me"
|> insert "And Me"
difference first second == expected
expect
first =
single 1
|> insert 2
second =
single 1
|> insert 3
|> insert 4
expected =
single 1
|> insert 2
|> insert 3
|> insert 4
union first second == expected
expect
base =
single "Remove Me"
|> insert "Keep Me"
|> insert "And Me"
expected =
single "Keep Me"
|> insert "And Me"
remove base "Remove Me" == expected
expect
x =
single 0
|> insert 1
|> insert 2
|> insert 3
|> insert 4
|> insert 5
|> insert 6
|> insert 7
|> insert 8
|> insert 9
x == fromList (toList x)
expect
orderOne : Set Nat
orderOne =
single 1
|> insert 2
orderTwo : Set Nat
orderTwo =
single 2
|> insert 1
wrapperOne : Set (Set Nat)
wrapperOne =
single orderOne
|> insert orderTwo
wrapperTwo : Set (Set Nat)
wrapperTwo =
single orderTwo
|> insert orderOne
wrapperOne == wrapperTwo