A Ruby Set Module

Version 0.6

Hal E. Fulton


This module is intended to supplement the regular set-handling features that arrays in Ruby have. It is more of an exercise than a real project, though anyone who finds it useful is of course welcome to use it.

Some features it adds are: Subset and superset operators; an "in" method added to Object so that any object can be tested for membership in a set; a powerset method; a constant Null set; and so on. Sets can hold any type, i.e., integers, strings, arrays, other sets, etc.



#
# Ruby set module
# Hal E. Fulton
# (C) 1999-2000 (See Ruby license)
#

class Object   # Why has to be outside module?
  def in(x)
    x.include? self
  end
end

module Sets

class Array
  alias old_inspect inspect
  def inspect
    "["+(self*",")+"]"
  end
  def to_s
    inspect
  end
end

def Set(*elts)
  Set.new(*elts)
end

def Internal_set(*elts)
  Set.new(*elts)
end


class Set

  attr_reader   :elts
  attr_accessor :delim
  attr_accessor :sep

  U = [[]]    # in a later Ruby, this should be a class variable.

  def initialize(*elts)
    @delim = "{}"
    @sep = ", "
    @elts = Array.new
    @elts = elts.dup
    @elts.uniq!
    @elts.sort! {|x,y| x.to_s <=> y.to_s}  # Not really necessary
    if caller[1] =~ /Set/
      U[0] |= @elts
    end
  end

  def |(other)
    Set(*(@elts | other.elts))
  end

  def &(other)
    Set(*(@elts & other.elts))
  end

  def -(other)
    Set(*(@elts - other.elts))
  end

  def <(other)
    self.elts.each  {|x| if (!(other.elts.include? x)); return false; end }
    true
  end

  def >(other)
    other < self
  end

  def ==(other)
    @elts == other.elts
  end

  def <=(other)
    self < other || self == other
  end

  def >=(other)
    self > other || self == other
  end

  def include?(elt)
    @elts.include? elt
  end

  def union!(other)
    @elts |= other.elts
    self
  end

  def intersection!(other)
    @elts &= other.elts
    self
  end

  def difference!(other)
    @elts -= other.elts
    self
  end

  def -@
    @U - self
  end

  def *(other)
    result = Array.new
    @elts.each do |x|
      other.elts.each do |y|
        $sum += 1
        result << [x, y]
      end
    end
    Internal_set(*result)
  end

  def powerset
    lim=(2**@elts.size)-1
    ps = (0..lim).collect {|x| []}
    @elts.each_index do |i| 
      a=(2**i)
      b=2**(i+1)-1
      j=0
      while j < lim
        for j in j+a..j+b
          ps[j] += [@elts[i]]
        end
        j += 1
      end
    end
    Internal_set(*ps)
  end

  def empty?
    @elts.size==0
  end

  def size
    @elts.size
  end

  def Set.universe
    Internal_set(*U[0])
  end

  def min
    @elts.min
  end

  def max
    @elts.max
  end


  alias union         |
  alias +             &

  alias intersection  &
  alias inter         &
  alias inter!        intersection!
  # alias *             &

  alias difference    -              # Set difference
  alias diff          -
  alias diff!         difference!

  alias subset        <
  alias superset      >

  alias contains?     include?
  alias null?         empty?

  alias cardinality   size
  alias card          size

  def inspect
    s=""
    @elts.each_index { |x|
      z = @elts[x].inspect
      case x
        when 0
          s += z+(@elts.size>1?@sep:"")
        when 1..(@elts.size-2)
          s += z+@sep
        when (@elts.size-1)
          s += z
      end
    }
    s = @delim[0..0] + s + @delim[1..1]
  end

  def to_s
    inspect
  end

  def size
    @elts.size
  end

  def each
    @elts.each {|x| yield x}
  end

  def to_a
    @elts
  end

end  # class

  Null = Set.new()


end  # module


###################################

if __FILE__ == $0      # Test code below...

include Sets

####
# Run a function named test_TESTCASE and report pass/fail
####

def run(testcase)
  result = eval("test_"+testcase)
  passfail = result ? "passed" : "FAILED..."
  print "Test #{'%10s' % testcase} #{passfail}\n"
end

####
# Test functions...
####

def test_output_01
  x = Set(1,2,3)
  x.inspect == "{1, 2, 3}"
end

def test_output_02
  x = Set(1,2,3)
  x.to_s == "{1, 2, 3}"
end

def test_output_03
  x = Set(1,2,3)
  x.sep = ";"
  x.to_s == "{1;2;3}"
end

def test_output_04
  x = Set(1,2,3)
  x.delim = "<>"
  x.to_s == "<1, 2, 3>"
end

def test_output_05
  Null.to_s == "{}"
end

def test_output_06
  words = %w[alpha beta gamma delta epsilon]
  wordset = Set(*words)
  str = wordset.inspect # '{"alpha", "beta", "gamma", "delta", "epsilon"}'
  first3 = str.include?("alpha") && str.include?("beta") && str.include?("gamma")
  last2 = str.include?("delta") && str.include?("epsilon")
  counts = str.count("\"")==10 && str.count(",")==4 && str[0..0]=="{" && str[-1..-1]=="}"
  first3 && last2 && counts
end

def test_member_01
  x = Set(2,3,4)
  (2.in x) && (3.in x) && (4.in x)
end

def test_member_02
  x = Set(2,3,4)
  !(1.in x) && !(5.in x)
end

def test_member_03
  x = Set(2,3,4)
  x.include? 3
end

def test_equal_01
  x = Set(1,2,3)
  x == Set(3,2,1)
end

def test_equal_02
  Set(1,2,3) != Set(1,2,4)
end

def test_equal_03
  Set(1,2,3) != Set()
end

def test_null_01
  Null.empty?
end

def test_null_02
  Null.size == 0
end

def test_null_03
  Null.cardinality == 0
end

def test_null_04
  Null < Null
end

def test_subset_01
  Set(1,2,3) < Set(1,2,3)
end

def test_subset_02
  Set(1,2,3) < Set(1,2,3,4)
end

def test_subset_03
  Set() < Set(1,2,3)
end

def test_subset_04
  ! (Set(1,2,4) < Set(1,2,3))
end

def test_subset_05
  Set(1,2) <= Set(1,2,3)
end

def test_subset_06
  ! (Set(1,2,4) <= Set(1,2,3))
end

def test_subset_07
  ! (Set(1,2) >= Set(1,2,3))
end

def test_subset_08
  Set(1,2,3) >= Set(1,2)
end



####
# "Main"
####

run "output_01"
run "output_02"
run "output_03"
run "output_04"
run "output_05"
run "output_06"

run "member_01"
run "member_02"
run "member_03"

run "equal_01"
run "equal_02"
run "equal_03"

run "null_01"
run "null_02"
run "null_03"
run "null_04"

run "subset_01"
run "subset_02"
run "subset_03"
run "subset_04"
run "subset_05"
run "subset_06"
run "subset_07"
run "subset_08"

# More to come...

end  # if __FILE__

Back to my Ruby page