0

I need to create a method that takes a Hash and number as arguments and returns a new array of pet names by age.

my_family_pets_ages = {"Evi" => 6, "Hoobie" => 3, "George" => 12, "Bogart" => 4, "Poly" => 4,
  "Annabelle" => 0, "Ditto" => 3}

My I have two solutions to my problem:

def my_hash_finding_method(source, thing_to_find)
    source.select {|k,v| v.include? thing_to_find}
end
my_hash_finding_method(my_family_pets_ages, 3)

or

def my_hash_finding_method(source, thing_to_find)
    source.select {|k,v| v.has_value? thing_to_find}
end
my_hash_finding_method(my_family_pets_ages, 3)

When I run my program with IRB or the command line, I get:

my_solution.rb:15:in `block in my_hash_finding_method': 
undefined method `include?'for 6:Fixnum (NoMethodError)

Why is include? an undefined method? It also says the same thing when I use has_value? Could I be using an outdated version of Ruby? I'm on 2.0.0?

UPDATE: This is the new solution I came up with, it seems to work.

def my_hash_finding_method(source, thing_to_find)
    num_array = []
    source.each do |k,v|
      if v == thing_to_find
        num_array << k
      end
  end
    print num_array.sort
end
Lasonic
  • 841
  • 2
  • 9
  • 28
  • 1
    This smells like homework or an assignment of some sort. Version 2.0 is out of date as the current version of Ruby is 2.1.3, but I doubt that's the problem. (Though you should upgrade since 2.0 was buggy.) – the Tin Man Oct 08 '14 at 22:20

3 Answers3

0

I'm going to be a bit coy because this does sound like homework, but are you aware of what k and v stand for in that block?

The error message is a big hint. It's telling you that you tried sending a message to something that isn't a Hash, and naturally it doesn't have the methods defined for Hashes. You called include? on a Fixnum.

(Check Ruby documentation for Hash select to know what's actually being put there if you want to know more.)

In short: inside the block, you're working with variables whose value get set by the Hash, not operating on the Hash itself. You need to check based on those.

0

The two methods in question don't exist by default, at least not for the objects you're using.

v.include?

would work if v was an Enumerable, like a hash or an array, but instead it's a numeric value because it's the value for that particular key, and doesn't have that method defined.

The same is true for v.has_value?. v is still the value for that particular key. select passes in the key and value as separate parameters if you use |k,v|. (In Ruby's way, they'd be a two-element array if you used a single variable.)

To help you learn to help yourself, Ruby includes the ri command available at the command-line. You can type in ri has_value? to see if that's implemented by any classes Ri knows about. Among other results is:

=== Implementation from Hash
------------------------------------------------------------------------------
  hsh.has_value?(value)    -> true or false

------------------------------------------------------------------------------

Returns true if the given value is present for some key in hsh.

  h = { "a" => 100, "b" => 200 }
  h.has_value?(100)   #=> true
  h.has_value?(999)   #=> false

Along the same lines, for include?:

=== Implementation from Hash
------------------------------------------------------------------------------
  hsh.include?(key)    -> true or false

------------------------------------------------------------------------------

Returns true if the given key is present in hsh.

  h = { "a" => 100, "b" => 200 }
  h.has_key?("a")   #=> true
  h.has_key?("z")   #=> false

In either case, a numeric won't have those methods, which is where the error is coming from.


Would something along the lines of this work?

  def my_hash_finding_method(source, thing_to_find)
    source.select {|k,v|
      if v == thing_to_find then return source[k] end
    }
  end

That's close, but return doesn't belong there and will result in only one value, even if there are multiple values existing. Consider this:

MY_FAMILY_PETS_AGES = {
  "Evi"       => 6,
  "Hoobie"    => 3,
  "George"    => 12,
  "Bogart"    => 4,
  "Poly"      => 4,
  "Annabelle" => 0,
  "Ditto"     => 3
}

def my_hash_finding_method(source, thing_to_find)
  source.select{ |k, v| v == thing_to_find }
end

my_hash_finding_method(MY_FAMILY_PETS_AGES, 3) # => {"Hoobie"=>3, "Ditto"=>3}

If you want to receive sorted results, well, why? select in current Rubies will return a hash, and hashes don't benefit from sorting since they're basically random-access objects, resulting in the ability to access values just as fast whether the hash is sorted or unsorted.

If you want to return a sorted array of key/value pairs, then this is one way to do it:

def my_hash_finding_method(source, thing_to_find)
  source.select{ |k, v| v == thing_to_find }.sort_by{ |k, v| k }
end

my_hash_finding_method(MY_FAMILY_PETS_AGES, 3) # => [["Ditto", 3], ["Hoobie", 3]]

If you only want the names of the pets, instead of name and age, grab only the keys returned by select:

def my_hash_finding_method(source, thing_to_find)
  source.select{ |k, v| v == thing_to_find }.keys.sort
end

my_hash_finding_method(MY_FAMILY_PETS_AGES, 3) # => ["Ditto", "Hoobie"]

Notice that in the solution returning a hash from select I'm using sort_by, and a regular sort in the last one. sort is fine for values like numerics and characters/strings which already have an established order, however it's inefficient when sorting using a block to access attributes inside the value passed in because it has to do a lot of redundant computing to determine how to order the incoming values. sort_by is an interesting algorithm commonly known as a Schwartzian Transform, which computes the value being sorted once per entry, remembers it, sorts by that value, then spits out the associated items afterward, in the correct order. That results in a very efficient sorting, which can greatly outperform a regular sort if the value being sorted isn't readily available.

I did benchmarks of the differences and how to quickly perform a descending sort in "Sorting an array in descending order in Ruby", so spend some time looking through the selected answer to that question.

Community
  • 1
  • 1
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
  • @the_Tin_Man, I understand what you mean by calling .include? on an enumerable. Would something along the lines of this work? def my_hash_finding_method(source, thing_to_find) source.select {|k,v| if v == thing_to_find then return source[k] end} end – Lasonic Oct 08 '14 at 23:24
0

Are you trying to group by value the hash?

Then try:

pets.group_by{|k,v| v}

If you are trying to return just the array of items with a value:

pets.map { |k, v| v == value }
Jorge de los Santos
  • 4,583
  • 1
  • 17
  • 35