Table of Contents

Wypełnianie tablic zbiorem

Jak Ruby widzi mój kod?

$ ruby -e 'require "pp"; pp Array.new([*(1..10)])' --dump parsetree_with_comment
###########################################################
## Do NOT use this node dump for any purpose other than  ##
## debug and research.  Compatibility is not guaranteed. ##
###########################################################

# @ NODE_SCOPE (line: 1)
# | # new scope
# | # format: [nd_tbl]: local table, [nd_args]: arguments, [nd_body]: body
# +- nd_tbl (local table): (empty)
# +- nd_args (arguments):
# |   (null node)
# +- nd_body (body):
#     @ NODE_BLOCK (line: 1)
#     | # statement sequence
#     | # format: [nd_head]; [nd_next]
#     | # example: foo; bar
#     +- nd_head (current statement):
#     |   @ NODE_FCALL (line: 1)
#     |   | # function call
#     |   | # format: [nd_mid]([nd_args])
#     |   | # example: foo(1)
#     |   +- nd_mid (method id): :require
#     |   +- nd_args (arguments):
#     |       @ NODE_ARRAY (line: 1)
#     |       | # array constructor
#     |       | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
#     |       | # example: [1, 2, 3]
#     |       +- nd_alen (length): 1
#     |       +- nd_head (element):
#     |       |   @ NODE_STR (line: 1)
#     |       |   | # string literal
#     |       |   | # format: [nd_lit]
#     |       |   | # example: 'foo'
#     |       |   +- nd_lit (literal): "pp"
#     |       +- nd_next (next element):
#     |           (null node)
#     +- nd_next (next block):
#         @ NODE_BLOCK (line: 1)
#         | # statement sequence
#         | # format: [nd_head]; [nd_next]
#         | # example: foo; bar
#         +- nd_head (current statement):
#         |   @ NODE_FCALL (line: 1)
#         |   | # function call
#         |   | # format: [nd_mid]([nd_args])
#         |   | # example: foo(1)
#         |   +- nd_mid (method id): :pp
#         |   +- nd_args (arguments):
#         |       @ NODE_ARRAY (line: 1)
#         |       | # array constructor
#         |       | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
#         |       | # example: [1, 2, 3]
#         |       +- nd_alen (length): 1
#         |       +- nd_head (element):
#         |       |   @ NODE_CALL (line: 1)
#         |       |   | # method invocation
#         |       |   | # format: [nd_recv].[nd_mid]([nd_args])
#         |       |   | # example: obj.foo(1)
#         |       |   +- nd_mid (method id): :new
#         |       |   +- nd_recv (receiver):
#         |       |   |   @ NODE_CONST (line: 1)
#         |       |   |   | # constant reference
#         |       |   |   | # format: [nd_vid](constant)
#         |       |   |   | # example: X
#         |       |   |   +- nd_vid (local variable): :Array
#         |       |   +- nd_args (arguments):
#         |       |       @ NODE_ARRAY (line: 1)
#         |       |       | # array constructor
#         |       |       | # format: [ [nd_head], [nd_next].. ] (length: [nd_alen])
#         |       |       | # example: [1, 2, 3]
#         |       |       +- nd_alen (length): 1
#         |       |       +- nd_head (element):
#         |       |       |   @ NODE_SPLAT (line: 1)
#         |       |       |   | # splat argument
#         |       |       |   | # format: *[nd_head]
#         |       |       |   | # example: foo(*ary)
#         |       |       |   +- nd_head (splat'ed array):
#         |       |       |       @ NODE_LIT (line: 1)
#         |       |       |       | # literal
#         |       |       |       | # format: [nd_lit]
#         |       |       |       | # example: 1, /foo/
#         |       |       |       +- nd_lit (literal): 1..10
#         |       |       +- nd_next (next element):
#         |       |           (null node)
#         |       +- nd_next (next element):
#         |           (null node)
#         +- nd_next (next block):
#             (null node)

Możemy zażyczyć sobie również listę wykonywanych instrukcji przez VM:

$ ruby -e 'require "pp"; pp Array.new([*(1..10)])' --dump insns
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
0000 trace            1                                               (   1)
0002 putself          
0003 putstring        "pp"
0005 send             :require, 1, nil, 8, <ic:0>
0011 pop              
0012 trace            1
0014 putself          
0015 getinlinecache   22, <ic:1>
0018 getconstant      :Array
0020 setinlinecache   <ic:1>
0022 trace            1
0024 putobject        1..10
0026 splatarray       true
0028 send             :new, 1, nil, 0, <ic:2>
0034 send             :pp, 1, nil, 8, <ic:3>
0040 leave            

Można też zobaczyć jak działa parser Ruby'ego:

$ ruby -e 'require "pp"; pp Array.new([*(1..10)])' --dump yydebug
Starting parse
Entering state 0
Reducing stack by rule 1 (line 782):
-> $$ = nterm @1 ()
Stack now 0
Entering state 2
Reading a token: Next token is token tIDENTIFIER ()
Shifting token tIDENTIFIER ()
Entering state 35
Reading a token: Next token is token tSTRING_BEG ()
Reducing stack by rule 547 (line 4818):
   $1 = token tIDENTIFIER ()
-> $$ = nterm operation ()
Stack now 0 2
Entering state 110
Next token is token tSTRING_BEG ()
[...]

collection.fetch

Za pomocą tej metody, dostępnej większości kolekcji, można spróbować pobrać dany element, a w wypadku jego braku uruchomić zdefiniowaną akcję. Gdy nie podamy bloku, podniesie wyjątek.

TODO: Napisać jakiś przykład!

# Wartość standardowa
width = options.fetch(:width) {40}
 
# Podnoszenie wyjątku
opt = {}.fetch(:required_opt) do
  raise ArgumentError, "Missing option!"
end

Range#cover?

Range#include? iteruje od min do szukanej wartości (lub max). Przy dużych zasięgach, jest to bardzo wolne (O(N) zamiast O(1)). Możemy skorzystać z Range#cover?, która porównuje tylko największą i najmniejszą wartość z szukaną wartością.

#!/usr/bin/env ruby -wKU
 
require 'date'
require 'benchmark'
 
start_date  = Date.strptime('1410-07-15', '%Y-%m-%d')
end_date    = Date.strptime('2012-12-12', '%Y-%m-%d')
date        = Date.strptime('2011-12-12', '%Y-%m-%d')
 
range = (start_date..end_date)
 
Benchmark.bmbm do |results|
  results.report("include?") { range.include?(date) }
  results.report("cover?")   { range.cover?(date) }
end

Output:

Rehearsal --------------------------------------------
include?   0.430000   0.010000   0.440000 (  0.441153)
cover?     0.000000   0.000000   0.000000 (  0.000009)
----------------------------------- total: 0.440000sec

               user     system      total        real
include?   0.430000   0.000000   0.430000 (  0.502014)
cover?     0.000000   0.000000   0.000000 (  0.000013)

UWAGA: http://rhnh.net/2009/08/03/range-include-in-ruby-1-9

Method

Wywołanie metody

 > Project.first.method(:name).call
 => "Server Abloesung"

Położenie definicji metody

 > User.last.method(:address).source_location
 => ["/home/sqbell/.rvm/gems/ruby-1.9.3-p392/gems/mongoid-3.1.4/lib/mongoid/fields.rb", 388]

Różnica pomiędzy ::FindIt a FindIt

::Rails::Engine   # absolute path to the constant
Rails::Engine     # path relative to the current tree level

SEE: http://stackoverflow.com/questions/10482772/rubys-double-colon-operator-usage-differences

Hash z default_value array

hsh = Hash.new([])
 
hsh[:a].push(1, 2, 3) # => [1, 2, 3]            # So far, so good
hsh[:b].push(4, 5, 6) # => [1, 2, 3, 4, 5, 6]   # LOLWUT?!
hsh[:a]               # => [1, 2, 3, 4, 5, 6] 
hsh[:b]               # => [1, 2, 3, 4, 5, 6]

Ta sama, pusta, tablica jest przekazywana! Spróbujmy jeszcze raz:

hsh = Hash.new { [] }
 
hsh[:a].push(1, 2, 3) # => [1, 2, 3]
hsh[:b].push(4, 5, 6) # => [4, 5, 6]
hsh[:c] << 7          # => [7]
hsh[:a]               # => []
hsh[:b]               # => []
hsh[:c]               # => []

Ciągle coś jest nie tak. Za każdym razem, gdy próbujemy się dostać do wartości klucza, który nie istnieje, wykonywany jest kod przekazanego bloku. Wartość dodawana jest do tablicy, ale tablica nie jest nigdzie przypisywana, więc jest czyszczona przez GC.

hsh = Hash.new { |hash, key| hash[key] = [] }    # => {} 
hsh[:a].push(1, 2, 3)                            # => [1, 2, 3] 
hsh[:b].push(4, 5, 6)                            # => [4, 5, 6] 
hsh[:c] << 7                                     # => [7] 
hsh[:a]                                          # => [1, 2, 3] 
hsh[:b]                                          # => [4, 5, 6] 
hsh[:c]                                          # => [7]

SEE: http://stackoverflow.com/questions/2552579/ruby-method-array-not-updating-the-array-in-hash

Ruby i koercja

FIXME: http://stackoverflow.com/questions/2799571/in-ruby-how-does-coerce-actually-work

Complex Ruby concepts

Enumerable#slice_before, #slice_after i #slice_when

Druga z tych metod dodana została dopiero w Rubym 2.2.

 > [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_before { |e| e.is_a?(Integer) }.to_a
 #=> [[1, "a"], [2, "b", "c"], [3, "d", "e", "f"]]

Zamiast bloku, możemy podać argument. Będzie on porównany za pomocą ===. Zatem powyższy przykład można zapisać róœnież nastęþująco:

 > [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_before(Integer).to_a

#slice_after:

 > [1, 'a', 2, 'b', 'c', 3, 'd', 'e', 'f'].slice_after(Integer).to_a
 #=> [[1], ["a", 2], ["b", "c", 3], ["d", "e", "f"]]

#slice_when można wykorzystać do szukania następujących po sobie liczb:

> [1, 3, 4, 5, 7, 8, 9, 10, 12].slice_when { |a, b| a + 1 != b }.to_a
 #=> [[1], [3, 4, 5], [7, 8, 9, 10], [12]]