check if the index of similar elements in two arrays are different - ruby

This is my first question on StackOverFlow! Please let me know if my question is not clear.
Here is what I have so far:
code = ["4", "4", "1", "1"]
guess = ["4", "4", "4", "1"]
#A = 0
#B = 0
code.each_with_index do |item, index|
if item == guess[index]
#A += 1
elsif code.include?(guess[index])
#B += 1
end
print "\nA:#{#A} B:#{#B}"
end
I would like to increase #A by 1 if a number is in both arrays and in the same position (index).
If the number is in both arrays but in different positions (index), increase #B by 1.
I should've gotten A:3 B:0 but I am getting A:3 B:1. In the code array, there is no third "4" so B shouldn't have increased by 1.
Is it because .include? doesn't work with duplicates in arrays? How can I fix this?

After each successful (individual) match between the code and guess, we must remove the matched elements from the equation.
For example, code = ['1', 2'] and guess = ['1', '1']. We get an exact match at index 0. If we remove the matched elements: code = ['2'] and guess = ['1'], we can clearly see that there is no further match.
If we had not removed the elements, then (in addition to the exact match) index 0 of code would match with index 1 of guess... which would wrongly result in #A = 1 and #B = 1. We do not want to use an already matched code element to match with a different guess element.
code = ["4", "4", "1", "1"]
guess = ["4", "4", "4", "1"]
unmatched_code = []
unmatched_guess = []
#A = 0
#B = 0
code.size.times do |i|
if code[i] == guess[i]
#A += 1
else
unmatched_code << code[i]
unmatched_guess << guess[i]
end
end
unmatched_guess.each do |g|
matched_at_index = unmatched_code.index(g)
if matched_at_index
#B += 1
unmatched_code.delete_at(matched_at_index)
end
end

This should work:
code = ["4", "4", "1", "1"]
guess = ["4", "4", "4", "1"]
visited = Array.new(guess.length) { false }
#A = 0
#B = 0
code.each_with_index do |item, index|
if item == guess[index]
#A += 1
#B -= 1 if visited[index]
visited[index] = true
elsif guess.each_index.any? { |idx| guess[idx] == item && !visited[idx] && visited[idx] = true }
#B += 1
end
end
print "\nA:#{#A} B:#{#B}"
Here, I am keeping track of already visited indexes in the guess array so that I don't count the same element in both #A and #B count.

You could start by combining code and guess pairs via zip:
code = ["4", "3", "1", "1"]
guess = ["2", "3", "1", "4"]
pairs = code.zip(guess)
#=> [["4", "2"], ["3", "3"], ["1", "1"], ["1", "4"]]
To separate the exact matches, you could use partition:
exact_matches, remaining = pairs.partition { |a, b| a == b }
exact_matches
#=> [["3", "3"], ["1", "1"]]
remaining
#=> [["4", "2"], ["1", "4"]]
Your A-value is the number of exact matches:
a = exact_matches.size
#=> 2
And if remaining is empty?, you're already done here. But it is not:
remaining.empty?
#=> false
We therefore have to determine the number of partial matches. To do so, I would start by converting the remaining pairs back into their respective arrays via transpose:
code_remaining, guess_remaining = remaining.transpose
code_remaining
#=> ["4", "1"]
guess_remaining
#=> ["2", "4"]
Based on that arrays, you could calculate the number of their entries via tally:
code_hash = code_remaining.tally
#=> {"4"=>1, "1"=>1}
guess_hash = guess_remaining.tally
#=> {"2"=>1, "4"=>1}
There's 1 "4" and 1 "2" in the guess_hash. For each of these, we can check how many are actually available in the code_hash:
code_hash["4"] #=> 1
code_hash["2"] #=> nil
Because nil means 0 here, I would use fetch instead, which allows a default value to be passed:
code_hash.fetch("4", 0) #=> 1
code_hash.fetch("2", 0) #=> 0
And because there could be more available than guessed (or vice-versa), the actual value would be the minimum of them:
[guess_hash["4"], code_hash.fetch("4", 0)].min #=> 1
[guess_hash["2"], code_hash.fetch("2", 0)].min #=> 0
To get the B-value out of this, we have to sum the above:
b = guess_hash.sum { |k, v| [v, code_hash.fetch(k, 0)].min }
#=> 1

Related

How to detect duplicate keys in hash and add prefix to the duplicate?

I have two arrays and I am creating a key-value-pair using hash in Ruby. How can I detect a duplicate key when zipping two arrays into key-value-pair and adding a prefix like "A-" in front of the key name for the duplicates?
I am using .zip to merge two arrays and making one a key and other one a value
[0] = "David"
[1] = "John"
[2] = "Alex"
[3] = "Sam"
[4] = "Caleb"
[5] = "David"
[6] = "John"
[7] = "Alex"
[8] = "Sam"
[0] = "1"
[1] = "2"
[2] = "3"
[3] = "4"
[4] = "5"
[5] = "6"
[6] = "7"
[7] = "8"
[8] = "9"
name_number_key_value_pair_hash = first_names.zip(numbers).to_h
puts(name_number_key_value_pair_hash)
Expected:
{"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4", "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7", "A-Alex"=>"8", "A-Sam"=>"9"}
Actual:
{"David"=>"6", "John"=>"7", "Alex"=>"8", "Sam"=>"9", "Caleb"=>"5"}
It seems straight forward Have attached code snippet
names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9]
key_pair = {}
names.each_with_index do |name, index|
name = "A-#{name}" if key_pair[name]
key_pair[name] = numbers[index]
end
It generates the expected output:
{"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4", "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7", "A-Alex"=>"8", "A-Sam"=>"9"}
You basically just need to keep track of the state of the hash as you build it and, when you find a conflict, create a new key instead. This captures the general approach:
def hash_with_prefixes(a, b, prefixes)
kv_pairs = a.zip(b)
prefixes = prefixes.to_enum
result_hash = {}
kv_pairs.each do |initial_key, value|
final_key = initial_key
while result_hash.include? final_key
final_key = "#{pfx.next}-#{initial_key}"
end
prefixes.rewind
result_hash[final_key] = value
end
result_hash
rescue StopIteration
fail "Insufficient prefixes to provide unique keys for input lists."
end
At the slight expense of clarity, you can also write it in a rather shorter form:
def hash_with_prefixes(a, b, prefixes)
pi = Hash[a.map {|k| [k, prefixes.lazy.map {|p| "#{p}-#{k}"}]}]
a.zip(b).inject({}) {|h, kv| h[h.include?(kv[0]) ? pi[kv[0]].next : kv[0]] = kv[1]; h}
rescue StopIteration
fail "Insufficient prefixes to provide unique keys for input lists."
end
(Don't do this.)
This is really very simple.
names = ["John","John", "John", "David", "David", "Susan", "Sue"]
numbers = ["1", "2", "3", "4", "5", "6","7"]
def uniq_hash_keys(names, numbers)
hash = {}
names.each_with_index do |name,i|
if hash[name]
prefix = 'A1-'
key = prefix + name
while hash[key]
version = prefix.match(/A(\d+)-.*/i)[1].to_i
prefix = "A#{version + 1}-"
key = prefix + name
end
name = key
end
hash[name] = numbers[i]
end
hash
end
This function produces:
{
"John"=>"1",
"A1-John"=>"2",
"A2-John"=>"3",
"David"=>"4",
"A1-David"=>"5",
"Susan"=>"6",
"Sue"=>"7"
}
Notice that there are 3 Johns, this is why the while loop is inside the function.
This is one way to create the desired hash. Note that in arr1 "John" appears three times.
arr1 = ["David", "John", "Alex", "Sam", "Caleb",
"David", "John", "Alex", "John", "Sam"]
arr2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
prefixes =
arr1.each_with_object({}) do |s,h|
if h.key?(s)
prefix = "A-"
(h[s].size-1).times { prefix = prefix.next }
h[s] << prefix
else
h[s] = ['']
end
end
#=> {"David"=>["", "A-"], "John"=>["", "A-", "B-"],
# "Alex"=>["", "A-"], "Sam"=>["", "A-"],
# "Caleb"=>[""]}
arr1.map { |s| "#{prefixes[s].shift}#{s}" }.zip(arr2).to_h
#=> {"David"=>"1", "John"=>"2", "Alex"=>"3", "Sam"=>"4",
# "Caleb"=>"5", "A-David"=>"6", "A-John"=>"7",
# "A-Alex"=>"8", "B-John"=>"9", "A-Sam"=>"10"}
Note that "A-".next #=> "B-" and "Z-".next #=> "AA-".
Alternative data structure
You may wish to consider a different data structure, one that returns
{"David"=>["1", "6"], "John"=>["2", "7", "9"],
"Alex" =>["3", "8"], "Sam" =>["4", "10"], "Caleb"=>["5"]}
You could do that as follows.
arr1.each_with_index.
group_by(&:first).
transform_values { |v| arr2.values_at(*v.map(&:last)) }
#=> {"David"=>["1", "6"], "John"=>["2", "7", "9"],
# "Alex" =>["3", "8"], "Sam" =>["4", "10"],
# "Caleb"=>["5"]}
See Enumerable#each_with_index, Enumerable#group_by, Hash#transform_values1 and Array#values_at. v.map(*:last) is here the same as v.map { |arr| arr.last }.
The steps are as follows.
a = arr1.each_with_index
#=> #<Enumerator: ["David", "John", "Alex", "Sam",
# "Caleb", "David", "John", "Alex", "John", "Sam"]:
# each_with_index>
We can see the values that will be generated by this enumerator by converting it to an array.
a.to_a
#=> [["David", 0], ["John", 1], ["Alex", 2], ["Sam", 3],
# ["Caleb", 4], ["David", 5], ["John", 6], ["Alex", 7],
# ["John", 8], ["Sam", 9]]
Continuing,
b = a.group_by(&:first)
#=> {"David"=>[["David", 0], ["David", 5]],
# "John"=> [["John", 1], ["John", 6], ["John", 8]],
# "Alex"=> [["Alex", 2], ["Alex", 7]],
# "Sam"=> [["Sam", 3], ["Sam", 9]],
# "Caleb"=>[["Caleb", 4]]}
b.transform_values { |v| arr2.values_at(*v.map(&:last)) }
#=> {"David"=>["1", "6"], "John"=>["2", "7", "9"],
# "Alex"=> ["3", "8"], "Sam"=> ["4", "10"], "Caleb"=>["5"]}
For the last step, the first value of the hash b is passed to the block and the block variable is assigned to that value.
v = b.values.first
#=> [["David", 0], ["David", 5]]
The block calculations are then as follows.
c = v.map(&:last)
#=> [0, 5]
arr2.values_at(*c)
#=> arr2.values_at(0, 5)
#=> ["1", "6"]
The calculations are similar for each of the remaining values of b that are passed to the block.
1. New in Ruby MRI v2.4.
This code is less readable but compact and functional-style.
It conceptually the same as rahul mishra code https://stackoverflow.com/a/54697573/2109121
names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9]
result = names.zip(numbers).reduce({}) { |a, (b, c)| a.merge(a.key?(b) ? "A-#{b}" : b => c) }
Using zip and each_with_object
names = %w[David John Alex Sam Caleb David John Alex Sam]
numbers = %w[1 2 3 4 5 6 7 8 9]
names.zip(numbers).each_with_object({}) do |(name, number), hash|
key = hash.key?(name) ? "A-#{name}" : name
hash[key] = number
end

How to write a method that finds the most common letter in a string?

This is the question prompt:
Write a method that takes in a string. Your method should return the most common letter in the array, and a count of how many times it appears.
I'm not entirely sure where to go with what I have so far.
def most_common_letter(string)
arr1 = string.chars
arr2 = arr1.max_by(&:count)
end
I suggest you use a counting hash:
str = "The quick brown dog jumped over the lazy fox."
str.downcase.gsub(/[^a-z]/,'').
each_char.
with_object(Hash.new(0)) { |c,h| h[c] += 1 }.
max_by(&:last)
#=> ["e",4]
Hash::new with an argument of zero creates an empty hash whose default value is zero.
The steps:
s = str.downcase.gsub(/[^a-z]/,'')
#=> "thequickbrowndogjumpedoverthelazyfox"
enum0 = s.each_char
#=> #<Enumerator: "thequickbrowndogjumpedoverthelazyfox":each_char>
enum1 = enum0.with_object(Hash.new(0))
#=> #<Enumerator: #<Enumerator:
# "thequickbrowndogjumpedoverthelazyfox":each_char>:with_object({})>
You can think of enum1 as a "compound" enumerator. (Study the return value above.)
Let's see the elements of enum1:
enum1.to_a
#=> [["t", {}], ["h", {}], ["e", {}], ["q", {}],..., ["x", {}]]
The first element of enum1 (["t", {}]) is passed to the block by String#each_char and assigned to the block variables:
c,h = enum1.next
#=> ["t", {}]
c #=> "t"
h #=> {}
The block calculation is then performed:
h[c] += 1
#=> h[c] = h[c] + 1
#=> h["t"] = h["t"] + 1
#=> h["t"] = 0 + 1 #=> 1
h #=> {"t"=>1}
Ruby expands h[c] += 1 to h[c] = h[c] + 1, which is h["t"] = h["t"] + 1 As h #=> {}, h has no key "t", so h["t"] on the right side of the equal sign is replaced by the hash's default value, 0. The next time c #=> "t", h["t"] = h["t"] + 1 will reduce to h["t"] = 1 + 1 #=> 2 (i.e., the default value will not be used, as h now has a key "t").
The next value of enum1 is then passed into the block and the block calculation is performed:
c,h = enum1.next
#=> ["h", {"t"=>1}]
h[c] += 1
#=> 1
h #=> {"t"=>1, "h"=>1}
The remaining elements of enum1 are processed similarly.
A simple way to do that, without worrying about checking empty letters:
letter, count = ('a'..'z')
.map {|letter| [letter, string.count(letter)] }
.max_by(&:last)
Here is another way of doing what you want:
str = 'aaaabbbbcd'
h = str.each_char.with_object(Hash.new(0)) { |c,h| h[c] += 1 }
max = h.values.max
output_hash = Hash[h.select { |k, v| v == max}]
puts "most_frequent_value: #{max}"
puts "most frequent character(s): #{output_hash.keys}"
def most_common_letter(string)
string.downcase.split('').group_by(&:itself).map { |k, v| [k, v.size] }.max_by(&:last)
end
Edit:
Using hash:
def most_common_letter(string)
chars = {}
most_common = nil
most_common_count = 0
string.downcase.gsub(/[^a-z]/, '').each_char do |c|
count = (chars[c] = (chars[c] || 0) + 1)
if count > most_common_count
most_common = c
most_common_count = count
end
end
[most_common, most_common_count]
end
I'd like to mention a solution with Enumerable#tally, introduced by Ruby 2.7.0:
str =<<-END
Tallies the collection, i.e., counts the occurrences of each element. Returns a hash with the elements of the collection as keys and the corresponding counts as values.
END
str.scan(/[a-z]/).tally.max_by(&:last)
#=> ["e", 22]
Where:
str.scan(/[a-z]/).tally
#=> {"a"=>8, "l"=>9, "i"=>6, "e"=>22, "s"=>12, "t"=>13, "h"=>9, "c"=>11, "o"=>11, "n"=>11, "u"=>5, "r"=>5, "f"=>2, "m"=>2, "w"=>1, "k"=>1, "y"=>1, "d"=>2, "p"=>1, "g"=>1, "v"=>1}
char, count = string.split('').
group_by(&:downcase).
map { |k, v| [k, v.size] }.
max_by { |_, v| v }

Array elements to integers

I have an array of strings containing numbers:
array = ["1", "2", "3"]
I want to convert every string in the array to an integer.
array.each { |n| n.to_i } does not work, because
p array.inject(:+)
returns "123" (string) rather than 6 (integer)
array = ["1", "2", "3"]
new_array = array.map { |n| n.to_i }
p new_array.inject(:+)
=> 6
one-line solution:
array.map(&:to_i).inject(:+)
# => 6

Converting an array into a hash with format { value => position_in_array }

I have an array of numbers in string format, and I want to convert them into a hash where the keys are the numbers and the values are the positions of those numbers in the array. So for example:
["1", "5", "3"]
should result in:
{ 1 => 0, 5 => 1, 3 => 2 }
I have the following code, which works:
my_hash = {}
my_array.each do |number_string|
my_hash[number_string.to_i] = my_array.index(number_string)
end
which iterates through the array and pushes each value and its position into the hash.
Is there a shorter and more elegant way to do it? Maybe something similar to Ruby's to_a function, but more like to_h(options).
Hash[["1", "5", "3"]
.map.with_index{|e, i| [e.to_i, i]}]
# => {1=>0, 5=>1, 3=>2}
or
["1", "5", "3"]
.each_with_object({}).with_index{|(e, h), i| h[e.to_i] = i}
# => {1=>0, 5=>1, 3=>2}
arr = ["1", "5", "3"]
ha = Hash[arr.map.with_index {|a, i| [a.to_i, i]}]
puts "ha: #{ha.inspect}"
irb(main):038:0> arr=["1", "5", "3"]
=> ["1", "5", "3"]
irb(main):039:0> Hash[arr.map.with_index {|a, i| [a, i]}]
=> {"1"=>0, "5"=>1, "3"=>2}
irb(main):040:0> Hash[arr.map.with_index {|a, i| [a.to_i, i]}]
=> {1=>0, 5=>1, 3=>2}

Nested Loops Ruby

Having Difficulty understanding this nested loop problem:
You have 10 pebbles (numbered 1-10). They are by default black. You must alter them by painting them white if they are black or painting them black if they are white. There are 10 rounds. Every round, you must alter the pebbles that are multiples of the current round.
The pebbles are by default black.
1st round, you alter every pebble (paint them all white).
2nd round, you alter every other pebble(you paint pebbles
#2,4,6,8,10 black).
3rd round, you alter pebbles #3,6,9.
4th round you alter pebbles #4,8.
...
...
10th round, you alter pebble #10.
After the 10th round, which pebbles are painted black and which are painted white?
My solution which doesn't run is below (I attempt to do so by making an array of numbers(turned to strings) and adding "w" if painted white and deleting "w" if painted black.
(I have tried editing it to make it run, however I am new to nested loops and I am just not grasping this concept). I would greatly appreciate it if someone could explain to me what I am doing wrong and give a better solution.
pebbles = (1..10).map {|element| element.to_s}
pebble_colors = (1..10).map {|element| element.to_s}
(1..10).each do |round|
pebbles_to_paint = []
pebbles.each_with_index {|element, index| pebbles_to_paint << index if element % round == 0}
pebbles_to_paint.each do |pebble_number|
if pebble_color[pebble_number].include?("w")
pebble_color[pebble_number].delete!("w")
else
pebble_color[pebble_number] << "w"
end
end
end
Because this is about nested loops, I just wanted to add that you don't necessarily have to iterate through all pebbles on each round. (that's 100 iterations for 10 pebbles!)
Instead you can use Range#step to iterate over each nth element, starting with the round's index:
(1..10).each { |r|
print "%2d:" % r
(r..10).step(r) { |i|
print " #{i}"
}
puts
}
produces:
1: 1 2 3 4 5 6 7 8 9 10
2: 2 4 6 8 10
3: 3 6 9
4: 4 8
5: 5 10
6: 6
7: 7
8: 8
9: 9
10: 10
That's only 27 iterations. A nice side effect is that you don't have to calculate the remainder any more.
Full example:
pebbles = Hash[(1..10).map{|i| [i, :black]}]
toggle = {:black => :white, :white => :black}
(1..10).each { |r|
(r..10).step(r) { |i|
pebbles[i] = toggle[pebbles[i]]
}
}
p pebbles
#=> {1=>:white, 2=>:black, 3=>:black, 4=>:white, 5=>:black, 6=>:black, 7=>:black, 8=>:black, 9=>:white, 10=>:black}
Your main problem appears to be in the deciding of which pebbles to paint. The following is not right:
element % round == 0
It should be:
(index+1) % round
You want to compare the pebble's index rather than the current value of the pebble. As well, you need to remember that indexes are 0-based (ie they start counting from 0). You need to have the indexes be 1-based (hence the adding of 1) otherwise the first element would always change and the others would be off by 1.
There was also a typo for pebble_color, which should be pebble_colors.
You could definitely re-factor the code to make it shorter, but the following appears to work (just making the minimal changes mentioned above):
pebbles = (1..10).map {|element| element.to_s}
pebble_colors = (1..10).map {|element| element.to_s}
(1..10).each do |round|
pebbles_to_paint = []
pebbles.each_with_index {|element, index| pebbles_to_paint << index if (index+1) % round == 0}
pebbles_to_paint.each do |pebble_number|
if pebble_colors[pebble_number].include?("w")
pebble_colors[pebble_number].delete!("w")
else
pebble_colors[pebble_number] << "w"
end
end
p pebble_colors
end
The output is:
["1w", "2w", "3w", "4w", "5w", "6w", "7w", "8w", "9w", "10w"]
["1w", "2", "3w", "4", "5w", "6", "7w", "8", "9w", "10"]
["1w", "2", "3", "4", "5w", "6w", "7w", "8", "9", "10"]
["1w", "2", "3", "4w", "5w", "6w", "7w", "8w", "9", "10"]
["1w", "2", "3", "4w", "5", "6w", "7w", "8w", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7w", "8w", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8w", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8", "9", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8", "9w", "10w"]
["1w", "2", "3", "4w", "5", "6", "7", "8", "9w", "10"]
There are a couple of problems in your code.
You use element % round instead of (index + 1) % round
You modify array pebble_color instead of pebble_colors
Fixing these two problems, the program leaves pebble_colors with the value
["1w", "2", "3", "4w", "5", "6", "7", "8", "9w", "10"]
although I think that isn't quite what you had in mind!
You have three arrays where one will do. All you need is an array of ten colours, starting all black. I would code it like this
pebbles = Array.new(10, :black)
(1..10).each do |round|
pebbles.each_with_index do |pebble, i|
if (i + 1).remainder(round) == 0
pebbles[i] = pebble == :black ? :white : :black
end
end
end
p pebbles
output
[:white, :black, :black, :white, :black, :black, :black, :black, :white, :black]
For make the problem simple, I think it is better calculate all round multiples before update the pebble.
pebbles = Array.new(10)
10.times do |i|
# calculate all multiples for each index
multiples = Array(1..10).select {|j| j % (i + 1) == 0}
# observer that we must have to sub 1 since Ruby use 0 indexed arrays
multiples.map! {|j| j - 1}
# update each pebble
multiples.each do |j|
pebbles[j] = pebbles[j] == 'b' ? 'w' : 'b'
end
end
puts "Black pebbles: #{pebbles.select {|p| p == 'b'}.size}"

Resources