-
-
Notifications
You must be signed in to change notification settings - Fork 410
Add new concept mapsets and concept exercise gotta-snatch-em-all
#1578
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 5 commits
7cb0fe1
6e03b9f
cd560ba
4988a9f
a271179
74e724f
d6269d3
2829b9e
2a372e2
2f4f915
01c1b53
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| { | ||
| "blurb": "Learn how to use sets in Elixir", | ||
| "authors": [ | ||
| "jiegillet" | ||
| ], | ||
| "contributors": [ | ||
| ] | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| # About | ||
|
|
||
| A `MapSet` is a collection of unique values, representing sets in Elixir. | ||
| It can contain values of any kind, without a notion of order. | ||
|
|
||
| Note that the `Set` module also exists, but is deprecated in favor of `MapSet`. | ||
|
|
||
| You can create sets using `MapSet.new/0`, `MapSet.new/1` and `MapSet.new/2`, and transform them into lists using `MapSet.to_list/1`. | ||
|
|
||
| ```elixir | ||
| MapSet.new() | ||
| # => MapSet.new([]) | ||
|
|
||
| MapSet.new([2, 3, 3, 3, 1, 1, 2, "hello"]) | ||
| # => MapSet.new([1, 2, 3, "hello"]) | ||
|
|
||
| MapSet.new([2, 3, 3, 3, 1, 1, 2], fn n -> 10 * n end) | ||
| # => MapSet.new([10, 20, 30]) | ||
|
|
||
| [2, 3, 3, 3, 1, 1, 2] |> MapSet.new() |> MapSet.to_list() | ||
| # => [1, 2, 3] | ||
| ``` | ||
|
|
||
| Note that since `MapSet`s do not have a notion of order, `MapSet.to_list/1` is not guaranteed to return a sorted list. | ||
|
|
||
| You can add or remove elements with `MapSet.put/2` and `MapSet.delete/2`. | ||
|
|
||
| You can query the contents of a set with the functions `MapSet.size/1`, `MapSet.member?/2`, and compare sets with `MapSet.equal?/2`, `MapSet.subset?/2` and `MapSet.disjoint?/2`. | ||
|
|
||
| ```elixir | ||
| a = MapSet.new([1, 10]) | ||
| b = MapSet.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) | ||
|
|
||
| MapSet.size(b) | ||
| # => 10 | ||
|
|
||
| MapSet.member?(a, 10) | ||
| # => true | ||
|
|
||
| MapSet.subset?(a, b) | ||
| # => true | ||
|
|
||
| MapSet.disjoint?(a, b) | ||
| # => false | ||
| ``` | ||
|
|
||
| MapSets can be combined with `MapSet.union/2`, `MapSet.intersection/2`, `MapSet.difference/2` and `MapSet.symmetric_difference/2`. | ||
|
|
||
| ```elixir | ||
| a = MapSet.new([1, 10, 100]) | ||
| b = MapSet.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) | ||
|
|
||
| MapSet.union(a, b) | ||
| # => MapSet.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 100]) | ||
|
|
||
| MapSet.intersection(a, b) | ||
| # => MapSet.new([1, 10]) | ||
|
|
||
| MapSet.difference(a, b) | ||
| # => MapSet.new([100]) | ||
|
|
||
| MapSet.difference(b, a) | ||
| # => MapSet.new([2, 3, 4, 5, 6, 7, 8, 9]) | ||
|
|
||
| MapSet.symmetric_difference(b, a) | ||
| # => MapSet.new([2, 3, 4, 5, 6, 7, 8, 9, 100]) | ||
| ``` | ||
|
|
||
| You can filter and partition sets with `MapSet.filter/2`, `MapSet.reject/2` and `MapSet.split_with/2` (from Elixir 1.15). | ||
|
|
||
| ```elixir | ||
| a = MapSet.new([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) | ||
| even = fn n -> rem(n, 2) == 0 end | ||
|
|
||
| MapSet.filter(a, even) | ||
| # => MapSet.new([2, 4, 6, 8, 10]) | ||
|
|
||
| MapSet.reject(a, even) | ||
| # => MapSet.new([1, 3, 5, 7, 9]) | ||
|
|
||
| MapSet.split_with(a, even) | ||
| # => {MapSet.new([2, 4, 6, 8, 10]), MapSet.new([1, 3, 5, 7, 9])} | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Introduction | ||
|
|
||
| TODO - copy and maybe trim text from about.md after review |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [ | ||
| { | ||
| "url": "https://hexdocs.pm/elixir/MapSet.html", | ||
| "description": "MapSet module documentation" | ||
| } | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -681,6 +681,23 @@ | |
| "if" | ||
| ], | ||
| "status": "beta" | ||
| }, | ||
| { | ||
| "slug": "gotta-snatch-em-all", | ||
| "name": "Gotta Snatch'Em All", | ||
| "uuid": "e1059f7a-d75f-4141-af19-fe548449167f", | ||
| "concepts": [ | ||
| "mapsets" | ||
| ], | ||
| "prerequisites": [ | ||
| "strings", | ||
| "lists", | ||
| "enum", | ||
| "tuples", | ||
| "pattern-matching", | ||
| "booleans" | ||
| ], | ||
| "status": "beta" | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Lol, there are concept exercises are in "beta"? Maybe we should just make all of the "active" |
||
| } | ||
| ], | ||
| "practice": [ | ||
|
|
@@ -2334,7 +2351,8 @@ | |
| "multiple-clause-functions", | ||
| "pattern-matching", | ||
| "guards", | ||
| "enum" | ||
| "enum", | ||
| "mapsets" | ||
| ], | ||
| "difficulty": 5 | ||
| }, | ||
|
|
@@ -2347,6 +2365,7 @@ | |
| ], | ||
| "prerequisites": [ | ||
| "randomness", | ||
| "mapsets", | ||
| "dates-and-time", | ||
| "lists", | ||
| "enum", | ||
|
|
@@ -2492,10 +2511,13 @@ | |
| "slug": "relative-distance", | ||
| "name": "Relative Distance", | ||
| "uuid": "ba1d165a-774f-4b8f-9763-2d4279769e75", | ||
| "practices": [], | ||
| "practices": [ | ||
| "mapsets" | ||
| ], | ||
| "prerequisites": [ | ||
| "recursion", | ||
| "maps", | ||
| "mapsets", | ||
| "tuples", | ||
| "lists", | ||
| "list-comprehensions", | ||
|
|
@@ -2531,7 +2553,8 @@ | |
| "name": "Satellite", | ||
| "uuid": "a07e01e6-7cea-4db9-a514-7c95788e90a9", | ||
| "practices": [ | ||
| "recursion" | ||
| "recursion", | ||
| "mapsets" | ||
| ], | ||
| "prerequisites": [ | ||
| "pattern-matching", | ||
|
|
@@ -2540,7 +2563,8 @@ | |
| "recursion", | ||
| "atoms", | ||
| "lists", | ||
| "enum" | ||
| "enum", | ||
| "mapsets" | ||
| ], | ||
| "difficulty": 6 | ||
| }, | ||
|
|
@@ -2767,8 +2791,11 @@ | |
| "slug": "two-bucket", | ||
| "name": "Two Bucket", | ||
| "uuid": "07b4679a-3d9f-42bc-ad1e-b9ba272a1c15", | ||
| "practices": [], | ||
| "practices": [ | ||
| "mapsets" | ||
| ], | ||
| "prerequisites": [ | ||
| "mapsets", | ||
| "atoms", | ||
| "tuples", | ||
| "integers", | ||
|
|
@@ -3048,6 +3075,7 @@ | |
| "integers", | ||
| "list-comprehensions", | ||
| "maps", | ||
| "mapsets", | ||
| "pattern-matching", | ||
| "pipe-operator", | ||
| "recursion" | ||
|
|
@@ -3090,7 +3118,8 @@ | |
| "if", | ||
| "enum", | ||
| "maps", | ||
| "atoms" | ||
| "atoms", | ||
| "mapsets" | ||
| ], | ||
| "difficulty": 9 | ||
| }, | ||
|
|
@@ -3308,6 +3337,11 @@ | |
| "slug": "lists", | ||
| "name": "Lists" | ||
| }, | ||
| { | ||
| "uuid": "8c1fcb63-5e43-49c9-b1f9-46ceec099c28", | ||
| "slug": "mapsets", | ||
| "name": "MapSets" | ||
| }, | ||
| { | ||
| "uuid": "c1dca378-b124-4b9b-8822-ddfa23643047", | ||
| "slug": "maps", | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # Hints | ||
|
|
||
| ## General | ||
|
|
||
| - Read the documentation for [MapSet][mapset] | ||
| - The documentation for [Enum][enum] might be useful too | ||
|
|
||
| ## 1. Start a collection | ||
|
|
||
| - [This is the most appropriate `MapSet` function][new] to solve the task | ||
|
|
||
| ## 2. Grow the collection | ||
|
|
||
| - Use [this `MapSet` function][put] | ||
| - Use [that `MapSet` function][member] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: It would be nice if there was a better indicator of the difference between those two functions than just "this" and "that". Maybe describing briefly their purpose, like the other links? "To add card to a collection...", "To check whether...". (Same nitpick about step 5 hints) |
||
|
|
||
| ## 3. Start trading | ||
|
|
||
| - To check if you can trade a card, use [this `MapSet` function][member] | ||
| - To update your collection, use [this `MapSet` function][delete] and [that one][put] | ||
|
|
||
| ## 4. There can be only one of each | ||
|
|
||
| - A `MapSet` has the property of only holding unique values | ||
| - Use [this `MapSet` function][new] to create a set and [that one][to_list] to turn it back into a list | ||
| - `MapSet`s do not have a notion of order, you should use [this `Enum` function][sort] to sort the cards, even if the tests pass without it | ||
|
|
||
| ## 5. Cards they don't have | ||
|
|
||
| - Use the [`MapSet.difference/2` function][difference] | ||
| - Use the [`MapSet.size/1` function][size] | ||
|
|
||
| ## 6. Cards they all have | ||
|
|
||
| - [This `Enum` function][reduce] will be helpful | ||
| - Use pattern matching on the list to differentiate empty and non-empty lists | ||
| - [This `MapSet` function][intersection] will help you keep the common cards | ||
| - [This `MapSet` function][to_list] can create a list | ||
| - `MapSet`s do not have a notion of order, you should use [this `Enum` function][sort] to sort the cards, even if the tests pass without it | ||
|
|
||
| ## 7. All of the cards | ||
|
|
||
| - [This `Enum` function][reduce] will be helpful | ||
| - Use [this `MapSet` function][new_empty] as accumulator | ||
| - [This `MapSet` function][union] will help you accumulate all cards | ||
| - [This `MapSet` function][size] will count how many cars you have | ||
|
|
||
| ## 8. Shiny for the win | ||
|
|
||
| - Use [this `MapSet` function][split_with] to separate your collection (from Elixir 1.15, otherwise, [this][filter] and [that][reject] function can be used instead) | ||
| - Use [this `String` function][starts_with] to detect shiny cards | ||
| - [This `MapSet` function][to_list] can create a list | ||
| - `MapSet`s do not have a notion of order, you should use [this `Enum` function][sort] to sort the cards, even if the tests pass without it | ||
|
|
||
| [mapset]: https://hexdocs.pm/elixir/MapSet.html | ||
| [new]: https://hexdocs.pm/elixir/MapSet.html#new/1 | ||
| [member]: https://hexdocs.pm/elixir/MapSet.html#member?/2 | ||
| [put]: https://hexdocs.pm/elixir/MapSet.html#put/2 | ||
| [delete]: https://hexdocs.pm/elixir/MapSet.html#delete/2 | ||
| [to_list]: https://hexdocs.pm/elixir/MapSet.html#to_list/1 | ||
| [difference]: https://hexdocs.pm/elixir/MapSet.html#difference/2 | ||
| [size]: https://hexdocs.pm/elixir/MapSet.html#size/1 | ||
| [intersection]: https://hexdocs.pm/elixir/MapSet.html#intersection/2 | ||
| [new_empty]: https://hexdocs.pm/elixir/MapSet.html#new/0 | ||
| [union]: https://hexdocs.pm/elixir/MapSet.html#union/2 | ||
| [split_with]: https://hexdocs.pm/elixir/MapSet.html#split_with/2 | ||
| [filter]: https://hexdocs.pm/elixir/MapSet.html#filter/2 | ||
| [reject]: https://hexdocs.pm/elixir/MapSet.html#reject/2 | ||
| [starts_with]: https://hexdocs.pm/elixir/String.html#starts_with?/2 | ||
| [enum]: https://hexdocs.pm/elixir/Enum.html | ||
| [reduce]: https://hexdocs.pm/elixir/Enum.html#reduce/3 | ||
| [sort]: https://hexdocs.pm/elixir/Enum.html#sort/1 | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,100 @@ | ||||||
| # Instructions | ||||||
|
|
||||||
| Your nostalgia for Blorkemon™️ cards is showing no sign of slowing down, you even started collecting them again, and you are getting your friends to join you. | ||||||
|
|
||||||
| In this exercise, a card collection is represented by a `MapSet`, since duplicate cards are not important when your goal is to get all existing cards. | ||||||
|
|
||||||
| ## 1. Start a collection | ||||||
|
|
||||||
| You really want your friends to join your Blorkemon™️ madness, and the best way is to kickstart their collection by giving them one card. | ||||||
|
|
||||||
| Implement `new_collection`, which transforms a card into a collection. | ||||||
|
|
||||||
| ```elixir | ||||||
| new_collection("Newthree") | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think in all Elixir concept exercises, we demo the function calls using the module name too
Suggested change
|
||||||
| # => MapSet.new(["Newthree"]) | ||||||
| ``` | ||||||
|
|
||||||
| ## 2. Grow the collection | ||||||
|
|
||||||
| Once you have a collection, it takes a life of its own and must grow. | ||||||
|
|
||||||
| Implement `add_card`, which takes a card and a collection, and returns a tuple with two values: a boolean that indicates if the card was already in the collection, and the collection with the card added. | ||||||
|
|
||||||
| ```elixir | ||||||
| add_card("Scientuna", MapSet.new(["Newthree"])) | ||||||
| # => {false, MapSet.new(["Newthree", "Scientuna"])} | ||||||
| ``` | ||||||
|
|
||||||
| ## 3. Start trading | ||||||
|
|
||||||
| Now that your friends are Blorkemon™️ crazy again, you can use this to grow your own collection by trading cards. | ||||||
|
|
||||||
| Not every trade is worth doing, or can be done at all. | ||||||
| You cannot trade a card you don't have, and you shouldn't trade a card for one that you already have. | ||||||
|
|
||||||
| Implement `trade_card`, that takes two cards to trade (yours and theirs) and your current collection. | ||||||
| The return value is a tuple of two values: a `Bool` stating if the trade is possible and worth doing, and the collection you would end up with if you did the trade (even if it's not actually possible). | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick:
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch, it's a leftover from the Elm instructions, where |
||||||
|
|
||||||
| ```elixir | ||||||
| trade_card("Scientuna", "Newthree", MapSet.new(["Scientuna"])) | ||||||
| # => {true, MapSet.new(["Newthree"])} | ||||||
| ``` | ||||||
|
|
||||||
| ## 4. There can be only one of each | ||||||
|
|
||||||
| You find an old stash of cards in a flea market. | ||||||
| You must sort the cards and remove the duplicates to compare the list to your collection. | ||||||
|
|
||||||
| Implement `remove_duplicates` which will sort a list of cards and return a list of sorted, unique cards. | ||||||
|
|
||||||
| ```elixir | ||||||
| remove_duplicates(["Newthree", "Newthree", "Newthree", "Scientuna"]) | ||||||
| # => ["Newthree", "Scientuna"] | ||||||
| ``` | ||||||
|
|
||||||
| ## 5. Cards they don't have | ||||||
|
|
||||||
| Time to feel good about your collection. | ||||||
|
|
||||||
| Implement `extra_cards`, which takes your collection and some other collection, and returns the number of cards that the other collection doesn't have. | ||||||
|
|
||||||
| ```elixir | ||||||
| extra_cards(MapSet.new(["Scientuna"]), MapSet.new(["Newthree", "Scientuna"])) | ||||||
| # => 0 | ||||||
| ``` | ||||||
|
|
||||||
| ## 6. Cards they all have | ||||||
|
|
||||||
| You and your Blorkemon™️ enthusiast friends gather and wonder which cards are the most common. | ||||||
|
|
||||||
| Implement `boring_cards`, which takes a list of collections and returns a list of sorted cards that all collections have. | ||||||
|
|
||||||
| ```elixir | ||||||
| boring_cards([MapSet.new(["Scientuna"]), MapSet.new(["Newthree", "Scientuna"])]) | ||||||
| # => ["Scientuna"] | ||||||
| ``` | ||||||
|
|
||||||
| ## 7. All of the cards | ||||||
|
|
||||||
| Do you and your friends collectively own all of the Blorkemon™️ cards? | ||||||
|
|
||||||
| Implement `total_cards`, which takes a list of collections and returns the total number of different cards in the all of the collections. | ||||||
|
|
||||||
| ```elixir | ||||||
| total_cards([MapSet.new(["Scientuna"]), MapSet.new(["Newthree", "Scientuna"])]) | ||||||
| # => 2 | ||||||
| ``` | ||||||
|
|
||||||
| ## 8. Shiny for the win | ||||||
|
|
||||||
| You nephew is coming to visit you soon, and you feel like impressing him. | ||||||
|
jiegillet marked this conversation as resolved.
Outdated
|
||||||
| Kids like shiny things right? | ||||||
| Blorkemon™️ cards can be shiny! | ||||||
|
|
||||||
| Implement `split_shiny_cards`, which takes a collection and returns a tuple with two lists of sorted cards: one with all the cards that start with `"Shiny"` and one with the other cards. | ||||||
|
|
||||||
| ```elixir | ||||||
| split_shiny_cards(MapSet.new(["Newthree", "Scientuna", "Shiny Scientuna"])) | ||||||
| # => {["Shiny Scientuna"], ["Newthree", "Scientuna"]} | ||||||
| ``` | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had to do a double-take to believe that this is how the REPL prints that value 😂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it used to be something like
#MapSet<>or whatever, but they've been moving away from these notations to things that you can copy/paste. In that sense, this is the best way to represent sets :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's so clever! I've always been annoyed at those
#things that I couldn't copy-paste. I think they showed up most often for Ecto structs.