Title: Checking if two strings contain the same letters
Tags: ruby
Question: I'm comparing a given word against user's guesses for a hangman game. word is a string that the user wants to guess, guesses is a string that's concatenated with letters the user guessed correctly.
def initialize(word)
@word = word
@guesses = ''
@wrong_guesses = ''
I'm using if word.delete(guesses).empty? to determine the win condition. This code is run for every guess, so there's no way that I can find where the user guessed all the correct letters, and word.delete(guesses) doesn't evaluate to empty.
def check_win_or_lose
if @word.delete(@guesses).empty?
#is this a better choice?
#if @word == self.word_with_guesses
return :win
elsif @wrong_guesses.length &gt;= 7
return :lose
else
return :play
It works, but I feel this is a bit hacky, using a side effect of delete. I would like to know if there's a better, concise, way to do this. Maybe using some regex?
There's a function that I'm already using for another part of the program; not sure if its faster/better than the .delete.empty? method. It returns word with '-' in place of unguessed letters:
def word_with_guesses
displayed = @word
@guesses.length &gt; 0 ? displayed.gsub(/[^#{guesses}]/i, '-') : displayed.gsub(/./, '-')
Comment: Possible duplicate of [Check the characters inside two strings are the same in Ruby](http://stackoverflow.com/questions/17812190/check-the-characters-inside-two-strings-are-the-same-in-ruby)

Comment: _"like I'm using a side effect of .delete()"_ No, you're using the _result_ of .delete(). .delete() has no side-effects. I see nothing wrong in using the result of a method call (that's what they're for). Especially considering that it seems to be the fastest way according to an answer in the duplicate.

Comment: @CarySwoveland I noticed that OP already knew about it.

Comment: What is `guesses`? Please post your code snippet so we have a chance to understand.

Comment: @zenith suggested `a.delete(b).empty?`, but for some reason deleted the answer. I think it's the best answer so far as it does not convert `a` to an array. Note it does not mutate `a`. zenith, why did you delete it?

Comment: If the word is "foo" and "o" is guessed, does that expose one or two "o"'s?

Comment: According to [Wiki](https://en.wikipedia.org/wiki/Hangman_(game)), if the word is "foo" and "o" is guessed, both "o"'s are exposed, so I assume that's the intent.

Comment: let me know if there's anything else I can add. `guesses` are the CORRECT guesses the user has made, not TOTAL. theres a function that takes 1 letter from the user and `@guesses << letter` under the appropriate conditions (< max turns, valid character, etc)

Comment: yes, if the user guesses a letter, all instances of that letter in `word` should be exposed. How would that compare to using my `word_with_guesses` function, which is used for another part of the program but can be reused to solve the problem. I'm just not sure if it's a more efficient way to solve it (than using `.delete.empty?`)


Here is another answer: I guess I'd do it this way:
word.chars.sort == guesses.sort
Edit: Upon further consideration, I would probably do this:
def initialize(word)
@word = word
@letters_remaining = @word.chars
@correct_guesses = []
@wrong_guesses = []
def guess(letter)
if found_idx = @letters_remaining.index(letter)
@correct_guesses << @letters_remaining.delete_at(found_idx)
return :win if @letters_remaining.empty?
else
@wrong_guesses << letter
return :lose if @wrong_guesses.size &gt;= MAX_WRONG_GUESSES
:play
What's happening here is we're keeping a @letters_remaining array, which in initialize is populated with the letters of the word. Upon each guess, we look for the guessed letter in@letters_remaining. If it's found, we delete it and add it to @correct_guesses, then check if @letters_remaining is empty. If it is, the user has won.
If the guessed letter isn't in @letters_remaining, we add it to @wrong_guesses and check the latter's size to determine if the user has lost.
If none of the above is true, the user plays on.
You'll note that I'm storing guesses, etc. in arrays instead of strings. Since most of our logic concerns letters rather than entire strings, this makes more sense.
Edit 2: The above assumes each guess "fills in" only one letter at a time. If you want each guess to fill in all of the matching letters, it would look something like this:
def guess(letter)
if @letters_remaining.delete(letter)
@correct_guesses << letter
return :win if @letters_remaining.empty?
else
@wrong_guesses << letter
return :lose if @wrong_guesses.size &gt;= MAX_WRONG_GUESSES
:play
Comment for this answer: It appears you are assuming that if the word is "foo" and "o" is guessed, only one "o" is exposed (more challenging than the case where all "o"'s are exposed). You may wish to clarify your assumption.

Here is another answer: Most idiomatic is probably:
word.chars.all?{|c| guesses[c]}
Comment for this answer: Is guesses an array? I believe it's a string.
Comment for this answer: didn't you mean `guesses.include? c`?

Here is another answer: Considering that a guess of a letter exposes all instances of that letter in the secret word, you can write:
(word.chars - guesses).empty?
Edit: On reflection, it seems you may want a method that takes an argument that is one guess and returns :win, :lose or :continue, taking account of all previous guesses. If so, you could do the following.
MAX_INCORRECT_GUESSES = 7
Execute at the beginning:
def init(word)
@word = word
@nbr_incorrect = 0
Execute after every guess until :win or :lose is returned:
def win_lose_or_continue(guess)
if @word.include?(guess)
@word.delete!(guess)
return @word.empty? ? :win : :continue
@nbr_incorrect += 1
@nbr_incorrect == MAX_INCORRECT_GUESSES ? :lose : :continue
Examples
First, a helper for displaying results:
def results(guesses)
puts "@word = #{@word}"
guesses.each_char.each do |c|
puts "win_lose_or_continue(#{c}) = #{win_lose_or_continue(c)}, @word now #{@word}"
init('cat')
guesses = "argptc"
results(guesses)
@word = cat
win_lose_or_continue(a) = continue, @word now ct
win_lose_or_continue(r) = continue, @word now ct
win_lose_or_continue(g) = continue, @word now ct
win_lose_or_continue(p) = continue, @word now ct
win_lose_or_continue(t) = continue, @word now c
win_lose_or_continue(c) = win, @word now
init('cat')
guesses = "argptbfjk"
results(guesses)
@word = cat
win_lose_or_continue(a) = continue, @word now ct
win_lose_or_continue(r) = continue, @word now ct
win_lose_or_continue(g) = continue, @word now ct
win_lose_or_continue(p) = continue, @word now ct
win_lose_or_continue(t) = continue, @word now c
win_lose_or_continue(b) = continue, @word now c
win_lose_or_continue(f) = continue, @word now c
win_lose_or_continue(j) = continue, @word now c
win_lose_or_continue(k) = lose, @word now c

Here is another answer: I think your initial solution isn't as bad as it sounds. The problem is that "delete" and "empty?" aren't obviously related to the lisashaffer@example.com. Anyone reading the code needs to make the connection of why they're relevant.
A simple fix is to introduce an explaining variable to make it clear why delete and empty? are relevant.
Conceptually, I think you're perfectly on-track with the core idea:  comparing the set of "actual guesses" with the set of "necessary guesses".
The player wins when there's nothing left to guess -- that is, when the set of actual guesses includes all of the necessary guesses.
Your necessary_guesses.delete(actual_guesses).empty? solution checks whether actual_guesses is a subset of necessary_guesses. You can write it a few different ways, depending on what you want to emphasise:
unguessed_characters = necessary_guesses.delete(actual_guesses)
win if unguessed_characters.empty?
unguessed_characters = necessary_guesses.chars - actual_guesses.chars
win if unguessed_characters.empty?
If you're willing to use actual Set objects, you can even write something like:
win if set_of_necessary_guesses.subset?(set_of_actual_guesses)
In short, the computer is happy with your solution, but focusing on names could make it easier for a human.

Here is another answer: Another hack: you can do to make it more concise is by using bit manipulation.
Don't know a lot about ruby, but this how it will looks like in C++
long int checker = 0;
for(int i=0;i<word.length();i++)
long int t = word[i]-'a';
t = 1<<t;
if((t&amp;checker)==0)
checker |= t;
for(int i=0;i<guesses.length();i++)
long int t = guesses[i]-'a';
t = 1<<t;
if((t&amp;checker)==0){
return false;
return true;
Perhaps built in function could solve elegantly within much shorter but if you like to define your own function you could try this. Hope this helps
Comment for this answer: _"to make it more concise"_ Yeah, and completely obscure and unclear. Also I fail to see how this is more "concise" than `a.chars.sort == b.sort`.

Here is another answer: Considering guesses is a list of chars (at least that's what I would expect from a hangman game), this would do:
def won word, guess
(word.chars &amp; guess) == word.chars.uniq
Testing some input:
word = 'banana'
guess1 = %w(b a m)
guess2 = %w(b a m n)
puts won(word, guess1)
puts won(word, guess2)
#$ ruby words.rb
#false

{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 6, 'CommentCount': 9, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2016:00:02/16/16', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<ruby>', 'Title': 'Checking if two strings contain the same letters', 'ViewCount': 820, 'id': 35421978, 'user_AboutMe': None, 'user_AccountId': 3420790.0, 'user_DisplayName': 'Tommy K', 'user_Reputation': 1659.0, 'user_Views': 213.0, 'user_id': 2868510.0}}