tags:

views:

282

answers:

2

I feel like I'm using Ruby the wrong way here: I want to generate all possible matches for the regular expression /[0-9A-Za-z]{3}/

I can't use succ because "999".succ => "1000" and "zZz".succ => "aaAa". I'm having trouble using ranges because I can't seem to union (0..9), ('A'..'Z'), ('a'..'z')

So I wrote:

def alphaNumeric
  #range and succ don't cut it for [0-9a-zA-Z]
  (0..9).each{|x|yield x.to_s}
  ('a'..'z').each{|x|yield x}
  ('A'..'Z').each{|x|yield x}
end
def alphaNumericX3
  alphaNumeric{ |a|
    alphaNumeric{ |b|
      alphaNumeric{ |c|
        yield a+b+c
      }
    }
  }
end
alphaNumericX3.each{|x|p x}

My question is 2 fold:

Is there a less ugly way, and is there a way where alphaNumericX3 could be defined from the parameters (alphaNumeric, 3)?

PS I'm aware that I could define a new class for range. But thats definitly not shorter. If you can make this next block shorter and clearer than the above block, please do:

class AlphaNum
  include Comparable
  attr :length
  def initialize(s)
    @a=s.chars.to_a
    @[email protected]
  end
  def to_s
    @a.to_s
  end
  def <=>(other)
    @a.to_s <=> other.to_s
  end
  def succ
    def inc(x,n)
      return AlphaNum.new('0'*(@length+1)) if x<0
      case n[x]
      when '9'
        n[x]='A'
      when 'Z'
        n[x]='a'
      when 'z'
        n[x]='0'
        return inc(x-1,n)
      else
        n[x]=n[x].succ
      end
      return AlphaNum.new(n.to_s)
    end
    inc(@length-1,@a.clone)
  end
end
# (AlphaNum.new('000')..AlphaNum.new('zzz')).each{|x|p x}
#  === alphaNumericX3.each{|x|p x}
+3  A: 

Ruby Facets provides a cartesian product method for enumerable.

# File lib/core/facets/array/product.rb, line 21
def product(*enums, &block)
  enums.unshift self
  result = [[]]
  while [] != enums
    t, result = result, []
    b, *enums = enums
    t.each do |a|
      b.each do |n|
        result << a + [n]
      end
    end
  end
  if block_given?
    result.each{ |e| block.call(e) }
  else
    result
  end
end

You could include facets or add the method to Enumerable. Once you've got it your question becomes easier:

alpha_numerics = ('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a
list = alpha_numerics.product(alpha_numerics, alpha_numerics).map{ |triplet|
  triplet.join ''}
Daniel X Moore
yeah that's pretty snappy too.
dlamblin
is it maybe worth using ('0'..'9').to_a to ensure all single-character strings?
Mike Woodhouse
It's not strictly necessary, but it adds consistency. Edited, thanks!
Daniel X Moore
what's faster: an = alpha_numerics ; an.product(an,an).map{|t|t.join} ;or; an.product(an,an).join.split/(...)/
dlamblin
A: 
class String
  def nextify
    case self
    when '9' then 'A'
    when 'Z' then 'a'
    when 'z' then '0'
    else self.succ
    end
  end
end

class AlphaNum
  def initialize(string)
    @string = string
  end

  def succ
    @string.split(//).inject("") { |s,n| s << n.nextify }
  end

  def method_missing(*args, &block)
    @string.send(*args, &block)
  end
end

a = AlphaNum.new("999")
puts a.succ #=> 'AAA'
Allyn