Ruby Training
Introduction
This is to summarised what I learn on the ruby Training
Returning if true
Quite liked this. On do the return if true
def remaining_minutes_in_oven(actual_minutes_in_oven)
return EXPECTED_MINUTES_IN_OVEN - actual_minutes_in_oven if actual_minutes_in_oven < EXPECTED_MINUTES_IN_OVEN
raise 'Please implement the Lasagna#remaining_minutes_in_oven method'
end
Symbols
Symbols are just an identifier
class User
STATES = [:active, :inactive, :banned]
...
Provide you pass the same thing, they are the same thing e.g.
User.new(:active)
Predicate Method
So in ruby you have a predicate method of a method that returns true or false which is, by convention, not enforced, a method with a ?. The example below returns true if status equal true.
class User
STATES = [:active, :inactive, :banned]
def initialize(status)
raise "Invalid state" unless STATES.include?(status)
@status = status
end
def active?
@status == :active
end
end
This became a bit more obvious the usage with the example with ranges
module Chess
RANKS = 1..8
FILES = 'A'..'H'
def self.valid_square?(rank, file)
RANKS.include?(rank) && FILES.include?(file)
end
end
Interpolation
This is how to interpolation works
my_var1 = "HELLO"
my_var2 = "FRED WAS HERE #{my_var1}"
puts my_var2
Better is
my_var1 = "HELLO1"
my_var2 = 123
my_var3 = "FRED WAS HERE %s, %d" % [my_var1, my_var2]
puts my_var3
Arrays
These have a lot of functions e.g. array_name.blah which include
- last
- first
- max
- sort
- reverse
Series
This was very fancy. In ruby you can yields consecutive groups to the block. Or generate groups from a block. (sliding windows). So with this you can do
class Series
def initialize(series_string)
@series = series_string.chars
end
def slices(slice_length)
raise ArgumentError if slice_length > @series.length || slice_length <= 0
@series.each_cons(slice_length).map(&:join)
end
end
Series.new('918493904243').slices(5)
=>["91849", "18493", "84939", "49390", "93904", "39042", "90424", "04243"]
LINQ in Ruby?
So this is what was given
class BirdCount
def self.last_week
[0, 2, 5, 3, 7, 8, 4]
end
def initialize(birds_per_day)
@birds_per_day = birds_per_day
end
def yesterday
@birds_per_day[-2]
end
def total
@birds_per_day.sum
end
def busy_days
@birds_per_day.count { |day| day >= 5 }
end
def day_without_birds?
@birds_per_day.any? { |day| day == 0 }
end
end
Which looks canna complicated but really it just like this in C#
public class BirdCount
{
public int[] BirdsPerDay { get; }
public BirdCount(int[] birdsPerDay)
{
BirdsPerDay = birdsPerDay;
}
}
var birds = new BirdCount(new[] { 0, 2, 5, 3, 7, 8, 4 });
birds.Where(day => day >= 5).Count();
Other functions include
fibonacci = [0, 1, 1, 2, 3, 5, 8, 13]
fibonacci.count { |number| number == 1 } #=> 2
fibonacci.any? { |number| number == 6 } #=> false
fibonacci.select { |number| number.odd? } #=> [1, 1, 3, 5, 13]
fibonacci.all? { |number| number < 20 } #=> true
fibonacci.map { |number| number * 2 } #=> [0, 2, 2, 4, 6, 10, 16, 26]
Mapping over arrays
This was quite hard for me. I think just not used to syntax. Once I see the answer it is obvious. compact removes the nil values in an array.
# So an example of the data is
shoes = { price: 30.00, name: "Shoes", quantity_by_size: { s: 1, xl: 4 } }
coat = { price: 65.00, name: "Coat", quantity_by_size: { s: 2 } }
handkerchief = { price: 19.99, name: "Handkerchief", quantity_by_size: { m: 3, l: 2 } }
items = [shoes, coat, handkerchief]
class BoutiqueInventory
def initialize(items)
@items = items
end
def item_names
@items.map { |item| item[:name] }.flatten.sort
end
def cheap
@items.map { |item| item[:price] < 30.00 ? item : nil }.compact
end
def out_of_stock
@items.map { |item| item[:quantity_by_size].empty? ? item : nil }.compact
end
def stock_for_item(name)
@items.map { |item| item[:name] == name ? item[:quantity_by_size] : nil }.compact.first || {}
end
def total_stock
@items.map { |item| item[:quantity_by_size].values.sum }.sum
end
private
attr_reader :items
end
Openstruct
With this we create an object from a hashes. E.g.
attributes = { name: "Jeremy Walker", age: 21, location: "Nomadic" }
person = OpenStruct.new(attributes)
# You can now do
person.name
#=> Jeremy Walker
person.location
#=> Nomadic
So for out example above we can do
# So an example of the data is
shoes = { price: 30.00, name: "Shoes", quantity_by_size: { s: 1, xl: 4 } }
coat = { price: 65.00, name: "Coat", quantity_by_size: { s: 2 } }
handkerchief = { price: 19.99, name: "Handkerchief", quantity_by_size: { m: 3, l: 2 } }
items = [shoes, coat, handkerchief]
class BoutiqueInventory
def initialize(items)
# @items = items
@items = items.map { |item| OpenStruct.new(item) }
end
def item_names
# @items.map { |item| item[:name] }.flatten.sort
# First Bash
# items.map { |item| item.name }.sort
items.map(&:name).sort
end
def total_stock
# @items.map { |item| item[:quantity_by_size].values.sum }.sum
# First Bash
# items.sum do |item|
# item.quantity_by_size.values.sum
# end
items.map(&:quantity_by_size).map(&:values).map(&:sum).sum
end
private
attr_reader :items
end
Decomposing Arrays and Hashes
I think it probably is just arrays as hashes will need to be converted to arrays
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Instead use to_a
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]
Once a array, we can decompose stuff any way we choose using splats to capture remaining. Did actually quite like this.
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
Composing Arrays and Hashes
Composing can be done with single splat (*) for arrays
>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits
>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
And a double splat (**)for hashes
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}
Splats with Arguments
Maybe the only sensible thing to say about this is like Gary Eats ("let's give a go"). If they can fit it in they will. It suggests
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
def my_method(a, b, *arguments,c, symbol_bash:, **d)
p a
p b
p arguments
p c
p symbol_bash
p d
end
my_method(1, 2, 3, 4, 5, 6, symbol_bash: 100, a: 1, b: 2)
Ruby String functions
translate characters (tr)
Just substitute a for b
"metal-oxide".tr("-", " ")
global substitute with regex
So will never learn regex just to difficult for me. Know how to solve so not an issue.
- [...] means set
- ^ inside bracket means not like maths
- a-z means what it says a,b,...z
- A-Z means what it says A,B,...Z
- \s means whitespace, space, tab, newline, carriage return
So substitute anything not in set with ""
phrase.gsub(/[^a-zA-Z\s]/, "")
Exceptions
So quick reminder of mainly rescue and exceptions
class SimpleCalculator
ALLOWED_OPERATIONS = ['+', '/', '*'].freeze
class UnsupportedOperation < StandardError; end
def self.calculate(first_operand, second_operand, operation)
unless ALLOWED_OPERATIONS.include?(operation)
raise UnsupportedOperation, "Operation must be one of #{ALLOWED_OPERATIONS.join(', ')}"
end
raise ArgumentError, 'Operands must be numeric' unless first_operand.is_a?(Numeric) && second_operand.is_a?(Numeric)
case operation
when '+'
"#{first_operand} + #{second_operand} = #{first_operand + second_operand}"
when '/'
begin
"#{first_operand} / #{second_operand} = #{first_operand / second_operand}"
rescue ZeroDivisionError
'Division by zero is not allowed.'
end
when '*'
"#{first_operand} * #{second_operand} = #{first_operand * second_operand}"
end
end
end
Floor and Ceil
Never does alot of this and always with unit tests so it does not matter. Floor round down, ceil round up. By default ruby floors so
# This will round down
(score - 10) / 2
Reduce in Ruby
Took a while to get through this one because of the ruby syntax. This was the test case
Given this
one fish two fish red fish blue fish
return
{ "one" => 1, "fish" => 4, "two" => 1, "red" => 1, "blue" => 1 }
For typescript this is relatively easy. I don't know and will never know regex (getting better)
function wordCount(phrase: string): Record<string, number> {
<syntaxhighlight lang="rb">
const words = phrase.match(/\b\w+'\w+|\b\w+\b/g) ?? [];
return words.reduce<Record<string, number>>((acc, word) => {
acc[word] = (acc[word] ?? 0) + 1;
return acc;
}, {});
}
So the equivalent for ruby is this
class Phrase
attr_reader :phrase
def initialize(phrase)
@phrase = phrase.downcase
end
def word_count
words = phrase.scan(/\b\w+'\w+|\b\w+\b/)
words.each_with_object(Hash.new(0)) { |word, counts| counts[word] += 1 }
end
end
And the regex /\b\w+'\w+|\b\w+\b/ Always seems to be hard until explained.
- \b\w+'\w+ word boundary, letters, apostrophe, letters - This allows for apostophe. so Iain's, don't
- Or
- \b\w+ word boundary, letters
zip
Never seen this but u can zip things provided they are the same lengths. So a is compared with b and it counts how many not equal.
module Hamming
def self.compute(a, b)
raise ArgumentError unless a.length == b.length
a.chars.zip(b.chars).count { |x, y| x != y }
end
end