Seven Languages: Week 1 (Ruby) - Day 2
Day 2 of Ruby covers collections, code blocks, classes and modules. I’m familiar enough with these already in Ruby so in this case it will be hard to provide a ‘learning the language’ perspective. Instead I will try to highlight a few of the parts of Ruby in this chapter that I particularly like.
(This article is part of a series of posts I am doing about my journey through the exercises of the book Seven Languages In Seven Weeks. The article previous to this one is Week 1 (Ruby) - Day 1. For an overview see the Seven Languages project page.)
Topics covered
Ruby has closures and first-class functions - code blocks are one way of creating a Proc - an anonymous function. A Proc is almost but not quite a lambda - there are a few differences between them, and Ruby has a separate lambda
method for creating those.
Ruby’s arrays and hashes are very multipurpose: hashes are frequently used to give a method named parameters; and arrays have built in methods so that they can be used as queues, stacks, sets, and matrices.
Its syntax is consistent and pretty. Arrays are very rich, you can do ranges ([1..10]
), negative indexes ("string"[1..-2]
), and there are convenience methods for operations like push, pop, transpose, and set intersection. You have the three basic list manipulating higher order function equivalents: map, filter, reduce (fold).
There are many useful methods you can call right off of number literals. One common example is times
, which takes a code block as a parameter and executes it. This can sometimes be a nice way to express a loop: 4.times { puts "testing" }
or 4.times { |i| puts i }
.
For anyone coming from C#, ruby’s blocks can substitute for using
in many places, like when opening a file:
File.open("tmp.txt", "r").each { |line|
puts line
}
No need to worry about forgetting to .close()
the file later on.
Full solutions
Here is a nicely formatted version of my solutions to the exercises from Day 2 of Ruby. The home of the following code is on github with the other exercises.
Find:
file = File.open("tmp.txt", "w+")
file.puts "a spoonful is great but I'd rather have a bowl"
file.close
# safer, less error-prone, more readable
File.open("tmp2.txt", "w+") { |file|
file.puts "a spoonful is great but I'd rather have a bowl"
}
puts IO.read("tmp.txt")
puts IO.read("tmp2.txt")
a spoonful is great but I'd rather have a bowl
scores = { gary: 5, nick: 11, ted: 8, the_dude:9 }
print "hash: "; p scores
print "array1: "; p scores.to_a
print "array2: "; p scores.to_a.flatten
scores_array1 = scores.to_a
print "array1 to hash again 1: "; p scores_array1.inject(Hash.new) { |memo, pair| memo[pair.first] = pair.last; memo }
print "array1 to hash again 2: "; p Hash[scores_array1]
scores_array2 = scores.to_a.flatten
print "array2 to hash again 1: "; p Hash[scores_array2]
print "array2 to hash again 2: "; p Hash[*scores_array2]
puts "Yup."
hash: {:gary=>5, :nick=>11, :ted=>8, :the_dude=>9}
array1: [[:gary, 5], [:nick, 11], [:ted, 8], [:the_dude, 9]]
array2: [:gary, 5, :nick, 11, :ted, 8, :the_dude, 9]
array1 to hash again 1: {:gary=>5, :nick=>11, :ted=>8, :the_dude=>9}
array1 to hash again 2: {:gary=>5, :nick=>11, :ted=>8, :the_dude=>9}
array2 to hash again 1: {}
array2 to hash again 2: {:gary=>5, :nick=>11, :ted=>8, :the_dude=>9}
Yup.
scores.each { |key, value| puts "key:'#{key}', value:'#{value}'" }
puts "Yup."
key:'gary', value:'5'
key:'nick', value:'11'
key:'ted', value:'8'
key:'the_dude', value:'9'
Yup.
puts "queue/deque: "
deque = [].push("1").push("2")
deque.unshift("a").unshift("b")
p deque
puts deque.shift
puts deque.shift
puts deque.pop
puts deque.pop
puts "list: "
list = [1,2,3].insert(2, "c")
puts list
puts "removed: " + list.delete("c")
puts "(rudimentary) bag/set:"
bag = [1,2,3,3,4,5]
p bag
set = bag.uniq
other_set = [3,5]
p set
p set & other_set
puts "(rudimentary) matrix:"
matrix = [[1,2,3],[4,5,6],[7,8,9]]
p matrix
p matrix.transpose
queue/deque:
["b", "a", "1", "2"]
b
a
2
1
list:
1
2
c
3
removed: c
(rudimentary) bag/set:
[1, 2, 3, 3, 4, 5]
[1, 2, 3, 4, 5]
[3, 5]
(rudimentary) matrix:
[[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Do:
sixteen_numbers = [*(1..16)]
sixteen_numbers.each do |number|
p sixteen_numbers[((number-4)...number)] if number % 4 == 0
end
puts "and"
sixteen_numbers.each_slice(4) { |slice| p slice }
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 16]
and
[1, 2, 3, 4]
[5, 6, 7, 8]
[9, 10, 11, 12]
[13, 14, 15, 16]
class Tree
attr_accessor :children, :node_name
def initialize(name, children=[])
if name.respond_to?('keys') then
root_node = name.first
name = root_node[0]
children = root_node[1]
end
if children.respond_to?('keys') then
children = children.map {|child_name, grandchildren| Tree.new(child_name, grandchildren) }
end
@node_name = name
@children = children
end
def visit_all(&block)
visit(&block)
children.each {|c| c.visit_all(&block)}
end
def visit(&block)
block.call self
end
end
tree_test = Tree.new("Ruby",
[Tree.new("Reia"),
Tree.new("MacRuby")] )
tree_test2 = Tree.new({"Ruby" =>
{"Reia" => {},
"MacRuby" => {}}
})
tree_test.visit_all { |node| p node.node_name }
tree_test2.visit_all { |node| p node.node_name }
"Ruby"
"Reia"
"MacRuby"
"Ruby"
"Reia"
"MacRuby"
def rbgrep(pattern, filename)
regexp = Regexp.new(pattern)
File.foreach(filename).with_index { |line, line_num|
puts "#{line_num}: #{line}" if regexp =~ line
}
end
rbgrep("guitar", "wikipedia_page.txt")
15: bass) and Andre Olbrich (guitar) under the name Lucifer's Heritage. The band
17: changes: Markus Dörk (guitar) and Thomen Stauch (drums) were replaced by
48: Nightfall, bass guitar has been played by sessional member Oliver Holzwarth,
55: orchestral backing and a consistent vocal and guitar layering throughout.[9]
87: Blind Guardian's music features the staccato guitars and double bass drumming
90: the guitar and vocal tracks, creates the impression of a vast army of musicians
Next in this series: Day 3 of Ruby.