Reputation: 11663
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  . 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 == " " ? " " : " "); 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 == " " ? " " : " "); 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] == " " and chars[i] == char_clicked
end
end
def chars_left(revealed_word)
revealed_word.count { |c| c == " " }
end
end
end
Upvotes: 0
Views: 576
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   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, [" "], is returned by the block. disguise now equals [" "]. On the second iteration, char='l', also not a space, so another   gets pushed onto the array, which becomes [" ", " "]. 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 [" ", " ", " ", etc], or if the country name had spaces in it you would get [" ", " ",  "," ", " ", 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.
Upvotes: 1
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 == " " ? " " : " "); d }
=> [" ", " ", " ", " ", " "]
[2] pry(main)> "wor d".each_char.inject([]) { |d, c| d << ( c == " " ? " " : " ") }
=> [" ", " ", " ", " ", " "]
upd: Masking here is to replace characters with non-breaking spaces
Upvotes: 1