Home > OS >  Fetch from hash with either Singular or Plural
Fetch from hash with either Singular or Plural

Time:05-27

I get the following input hash in my ruby code

 my_hash = { include: 'a,b,c' }

(or)

 my_hash = { includes: 'a,b,c' }

Now I want the fastest way to get 'a,b,c'

I currently use

 def my_includes
   my_hash[:include] || my_hash[:includes]
 end

But this is very slow because it always checks for :include keyword first then if it fails it'll look for :includes. I call this function several times and the value inside this hash can keep changing. Is there any way I can optimise and speed up this? I won't get any other keywords. I just need support for :include and :includes.

CodePudding user response:

You could work with a modified hash that has both keys :include and :includes with the same values:

my_hash = { include: 'a,b,c' }
my_hash.update(my_hash.key?(:include) ? { includes: my_hash[:include] } :
  { include: my_hash[:includes] })
  #=> {:include=>"a,b,c", :includes=>"a,b,c"}

This may be fastest if you were using the same hash my_hash for multiple operations. If, however, a new hash is generated after just a few interrogations, you might see if both the keys :include and :includes can be included when the hash is constructed.

CodePudding user response:

Caveats and Considerations

First, some caveats:

  1. You tagged this Rails 3, so you're probably on a very old Ruby that doesn't support a number of optimizations, newer Hash-related method calls like #fetch_values or #transform_keys!, or pattern matching for structured data.
  2. You can do all sorts of things with your Hash lookups, but none of them are likely to be faster than a Boolean short-circuit when assuming you can be sure of having only one key or the other at all times.
  3. You haven't shown any of the calling code, so without benchmarks it's tough to see how this operation can be considered "slow" in any general sense.
  4. If you're using Rails and not looking for a pure Ruby solution, you might want to consider ActiveModel::Dirty to only take action when an attribute has changed.

Use Memoization

Regardless of the foregoing, what you're probably missing here is some form of memoization so you don't need to constantly re-evaluate the keys and extract the values each time through whatever loop feels slow to you. For example, you could store the results of your Hash evaluation until it needs to be refreshed:

attr_accessor :includes

def extract_includes(hash)
  @includes = hash[:include] || hash[:includes]
end

You can then call #includes or #includes= (or use the @includes instance variable directly if you like) from anywhere in scope as often as you like without having to re-evaluate the hashes or keys. For example:

def count_includes
  @includes.split(?,).count
end

500.times { count_includes }

The tricky part is basically knowing if and when to update your memoized value. Basically, you should only call #extract_includes when you fetch a new Hash from somewhere like ActiveRecord or a remote API. Until that happens, you can reuse the stored value for as long as it remains valid.

  • Related