All articles

Depth First Search

Category:

The graph is given.

An image of graph

Graph as a hash:

graph = {
    0 => [1,9],
    1 => [0,8],
    2 => [3],
    3 => [2,4,5,7],
    4 => [3],
    5 => [3,6],
    6 => [5,7],
    7 => [3,6,11,10,8],
    8 => [1,7,9],
    9 => [0,8],
    10 => [7,11],
    11 => [7,10],
    12 => [],
}

Algorithm code with a loop instead of recursion

def dfs(graph, start_node)
  current_node = start_node
  stack = [start_node]
  visited = [start_node]
  loop do
    p "stack: #{stack}"
    p "visited: #{visited}"
    unvisited_neighbour = graph[current_node].find do |node|
      !visited.include?(node)
    end
    if unvisited_neighbour
      current_node = unvisited_neighbour
      visited << current_node
      stack << current_node
      next
    else
      stack.pop
      current_node = stack.last
      break unless current_node
    end
  end
end

dfs(graph, 0)

String vs Symbol

Category:

String

Is a sequence of bytes. The text is converted into those bytes usually by UTF-8 encoding.

String object are mutable.

Symbol

Symbol objects are immutable.

Same symbols, for example :book and :book, are in fact one object that is shared globally in all contexts for the duration of a program’s execution.

Thanks to that sharing symbols costs less memory than strings, but they cannot be collected by Garbage Collector once created.

Comparing symbols is quicker than comparing strings.

Decorator Pattern

Category:

Adding functionality to objects by creating new small classes instead of adding and changing methods in old classes.

class Robot
  def initialize(name)
    @name = name 
  end
  
  def name
    puts @name
  end
end

class Aged < SimpleDelegator
  def initialize(source, age=0)
    super(source)
    @age = age
  end

  def age
    puts @age
  end
end

class Ranked < SimpleDelegator
  def initialize(source, rank=10)
    super(source)
    @rank = rank
  end

  def rank
    puts @rank
  end
end

class HumanFriendly < SimpleDelegator
  def age
    puts "My age is:"
    super
  end
  
  def rank
    puts "My rank is:"
    super
  end
  
  def name
    puts "My name is:"
    puts "Friend of Humanity"
  end
end

robot = HumanFriendly.new(Ranked.new(Aged.new(Robot.new("Destroyer of Humanity"))))
robot.name
robot.age
robot.rank

# output
# My name is:
# Friend of Humanity
# My age is:
# 0
# My rank is:
# 10

Setup a new pure Ruby project

Category:

I assume that you use rbenv.


Update your rbenv and ruby-build

cd ~/.rbenv && git pull

cd "$(rbenv root)"/plugins/ruby-build && git pull


Check what versions are installed

rbenv versions


Check what versions of Ruby are available for installing

rbenv install -l


Install Ruby in some version

rbenv install 2.4.2


Create the project directory and go there

mkdir ~/projects/hosting_manager

cd ~/projects/hosting_manager/


Switch the directory to your Ruby version

rbenv local 2.4.2


Install Bundler and init it

gem install bundler

bundle init

bundle install --path vendor/bundle

Percent String Literals

Category:

The most common delimeters are { and }, but it is possible to use any kind of brackets or pair of @, #, %, ^, &, *, -, =, +, /, \, ;, |, ?, ., !, $

type definition interpolation
%W Array of double-quoted Strings yes
%w Array of single-quoted Strings  
%Q Double-quoted String yes
%q Single-quoted String  
%i Array of Symbols  
%s Symbol  
% Same as %Q yes
%r Regular Expression yes
%x Capture output of running the command in a subshell (backtick ). yes

Block vs proc vs lambda

Category:

Block

A block is code inside do and end or { and }. It’s not an object.

A block alone is a syntax error, it can only exist as a part of method calls.

{ puts "lol" }.call
# BAD! Syntax error! Block is not a proc!

{ puts "lol" }.to_proc.call
# BAD! Syntax error! Block is not even an object!

A method call can have zero or one block after its regular arguments. Every method can take a block, even if it doesn’t make sense because the method doesn’t use block.

puts "lol" do
  puts "this block has no sense, but it will not throw any error"
end

# the result is:
# lol
# => nil

Block is just a special syntax for a piece of code just for passing that piece of code as an argument of a method.

Blocks are made to inject code (in anonymous and literally form) into methods to make them more versatile.

def universal_but_useless_method
  yield 
end

universal_but_useless_method do
  puts "I am a block"
end

# the result is:
# I am a block
# => nil
def print_block_result_in_a_fancy_way(a, b)
  result = yield
  puts "The result of doing something with #{a} and #{b} is... #{result}!!!!!!"
end

a = 2
b = 7

print_block_result_in_a_fancy_way(a, b) do
  a + b
end

# the result is:
# The result of doing something with 2 and 7 is... 9!!!!!!
# => nil

print_block_result_in_a_fancy_way(a, b) do
  a * b
end

# the result is:
# The result of doing something with 2 and 7 is... 14!!!!!!
# => nil

There are not such things as “named blocks”.

def foo(&block)
 word = "Ho"
 block.call(word)
 block.call()
 p block
end

foo do | name |
  puts "Hey! #{name}!"
end

# it prints:
# Hey! Ho!
# Hey! !
# #<Proc:0x007f702ab4a608@main.rb:11>

block is an instance of Proc.

This method definition

def foo(&block)
  block.call("John")
end

is the same as

def foo
  block = Proc.new   
  block.call("John")
end

Proc

Procs exist in Ruby because Ruby doesn’t have functions and properties. Ruby has only methods and Ruby allows you to skip parenthesis during the method call.

But functions are useful in many languages if they are “first-class citizens”. So in Ruby procs (instances of Proc class) are objects with just a #call method with a few aliases. They can be called and passed as argument and returned like every objects.

def print_proc_result_in_a_fancy_way(a, b, operation)
  result = operation.call(a, b)
  puts "The result of doing something with #{a} and #{b} is... #{result}!!!!!!"
end

adding = Proc.new { |a, b| a + b }
multiplying = Proc.new { |a, b| a * b }

print_proc_result_in_a_fancy_way(3, 5, adding)

# the result is:
# The result of doing something with 3 and 5 is... 8!!!!!!
# => nil

print_proc_result_in_a_fancy_way(3, 5, multiplying)

# the result is:
# The result of doing something with 3 and 5 is... 15!!!!!!
# => nil

Proc doesn’t care about number of arguments. It takes argument as nil if there was nothing passed and ignores any extra arguments.

proc = Proc.new do |x|
  if x
    result = x.to_s
  else 
    result = "this was nil"
  end
  result
end
puts proc.call
puts proc.call(1)
puts proc.call(2, 3, 1)
# it prints:
# this was nil
# 1
# 2

Proc can be invoked in many ways.

adding = Proc.new { |a, b| a + b }

puts adding.call(2, 3)
# 5
# => nil
puts adding.(2, 3)
# 5
# => nil
puts adding[2, 3]
# 5
# => nil
puts adding.yield(2, 3)
# 5
# => nil

Proc can be created in many ways too.

adding = Proc.new { |a, b| a + b }

adding = proc { |a, b| a + b }

But procs can’t be created without any block!

This code:

blockless_proc = Proc.new

blockless_proc.call

will raise an exception.

Unless there is some block passed to the current scope.

That’s why this code will work;

def foo
  block = Proc.new   
  block.call
end

foo do
  puts "this block will be passed automatically"
end

Lambda

Lambda, unlike proc, raise ArgumentError if number of arguments is incorrect.

Creating lambda:

adding = lambda { |a, b| a + b }

adding = ->(a, b) { a + b }

Calling lambda (same as calling proc):

p adding.call(8, 5)
p adding.(3, 9)
p adding[2, 2]
p adding.yield(2, 3)

Return in proc and lambda

In lambdas return means the same as next.

a = [4,6,3,2,5]
l = lambda do |n|
  if n % 2 == 0
    return n * 2 # or next n * 2
  else
    return 1 # or next 1
  end
end
p = proc do |n|
  if n % 2 == 0
    next n * 2
  else
    next 1
  end
end

p a.map(&l) # => [8, 12, 1, 4, 1]
p a.map(&p) # => [8, 12, 1, 4, 1]

Return in non-lambda procs (and blocks) raises LocalJumpError:

a = [4,6,3,2,5]
p = proc do |n|
  if n % 2 == 0
    return n * 2
  else
    return 1
  end
end

p a.map(&p) # => unexpected return (LocalJumpError)

Here the first return terminates the whole method because the proc is defined in a method definition:

def convert_array(a)
  p = proc do |n|
    if n % 2 == 0
      return n * 2
    else
      return 1
    end
  end
  return a.map(&p)
end

p convert_array([8, 12, 1, 4, 1]) # => 16

Here it raises LocalJumpError too, because the proc definition is not in a method definition:

p = proc do |n|
  if n % 2 == 0
    return n * 2
  else
    return 1
  end
end

def convert_array(a, p)
  return a.map(&p)
end

p convert_array([8, 12, 1, 4, 1], p) # => unexpected return (LocalJumpError)

include vs extend

Category:

include

Adds instance methods of the module to the class as instance methods of that class.

extend

Adds instance methods of the module to the class as class methods of that class.


So it is only about instance methods of the module.

public vs protected vs private

Category:

public

Can be called by any object. Default for methods.

protected

Can be called by objects of the defining class and its subclasses.

private

Can be called only by current object. The object sends message to itself. The receiver is always self and it can’t be set explicitly even with self keyword. initialize method is always private.

Advanced parameters

Category:

Parameters made of hash

Using fetch with default values.

def stats(args)
  health = args.fetch(:health, 10)
  mana = args.fetch(:mana, 5)
  attack = args.fetch(:attack, 5)
  defense = args.fetch(:defense, 5)
  puts "Health: #{health}, Mana: #{mana}, Attack: #{attack}, Defense: #{defense}."
end

stats(attack: 30)

It prints: Health: 10, Mana: 5, Attack: 30, Defense: 5.


Using default arguments and merge method.

def stats(args)
  defaults = {
    health: 10,
    mana: 5,
    attack: 5,
    defense: 5
  }
  args = defaults.merge args
  health = args[:health]
  mana = args[:mana]
  attack = args[:attack]
  defense = args[:defense]
  puts "Health: #{health}, Mana: #{mana}, Attack: #{attack}, Defense: #{defense}."
end

stats(health: 30)

It prints: Health: 30, Mana: 5, Attack: 5, Defense: 5.


Keyword arguments (since Ruby 2.0)

def stats(health: 10, mana: 5, attack: 5, defense: 5)
  puts "Health: #{health}, Mana: #{mana}, Attack: #{attack}, Defense: #{defense}."
end

stats(mana: 20)

It prints: Health: 10, Mana: 20, Attack: 5, Defense: 5.