I have built a version of mastermind that checks a user's input and provides feedback based on how close the user's guess was to the winning sequence. If you're not familiar with the game, you get feedback indicating how many of your characters were guessed correctly at the same index and how many characters guessed are in the sequence, but at the wrong index. If there are duplicates in the guess, then you would not count the extra values unless they correspond to the same number of duplicates in the secret code.
Example: If the sequence is ["G","G","G","Y"] and the user guesses ["G", "Y","G","G"] then you'd want to return 2 for items at the same index and 2 for items at different indexes that are included in the secret sequence.
Another example: If the sequence is ["X","R","Y","T"] and the user guesses ["T","T","Y","Y"] then you'd return 1 for items at the same index 1 for the character guessed that is in the sequence but at the wrong index.
Anyway, to me this is not a simple problem to solve. Here's the code I used to get it to work, but it's not elegant. There must be a better way. I was hoping someone can tell me what I'm missing here?? New to Ruby...
def index_checker(input_array, sequence_array)
count = 0
leftover_input = []
leftover_sequence = []
input.each_with_index do |char, idx|
if char == sequence[idx]
count += 1
else
leftover_input << char
leftover_sequence << sequence[idx]
end
end
diff_index_checker(leftover_input, leftover_sequence, count)
end
def diff_index_checker(input, sequence, count)
count2 = 0
already_counted = []
input.each do |char|
if sequence.include?(char) && !already_counted.include?(char)
count2 += 1
already_counted << char
end
end
[count, count2]
end
Here's a clean Ruby solution, written in idiomatic Ruby object-oriented style:
class Mastermind
def initialize(input_array, sequence_array)
#input_array = input_array
#sequence_array = sequence_array
end
def matches
[index_matches, other_matches]
end
def results
[index_matches.size, other_matches.size]
end
private
attr_reader :input_array, :sequence_array
def index_matches
input_array.select.with_index { |e, i| e == sequence_array[i] }
end
def other_matches
non_exact_input & non_exact_sequence
end
def non_exact_input
array_difference(input_array, index_matches)
end
def non_exact_sequence
array_difference(sequence_array, index_matches)
end
# This method is based on https://stackoverflow.com/a/3852809/5961578
def array_difference(array_1, array_2)
counts = array_2.inject(Hash.new(0)) { |h, v| h[v] += 1; h }
array_1.reject { |e| counts[e] -= 1 unless counts[e].zero? }
end
end
You would use this class as follows:
>> input_array = ["G","G","G","Y"]
>> sequence_array = ["G", "Y","G","G"]
>> guess = Mastermind.new(input_array, sequence_array)
>> guess.results
#> [2, 2]
>> guess.matches
#> [["G", "G"], ["G", "Y"]]
Here's how it works. First everything goes into a class called Mastermind. We create a constructor for the class (which in Ruby is a method called initialize) and we have it accept two arguments: input array (the user guess), and sequence array (the answer).
We set each of these arguments to an instance variable, which is indicated by its beginning with #. Then we use attr_reader to create getter methods for #input_array and #sequence_array, which allows us to get the values by calling input_array and sequence_array from any instance method within the class.
We then define two public methods: matches (which returns an array of exact matches and an array of other matches (the ones that match but at the wrong index), and results (which returns a count of each of these two arrays).
Now, within the private portion of our class, we can define the guts of the logic. Each method has a specific job, and each is named to (hopefully) help a reader understand what it is doing.
index_matches returns a subset of the input_array whose elements match the sequence_array exactly.
other_matches returns a subset of the input_array whose elements do not match the sequence_array exactly, but do match at the wrong index.
other_matches relies on non_exact_input and non_exact_sequence, each of which is computed using the array_difference method, which I copied from another SO answer. (There is no convenient Ruby method that allows us to subtract one array from another without deleting duplicates).
Code
def matches(hidden, guess)
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
guess_counts = counting_hash(guess.values_at *indices_wo_match)
[hidden.size - indices_wo_match.size, guess_counts.reduce(0) { |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min }]
end
def counting_hash(arr)
arr.each_with_object(Hash.new(0)) { |s, h| h[s] += 1 }
end
Examples
matches ["G","G","G","Y"], ["G", "Y","G","G"]
#=> [2, 2]
matches ["X","R","Y","T"] , ["T","T","Y","Y"]
#=> [1, 1]
Explanation
The steps are as follows.
hidden = ["G","G","G","Y"]
guess = ["G", "Y","G","G"]
Save the indices i for which hidden[i] != guess[i].
indices_wo_match = hidden.each_index.reject { |i| hidden[i] == guess[i] }
#=> [1, 3]
Note that the number of indices for which the values are equal is as follows.
hidden.size - indices_wo_match.size
#=> 2
Now compute the numbers of remaining elements of guess that pair with one of the remaining values of hidden by having the same value. Begin by counting the numbers of instances of each unique element of hidden and then do the same for guess.
hidden_counts = counting_hash(hidden.values_at *indices_wo_match)
#=> {"G"=>1, "Y"=>1}
guess_counts = counting_hash(guess.values_at *indices_wo_match)
#=> {"Y"=>1, "G"=>1}
To understand how counting_hash works, see Hash::new, especially the explanation of the effect of providing a default value as an argument of new. In brief, if a hash is defined h = Hash.new(3), then if h does not have a key k, h[k] returns the default value, here 3 (the hash is not changed).
Now compute the numbers of matches of elements of guess that were not equal to the value of hidden at the same index and which pair with an element of hidden that have the same value.
val_matches = guess_counts.reduce(0) do |tot, (k, cnt)|
tot + [hidden_counts[k], cnt].min
end
#=> 2
Lastly, return the values of interest.
[hidden.size - indices_wo_match.size, val_matches]
#=> [2, 2]
In the code presented above I have substituted out the variable val_matches.
With Ruby 2.4+ one can use Enumerable#sum to replace
guess_counts.reduce(0) { |tot, (k, cnt)| tot + [hidden_counts[k], cnt].min }
with
guess_counts.sum { |k, cnt| [hidden_counts[k], cnt].min }
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
semi = secret.uniq.sum { |s| [secret.count(s), guess.count(s)].min } - full
[full, semi]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
A shorter alternative, though I find it less clear:
full = secret.zip(guess).count(&:uniq!)
I prefer my other answer for its simplicity, but this one would be faster if someone wanted to use this for arrays larger than Mastermind's.
def judge(secret, guess)
full = secret.zip(guess).count { |s, g| s == g }
pool = secret.group_by(&:itself)
[full, guess.count { |g| pool[g]&.pop } - full]
end
Demo:
> judge(["G","G","G","Y"], ["G","Y","G","G"])
=> [2, 2]
> judge(["X","R","Y","T"], ["T","T","Y","Y"])
=> [1, 1]
There is a method called all? in Enumerable.
I'm trying to learn all the methods of Enumberable's library by writing them myself.
This is what I've come up so far for the all? method. I sorta understand it but I got stumped when trying to pass initialized values to my method.
EDIT for the record, I'm aware that enum method that I have is not the right way ie, it's hard-coded array. This is for self-learning purposes. I'm just trying to figure out how to pass the initialized values to my all? method. That's why I wrote enum in the first place, to see that it is working for sure. Please don't take this class as a literal gospel. Thank you.
class LearningMethods
def initialize(values)
#values = values
end
def enum
array = [10, 3, 5]
end
def all?(a)
yield(a)
end
end
c = LearningMethods.new([10, 3, 5])
p c.enum.all? {|x| x >= 3 } #this works
p c.all?(10) { |x| x >= 3 } #this works
p c.all?(#values) { |x| x >= 3 } #this doesn't work. Why not? And how do I pass the initialized values?
I'm not sure why you need enum at all? Enumerable is a module included in array, so if you're not familiar with this I recommend you read about "modules and mix-ins" in Ruby.
all? works simply by passing EACH of the array elements to the block. If there is ANY element (at least 1) for which the block returns false, then all? evaluates to false. Try analyzing this code:
class MyAllImplementation
def initialize(array)
#array = array
end
def my_all?
#array.each do |element| # for each element of the array
return true unless block_given? # this makes sure our program doesn't crash if we don't give my_all? a block.
true_false = yield(element) # pass that element to the block
return false unless true_false # if for ANY element the block evaluates to false, return false
end
return true # Hooray! The loop which went over each element of our array ended, and none evaluted to false, that means all elements must have been true for the block.
end
end
a = MyAllImplementation.new([1,2,3])
p a.my_all? { |x| x > 0 } #=> true
p a.my_all? { |x| x > 1 } # false, because 1 is not bigger than 1, it's equal to 1
What is the best way to pass a method to reduce or inject instead of block like this:
def super_process(list, item)
list ||= []
list << another_method(item) + just_another_method
end
arr = ['1', '2', '3']
arr.reduce(&method(:super_process))
I have a problem with handling of list (it's default value). It's assigned to the first element of arr on first iteration but on next iteration it's assigned to the result of the first one.
I know I can write:
arr.reduce {|list, item| list << another_method(list, item) }
But that seems quite long and inexpressive to me.
The problem in your example is due to not passing the initial value to reduce. From ruby-doc.org:
reduce { |memo, obj| block } → obj
...
If you do not explicitly specify an initial value for memo, then the
first element of collection is used as the initial value of memo.
Therefore you probably want to pass an array as the first argument. I've changed the definition of super_process to something simpler:
def super_process list, item
list.push item + 1
end
arr = [1, 2, 3]
res = arr.reduce [], &method(:super_process)
puts res
This will output
2
3
4
I am creating a method that will take an array of numbers and add them together. I don't want to use inject because I haven't learned it yet. I prefer to start with the basics. I want to use either each or while.
I've been re-writing this code and testing it against rspec, and I keep running into a problem because the first test consists of the array being empty with nil. I tried doing an if else statement to set nil to 0 if the array is empty?, but that didn't seem to work. Here is what I've got right now.
def sum(x)
total = 0
sum.each { |x| total += x}
total
end
The rspec is testing an empty array [] as well as others that have multiple integers. Thoughts?
You're not enumerating the array passed in to the method, you're enumerating the variable sum. You want x.each { |x| total += x}, although using x within the {} is a little odd in this case because you've used the name for your method parameter.
You can use compact! to remove the nils from your array.
def sum(x)
total = 0
x.compact! #lose the nils
x.each { |i| total += i}
total
end
Edit:
If the x being passed to your sum() method is nil, you can check for that with nil?.
The do something like
if x.nil?
0 #assuming you want to return 0
else
#rest of your function
You want to return nil if the array passed in is empty?
You are getting confused with your identifiers. You are trying to iterate over sum, which is the name of the method, and you are using x as both the method parameter and the iteration block parameter.
I suggest you use something more descriptive, like arr for the method parameter and v for the block parameter (holding the value of each value from the array).
Finally, you need to initialise the total to nil so that the correct value is returned if the array is empty. Unfortunately you can't do arithmetic on nil, so in the code below I have added a line to set total to zero if it isn't already set.
This will do what you ask.
def sum(arr)
total = nil
arr.each do |v|
total = 0 unless total
total += v
end
total
end
p sum [1,2,3]
p sum []
output
6
nil
You could create a new instance method for the Array class:
class Array
def sum
total = 0.0
self.each {|x| total += x if ['Fixnum', 'Float'].include?(x.class.name)}
total%1==0 ? total.to_i : total
end
end
Then you would use it like so:
puts [].sum # => 0
puts [1, 2, 3].sum # => 6
puts [2, nil, "text", 4.5].sum # => 6.5
I have a class Test:
class Test
attr_accessor :data
def initialize
#data = [0, 1, 2, 3]
end
def map
#data.map!{|i| i = yield i }
end
end
and I attempt to use it like:
a = Test.new
a.map{|i|
if(i==2)
i+=1
break i #<--- -This line is the focus
else
1
end
}
puts a.data
The output I expect is [1, 1, 3, 3]. Instead, I get [1, 1, 2, 3]. The last iteration of the block in map doesn't return the modified i.
I replaced break i with next i. This performed as I expected, and produced the output [1, 1, 3, 1].
How can I modify this piece of code (or, ideally the line I point out in my second code-snippet) so that I would get the output [1, 1, 3, 3]? In other words, how can I make the block finish, but pass one last value back to map? Is there a neat and readable way to do this (besides, say, toggling a boolean flag break_now)?
I'm assuming you're asking how to leave a block and make use of the last value that was calculated rather than how to calculate a specific set of numbers; for the latter, there is probably a clever one-liner.
How about something like this:
class Test
attr_accessor :data
def initialize
#data = [0, 1, 2, 3]
end
def modify
#data.map! {|i| yield i }
end
end
a = Test.new
a.modify do |i|
break i if #done
#done = i == 2
#done ? (i + 1) : 1
end
puts a.data
An additional thought—#map is an important method in Ruby with a specific interface. In your example you're violating the interface by modifying a field in Test. For this reason I've used the name #modify instead.
In general, you could get away with this by modifying the yielded values in place. For example, if your array consisted of Strings instead of Fixnums:
class Test
attr_accessor :data
def initialize
#data = %w{a b c d}
end
def map
#data.map! { |i| yield i }
end
end
a = Test.new
a.map do |i|
if i == 'c'
i.next!
break
else
'b'
end
end
p a.data #=> ["b", "b", "d", "d"]
The problem with your example is this:
Fixnum objects have immediate value. This means that when they are assigned or passed as parameters, the actual object is passed, rather than a reference to that object. Assignment does not alias Fixnum objects. There is effectively only one Fixnum object instance for any given integer value…
Fixnums can't be altered in-place, so your expression i += 1 in the lower block doesn't affect the value of i in the upper block. That's why you get 2 in your example but d in my example.
You have to do this:
a.map{ |i| (i % 2 == 0) ? i + 1 : i }
When you use map function you don't change 'a' variable, if you want change 'a' variable do this:
a.map!{ |i| (i % 2 == 0) ? i + 1 : i }
The new value of 'i' is the value return by the block, so don't do something like:
a.map{|i| i = 1 }
because if you do:
a.map{|i| i = 1; 5 }
the result will be:
[5, 5, 5, 5]