diff --git a/concepts/hashes/.meta/config.json b/concepts/hashes/.meta/config.json new file mode 100644 index 0000000000..36312afa35 --- /dev/null +++ b/concepts/hashes/.meta/config.json @@ -0,0 +1,5 @@ +{ + "blurb": "Hashes are collections of key-value pairs, where each unique key maps to a specific value. They are useful for storing and retrieving data based on keys rather than numerical indices.", + "authors": ["meatball133"], + "contributors": ["kotp"] +} diff --git a/concepts/hashes/about.md b/concepts/hashes/about.md new file mode 100644 index 0000000000..acc21b26fd --- /dev/null +++ b/concepts/hashes/about.md @@ -0,0 +1,203 @@ +# About + +[Hashes][hash] are also known as dictionary or map in other languages, they are a mutable unsorted collection which maps keys to values. +Each key is unique and is used to retrieve the corresponding value. +The keys can be of any data type which is hashable (has a `hash` method), this includes strings, numbers, and most data types and objects in Ruby. + +Even though Hashes are unordered collections, [Ruby maintains the insertion order of key-value pairs][entry_order]. +This means that when you iterate over a `Hash`, the pairs will be returned in the order they were added. +However, deleting elements may affect the order of remaining elements. +Hashes behavior of maintaining insertion order was introduced in Ruby [1.9][ruby-1.9]. + +## Creating Hashes + +You can create a `Hash` using curly braces `{}` with key-value pairs formed as `key => value` and separated by commas: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +``` + +You can also mix and match different types of keys and values: + +```ruby +my_hash = {1 => "one", :two => 2, "three" => [3, "three"]} +``` + +Alternatively if the keys are symbols, you can use a more the newer syntax which was introcued in Ruby 1.9: + +```ruby +my_hash = {name: "Alice", age: 30, city: "New York"} +``` + +You can create an empty `Hash` using the `Hash.new` method: + +```ruby +empty_hash = Hash.new +``` + +## Accessing values + +You can access values in a `Hash` instance using its corresponding keys, the syntax reminds of array indexing, but using the key instead of an index: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +my_hash["name"] +# => "Alice" +``` + +If the key does not exist in the `Hash` instance, the `[]` method will return `nil`: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash["city"] +# => nil +``` + +If the disired behavior is to not return `nil` for non-existing keys, another way of accessing values is by using the [`fetch`][fetch] method, which allows you provide a default value for non-existing keys. +If the `fetch` method is used without a default value and the key does not exist, it will raise a `KeyError` exception. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.fetch("city", "Unknown") +# => "Unknown" + +my_hash.fetch("city") +# => KeyError: key not found: "city" +``` + +## Modifying + +You can add or update key-value pairs in a `Hash` instance by assigning a value to a key using the assignment operator `=`. +Assigning a value to an existing key will update the value, while assigning a value to a new key will add a new key-value pair: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash["city"] = "New York" +my_hash["age"] = 31 + +my_hash +# => {"name" => "Alice", "age" => 31, "city" => "New York"} +``` + +## Default values + +When fetching a value with `[]` for a key that does not exist in the `Hash` instance, Ruby returns `nil` by default. + +```ruby +my_hash = {"name" => "Alice"} +my_hash["age"] +# => nil +``` + +This might not always be desirable, so you can set a default value for the `Hash` instance using `Hash.new(default_value)`. +See that the default value is returned only for keys that do not exist in the `Hash` instance. + +```ruby +my_hash = Hash.new("unknown") +my_hash["name"] = "Alice" +my_hash["age"] +# => "unknown" + +my_hash["name"] +# => "Alice" +``` + +~~~~exercism/note +Be cautious when using mutable objects (like Arrays or other Hashes) as default values, as they can lead to unexpected behavior. +~~~~ + +## Deleting key-value pairs + +You can delete a key-value pair from a `Hash` instance using the [`delete`][delete] method, which takes the key as an argument: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +my_hash.delete("age") +my_hash +# => {"name" => "Alice", "city" => "New York"} +``` + +## Methods + +There are several useful instance methods available for Hashes in Ruby. +Here are some commonly used ones: + +### `has_value?` and `include?` + +You can check if a `Hash` instance contains a specific value using the [`has_value?`][has_value?] method. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.has_value?(30) +# => true +my_hash.has_value?(25) +# => false +``` + +You can check if a `Hash` instance contains a specific key using the [`include?`][include?] method. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.include?("name") +# => true +my_hash.include?("city") +# => false +``` + +### `keys` and `values` + +You can retrieve all the keys of a `Hash` instance using the [`keys`][keys] method, which returns an array of keys. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.keys +# => ["name", "age"] +``` + +You can retrieve all the values of a `Hash` instance using the [`values`][values] method, which returns an array of values. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.values +# => ["Alice", 30] +``` + +## Iterating over Hashes + +You can iterate over the key-value pairs in a `Hash` instance using the `each_pair` method. +This will give you access to both the key and the value for each pair: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.each_pair do |key, value| + puts "#{key}: #{value}" +end +# Output: +# name: Alice +# age: 30 +``` + +You can also iterate over just the keys or just the values using the [`each_key`][each_key] or [`each_value`][each_value] methods, respectively: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.each_key do |key| + puts key +end +# Output: +# name +# age +``` + +[entry_order]: https://docs.ruby-lang.org/en/master/Hash.html#class-Hash-label-Entry+Order +[each_pair]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_pair +[each_key]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_key +[keys]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-keys +[each_value]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_value +[values]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-values +[has_value?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-has_value-3F +[include?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-include-3F +[hash]: https://docs.ruby-lang.org/en/master/Hash.html +[fetch]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-fetch +[delete]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-delete +[ruby-1.9]: https://ruby-doc.org/3.4/NEWS/NEWS-1_9_1.html diff --git a/concepts/hashes/introduction.md b/concepts/hashes/introduction.md new file mode 100644 index 0000000000..dd30523ecb --- /dev/null +++ b/concepts/hashes/introduction.md @@ -0,0 +1,176 @@ +# About + +[Hashes][hash] are also known as dictionary or map in other languages, they are a mutable unsorted collection which maps keys to values. +Each key is unique and is used to retrieve the corresponding value. +The keys can be of any data type which is hashable (has a `hash` method), this includes strings, numbers, and most data types and objects in Ruby. + +Even though Hashes are unordered collections, [Ruby maintains the insertion order of key-value pairs][entry_order]. +This means that when you iterate over a `Hash`, the pairs will be returned in the order they were added. +However, deleting elements may affect the order of remaining elements. +Hashes behavior of maintaining insertion order was introduced in Ruby [1.9][ruby-1.9]. + +## Creating Hashes + +You can create a `Hash` using curly braces `{}` with key-value pairs formed as `key => value` and separated by commas: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +``` + +You can also mix and match different types of keys and values: + +```ruby +my_hash = {1 => "one", :two => 2, "three" => [3, "three"]} +``` + +Alternatively if the keys are symbols, you can use a more the newer syntax which was introcued in Ruby 1.9: + +```ruby +my_hash = {name: "Alice", age: 30, city: "New York"} +``` + +You can create an empty `Hash` using the `Hash.new` method: + +```ruby +empty_hash = Hash.new +``` + +## Accessing values + +You can access values in a `Hash` instance using its corresponding keys, the syntax reminds of array indexing, but using the key instead of an index: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +my_hash["name"] +# => "Alice" +``` + +If the key does not exist in the `Hash` instance, the `[]` method will return `nil`: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash["city"] +# => nil +``` + +If the disired behavior is to not return `nil` for non-existing keys, another way of accessing values is by using the [`fetch`][fetch] method, which allows you provide a default value for non-existing keys. +If the `fetch` method is used without a default value and the key does not exist, it will raise a `KeyError` exception. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.fetch("city", "Unknown") +# => "Unknown" + +my_hash.fetch("city") +# => KeyError: key not found: "city" +``` + +## Modifying + +You can add or update key-value pairs in a `Hash` instance by assigning a value to a key using the assignment operator `=`. +Assigning a value to an existing key will update the value, while assigning a value to a new key will add a new key-value pair: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash["city"] = "New York" +my_hash["age"] = 31 + +my_hash +# => {"name" => "Alice", "age" => 31, "city" => "New York"} +``` + +## Deleting key-value pairs + +You can delete a key-value pair from a `Hash` instance using the [`delete`][delete] method, which takes the key as an argument: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +my_hash.delete("age") +my_hash +# => {"name" => "Alice", "city" => "New York"} +``` + +## Methods + +There are several useful instance methods available for Hashes in Ruby. +Here are some commonly used ones: + +### `has_value?` and `include?` + +You can check if a `Hash` instance contains a specific value using the [`has_value?`][has_value?] method. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.has_value?(30) +# => true +my_hash.has_value?(25) +# => false +``` + +You can check if a `Hash` instance contains a specific key using the [`include?`][include?] method. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.include?("name") +# => true +my_hash.include?("city") +# => false +``` + +### `keys` and `values` + +You can retrieve all the keys of a `Hash` instance using the [`keys`][keys] method, which returns an array of keys. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.keys +# => ["name", "age"] +``` + +You can retrieve all the values of a `Hash` instance using the [`values`][values] method, which returns an array of values. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.values +# => ["Alice", 30] +``` + +## Iterating over Hashes + +You can iterate over the key-value pairs in a `Hash` instance using the `each_pair` method. +This will give you access to both the key and the value for each pair: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.each_pair do |key, value| + puts "#{key}: #{value}" +end +# Output: +# name: Alice +# age: 30 +``` + +You can also iterate over just the keys or just the values using the [`each_key`][each_key] or [`each_value`][each_value] methods, respectively: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.each_key do |key| + puts key +end +# Output: +# name +# age +``` + +[entry_order]: https://docs.ruby-lang.org/en/master/Hash.html#class-Hash-label-Entry+Order +[each_pair]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_pair +[each_key]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_key +[keys]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-keys +[each_value]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_value +[values]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-values +[has_value?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-has_value-3F +[include?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-include-3F +[hash]: https://docs.ruby-lang.org/en/master/Hash.html +[fetch]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-fetch +[delete]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-delete +[ruby-1.9]: https://ruby-doc.org/3.4/NEWS/NEWS-1_9_1.html diff --git a/concepts/hashes/links.json b/concepts/hashes/links.json new file mode 100644 index 0000000000..7125c42ff8 --- /dev/null +++ b/concepts/hashes/links.json @@ -0,0 +1,10 @@ +[ + { + "url": "https://docs.ruby-lang.org/en/4.0/Hash.html", + "description": "Ruby docs: Hash" + }, + { + "url": "https://www.geeksforgeeks.org/ruby/ruby-hash-class/", + "description": "GeeksforGeeks: Ruby Hash Class" + } +] diff --git a/config.json b/config.json index 398f48312f..cc28dfa7fa 100644 --- a/config.json +++ b/config.json @@ -166,6 +166,17 @@ "enumeration" ] }, + { + "slug": "gross-store", + "name": "Gross Store", + "uuid": "8a1dcca6-bccc-4684-8e77-ea3d77b44999", + "concepts": [ + "hashes" + ], + "prerequisites": [ + "enumeration" + ] + }, { "slug": "boutique-inventory-improvements", "name": "Boutique Inventory Improvements", @@ -174,7 +185,7 @@ "ostruct" ], "prerequisites": [ - "advanced-enumeration" + "hashes" ] }, { @@ -185,7 +196,7 @@ "multiple-assignment-and-decomposition" ], "prerequisites": [ - "ostruct" + "hashes" ] }, { @@ -1699,6 +1710,11 @@ "slug": "enumeration", "name": "Enumeration" }, + { + "uuid": "609e43cc-8f7f-4672-9e77-7d90b44be977", + "slug": "hashes", + "name": "Hashes" + }, { "uuid": "ed6e1642-3f85-404f-85fa-6d014662d1e4", "slug": "advanced-enumeration", diff --git a/exercises/concept/gross-store/.docs/hints.md b/exercises/concept/gross-store/.docs/hints.md new file mode 100644 index 0000000000..cf9c207844 --- /dev/null +++ b/exercises/concept/gross-store/.docs/hints.md @@ -0,0 +1,25 @@ +# Hints + +## General + +- The needed methods for working with Hashes are covered in the [Ruby docs about Hashes][hash] + +## 1. Create a new bill + +- To create a new bill, you need to reinitialize the customer, see [Ruby docs about Hash new][hash-new] + +## 2. Add item to the customer bill + +- To check whether the given unit of measurement is correct, you can test your measurement map for a key without retrieving a value, you can use the [`include?`][include?] method, see [Ruby docs about Hash include?][include?] + +## 3. Remove item from the customer bill + +- To check whether the given item is in customer bill, you can test your measurement map for a key without retrieving a value, you can use the [`include?`][include?] method, see [Ruby docs about Hash include?][include?] +- To check whether the given unit of measurement is correct, you can test your measurement map for a key without retrieving a value, you can use the [`include?`][include?] method, see [Ruby docs about Hash include?][include?] + +## 4. Return the number of specific item that is in the customer bill + +- To check whether the given item is in customer bill, you can test your measurement map for a key without retrieving a value, you can use the [`include?`][include?] method, see [Ruby docs about Hash include?][include?] +[include?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-include-3F +[hash-new]: https://docs.ruby-lang.org/en/master/Hash.html#class-Hash-label-Creating+a+Hash +[hash]: https://docs.ruby-lang.org/en/master/Hash.html \ No newline at end of file diff --git a/exercises/concept/gross-store/.docs/instructions.md b/exercises/concept/gross-store/.docs/instructions.md new file mode 100644 index 0000000000..30b807a898 --- /dev/null +++ b/exercises/concept/gross-store/.docs/instructions.md @@ -0,0 +1,88 @@ +# Instructions + +A friend of yours has an old wholesale store called **Gross Store**. +The name comes from the quantity of the item that the store sell: it's all in [gross unit][gross-unit]. +Your friend asked you to implement a point of sale (POS) system for his store. +**First, you want to build a prototype for it.** +**In your prototype, your system will only record the quantity.** +Your friend gave you a list of measurements to help you: + +| Unit | Score | +| ------------------ | ----- | +| quarter_of_a_dozen | 3 | +| half_of_a_dozen | 6 | +| dozen | 12 | +| small_gross | 120 | +| gross | 144 | +| great_gross | 1728 | + +## 1. Create a new customer bill + +You need to implement a method that create a new (empty) bill for the customer. +This should be done in the constructor of the `GrossStore` class. + +```ruby +gross_store = GrossStore.new() +gross_store.bill +# => {} +``` + +## 2. Add an item to the customer bill + +To implement this, you'll need to: + +- Return `false` if the given `unit` is not in the `UNITS` hash. +- Otherwise add the item to the customer `bill`, indexed by the item name, then return `true`. +- If the item is already present in the bill, increase its quantity by the amount that belongs to the provided `unit`. + +Implement the `add_item?` method, which takes two parameters: `item` (String) and `unit` (String). + +```ruby +gross_store = GrossStore.new() +gross_store.add_item?("apple", "dozen") +# => true (since dozen is a valid unit) +gross_store.bill +# => {"apple" => 12} +``` + +## 3. Remove an item from the customer bill + +To implement this, you'll need to: + +- Return `false` if the given item is **not** in the bill +- Return `false` if the given `unit` is not in the `UNITS` hash. +- Return `false` if the new quantity would be less than 0. +- If the new quantity is 0, completely remove the item from the `bill` then return `true`. +- Otherwise, reduce the quantity of the item and return `true`. + +Implement the `remove_item?` method, which takes two parameters: `item` (String) and `unit` (String). + +```ruby +gross_store = GrossStore.new() +gross_store.add_item?("apple", "dozen") +gross_store.remove_item?("apple", "dozen") +# => true +gross_store.bill +# => {} +``` + +## 4. Return the quantity of a specific item that is in the customer bill + +To implement this, you'll need to: + +- Return `0` if the `item` is not in the bill. +- Otherwise, return the quantity of the item in the `bill`. + +Implement the `quantity` method, which takes one parameter: `item` (String). + +```ruby +gross_store = GrossStore.new() +gross_store.add_item?("apple", "dozen") +gross_store.add_item?("carrot", "half_of_a_dozen") +gross_store.quantity("apple") +# => 18 +gross_store.quantity("banana") +# => 0 +``` + +[gross-unit]: https://en.wikipedia.org/wiki/Gross_(unit) diff --git a/exercises/concept/gross-store/.docs/introduction.md b/exercises/concept/gross-store/.docs/introduction.md new file mode 100644 index 0000000000..dd30523ecb --- /dev/null +++ b/exercises/concept/gross-store/.docs/introduction.md @@ -0,0 +1,176 @@ +# About + +[Hashes][hash] are also known as dictionary or map in other languages, they are a mutable unsorted collection which maps keys to values. +Each key is unique and is used to retrieve the corresponding value. +The keys can be of any data type which is hashable (has a `hash` method), this includes strings, numbers, and most data types and objects in Ruby. + +Even though Hashes are unordered collections, [Ruby maintains the insertion order of key-value pairs][entry_order]. +This means that when you iterate over a `Hash`, the pairs will be returned in the order they were added. +However, deleting elements may affect the order of remaining elements. +Hashes behavior of maintaining insertion order was introduced in Ruby [1.9][ruby-1.9]. + +## Creating Hashes + +You can create a `Hash` using curly braces `{}` with key-value pairs formed as `key => value` and separated by commas: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +``` + +You can also mix and match different types of keys and values: + +```ruby +my_hash = {1 => "one", :two => 2, "three" => [3, "three"]} +``` + +Alternatively if the keys are symbols, you can use a more the newer syntax which was introcued in Ruby 1.9: + +```ruby +my_hash = {name: "Alice", age: 30, city: "New York"} +``` + +You can create an empty `Hash` using the `Hash.new` method: + +```ruby +empty_hash = Hash.new +``` + +## Accessing values + +You can access values in a `Hash` instance using its corresponding keys, the syntax reminds of array indexing, but using the key instead of an index: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +my_hash["name"] +# => "Alice" +``` + +If the key does not exist in the `Hash` instance, the `[]` method will return `nil`: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash["city"] +# => nil +``` + +If the disired behavior is to not return `nil` for non-existing keys, another way of accessing values is by using the [`fetch`][fetch] method, which allows you provide a default value for non-existing keys. +If the `fetch` method is used without a default value and the key does not exist, it will raise a `KeyError` exception. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.fetch("city", "Unknown") +# => "Unknown" + +my_hash.fetch("city") +# => KeyError: key not found: "city" +``` + +## Modifying + +You can add or update key-value pairs in a `Hash` instance by assigning a value to a key using the assignment operator `=`. +Assigning a value to an existing key will update the value, while assigning a value to a new key will add a new key-value pair: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash["city"] = "New York" +my_hash["age"] = 31 + +my_hash +# => {"name" => "Alice", "age" => 31, "city" => "New York"} +``` + +## Deleting key-value pairs + +You can delete a key-value pair from a `Hash` instance using the [`delete`][delete] method, which takes the key as an argument: + +```ruby +my_hash = {"name" => "Alice", "age" => 30, "city" => "New York"} +my_hash.delete("age") +my_hash +# => {"name" => "Alice", "city" => "New York"} +``` + +## Methods + +There are several useful instance methods available for Hashes in Ruby. +Here are some commonly used ones: + +### `has_value?` and `include?` + +You can check if a `Hash` instance contains a specific value using the [`has_value?`][has_value?] method. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.has_value?(30) +# => true +my_hash.has_value?(25) +# => false +``` + +You can check if a `Hash` instance contains a specific key using the [`include?`][include?] method. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.include?("name") +# => true +my_hash.include?("city") +# => false +``` + +### `keys` and `values` + +You can retrieve all the keys of a `Hash` instance using the [`keys`][keys] method, which returns an array of keys. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.keys +# => ["name", "age"] +``` + +You can retrieve all the values of a `Hash` instance using the [`values`][values] method, which returns an array of values. + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.values +# => ["Alice", 30] +``` + +## Iterating over Hashes + +You can iterate over the key-value pairs in a `Hash` instance using the `each_pair` method. +This will give you access to both the key and the value for each pair: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.each_pair do |key, value| + puts "#{key}: #{value}" +end +# Output: +# name: Alice +# age: 30 +``` + +You can also iterate over just the keys or just the values using the [`each_key`][each_key] or [`each_value`][each_value] methods, respectively: + +```ruby +my_hash = {"name" => "Alice", "age" => 30} +my_hash.each_key do |key| + puts key +end +# Output: +# name +# age +``` + +[entry_order]: https://docs.ruby-lang.org/en/master/Hash.html#class-Hash-label-Entry+Order +[each_pair]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_pair +[each_key]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_key +[keys]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-keys +[each_value]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-each_value +[values]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-values +[has_value?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-has_value-3F +[include?]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-include-3F +[hash]: https://docs.ruby-lang.org/en/master/Hash.html +[fetch]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-fetch +[delete]: https://docs.ruby-lang.org/en/master/Hash.html#method-i-delete +[ruby-1.9]: https://ruby-doc.org/3.4/NEWS/NEWS-1_9_1.html diff --git a/exercises/concept/gross-store/.meta/config.json b/exercises/concept/gross-store/.meta/config.json new file mode 100644 index 0000000000..76a4496374 --- /dev/null +++ b/exercises/concept/gross-store/.meta/config.json @@ -0,0 +1,23 @@ +{ + "authors": [ + "meatball133" + ], + "contributors": [ + "kotp" + ], + "files": { + "solution": [ + "gross_store.rb" + ], + "test": [ + "gross_store_test.rb" + ], + "exemplar": [ + ".meta/exemplar.rb" + ] + }, + "forked_from": [ + "go/gross-store" + ], + "blurb": "Learn about `Hash` by selling items by the dozen at the Gross Store." +} diff --git a/exercises/concept/gross-store/.meta/design.md b/exercises/concept/gross-store/.meta/design.md new file mode 100644 index 0000000000..8fd320e66b --- /dev/null +++ b/exercises/concept/gross-store/.meta/design.md @@ -0,0 +1,32 @@ +# Design + +## Goal + +The goal is to introduce the student to `Hash` as the class, and as a concept. + +## Learning objectives + +- initializing `Hash` instances +- setting key-value pairs +- modifying values for existing keys +- deleting key-value pairs +- reading a key, non-existent key returns zero value of value type + +## Out of scope + +- iterate over Hash + +## Concepts + +- Hashes +## Prerequisites + +- enumerations +- arrays +- strings +- conditionals +- numbers + +## Representer + +## Analyzer diff --git a/exercises/concept/gross-store/.meta/exemplar.rb b/exercises/concept/gross-store/.meta/exemplar.rb new file mode 100644 index 0000000000..3287264efa --- /dev/null +++ b/exercises/concept/gross-store/.meta/exemplar.rb @@ -0,0 +1,38 @@ +class GrossStore + UNITS = {'quarter_of_a_dozen' => 3, 'half_of_a_dozen' => 6, 'dozen' => 12, 'small_gross' => 120, 'gross' => 144, 'great_gross' => 1728}.freeze + + attr_reader :bill + + # DO NOT MODIFY ANY OF THE CODE ABOVE THIS LINE + + def initialize + # Initialize an empty bill as a hash + @bill = {} + end + + def add_item?(item, quantity) + # Add the specified quantity of the item to the bill + return false unless UNITS.key?(quantity) + @bill[item] ||= 0 + @bill[item] += UNITS[quantity] + true + end + + def remove_item?(item, quantity) + # Remove the specified quantity of the item from the bill + return false unless UNITS.key?(quantity) + return false unless @bill.key?(item) + return false if @bill[item] < UNITS[quantity] + + @bill[item] -= UNITS[quantity] + if @bill[item].zero? + @bill.delete(item) + end + true + end + + def quantity(item) + # Return the quantity of the specified item in the bill + @bill.fetch(item, 0) + end +end diff --git a/exercises/concept/gross-store/gross_store.rb b/exercises/concept/gross-store/gross_store.rb new file mode 100644 index 0000000000..ee77d9f683 --- /dev/null +++ b/exercises/concept/gross-store/gross_store.rb @@ -0,0 +1,24 @@ +class GrossStore + UNITS = {'quarter_of_a_dozen' => 3, 'half_of_a_dozen' => 6, 'dozen' => 12, 'small_gross' => 120, 'gross' => 144, + 'great_gross' => 1728}.freeze + + attr_reader :bill + + # DO NOT MODIFY ANY OF THE CODE ABOVE THIS LINE + + def initialize + raise 'Please implement the GrossStore#initialize method' + end + + def add_item?(item, quantity) + raise 'Please implement the GrossStore#add_item? method' + end + + def remove_item?(item, quantity) + raise 'Please implement the GrossStore#remove_item? method' + end + + def quantity(item) + raise 'Please implement the GrossStore#quantity method' + end +end diff --git a/exercises/concept/gross-store/gross_store_test.rb b/exercises/concept/gross-store/gross_store_test.rb new file mode 100644 index 0000000000..5332521493 --- /dev/null +++ b/exercises/concept/gross-store/gross_store_test.rb @@ -0,0 +1,259 @@ +require 'minitest/autorun' +require_relative 'gross_store' + +class GrossStoreTest < Minitest::Test + def test_initialize_with_empty_bill + gross_store = GrossStore.new + + assert_empty(gross_store.bill) + end + + def test_no_unit + gross_store = GrossStore.new + result = gross_store.add_item?('pasta', '') + refute result + assert_empty(gross_store.bill) + end + + def test_unknown_unit + gross_store = GrossStore.new + result = gross_store.add_item?('onion', 'quarter') + refute result + assert_empty(gross_store.bill) + end + + def test_another_unknown_unit + gross_store = GrossStore.new + result = gross_store.add_item?('pasta', 'pound') + refute result + assert_empty(gross_store.bill) + end + + def test_add_item_to_bill + gross_store = GrossStore.new + result = gross_store.add_item?('banana', 'half_of_a_dozen') + assert result + assert_equal({ 'banana' => 6 }, gross_store.bill) + end + + def test_add_peas_to_bill + gross_store = GrossStore.new + result = gross_store.add_item?('peas', 'quarter_of_a_dozen') + assert result + assert_equal({ 'peas' => 3 }, gross_store.bill) + end + + def test_add_chili_to_bill + gross_store = GrossStore.new + result = gross_store.add_item?('chili', 'dozen') + assert result + assert_equal({ 'chili' => 12 }, gross_store.bill) + end + + def test_add_cucumber_to_bill + gross_store = GrossStore.new + result = gross_store.add_item?('cucumber', 'small_gross') + assert result + assert_equal({ 'cucumber' => 120 }, gross_store.bill) + end + + def test_add_potato_to_bill + gross_store = GrossStore.new + result = gross_store.add_item?('potato', 'gross') + assert result + assert_equal({ 'potato' => 144 }, gross_store.bill) + end + + def test_add_zucchini_to_bill + gross_store = GrossStore.new + result = gross_store.add_item?('zucchini', 'great_gross') + assert result + assert_equal({ 'zucchini' => 1728 }, gross_store.bill) + end + + def test_add_multiple_items_to_bill + gross_store = GrossStore.new + gross_store.add_item?('peas', 'quarter_of_a_dozen') + gross_store.add_item?('peas', 'quarter_of_a_dozen') + gross_store.add_item?('tomato', 'half_of_a_dozen') + gross_store.add_item?('tomato', 'quarter_of_a_dozen') + expected_bill = { + 'peas' => 6, + 'tomato' => 9 + } + assert_equal expected_bill, gross_store.bill + end + + def test_remove_item_that_does_not_exist_in_bill + gross_store = GrossStore.new + result = gross_store.remove_item?('papaya', 'gross') + refute result + assert_empty(gross_store.bill) + end + + def test_remove_item_with_invalid_measurement_unit + gross_store = GrossStore.new + gross_store.add_item?('peas', 'quarter_of_a_dozen') + result = gross_store.remove_item?('peas', 'pound') + refute result + assert_equal({ 'peas' => 3 }, gross_store.bill) + end + + def test_remove_item_with_another_invalid_measurement_unit + gross_store = GrossStore.new + gross_store.add_item?('tomato', 'half_of_a_dozen') + result = gross_store.remove_item?('tomato', 'kilogram') + refute result + assert_equal({ 'tomato' => 6 }, gross_store.bill) + end + + def test_remove_item_with_yet_another_invalid_measurement_unit + gross_store = GrossStore.new + gross_store.add_item?('cucumber', 'small_gross') + result = gross_store.remove_item?('cucumber', 'stone') + refute result + assert_equal({ 'cucumber' => 120 }, gross_store.bill) + end + + def test_remove_item_which_exceeds_existing_quantity + gross_store = GrossStore.new + gross_store.add_item?('peas', 'quarter_of_a_dozen') + result = gross_store.remove_item?('peas', 'half_of_a_dozen') + refute result + assert_equal({ 'peas' => 3 }, gross_store.bill) + end + + def test_remove_another_item_which_exceeds_existing_quantity + gross_store = GrossStore.new + gross_store.add_item?('tomato', 'half_of_a_dozen') + result = gross_store.remove_item?('tomato', 'dozen') + refute result + assert_equal({ 'tomato' => 6 }, gross_store.bill) + end + + def test_remove_multiple_items_from_bill + gross_store = GrossStore.new + gross_store.add_item?('chili', 'dozen') + gross_store.add_item?('cucumber', 'small_gross') + gross_store.add_item?('potato', 'gross') + + gross_store.remove_item?('chili', 'small_gross') + gross_store.remove_item?('cucumber', 'gross') + gross_store.remove_item?('potato', 'great_gross') + + expected_bill = { + 'chili' => 12, + 'cucumber' => 120, + 'potato' => 144 + } + assert_equal expected_bill, gross_store.bill + end + + def test_remove_items_to_zero_quantity + gross_store = GrossStore.new + gross_store.add_item?('peas', 'quarter_of_a_dozen') + + result = gross_store.remove_item?('peas', 'quarter_of_a_dozen') + assert result + assert_empty(gross_store.bill) + end + + def test_remove_another_item_to_zero_quantity + gross_store = GrossStore.new + gross_store.add_item?('tomato', 'half_of_a_dozen') + + result = gross_store.remove_item?('tomato', 'half_of_a_dozen') + assert result + assert_empty(gross_store.bill) + end + + def test_remove_multiple_items_to_zero_quantity + gross_store = GrossStore.new + gross_store.add_item?('chili', 'dozen') + gross_store.add_item?('cucumber', 'small_gross') + gross_store.add_item?('potato', 'gross') + gross_store.add_item?('zucchini', 'great_gross') + + gross_store.remove_item?('chili', 'dozen') + gross_store.remove_item?('cucumber', 'small_gross') + gross_store.remove_item?('potato', 'gross') + gross_store.remove_item?('zucchini', 'great_gross') + + assert_empty(gross_store.bill) + end + + def test_remove_item_to_reduce_quantity + gross_store = GrossStore.new + gross_store.add_item?('chili', 'dozen') + + result = gross_store.remove_item?('chili', 'half_of_a_dozen') + assert result + assert_equal({ 'chili' => 6 }, gross_store.bill) + end + + def test_remove_another_item_to_reduce_quantity + gross_store = GrossStore.new + gross_store.add_item?('cucumber', 'small_gross') + + result = gross_store.remove_item?('cucumber', 'dozen') + assert result + assert_equal({ 'cucumber' => 108 }, gross_store.bill) + end + + def test_remove_yet_another_item_to_reduce_quantity + gross_store = GrossStore.new + gross_store.add_item?('zucchini', 'gross') + + result = gross_store.remove_item?('zucchini', 'quarter_of_a_dozen') + assert result + assert_equal({ 'zucchini' => 141 }, gross_store.bill) + end + + def test_get_item_that_does_not_exist_in_bill + gross_store = GrossStore.new + quantity = gross_store.quantity('grape') + assert_equal 0, quantity + end + + def test_get_item + gross_store = GrossStore.new + gross_store.add_item?('peas', 'quarter_of_a_dozen') + quantity = gross_store.quantity('peas') + assert_equal 3, quantity + end + + def test_get_tomato + gross_store = GrossStore.new + gross_store.add_item?('tomato', 'half_of_a_dozen') + quantity = gross_store.quantity('tomato') + assert_equal 6, quantity + end + + def test_get_chili + gross_store = GrossStore.new + gross_store.add_item?('chili', 'dozen') + quantity = gross_store.quantity('chili') + assert_equal 12, quantity + end + + def test_get_cucumber + gross_store = GrossStore.new + gross_store.add_item?('cucumber', 'small_gross') + quantity = gross_store.quantity('cucumber') + assert_equal 120, quantity + end + + def test_get_potato + gross_store = GrossStore.new + gross_store.add_item?('potato', 'gross') + quantity = gross_store.quantity('potato') + assert_equal 144, quantity + end + + def test_get_zucchini + gross_store = GrossStore.new + gross_store.add_item?('zucchini', 'great_gross') + quantity = gross_store.quantity('zucchini') + assert_equal 1728, quantity + end +end