Compare two dimensional arrays - ruby

I have two two-dimensional arrays,
a = [[17360, "Z51.89"],
[17361, "S93.601A"],
[17362, "H66.91"],
[17363, "H25.12"],
[17364, "Z01.01"],
[17365, "Z00.121"],
[17366, "Z00.129"],
[17367, "K57.90"],
[17368, "I63.9"]]
and
b = [[17360, "I87.2"],
[17361, "s93.601"],
[17362, "h66.91"],
[17363, "h25.12"],
[17364, "Z51.89"],
[17365, "z00.121"],
[17366, "z00.129"],
[17367, "k55.9"],
[17368, "I63.9"]]
I would like to count similar rows in both the arrays irrespective of the character case, i.e., "h25.12" would be equal to "H25.12".
I tried,
count = a.count - (a - b).count
But (a - b) returns
[[17360, "Z51.89"],
[17361, "S93.601A"],
[17362, "H66.91"],
[17363, "H25.12"],
[17364, "Z01.01"],
[17365, "Z00.121"],
[17366, "Z00.129"],
[17367, "K57.90"]]
I need the count as 5 since there are five similar rows when we do not consider the character case.

Instead of a - b you should do this:
a.map{|k,v| [k,v.downcase]} - b.map{|k,v| [k,v.downcase]} # case-insensitive

You can convert Arrays to Hash, and use Enumerable#count with a block.
b_hash = b.to_h
a.to_h.count {|k, v| b_hash[k] && b_hash[k].downcase == v.downcase }
# => 5

It will convert second element of inner array to upcase for both array then you can perform subtraction, then It will return exact result that you want
a.map{|first,second| [first,second.upcase]} - b.map{|first,second| [first,second.upcase]}

You can zip them and then use the block form of count:
a.zip(b).count{|e| e[0][1].downcase == e[1][1].downcase}

a.count - (a.map{|e| [e[0],e[1].downcase] } - b.map{|e| [e[0],e[1].downcase] }).count
The above maps a and b to new arrays where the second sub-array element is downcase.

You want to count similar, so &(AND) operation is more suitable.
(a.map { |k, v| [k, v.upcase] } & b.map { |k, v| [k, v.upcase] }).count

Using Proc and '&':
procedure = Proc.new { |i, j| [i, j.upcase] }
(a.map(&procedure) & b.map(&procedure)).count
#=> 5
For better understanding, let's simplify it:
new_a = a.map {|i, j| [i, j.upcase]}
new_b = b.map {|i, j| [i, j.upcase]}
# Set intersection using '&'
(new_a & new_b).count
#=> 5

I have assumed that the ith element of a is to be compared with the ith element of b. (Edit: a subsequent comment by the OP confirmed this interpretation.)
I would be inclined to use indices to avoid the construction of relatively large temporary arrays. Here are two ways that might be done.
#1 Use indices
[a.size,b.size].min.size.times.count do |i|
af,al=a[i]
bf,bl=b[i];
af==bf && al.downcase==bl.downcase
end
#=> 5
#2 Use Refinements
My purpose in giving this solution is to illustrate the use of Refinements. I would not argue for its use for the problem at hand, but this problem provides a good vehicle for showing how the technique can be applied.
I could not figure out how best to do this, so I posted this question on SO. I've applied #ZackAnderson's answer below.
module M
refine String do
alias :dbl_eql :==
def ==(other)
downcase.dbl_eql(other.downcase)
end
end
refine Array do
def ==(other)
zip(other).all? {|x, y| x == y}
end
end
end
'a' == 'A' #=> false (as expected)
[1,'a'] == [1,'A'] #=> false (as expected)
using M
'a' == 'A' #=> true
[1,'a'] == [1,'A'] #=> true
I could use Enumerable#zip, but for variety I'll use Object#to_enum and Kernel#loop in conjunction with Enumerator#next:
ea, eb = a.to_enum, b.to_enum
cnt = 0
loop do
cnt += 1 if ea.next == eb.next
end
cnt #=> 5

Related

How to find count matching characters at the same indes and at an unmatching index

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]

How to get the nth element of an Enumerable in Ruby

For example, to return the 10,000th prime number I could write:
require 'prime'
Prime.first(10000).last #=> 104729
But creating a huge intermediate array, just to retrieve its last element feels a bit cumbersome.
Given that Ruby is such an elegant language, I would have expected something like:
Prime.at(9999) #=> 104729
But there is no Enumerable#at.
Is the above workaround intended or is there a more direct way to get the nth element of an Enumerable?
The closest thing I can think of to a hypothetical at method is drop, which skips the indicated number of elements. It tries to return an actual array though so you need to combine it with lazy if you are using with infinite sequences, e.g.
Prime.lazy.drop(9999).first
What about this?
Prime.to_enum.with_index(1){|e, i| break e if i == 10000}
# => 104729
For enumerators that are not lazy, you probably want to put lazy on the enumerator.
Based on sawa's solution, here's a more succinct alternative:
Prime.find.with_index(1) { |_, i| i == 10000 }
#=> 104729
or
Prime.find.with_index { |_, i| i == 9999 }
#=> 104723
module Enumerable
def at(n)
enum = is_a?(Enumerator) ? self : each
(n-1).times { enum.next }
enum.next
end
end
(0..4).at(3)
#=> 2
{ a:1, b:2, c:3, d:4, e:5 }.at(3)
#=> [:c, 3]
'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).at(3)
#=> "2"
require 'prime'; Prime.at(10000)
#=> 104729
e = 1.step; e.at(10)
#=> 10
[0,1,2,3,4].at(6)
#=> nil
'0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).at(6)
#=> StopIteration (iteration reached an end)
Note that '0ab_1ab_2ab_3ab_4ab'.gsub(/.(?=ab)/).class #=> Enumerator.
Some refinements would be needed, such as checking that n is an integer greater than zero and improving the exception handling, such as dealing with the case when self is not an enumerator but has no method each.

changing integers into words ruby without gems

I am trying to change numbers up to 100 from integers into words, but have run into some trouble, can anyone point out what is missing with my code:
def in_words(integer)
numWords = {
0=>"zero",
1=>"one",
2=>"two",
3=>"three",
4=>"four",
5=>"five",
6=>"six",
7=>"seven",
8=>"eight",
9=>"nine",
10=>"ten",
11=>"eleven",
12=>"twelve",
13=>"thirteen",
14=>"fourteen",
15=>"fifteen",
16=>"sixteen",
17=>"seventeen",
18=>"eighteen",
19=>"nineteen",
20=>"twenty",
30=>"thirty",
40=>"fourty",
50=>"fifty",
60=>"sixty",
70=>"seventy",
80=>"eighty",
90=>"ninety",
100=>"one hundred"
}
array = integer.to_s.split('')
new_array = []
numWords.each do |k,v|
array.each do |x|
if x = k
new_array.push(v)
end
end
end
new_array.join('')
end
Right now when I do:
inwords(0)
I get the following:
=>"zeroonetwothreefourfivesixseveneightnineteneleventwelvethirteenfourteenfiftee nsixteenseventeeneighteennineteentwentythirtyfourtyfiftysixtyseventyeightyninetyone hundred"
Edit
I noticed your code iterates through the array a lot of times and uses the = instead of the == in your if statements.
Your code could be more efficient using the Hash's #[] method in combination with the #map method.., here's a one-line alternative:
integer.to_s.split('').map {|i| numWords[i.to_i]} .join ' '
Also, notice that the integer.to_s.split('') will split the array into one-digit strings, so having numbers up to a hundred isn't relevant for the code I proposed.
To use all the numbers in the Hash, you might want to use a Regexp to identify the numbers you have. One way is to do the following (I write it in one line, but it's easy to break it down using variable names for each step):
integer.to_s.gsub(/(\d0)|([1]?\d)/) {|v| v + " "} .split.map {|i| numWords[i.to_i]} .join ' '
# or:
integer.to_s.gsub(/(#{numWords.keys.reverse.join('|')})/) {|v| v + " "} .split.map {|i| numWords[i.to_i]} .join ' '
# out = integer.to_s
# out = out.gsub(/(#{numWords.keys.reverse.join('|')})/) {|v| v + " "}
# out = out.split
# out = out.map {|i| numWords[i.to_i]}
# out = out.join ' '
Edit 2
Since you now mention that you want the method to accept numbers up to a hundred and return the actual number (23 => twenty three), maybe a different approach should be taken... I would recommend that you update your question as well.
def in_words(integer)
numWords = {
0=>"zero",
1=>"one",
2=>"two",
3=>"three",
4=>"four",
5=>"five",
6=>"six",
7=>"seven",
8=>"eight",
9=>"nine",
10=>"ten",
11=>"eleven",
12=>"twelve",
13=>"thirteen",
14=>"fourteen",
15=>"fifteen",
16=>"sixteen",
17=>"seventeen",
18=>"eighteen",
19=>"nineteen",
20=>"twenty",
30=>"thirty",
40=>"fourty",
50=>"fifty",
60=>"sixty",
70=>"seventy",
80=>"eighty",
90=>"ninety",
100=>"one hundred"
}
raise "cannot accept such large numbers" if integer > 100
raise "cannot accept such small numbers" if integer < 0
return "one hundred" if integer == 100
if integer < 20 || integer %10 == 0
numWords[integer]
else
[numWords[integer / 10 * 10], numWords[integer % 10]].join ' '
end
end
the integer / 10 * 10 makes the number a round number (ten, twenty, etc') because integers don't have fractions (so, 23/10 == 2 and 2 * 10 == 20). The same could be achieved using integer.round(-1), which is probably better.
It seems like all you're trying to do is find a mapping from an implicit hash
module NumWords
INT2STR = {
0=>"zero",
1=>"one",
2=>"two",
3=>"three",
4=>"four",
5=>"five",
6=>"six",
7=>"seven",
8=>"eight",
9=>"nine",
10=>"ten",
11=>"eleven",
12=>"twelve",
13=>"thirteen",
14=>"fourteen",
15=>"fifteen",
16=>"sixteen",
17=>"seventeen",
18=>"eighteen",
19=>"nineteen",
20=>"twenty",
30=>"thirty",
40=>"fourty",
50=>"fifty",
60=>"sixty",
70=>"seventy",
80=>"eighty",
90=>"ninety",
100=>"one hundred"
}
module_function
def in_words(integer)
INT2STR[integer]
end
end
The above code separates the hash definition from the method call so that the hash doesn't get recreated every time you call in_words.
You can also use Hash#fetch instead of Hash#[] as Andrey pointed out.
Your test whether x = k is your first problem (in two ways).
Firstly, if x = k means assign the value of k to x and then execute the if block if that value is true (basically anything other than false or nil).
What you should actually be testing is x == k which will return true if x is equal to k.
The second problem is that you converted your number into an array of string representation so you are comparing, for example, if "0" == 0. This won't return true because they are different types.
If you convert it to if x.to_i == k then your if block will be executed and you'll get:
> in_words(0)
=> "zero"
Then you get to move onto the next problem which is that you're looking at your number digit by digit and some of the values you are testing against need two digits to be recognised:
> in_words(10)
=> "zeroone"
You might be in looking at a different question then - or maybe that is the question you wanted answered all along!
Here's another way you might do it:
ONES_TO_TEXT = { 0=>"zero", 1=>"one", 2=>"two", 3=>"three", 4=>"four",
5=>"five", 6=>"six", 7=>"seven", 8=>"eight", 9=>"nine" }
TEENS_TO_TEXT = { 10=>"ten", 11=>"eleven", 12=>"twelve",
13=>"thirteen", 15=>"fifteen" }
TENS_TO_TEXT = { 2=>"twenty", 3=>"thirty", 5=>"fifty", 8=>"eighty" }
def in_words(n)
raise ArgumentError, "#{n} is out-of_range" unless (0..100).cover?(n)
case n.to_s.size
when 1 then ONES_TO_TEXT[n]
when 3 then "one hundred"
else
case n
when (10..19)
TEENS_TO_TEXT.key?(n) ? TEENS_TO_TEXT[n] : ONES_TO_TEXT[n]+"teen"
else
t,o = n.divmod(10)
(TENS_TO_TEXT.key?(t) ? TENS_TO_TEXT[t] : ONES_TO_TEXT[t]+"ty") +
(o.zero? ? '' : "-#{ONES_TO_TEXT[o]}")
end
end
end
Let's try it:
in_words(5) #=> "five"
in_words(10) #=> "ten"
in_words(15) #=> "fifteen"
in_words(20) #=> "twenty"
in_words(22) #=> "twenty-two"
in_words(30) #=> "thirty"
in_words(40) #=> "fourty"
in_words(45) #=> "fourty-five"
in_words(50) #=> "fifty"
in_words(80) #=> "eighty"
in_words(99) #=> "ninety-nine"
in_words(100) #=> "one hundred"
Here the increased complexity may not be justified, but this approach may in fact simplify the calculations when the maximum permitted value of n is much greater than 100.

How do I merge two arrays of hashes based on same hash key value?

So I have two arrays of hashes:
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
How would I concatenate them with the condition that the value of the key c is equivalent in both a and b? Meaning I want to be able to concatenate with the condition of a['c'] == b['c']
This is the result I want to get:
final_array = [{"b"=>123,"c"=>456,"d"=>789}, {"b"=>456,"c"=>555}, {"b"=>222,"c"=>444}]
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
p a.zip(b).map{|h1,h2| h1["c"] == h2["c"] ? h1.merge(h2) : [h1 ,h2]}.flatten
# => [{"b"=>123, "c"=>456, "d"=>789}, {"b"=>456, "c"=>555}, {"b"=>222, "c"=>444}]
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
def merge_hashes_with_equal_values(array_of_hashes, key)
array_of_hashes.sort { |a,b| a[key] <=> b[key] }.
chunk { |h| h[key] }.
each_with_object([]) { |h, result| result << h.last.inject(&:merge) }
end
p merge_hashes_with_equal_values(a + b, 'c')
# => [{"b"=>222, "c"=>444}, {"c"=>456, "d"=>789, "b"=>123}, {"b"=>456, "c"=>555}]
Concatenate the arrays first, and pass it to the method with the hash key to combine on. Sorting that array then places the hashes to merge next to each other in another array, which makes merging a bit easier to program for. Here I chose #chunk to handle detection of continuous runs of hashes with equal keys to merge, and #each_with_object to compile the final array.
Since this method takes one array to work on, the length of the starting arrays does not need to be equal, and the ordering of those arrays does not matter. A downside is that the keys to operate on must contain a sortable value (no nils, for example).
Here is yet another approach to the problem, this one using a hash to build the result:
def merge_hashes_with_equal_values(array_of_hashes, key)
result = Hash.new { |h,k| h[k] = {} }
remainder = []
array_of_hashes.each_with_object(result) do |h, answer|
if h.has_key?(key)
answer[h.fetch(key)].merge!(h)
else
remainder << h
end
end.values + remainder
end
Enumerable#flat_map and Hash#update are the perfect methods for this purpose :
a = [{"b"=>123,"c"=>456}, {"b"=>456,"c"=>555}]
b = [{"c"=>456,"d"=>789}, {"b"=>222,"c"=>444}]
p a.zip(b).flat_map{|k,v| next k.update(v) if k["c"] == v["c"];[k,v]}
# >> [{"b"=>123, "c"=>456, "d"=>789}, {"b"=>456, "c"=>555}, {"b"=>222, "c"=>444}]

mapping elements of an array differently based on position

I want to map elements of an array such that all elements
of the array are floats, except the first element which
is a string.
Anyone know how I can do this?
Tried this but doesn't work:
arr = arr.map { |e| e.to_i if e != arr.first }
Another solution is
[array.first] + array.drop(1).map &:to_f
This makes it clear that you want the first element separate from the rest, and you want the rest of the elements to be of type Float. Other options include
array.map { |element, index| index == 0 ? element : element.to_f }
array.map { |element| element == array.first ? element : element.to_f }
You can use a short ternary expression here:
a.map { |e| ( e == a.first ) ? e : e.to_f }
Another option (if you don't want to use ternary operators) is to do the following:
arr = arr.map { |e| (e == arr.first) && e || e.to_f}
This alternative is discussed here. A limitation with this method is that the first element in the array cannot be nil (or some other value that would evaluate false in a boolean evaluation), because if so, it will evaluate to the || expression and return e.to_f instead of just e.
Ruby 1.9 only?
arr = arr.map.with_index { |e, i| i.zero? ? e.to_s : e.to_f }
You can ask the objects themselves whether they're numbers.
"column heading".respond_to?(:to_int) # => false
3.1415926.respond_to?(:to_int) # => true
new_arr = arr.map do |string_or_float|
if string_or_float.respond_to?(:to_int)
string_or_float.to_int # Change from a float into an integer
else
string_or_float # Leave the string as-is
end
end
respond_to?(:to_int) means "Can I call to_int on you?"
to_int is a method that only objects that are readily convertable to integers should have. Unlike to_i, which is "I'm not very much like an integer, but you can try to convert me into a integer", to_int means "I'm very much like an integer - convert me into an integer with full confidence!"

Resources