BrainLikeADullPencil
BrainLikeADullPencil

Reputation: 11663

Ruby: ternary operator in an inject

I'm following along with a tutorial that's building a hangman application in Ruby. It has a function masquerade which hides the word, which is just a country name that's imported from a file. Problem is I don't understand how this actually disguises the words, or how the ternary operator is working in inject. It's checking if the character is blank (i.e. " "), and, if it is, it's setting it to blank (i.e. " "), but if it's not, it's making it &nbsp. The function doesn't seem to deal with the fact that the words have actual characters i.e. letters.

Can anyone explain for this sort of noob, what I'm misunderstanding? Also, why does it add 'disguise' again at the end (before the closing }?

def masquerade(word)

      word.each_char.inject([]) { |disguise, char| disguise << (char == " " ? " " : "&nbsp;"); disguise }

end

Examples of game words

Afghanistan
Albania
Algeria
Andorra
Angola
Antigua & Deps
Argentina
Armenia

The whole Word class

class Word

  class << self
    def get_random
      content = File.read("countries.txt")
      words = content.split("\n")
      words[rand(words.size)].upcase
    end

    def masquerade(word)
      word.each_char.inject([]) { |disguise, char| disguise << (char == " " ? " " : "&nbsp;"); disguise }
    end

    def reveal(last_revealed_word, char_clicked, final_word)
      chars = final_word.each_char.to_a

      last_revealed_word.each_index do |i|
        last_revealed_word[i] = chars[i] if last_revealed_word[i] == "&nbsp;" and chars[i] == char_clicked
      end
    end

    def chars_left(revealed_word)
      revealed_word.count { |c| c == "&nbsp;" }
    end

  end

end

Upvotes: 0

Views: 576

Answers (2)

Farski
Farski

Reputation: 1766

Let's assume word = 'Albania'. word.each_char is returning an Enumerable object, where each "item" is a letter in the word.

Imagine using #each rather than #inject

word.each_char.each { |letter| puts letter }
A
l
b
...

#inject is meant to take all the items in an enumerable, and combine them somehow. In this case, the thing they're being combined into is an array. You can tell that because an empty array is being passed to inject as the argument in .inject([]); it could also have been inject(Array.new). Whatever's passed in there is going to be the default value for the inject's "memo", the object it uses to keep track of the final result as it's going through the items. In this case, that memo object is called disguise.

During the inject loop, the disguise memo will get carried over from one iteration to the next. Each iteration will be handling one of the items (letters) in the enumerable that inject was called on. Inside the block, that item is called char.

Whatever the block returns becomes the new value of the memo (disguise) for the next iteration of the inject.

So here, the first iteration would have disguise=[] and char='A'. The if/else says "if char is a single space, append a single space to the disguise array, otherwise, append the nonbreaking space to the disguise array.

The result of Array#push() (which is what << is doing here), is the array after the new item has been pushed. So if you had [1,2].push(3), that returns [1,2,3]. In the case of this inject, they want to carry the result of the push over to the next iteration, so basically making sure the array is what the block is returning by putting disguise; at the end. As it is right now, it is not necessary. If there were some other operation happening between the push and the end of the block, you would need to return the disguise array explicitly, otherwise the result of the second operation is what would get stored in the memo.

So as far as how the if/else is dealing with the word's letters, basically anytime it's a letter it's putting in the &nbsp string.

So in that first iteration, char='A' is not a single space, so a nbsp gets pushed onto the empty array, and that array, ["&nbsp"], is returned by the block. disguise now equals ["&nbsp"]. On the second iteration, char='l', also not a space, so another &nbsp gets pushed onto the array, which becomes ["&nbsp", "&nbsp"]. That's the result of the block and the value of disguise in the third iteration.

The inject will continue like that for all the letters, and then return the final value of disguise.

In most cases this would result in ["&nbsp", "&nbsp", "&nbsp", etc], or if the country name had spaces in it you would get ["&nbsp", " ", &nbsp","&nbsp", " ", etc]. It's hard to tell what the point of that is without seeing how that resulting array is being used in a view or presented to the user in some way.

  • I removed the ; from nbsp so they would show up.

Upvotes: 1

ck3g
ck3g

Reputation: 5929

Adding disguise at the end is not required. The << already changes the object.

You can see no difference:

[1] pry(main)> "wor d".each_char.inject([]) { |d, c| d << ( c == " " ? " " : "&nbsp;"); d }
=> ["&nbsp;", "&nbsp;", "&nbsp;", " ", "&nbsp;"]

[2] pry(main)> "wor d".each_char.inject([]) { |d, c| d << ( c == " " ? " " : "&nbsp;") }
=> ["&nbsp;", "&nbsp;", "&nbsp;", " ", "&nbsp;"]

upd: Masking here is to replace characters with non-breaking spaces

Upvotes: 1

Related Questions