Skip to content

Commit 8017229

Browse files
cuihtlauacCuihtlauac ALVARADOchristinerosesabine
authored
Import Rewrote Set Tutorial from V2 PR (#948)
* Import Rewrote Set Tutorial from V2 PR @NebuPookins rewrote the set tutorial for [V2](https://github.com/ocaml/v2.ocaml.org) in 2021. The PR was neither merged nor rejected: ocaml/v2.ocaml.org#1596 * Apply suggestions from code review Co-authored-by: Christine Rose <christinerose@users.noreply.github.com> * Minor Fixes As pointed by @Octachron * line editing * shorten a lot and present common operations instead of explaining functors * Review edits --------- Co-authored-by: Cuihtlauac ALVARADO <cuihtmlauac@tarides.com> Co-authored-by: Christine Rose <christinerose@users.noreply.github.com> Co-authored-by: Sabine Schmaltz <sabineschmaltz@gmail.com>
1 parent a05a3d2 commit 8017229

File tree

1 file changed

+151
-87
lines changed

1 file changed

+151
-87
lines changed

data/tutorials/language/3ds_03_set.md

Lines changed: 151 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -6,140 +6,204 @@ description: >
66
category: "Data Structures"
77
---
88

9-
# Sets
9+
# Set
1010

11-
## Module Set
12-
To make a set of strings:
11+
## Introduction
1312

13+
`Set` provides the functor `Set.Make`. You must start by passing `Set.Make` a module. It specifies the element type for your set. In return, you get another module with those elements' set operations.
14+
15+
If you need to work with string sets, you must invoke `Set.Make(String)`. That returns a new module.
1416
```ocaml
15-
# module SS = Set.Make(String);;
16-
module SS :
17+
# module StringSet = Set.Make(String);;
18+
module StringSet :
1719
sig
1820
type elt = string
1921
type t = Set.Make(String).t
2022
val empty : t
21-
val is_empty : t -> bool
22-
val mem : elt -> t -> bool
2323
val add : elt -> t -> t
2424
val singleton : elt -> t
2525
val remove : elt -> t -> t
2626
val union : t -> t -> t
2727
val inter : t -> t -> t
28-
val disjoint : t -> t -> bool
29-
val diff : t -> t -> t
30-
val compare : t -> t -> int
31-
val equal : t -> t -> bool
32-
val subset : t -> t -> bool
33-
val iter : (elt -> unit) -> t -> unit
34-
val map : (elt -> elt) -> t -> t
35-
val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a
36-
val for_all : (elt -> bool) -> t -> bool
37-
val exists : (elt -> bool) -> t -> bool
38-
val filter : (elt -> bool) -> t -> t
39-
val filter_map : (elt -> elt option) -> t -> t
40-
val partition : (elt -> bool) -> t -> t * t
41-
val cardinal : t -> int
42-
val elements : t -> elt list
43-
val min_elt : t -> elt
44-
val min_elt_opt : t -> elt option
45-
val max_elt : t -> elt
46-
val max_elt_opt : t -> elt option
47-
val choose : t -> elt
48-
val choose_opt : t -> elt option
49-
val split : elt -> t -> t * bool * t
50-
val find : elt -> t -> elt
51-
val find_opt : elt -> t -> elt option
52-
val find_first : (elt -> bool) -> t -> elt
53-
val find_first_opt : (elt -> bool) -> t -> elt option
54-
val find_last : (elt -> bool) -> t -> elt
55-
val find_last_opt : (elt -> bool) -> t -> elt option
56-
val of_list : elt list -> t
57-
val to_seq_from : elt -> t -> elt Seq.t
58-
val to_seq : t -> elt Seq.t
59-
val to_rev_seq : t -> elt Seq.t
60-
val add_seq : elt Seq.t -> t -> t
61-
val of_seq : elt Seq.t -> t
28+
...
6229
end
6330
```
6431

65-
To create a set you need to start somewhere so here is the empty set:
32+
After naming the newly-created module `StringSet`, OCaml's toplevel displays the module's signature. Since it contains a large number of functions, the output copied here is shortened for brevity (`...`).
33+
34+
This module also defines two types:
35+
- `type elt = string` for the elements, and
36+
- `type t = Set.Make(String).t` for the sets.
37+
38+
## Creating a Set
39+
40+
1. We can create an empty set using `StringSet.empty`:
41+
```ocaml
42+
# StringSet.empty ;;
43+
- : StringSet.t = <abstr>
44+
45+
# StringSet.empty |> StringSet.to_list;;
46+
- : string list = []
47+
```
48+
49+
For `StringSet.empty`, you can see that the OCaml toplevel displays the placeholder `<abstr>` instead of the actual value. However, converting the string set to a list using `StringSet.to_list` results in an empty list.
50+
51+
2. A set with a single element is created using `StringSet.singleton`:
52+
```ocaml
53+
# StringSet.singleton "hello";;
54+
- : StringSet.t = <abstr>
55+
56+
# StringSet.singleton "hello" |> StringSet.to_list;;
57+
- : string list = ["hello"]
58+
```
59+
60+
3. Converting a list into a set using `StringSet.of_list`:
61+
```ocaml
62+
# StringSet.of_list ["hello"; "hi"];;
63+
- : StringSet.t = <abstr>
64+
65+
# StringSet.of_list ["hello"; "hi"] |> StringSet.to_list;;
66+
- : string list = ["hello"; "hi"]
67+
```
68+
69+
There's another relevant function `StringSet.of_seq: string Seq.t -> StringSet.t` that creates a set from a [sequence](/doc/sequences).
70+
71+
## Working With Sets
72+
73+
Let's look at a few functions for working with sets using these two sets.
74+
```ocaml
75+
# let first_set = ["hello"; "hi"] |> StringSet.of_list;;
76+
- : StringSet.t = <abstr>
77+
78+
# let second_set = ["good morning"; "hi"] |> StringSet.of_list;;
79+
- : StringSet.t = <abstr>
80+
```
81+
82+
### Adding an Element to a Set
6683

6784
```ocaml
68-
# let s = SS.empty;;
69-
val s : SS.t = <abstr>
85+
# first_set |> StringSet.add "good morning" |> StringSet.to_list;;
86+
- : string list = ["good morning"; "hello"; "hi"]
7087
```
7188

72-
Alternatively if we know an element to start with we can create a set
73-
like
89+
The function `StringSet.add` with type `string -> StringSet.t -> StringSet.t` takes both a string and a string set. It returns a new string set. Sets created with the `Set.Make` functor in OCaml are immutable, so every time you add or remove an element from a set, a new set is created. The old value is unchanged.
90+
91+
### Removing an Element from a Set
7492

7593
```ocaml
76-
# let s = SS.singleton "hello";;
77-
val s : SS.t = <abstr>
94+
# first_set |> StringSet.remove "hello" |> StringSet.to_list;;
95+
- : string list = ["hi"]
7896
```
7997

80-
To add some elements to the set we can do.
98+
The function `StringSet.remove` with type `string -> StringSet.t -> StringSet.t` takes both a string and a string set. It returns a new string set without the given string.
99+
100+
### Union of Two Sets
81101

82102
```ocaml
83-
# let s =
84-
List.fold_right SS.add ["hello"; "world"; "community"; "manager";
85-
"stuff"; "blue"; "green"] s;;
86-
val s : SS.t = <abstr>
103+
# StringSet.union first_set second_set |> StringSet.to_list;;
104+
- : string list = ["good morning"; "hello"; "hi"]
87105
```
88106

89-
Now if we are playing around with sets we will probably want to see what
90-
is in the set that we have created. To do this we can write a function
91-
that will print the set out.
107+
With the function `StringSet.union`, we can compute the union of two sets.
108+
109+
### Intersection of Two Sets
92110

93111
```ocaml
94-
# let print_set s =
95-
SS.iter print_endline s;;
96-
val print_set : SS.t -> unit = <fun>
112+
# StringSet.inter first_set second_set |> StringSet.to_list;;
113+
- : string list = ["hi"]
97114
```
98115

99-
If we want to remove a specific element of a set there is a remove
100-
function. However if we want to remove several elements at once we could
101-
think of it as doing a 'filter'. Let's filter out all words that are
102-
longer than 5 characters.
116+
With the function `StringSet.inter`, we can compute the intersection of two sets.
103117

104-
This can be written as:
118+
### Subtracting a Set from Another
105119

106120
```ocaml
107-
# let my_filter str =
108-
String.length str <= 5;;
109-
val my_filter : string -> bool = <fun>
110-
# let s2 = SS.filter my_filter s;;
111-
val s2 : SS.t = <abstr>
121+
# StringSet.diff first_set second_set |> StringSet.to_list;;
122+
- : string list = ["hello"]
112123
```
113124

114-
or using an anonymous function:
125+
With the function `StringSet.diff`, we can remove the elements of the second set from the first set.
126+
127+
### Filtering a Set
115128

116129
```ocaml
117-
# let s2 = SS.filter (fun str -> String.length str <= 5) s;;
118-
val s2 : SS.t = <abstr>
130+
# ["good morning"; "hello"; "hi"]
131+
|> StringSet.of_list
132+
|> StringSet.filter (fun str -> String.length str <= 5)
133+
|> StringSet.to_list;;
134+
- : string list = ["hello"; "hi"]
119135
```
120136

121-
If we want to check and see if an element is in the set it might look
122-
like this.
137+
The function `StringSet.filter` of type `(string -> bool) -> StringSet.t -> StringSet.t` creates a new set by keeping the elements that satisfy a predicate from an existing set.
138+
139+
### Checking if an Element is Contained in a Set
123140

124141
```ocaml
125-
# SS.mem "hello" s2;;
142+
# ["good morning"; "hello"; "hi"]
143+
|> StringSet.of_list
144+
|> StringSet.mem "hello";;
126145
- : bool = true
127146
```
128147

129-
The Set module also provides the set theoretic operations union,
130-
intersection and difference. For example, the difference of the original
131-
set and the set with short strings (≤ 5 characters) is the set of long
132-
strings:
148+
To check if an element is contained in a set, use the `StringSet.mem` function.
149+
150+
## Sets With Custom Comparators
151+
152+
The `Set.Make` functor expects a module with two definitions: a type `t`
153+
that represents the element type and the function `compare`,
154+
whose signature is `t -> t -> int`. The
155+
`String` module matches that structure, so we could
156+
directly pass `String` as an argument to `Set.Make`. Incidentally, many
157+
other modules also have that structure, including `Int` and `Float`,
158+
so they too can be directly passed into `Set.Make` to construct a corresponding set module.
159+
160+
The `StringSet` module we created uses the built-in `compare` function provided by the `String` module.
161+
162+
Let's say we want to create a set of strings that performs a case-insensitive
163+
comparison instead of the case-sensitive comparison provided by `String.compare`.
164+
165+
We can accomplish this by passing an ad-hoc module to the `Set.Make` function:
166+
167+
```ocaml
168+
# module CISS = Set.Make(struct
169+
type t = string
170+
let compare a b = compare (String.lowercase_ascii a) (String.lowercase_ascii b)
171+
end);;
172+
```
173+
174+
We name the resulting module `CISS` (short for "Case Insensitive String Set").
175+
176+
You can see that this module has the intended behavior:
133177

134178
```ocaml
135-
# print_set (SS.diff s s2);;
136-
community
137-
manager
138-
- : unit = ()
179+
# CISS.singleton "hello" |> CISS.add "HELLO" |> CISS.to_list;;
180+
- : string list = ["hello"]
139181
```
182+
The value `"HELLO"` is not added to the set because it is considered equal to the value `"hello"`, which is already contained in the set.
183+
184+
You can use any type for elements, as long as you define a meaningful `compare` operation:
185+
```ocaml
186+
# type color = Red | Green | Blue;;
187+
type color = Red | Green | Blue
188+
189+
# module SC = Set.Make(struct
190+
type t = color
191+
let compare a b =
192+
match a, b with
193+
| (Red, Red) -> 0
194+
| (Red, Green) -> 1
195+
| (Red, Blue) -> 1
196+
| (Green, Red) -> -1
197+
| (Green, Green) -> 0
198+
| (Green, Blue) -> 1
199+
| (Blue, Red) -> -1
200+
| (Blue, Green) -> -1
201+
| (Blue, Blue) -> 0
202+
end);;
203+
...
204+
```
205+
206+
## Conclusion
140207

141-
Note that the Set module provides a purely functional data structure:
142-
removing an element from a set does not alter that set but, rather,
143-
returns a new set that is very similar to (and shares much of its
144-
internals with) the original set.
208+
We gave an overview of the `Set` module in OCaml by creating a `StringSet` module using the `Set.Make` functor. Further, we looked at how to create sets based on a custom comparison function. For more information, refer to [Set](https://ocaml.org/api/Set.Make.html) in the Standard Library documentation.
145209

0 commit comments

Comments
 (0)