Home > Net >  How to return a key in a hash when I only have the value?
How to return a key in a hash when I only have the value?

Time:10-15

The obvious answer (at least in my eyes) would be to use .key(value) however I keep getting an error 'undefined method for key'. I have to return the key of the first instance of the highest value. This is what I've put in;

def high(x)
  alphabet = Hash.new(0)
  count = 0
  ("a".."z").each do |char|
    alphabet[char] = count  = 1
  end

  words = Hash.new(0)

  x.split(" ").each do |word|
    count_a = 0
    word.each_char do |chars|
      if alphabet.has_key?(chars)
        count_a  = alphabet[chars]
      end
    end
    words[word] = count_a
  end

  highest = words.sort_by { |key, value| value }

  (highest[0][1]..highest[-1][1]).each do |val|
    if val == highest[-1][1]
      return highest.key(val)
    else
      return highest[-1][0]
    end
  end
end

I know it's messy code (I'm only a few months into learning coding). The issue I'm facing is specifically in the following section;

highest = words.sort_by { |key, value| value }

  (highest[0][1]..highest[-1][1]).each do |val|
    if val == highest[-1][1]
      return highest.key(val)
    else
      return highest[-1][0]
    end
  end

So where I write 'return highest.key(val)' I'm expecting it to return the word that is equal to the highest 'scoring' word, however it just gives me the undefined method error. Any help would be greatly appreciated.

CodePudding user response:

I managed to fix my problem! Once again, not very elegant code I know, however if someone else is having a similar issue this may help. (and thanks again to everyone who pointed out it was an array not a hash)

def high(x)
  alphabet = Hash.new(0)
  count = 0
  ("a".."z").each do |char|
    alphabet[char] = count  = 1
  end

  words = Hash.new(0)

  x.split(" ").each do |word|
    count_a = 0
    word.each_char do |chars|
      if alphabet.has_key?(chars)
        count_a  = alphabet[chars]
      end
    end
    words[word] = count_a
  end

  highest = words.sort_by { |key, value| value }.to_h
  highest_score = highest.values.last

  best = []

  highest.each_value do |value|
    if value = highest_score
      best << highest.key(value)
    end
  end

  return best[0]
end

so the general idea behind this code is that the argument 'x' is a string and letters are scored a = 1 b = 2 etc. This code returns the word with the biggest score. If there's multiple words with the top score, it will return whichever one of those words came first.

CodePudding user response:

Using Enumerable and String Methods to Make Magic Happen

I'm glad you've already answered your own question. Meanwhile, if you don't care about tracking all the words as you go, or keeping a running list of how well each word scores, then you can make this a lot simpler, faster, and less memory-intensive by letting Ruby do more of the work for you and just return the highest-scoring item.

Ruby has a lot of built-in methods to make jobs like this easier, and its core methods are often highly optimized. Here's a solution with seven lines of code (excluding comments) that makes use of the Enumerable#zip, Enumerable#sum, and Enumerable#max_by methods inherited by Array to do most of the heavy lifting.

Hash#merge helps us take care of some edge cases likes spaces or dashes in compound words. Then, with a little help from String#chars and String#downcase!, it's almost like Ruby does it all for us!

In Ruby 3.0.2:

# Map your numerical score to each letter and store in a Hash.
#
# @note We can prepend a value of zero for spaces and other special
#   characters, too!
ALPHA_SCORES = {" " => 0, "-" => 0}.merge (?a..?z).zip(1..26).to_h

# Convert all words to lowercase, and return the highest-scoring word
# and its score as an Array.
def find_highest_scoring_word_in *words
  words.flatten.map do |word|
    word.downcase!
    [word, word.chars.sum { ALPHA_SCORES[_1] }]
  end.max_by(&:last)
end

This approach provides a lot of flexibility. For example, you can call it in all of the following ways, and it will return the right result.

# pass a single Array of String objects
find_highest_scoring_word_in %w[foo bar baz]
#=> ["foo", 36]

# pass two Array of String arguments
find_highest_scoring_word_in %w[foo bar baz], %w[quux wuuble]
#=> ["wuuble", 84]

# pass in three separate String arguments
find_highest_scoring_word_in 'alpha', 'beta', 'gamma'
#=> ["alpha", 38]

# Pass in expressions that evaluate to a String. The first creates 100
# letter a's, and the second 4 z's. Since `z` is worth 26 points, and
# `a` only 1, it's "zzzz" for the win!
find_highest_scoring_word_in 'a' * 100, 'z' * 4
#=> ["zzzz", 104]
  • Related