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]
I am learning how to use the .index function to show a string's position inside an array. It is pretty useful. However, I am wondering how I can use it (or some other method) to show ALL the places a string exists in an array?
So for example if I have the code:
array= ['cat', 'dog', 'eagle', 'moose', 'pets', 'animal', 'eagle', 'hawk']
puts 'At position ' + array.index('eagle').to_s + ' in the array is eagle!'
puts array.index('eagle')
each time, it tells me that the position is 2, even though it is 2 and also position 6 in the array. Is there a simple way to get back all the instances of a particular string within an array? Or would I need to write a loop to do it manually?
Another way:
array.each_index.select { |i| array[i] == value } #=> [2, 6]
I would get the array elements into pairs of [obj, index] using each_with_index, then select pairs where obj is 'eagle', and finally return all the index values (last in a pair, so use the last method):
array.each_with_index.select { |x| x[0] == 'eagle' }.map(&:last)
# => [2, 6]
As a convenience method in Array:
class Array
def indices_of(obj)
each_with_index.select { |x| x[0] == obj }.map(&:last)
end
end
['cat', 'dog', 'eagle', 'moose', 'pets', 'animal', 'eagle', 'hawk'].indices_of 'eagle'
# => [2, 6]
You can try this:
value = "eagle"
array.each_with_index { |item, index| puts index if item == value }
In your question you just seemed to want to print the index value. you can do what ever you want in this block.
value = "eagle"
array.each_with_index do |item, index|
if item == value
# do whatever you like
puts index
end
end
I am wondering how one would search through an array of hashes and return a value based on a search string. For example, #contacts contains the hash elements: :full_name, :city, and :email. The variable #contacts (I guess it would be an array) contains three entries (perhaps rows). Below is the code I have so far to conduct a search based on :city value. However it's not working. Can anyone give me an idea what's going on?
def search string
#contacts.map {|hash| hash[:city] == string}
end
You should use select instead of map:
def search string
#contacts.select { |hash| hash[:city] == string }
end
In your code you tried to map (or transform) your array using a block, which yields boolean values. map takes a block and invokes the block for each element of self, constructing a new array containing elements returned by the block. As the result, you got an array of booleans.
select works similar. It takes a block and iterates over the array as well, but instead of transforming the source array it returns an array containing elements for which the block returns true. So it's a selection (or filtering) method.
In order to understand the difference between these two methods it's useful to see their example definitions:
class Array
def my_map
[].tap do |result|
self.each do |item|
result << (yield item)
end
end
end
def my_select
[].tap do |result|
self.each do |item|
result << item if yield item
end
end
end
end
Example usage:
irb(main):007:0> [1,2,3].my_map { |x| x + 1 }
[2, 3, 4]
irb(main):008:0> [1,2,3].my_select { |x| x % 2 == 1 }
[1, 3]
irb(main):009:0>
You can try this:
def search string
#contacts.select{|hash| h[:city].eql?(string) }
end
This will return an array of hashes which matches string.
Excuse the newbie question. I'm trying to create a two dimensional array in ruby, and initialise all its values to 1. My code is creating the two dimensional array just fine, but fails to modify any of its values.
Can anyone explain what I'm doing wrong?
def mda(width,height)
#make a two dimensional array
a = Array.new(width)
a.map! { Array.new(height) }
#init all its values to 1
a.each do |row|
row.each do |column|
column = 1
end
end
return a
end
It the line row.each do |column| the variable column is the copy of the value in row. You can't edit its value in such way. You must do:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.each do |row|
row.map!{1}
end
return a
end
Or better:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
a = Array.new(width){ Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
Array.new(width) { Array.new(height){1} }
end
each passes into the block parameter the value of each element, not the element itself, so column = 1 doesn't actually modify the array.
You can do this in one step, though - see the API docs for details on the various forms of Array#new. Try a = Array.new(width) {|i| Array.new(height) {|j| 1 } }
you can create it like this?
a=Array.new(width) { Array.new(height,1) }
column in your nested each loop is a copy of the value at that place in the array, not a pointer/reference to it, so when you change its value you're only changing the value of the copy (which ceases to exist outside the block).
If you just want a two-dimensional array populated with 1s something as simple as this will work:
def mda(width,height)
[ [1] * width ] * height
end
Pretty simple.
By the way, if you want to know how to modify the elements of a two-dimensional array as you're iterating over it, here's one way (starting from line 6 in your code):
#init all its values to 1
a.length.times do |i|
a[i].length.times do |j|
a[i][j] = 1
end
end
I came with below solution but I believe that must be nicer one out there ...
array = [ 'first','middle','last']
index = array.length
array.length.times { index -= 1; puts array[index]}
Ruby is smart
a = [ "a", "b", "c" ]
a.reverse_each {|x| print x, " " }
array.reverse.each { |x| puts x }
In case you want to iterate through a range in reverse then use:
(0..5).reverse_each do |i|
# do something
end
You can even use a for loop
array = [ 'first','middle','last']
for each in array.reverse do
print array
end
will print
last
middle
first
If you want to achieve the same without using reverse [Sometimes this question comes in interviews]. We need to use basic logic.
array can be accessed through index
set the index to length of array and then decrements by 1 until index reaches 0
output to screen or a new array or use the loop to perform any logic.
def reverseArray(input)
output = []
index = input.length - 1 #since 0 based index and iterating from
last to first
loop do
output << input[index]
index -= 1
break if index < 0
end
output
end
array = ["first","middle","last"]
reverseArray array #outputs: ["last","middle","first"]
In a jade template you can use:
for item in array.reverse()
item
You can use "unshift" method to iterate and add items to new "reversed" array.
Unshift will add a new item to the beginning of an array.
While << - adding in the end of an array. Thats why unshift here is good.
array = [ 'first','middle','last']
output = []
# or:
for item in array # array.each do |item|
output.unshift(item) # output.unshift(item)
end # end
puts "Reversed array: #{output}"
will print: ["last", "middle", "first"]
We can also use "until":
index = array.length - 1
until index == -1
p arr[index]
index -= 1
end