0
\$\begingroup\$

I created a method that takes a string value with a fixed length of 9 characters and extracts three values, house (4 characters), level (2), door (3). If the input value has another length than 9 then the method should return nil.

I found several ways to implement the method, but which implementation should I prefer and why?

def extract(value)
  return nil unless value.length == 9
  [value[0..3].to_i, value[4..5].to_i, value[6..8].to_i]
end

require 'scanf'

def extract(value)
  return nil unless value.length == 9
  value.scanf('%4d%2d%3d')
end

def extract(value)
  value.match(/^(\d{4})(\d{2})(\d{3})$/).captures.map(&:to_i) rescue nil
end

p extract('123456789')
#=> [1234, 56, 789]
p extract('123456789012')
#=> nil
\$\endgroup\$
3
\$\begingroup\$

My Thoughts

I haven't profiled any of these, but I'm going to assume speed is not a priority.

I like #2 the best. It is clean and simple, and easy to understand what the code is doing at a glance.

I think #1 is ugly, and the sequential array slicing makes me nervous and seems easy to break. If your number sizes change at some point in the future, this one would be the hardest to modify.

I think #3 is better, but not as clear or concise as #2.

\$\endgroup\$
1
\$\begingroup\$

I'd start with:

def extract(str)
  str.unpack('A4 A2 A3') if str.size == 9
end

extract('123456789') # => ["1234", "56", "789"]
extract('12345678') # => nil
extract('123456789012') # => nil

unpack is an unsung hero when it comes to tearing apart strings but it's REALLY powerful. Possibly that power is why people shy away from it. It's designed to tear apart sequences of characters and bytes, even binary data. Building on top of pack it's easy to test for the length and return nil for strings that are not the proper length.

I'd lean toward scanf as a second choice as, again, it's very powerful and designed for the purpose you're after, only it's oriented to text.

What I like between the two is that unpack allows whitespace between the format string entries, making it easier to read, and, as a result, easier to maintain. scanf strings can be pretty intense when you're dealing with a long string and have to break it into sub-strings.

\$\endgroup\$
  • \$\begingroup\$ Thanks. I wasn't aware of the unpack method. I like your usage of if after the extract expression. \$\endgroup\$ – sschmeck Apr 13 '16 at 11:32
  • 1
    \$\begingroup\$ Using if is taking advantage of Ruby's automatic return of a nil from a method if nothing is explicitly returned or no assignment occurred. In other words, it's just Ruby's default behavior in action. \$\endgroup\$ – the Tin Man Apr 13 '16 at 16:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.