DEV Community

Discussion on: It's Ruby, There Must Be a Better Way

Collapse
 
al2o3cr profile image
Matt Jones

There's another, more direct solution to the "running include? on an Array takes longer and longer as the array gets bigger" problem: use a Set.

Add require 'set' to the top of your file; the library ships with Ruby but isn't loaded by default.

Then replace @@taken_names = [] with @@token_names = Set.new.

Under the hood, Set uses a Hash to get constant-time lookups - source. See your favorite Ruby internals book for details on this - I'm a fan of Ruby Under A Microscope.


BTW, I was curious about the pitfalls of representing names as integers and then converting them - so I put together some code. Note: I've been writing a lot of Elixir lately, and the style reflects some of that.

module NameConverter
  DIGITS = ('0'..'9').to_a
  LETTERS = ('A'..'Z').to_a

  AVAILABLE = [LETTERS, LETTERS, DIGITS, DIGITS, DIGITS]
  BACKWARDS = AVAILABLE.reverse

  module_function

  def to_name(x)
    BACKWARDS.reduce(['', x]) do |(acc, n), digits|
      base = digits.length
      idx = n%base
      [acc+digits[idx], n/base]
    end.first.reverse
  end

  def from_name(name)
    AVAILABLE.reduce([name, 0]) do |(name, acc), digits|
      base = digits.length
      char = name[0]
      [name[1..-1], base*acc+digits.index(char)]
    end.last;
  end
end
irb(main):117:0> NameConverter.from_name('AZ700')
=> 25700
irb(main):090:0> NameConverter.to_name(25700)
=> "AZ700"

This would make using the Set approach even more efficient, since Integer objects are 8 bytes instead of 40 bytes for Strings.