Reading Set.rb - tipps for working with Ruby sets.

I like to read code. Often I take notes and it's a shame that I did never share them. But it's never to late to start.

So here are some notes on Ruby's set.rb. I hope you in turn share an interesting read with the world ;)

How to initialize a set

def initialize(enum = nil, &block)  
  @hash ||= Hash.new

  enum.nil? and return

  if block
    do_with_enum(enum) { |o| add(block[o]) }
  else
    merge(enum)
  end
end  

Ok much to notice here:

Some examples what this means:

Set.new
 => #<Set: {}>

 Set.new([1,2,2,3])
 => #<Set: {1, 2, 3}>

 Set.new(["ape", "APE", "aPe"]) { |val| val.downcase  }
 #<Set: {"ape"}>

A second possibility is hidden at the bottom of the file:

module Enumerable  
  def to_set(klass = Set, *args, &block)
    klass.new(self, *args, &block)
  end
end  

Some examples:

%w{hello, hello, you}.to_set
=> #<Set: {"hello,", "you"}>

 %w{hello, hello, world, "!"}.to_set {|val| val.upcase }
 => #<Set: {"HELLO,", "WORLD,", ""!""}>

Here is a third way:

def self.[](*ary)  
  new(ary)
end  

This is pretty nice trick. It produces a nice shortcut to create a set.

Set[1,2,3,4]
#<Set: {1, 2, 3, 4}>

Set arithmetic in set.rb

A nice feature of sets is, that they support set arithmetic. The implementation is pretty straightforward, this is one of the instances where Ruby's syntax really shines :)

  # given enumerable object.
  def |(enum)
    dup.merge(enum)
  end
  alias + |             ##
  alias union |         ##

  # Returns a new set built by duplicating the set, removing every
  # element that appears in the given enumerable object.
  def -(enum)
    dup.subtract(enum)
  end
  alias difference -    ##

  # Returns a new set containing elements common to the set and the
  # given enumerable object.
  def &(enum)
    n = self.class.new
    do_with_enum(enum) { |o| n.add(o) if include?(o) }
    n
  end
  alias intersection &  ##

  # Returns a new set containing elements exclusive between the set
  # and the given enumerable object.  (set ^ enum) is equivalent to
  # ((set | enum) - (set & enum)).
  def ^(enum)
    n = Set.new(enum)
    each { |o| if n.include?(o) then n.delete(o) else n.add(o) end }
    n
  end

Some examples:

Merging

a = Set["a", "b", "c"]
 => #<Set: {"a", "b", "c"}>

b = Set["d", "e"]
 => #<Set: {"d", "e"}>

a | b
 => <Set: {"a", "b", "c", "d", "e"}>

Substracting another enumerable

a = Set[1, 2, 3]
=> #<Set: {1, 2, 3}>

a - [3]
=> #<Set: {1, 2}>

Intersection between a set and an array

a = Set[2,3,4]
 => #<Set: {2, 3, 4}>

 b & [1,2,3]
 => #<Set: {2, 3}>

I think you get the idea. For some problems, this kind of arithmethic is ideal.

Set is just another enum

Of course it might be obvious to you, if you are not new to Ruby :) Set is just another enumerable. It is interchangable with an array for example.

Another nice trait of course is, that you have all the nice methods that come with enumerables at your disposal, wen working with a set.

I highly recommend you reading the enumerable documentation, since this is something out of scope for this little article.

Random stuff about set.rb

Bottom line

Sets are awesome. Sometimes it makes sense to write this kind of arithmetical functions.

This class is really big, but it still makes sense this way. The functions are very small and focused and its quite simple to read.

Set uses some nice tricks. I really love the def self.[] trick for example.

One of the biggest suprises for me was, that you can pass a block to the constructor. I'm not sure if I am a fan of that.

I loved the implementation of set arithmetics - its simple and beautiful.

Reading this file was fun, you should read it as well