Onze dagmethode #18: NilClass#method_missing

Geplaatst door Remco van 't Veer vr, 29 feb 2008 07:25:00 GMT

Waarschuwing: alleen gebruiken onder toezicht van een meerderjarige!

We kennen de whiny nil, zoals toegepast in Rails, maar wat nou whiny?! Bij deze de obedient nil:


class NilClass
  def method_missing(*args)
    nil
  end
end

Geen geklooi meer:


person && person.name && person.name.upcase

Niet echt DRY! Nu met de obedient nil:


person.name.upcase

Het is alweer de laatste dag methode. Ode aan Michiel voor de hoeveelheid gave trucs die hij uit z’n hoge hoed heeft kunnen trekken!

Geplaatst in ,  | 12 reacties

Onze dagmethode #17: Enumerable#make_index

Geplaatst door Michiel de Mare wo, 27 feb 2008 08:00:00 GMT

Weet je wat ik zo geweldig aan databases vind? En wat ik zo mis aan object-modellen? Dat is dat databases het zo makkelijk maken om data te indexeren.

Stel je voor, je hebt een ongelooflijke bak data, ongeordend, continue veranderend, maar omdat je database een paar indexen bijhoudt kun je toch binnen milliseconden bij elk item dat je wilt.

Zou het niet leuk zijn als dat in Ruby ook kon? Het overkomt me namelijk best vaak dat ik een lijst met honderden items waarin ik wil zoeken. En er overheen itereren wordt al heel snel traag. Dus voor dat ik het weet heb ik overal losse hashes liggen die ik als index gebruik. Maar wat een verschil met een database.

Laten we vandaag een eerste stap in de goede richting zetten met de make_index methode.


module Enumerable
  def make_index(*names)
    Struct.new(*names).new(*names.map{|n| group_by(&n)})
  end
end

index = User.find(:all).make_index(:login, :id, :nickname)

index.login['mdemare'] => [<#User ...>]
index.nickname['Michiel'] => [<#User ...>]

Voor een andere keer: indices op meer dan één attribuut, en het indexeren van attributen met een natuurlijke ordening.

Recommend Michiel de Mare

Geplaatst in ,  | geen reacties

Onze dagmethode #16: Array#bsearch

Geplaatst door Michiel de Mare di, 26 feb 2008 07:52:00 GMT

Zoeken in arrays is best langzaam. Gebruik liever een hash.

Maar als de array al gesorteerd is, kun je een binary search gebruiken. Die werkt door de array doormidden te delen, en vervolgens verder te zoeken in het linker of het rechter deel, net zolang totdat het element gevonden is of er nog maar een element over is..


class Array
  def bsearch(k)
    x,y = -1,size
    while y != x + 1 do
      m = (x + y) / 2
      if self[m] <= k
        x = m
      else
        y = m
      end
    end
    x >= 0 && self[x] == k && x
  end
end

Recommend Michiel de Mare

Geplaatst in ,  | 1 reactie

Onze dagmethode #15: Kernel#levenshtein

Geplaatst door Michiel de Mare ma, 25 feb 2008 08:00:00 GMT

Allerminst door ons verzonnen, maar toch vreselijk nuttig en geen onderdeel van de standaard-library: de Levenshteinafstand

Wat berekent het? Hoeveel karakters je moet toevoegen, weghalen of omwisselen om van de ene naar de andere string te gaan. Ideaal voor het detecteren van typefouten.


# example: 
levenshtein('levenssteen', 'levenshtein') # => 2
Of wat dacht je voor in plaats van je LIKE-clause? Niet zo snel misschien (uche uche) maar wel heel goed.

# Vroeger:
User.find(:all, 
    :conditions => ['name like ?', params[:name] + '%']])

# Nu:
User.find(:all).
    sort_by {|u| levenshtein(params[:name], u.name)}.
    first

def levenshtein(s1, s2)
  d = {}
  (0..s1.size).each { |row| d[[row, 0]] = row }
  (0..s2.size).each { |col| d[[0, col]] = col }
  (1..s1.size).each do |i|
    (1..s2.size).each do |j|
      i1,j1 = i-1,j-1
      cost = s1[i1] != s2[j1] ? 1 : 0
      d[[i, j]] = [d[[i1, j1]] + cost,
                   d[[i1, j]] + 1,
                   d[[i, j1]] + 1].min
      if i1*j1 > 0 and s1[i1] == s2[j-2] and s1[i-2] == s2[j1]
        d[[i, j]] = [d[[i,j]] , d[[i-2, j-2]] + cost].min
      end
    end
  end
  d[[s1.size, s2.size]]
end

Recommend Michiel de Mare

Geplaatst in ,  | 3 reacties

Onze dagmethode #14: Time#warp

Geplaatst door Remco van 't Veer vr, 22 feb 2008 08:00:00 GMT

Tijdreizen is verschrikkelijk handig maar jammer genoeg niet altijd even gemakkelijk. Als de realiteit het niet toestaat dan passen die gewoon aan!


class Time
  def self.now_with_warping
    @warptime || now_without_warping
  end

  class << self
    alias_method :now_without_warping, :now
    alias_method :now, :now_with_warping
  end

  def warp
    self.class.instance_variable_set('@warptime', self)
    yield
  ensure
    self.class.instance_variable_set('@warptime', nil)
  end
end
Hiermee kunnen we gemakkelijk terug naar “Unix Epoch”:

Time.at(0).warp do
  puts "The current time is: #{Time.now}" 
end
Of vlak voor het einde der tijden:

Time.at(2 ** 31 - 1).warp do
  Time.now + 1
end

Wat heb je er aan? Heel handig om tijds afhankelijke zaken te testen!

Geplaatst in ,  | 2 reacties

Onze dagmethode #13: Object#mymethods

Geplaatst door Michiel de Mare do, 21 feb 2008 08:00:00 GMT

Ik heb altijd wel een irb of rails console openstaan, en ik ben dol op methods om te kijken wat een object allemaal kan, maar wat krijg je toch een ontiegelijke hoeveelheid troep terug, vooral in Rails.

Daarom dit:


class Object
  def mymethods
    (methods - Object.instance_methods).sort
  end
end

(1..2).methods.size # => 150
(1..2).mymethods.size # => 46

Recommend Michiel de Mare

Geplaatst in , ,  | 2 reacties

Onze dagmethode #12: Comparable#at_least

Geplaatst door Michiel de Mare wo, 20 feb 2008 08:00:00 GMT

Het kostte me even voordat ik de algemeen aanvaarde manier om de grootste van twee waarden te bepalen had gevonden. Ik verwachtte een methode in Kernel of in Comparable. In plaats daarvan is de Ruby Manier om een array met beide waarden aan te maken en daaraan te vragen wat de hoogste waarde is.


[x,y].max
Nou, kort is het zeker, maar het staat me toch niet aan, om drie redenen:
  • Je maakt een array aan om twee ints te vergelijken? Dat moest haast wel vreselijk inefficiënt zijn, toch? Goed, ik weet dat je je als ervaren Rubyist niet druk hoort te maken om efficiëntie.
  • Ik vind de naam verwarrend. Wanneer ik max hoor denk ik aan een maximum. Ik denk aan maximaal. De maximumsnelheid in Nederland: [you.speed,120].min. Klopt niet.
  • Door twee waarden in de array op te nemen plaats je ze op gelijke voet, vaak ten onrechte. Vaak is de tweede waarde alleen een voetnoot. Je zegt: ik wil waarde size (en trouwens, die moet minstens 1.2 zijn.) Dat wordt niet uitgedrukt door [size, 1.2].max
Vandaar, at_most and at_least:

size.at_least(1.2)
seconds / attempts.at_least(1)

Implementatie te triviaal om te laten zien.

Recommend Michiel de Mare

Geplaatst in ,  | geen reacties

Onze dagmethode #11: Hash#inv_multi

Geplaatst door Michiel de Mare di, 19 feb 2008 08:00:00 GMT

Hash heeft een invert-methode, maar vaak heb je een hash die als values arrays heeft. Je wilt natuurlijk geen arrays als keys, maar juist dat elk element in de array een key wordt met als value een array met alle oorspronkelijke keys.

Simpel voorbeeld


{1 => [3,4]}.inv_multi
# => {3=>[1], 4=>[1]}

Complex voorbeeld


{1 => [3,4,5,6], 2 => [3,4], 4 => 11}.inv_multi
# => {5=>[1], 11=>[4], 6=>[1], 3=>[1, 2], 4=>[1, 2]}
De methode:

class Hash
  def inv_multi
    # there's inject! again. daily method #5
    inject!({}) do |h,(k,v)|  # Is this obvious? If not, say so!
      # this lambda binds h and k.
      l = lambda {|x| (h[x] ||= []) << k}
      # value doesn't have to be an array
      Array === v ? v.each(&l) : l[v]
    end
  end
end

Recommend Michiel de Mare

Geplaatst in ,  | geen reacties

Onze dagmethode #10: Object#r[rs][ds]s

Geplaatst door Michiel de Mare ma, 18 feb 2008 08:00:00 GMT

Ruby 1.9 heeft Object#tap. Lang geleden introduceerde DannyObject#with. Dan is er nog Object#instance_eval.

Hebben deze methodes iets gemeen behalve hun raadselachtige namen?

Het blijkt van wel.Deze methoden vallen in de familie methoden die:
  1. Voor elk Object gedefinieerd zijn.
  2. Een block nemen en dat altijd éénmaal uitvoeren
  3. Niets anders doen

Misschien verbaast het je dat er maar liefst drie methoden in deze categorie zijn. En er is zelfs nog een vierde!

Want zelfs binnen deze strenge specificaties zijn er nog een paar openstaande punten:
  1. Wat retourneren we? Het object zelf? Of het resultaat van het block?
  2. Binnen het block, wat is self? Is het het object? Of is het onveranderd (waardoor we het object als argument moeten meegeven)?
Hoe zit dat voor de methoden die we net hebben genoemd?
  • tap: retourneert resultaat, zelfde self in block
  • with: retourneert self, zelfde self in block
  • instance_eval: retourneert resultaat, andere self in block.
En de ontbrekende methode:
  • switch: retourneert self, andere self in block.

Laten we deze methoden in actie zien!

Lees verder...

Geplaatst in ,  | geen reacties

Onze dagmethode #9: String#+@

Geplaatst door Michiel de Mare vr, 15 feb 2008 08:00:00 GMT

Het is vrijdag dus we houden het kort.


[String,Symbol].each{|c|c.class_eval {alias +@ to_sym}}

Is het een Symbol? Is het een String? Who gives a damn!


hash[+key]

Recommend Michiel de Mare

Geplaatst in ,  | 2 reacties

Oudere artikelen: 1 2