0

Trying to understand how to write an instance method that I can call on an object (via the Object#my_method notation). I've only been able to get the desired results by passing my object as an argument to the method, but I'd like to understand an alternate way of writing methods.

class Anagram
  attr_reader :test_word

  def initialize(test_word)
    @test_word = test_word.downcase
  end

  def word_stats(word)
    word.downcase.split("").inject(Hash.new(0)) { |h,v| h[v] += 1; h }
  end

  def match(word_list)
    word_list.delete_if { |word| word.downcase == test_word }
      word_list.find_all do |word|
        word_stats(test_word) == word_stats(word)  # <= ** THIS LINE **
      end
  end

end

I know I'd need to change the word_stats method, but I'm unsure how to rewrite that line in bold so that I could instead do something like this:

test_word.word_stats == word.word_stats

Is this an appropriate place for send ? Is there a way to write the word_stats method so that it requires no argument?

Thanks!

3 Answers 3

1

If my understanding is correct, you need to define word_stats for class word.class, which I expect is String:

class String
  def word_stats
    self.downcase.split("").inject(Hash.new(0)) { |h,v| h[v] += 1; h }
  end
end

"My dog has fleas".word_stats
  #=> {"m"=>1, "y"=>1, " "=>3, "d"=>1, "o"=>1, "g"=>1, 
  #    "h=>1,  "a"=>2, "s"=>2, "f"=>1, "l"=>1, "e"=>1}

Is that what you are trying to do?

Sign up to request clarification or add additional context in comments.

5 Comments

The functionality this approach provides is what I'm looking for, but I've been warned against metaprogramming in this way (i.e. 'if you're sticking your method definitions inside of a core Ruby class, you're doing it wrong' unless of course you're writing a wrapper, a library, etc.)
re: "Doing it wrong": It's perfectly OK to do that if you're not sharing that code with others, where it could overwrite or cause collisions with other code, resulting in a bug that is really difficult to track down. The idea is we can extend classes, but we want to do it carefully and be aware of the possible consequences.
Jim, whether you want to define a new String method or not, it's your only choice if you want to write word.word_stats, which is the same as word.send(:word_stats). That will only work when the instance method :word_class has been defined for for class word.class. Incidentally, this isn't metaprogramming; it's garden-variety method creation.
Interesting. I was wondering why word.send(:word_stats) wasn't working. If I'm understanding you correctly, you saying that word.send(:word_stats) requires the method word_stats to be defined at the class level for the object I'm calling send on? To put it more simply, are both cases looking in the class of word, which is String, and finding nothing?
Yes, exactly, except it's not really "both cases", as methods are implemented with send (but I know what you mean). To be precise, you should say "the instance method word_stat", as class methods can be defined for the class as well.
1

If you don't want your method to require an argument, then don't specify one in its definition. In the method, if you need to refer to the whole object on which it was invoked then you can do so via the self keyword, but usually you need only to access its attributes, which you can do directly.

def word_stats
    @test_word.downcase.split("").inject(Hash.new(0)) { |h,v| h[v] += 1; h }
end

1 Comment

I'm wondering how I can call the word_stats method on multiple objects. Ideally I'd like to be able to call word_stats on more than just the @test_word object. Can I do this without hardcoding the name of a specific object inside of the method definition as in your example?
1

There is nothing wrong with MonkeyPatching, especially if you have a small program that only does Anagram stuff. If you aren't working on a large or open source project. Monkey Patch away!

aside: Look up Ruby Refinements for a good way to limit the scope of your monkey patches in Ruby 2.0+

I would suggest changing your method to a boolean and naming it for what it does, check to see if the word is an anagram.

I've also included a slightly more obvious algorithm.

class String
  def anagram? ( check_me )
    self.downcase.chars.sort == check_me.downcase.chars.sort
  end
end

def match(word_list)
  word_list.delete_if { |word| word.downcase == test_word.downcase } # might want to downcase test_word as well, unless you know it's downcase already
  word_list.find_all do |word|
    word.anagram?(test_word)
  end
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.