Ruby code to iterate through two array simulataneoulsy - ruby

How can I iterate two array in ruby simultaneously , I don't want to use for loop.
for e.g this are my array=
array 1=["a","b","c","d"]
array 2=[1,2,3,4]

You can use zip function for example like this :
array1.zip(array2).each do |array1_var, array2_var|
## whatever you want to do with array_1var and array_2 var
end

You can use Array#zip (no need to use each because zip accept optional block):
array1 = ["a","b","c","d"]
array2 = [1,2,3,4]
array1.zip(array2) do |a, b|
p [a,b]
end
Or, Array#transpose:
[array1, array2].transpose.each do |a, b|
p [a,b]
end

You can zip them together and then iterate through the pairs using each.
array1.zip(array2).each do |pair|
p pair
end

if both arrays are of the same size, you can do:
array1=["a","b","c","d"]
array2=[1,2,3,4]
for i in 0..arr1.length do
//here you do what you want with array1[i] and array2[i]
end

Assuming both arrays are the same size, you can use each_with_index to iterate through them, using the index for the second array:
array1.each_with_index do |item1, index|
item2 = array2[index]
# do something with item1, item2
end
Like so:
irb(main):007:0> array1.each_with_index do |item1, index|
irb(main):008:1* item2 = array2[index]
irb(main):009:1> puts item1, item2
irb(main):010:1> end
a
1
b
2
c
3
d
4

When both of the array are of having same size,you could do as below :
array1=["a","b","c","d"]
array2=[1,2,3,4]
array2.each_index{|i| p "#{array2[i]},#{array1[i]} at location #{i}"}
# >> "1,a at location 0"
# >> "2,b at location 1"
# >> "3,c at location 2"
# >> "4,d at location 3"
And if there is a chance that one array is larger than other array,then larger_array#each_index has to be called.

Related

Output index of hash in array of hashes?

This might be a silly question, but I'm struggling with outputting the positions of an array of hashes I have.
If I have an array of hashes, we'll call some_array, that looks like this:
some_array =
[{:id=>7, :people=>["Bob B", "Jimmy J"], :product=>"product1"},
{:id=>2, :people=>["Sally S"], :product=>"product1"},
{:id=>5, :people=>["Hank H", "Stan C"], :product=>"product2"},
{:id=>3, :people=>["Sue T"], :product=>"product2"},
{:id=>4, :people=>["Anne W"], :product=>"product3"}]
I then iterate though some_array like so:
some_array.select{|p| p[:product] == "product2"]}.each do |product|
product[:people].join("<br>")
product[:product]
Which outputs like:
Hank K product 2
Stan C
Sue T product 2
How would I go about outputting the index/position of each hash in the array?
Would I be able to do something along the lines of:
some_array.select{|p| p[:product] == "product2"]}.each do |product|
product.index
product[:people].join("<br>")
product[:product]
And get:
2 Hank K product2
Stan C
3 Sue T product2
Thank you!
You can use each_with_index and format to your use case:
some_array.each_with_index do |product, index|
if product[:product] == "product2"
p index
p product
end
end
In Ruby, you can chain methods on Enumerable, which allows you to call with_index before you select to get the original index of the element:
some_array.each_with_index.select do |element, _|
element[:product] == "product2"
end.each do |product, index|
p [index, product[:people].join("<br />"), product[:product]]
end
# outputs:
# [2, "Hank H<br />Stan C", "product2"]
# [3, "Sue T", "product2"]
While you can call select.with_index, and it may be tempting to do so, the index won't carry over into the each, because select returns the elements that matched and doesn't care about the input. When you call each_with_index (or each.with_index), though, you now have a new Enumerable which is each element in your array with its index in that array, and select ends up returning these new array elements:
some_array.each.with_index.select { |element, _| element[:product] == "product2" }
# => [[{:id=>5, :people=>["Hank H", "Stan C"], :product=>"product2"}, 2],
[{:id=>3, :people=>["Sue T"], :product=>"product2"}, 3]]
fmt_str_first = "%-4d%-10s%10s"
fmt_str_rest = "#{' '*4}%-10s"
some_array.each_with_index do |h,i|
next unless h[:product] == "product2"
first, *rest = h[:people]
puts fmt_str_first % [i, first, "product2"]
rest.each { |name| puts fmt_str_rest % name }
puts
end
2 Hank H product2
Stan C
3 Sue T product2
See Kernel#sprintf. Note that %-10s in the format string means that the corresponding entry, a string (s), is to be left-adjusted (-) in a field of width 10. %10s would cause the entry to be right-adjusted.
you can just use each_with_index and skip the item you don't need
some_array.each_with_index do |product, index|
next if product[:product] != "product2"
index
product[:people].join("<br>")
product[:product]
end

Compare two dimensional arrays

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

Ruby splat operator changing value inside loop

I want to define a method which can take an optional amount of arguments and hashes, like so
def foo(*b, **c)
2.times.map.with_index { |i|
new_hash, new_array = {}, b
c.map { |key, value| new_hash[key] = value[i] unless value[i].nil? }
new_array << new_hash if new_hash.length > 0
send(:bar, new_array)
}
end
def bar(*b)
p b
end
If I've understood the splat and double splat operators correctly (which I doubt), then this should send the array b to the bar method, and only adding the new_hash from foo if it contains something. However, something weird happens - I'll try and illustrate with some snippets below
# invoking #foo
foo(a, key: 'value')
# first iteration of loop in #foo
# i is 0
# b is []
# c is { :key => ['value1'] }
# send(:bar, new_array) => send(:bar, [{:key => 'value1'}])
# bar yields: [{:key => 'value1'}]
Now, however, something happens
# second iteration of loop in #foo
# i is 1
# b is [:key => 'value1'] <---- why?
# c is { :key => ['value1']
Why has the value of b changed inside the loop of foo?
edit Updated the code to reflect a new array is created for each iteration
new_hash, new_array = {}, b
This doesn't create a copy of b. Now new_array and b point to the same object. Modifying one in-place will modify the other.
new_array << new_hash
That modifies new_array (and thus b) in place, so the new element remains on the next iteration. Use something like +, which creates a copy:
send(:bar, *(b + (new_hash.empty? ? [] : [new_hash])))

concatenate with splat ruby

say I have
arr = [1,2,3]
How can I change this method so it adds each argument to the array?
def add(*number)
arr << *number
end
So add(4,5,6) produces:
arr #=> [1,2,3,4,5,6]
When accepting arguments via splat, they will always be an array. So you can simply add the two arrays together.
def add(*numbers)
arr + numbers
end
Use concat:
def add(*nums)
arr.concat nums
end
Or +:
def add(*nums)
arr + nums
end
$arr = [1,2,3]
def add(*number)
$arr.concat number
end
add(4,5,6)
$arr #=> [1,2,3,4,5,6]
Note: concat modifies the object it operates on ($arr). Plus (+) does not.
As the Tin Man mentions, you don't want to use a global to do this. It is better to simply do
arr.concat [4,5,6]
outside of a function call. Better yet:
arr += [4,5,6]

How to match data from 2 arrays efficiently with Ruby

Right now I have this:
array1 = [obj1, obj2, obj3, obj4]
array2 = [obj1, obj2, obj5, obj6]
array1.each do |item1|
array2.each do |item2|
if (item1[0] == item2[0]) && (item1[1] == item2[1])
p "do stuff"
end
end
end
I need to match 2 pieces of data from each array but both arrays are very large and I'm wondering if there is a faster way to do this.
My current setup requires looking at each element in the second array for each element in the first array which seems terribly inefficient.
Combining map and intersection:
(array1.map { |a| a.first 2 } & array2.map { |a| a.first 2 }).each do
p "do_stuff"
end
Performance should be good. Memory intensive though.
If the two arrays is all you have, you can't avoid the O(n^2) complexity that DigitalRoss mentioned. However, you can index the data so that the next time you don't have to do it all again. In the simplest case, you can use hashes to allow direct access to your data based on the criteria used in the test:
index1 = array1.each_with_object({}){|e, acc|
acc[[e[0], e[1]]] ||= []
acc[[e[0], e[1]]] << e
}
and the same thing for the other array. Then, your loop would look like:
index1.each do |key1, vals1|
if vals2 = index2[key1]
vals1.product(vals2).each do |e1, e2|
p do_stuff
end
end
end
which is, I believe, O(n).
This is very slow because - as DigitalRoss already stated - it is O(n^2). Assuming that eql? is just as fine for you as ==, you can build an index and iterate over that instead, that'll be O(n+m) instead:
array1 = [obj1, obj2, obj3, obj4]
array2 = [obj1, obj2, obj5, obj6]
index = {}
found = []
array1.each do |item1| index[item1.first(2)] = item1 end
array2.each do |item2|
item1 = index[item2.first(2)]
found << [item1,item2] if item1 then
end
found.each do |item1, item2| puts "do something" end
This assumes that the combination of the first 2 elements of all elements in array1 are unique within array1. If that's not the case, the code will be slightly more complex:
array1 = [obj1, obj2, obj3, obj4]
array2 = [obj1, obj2, obj5, obj6]
index = {}
found = []
array1.each do |item1|
key = item1.first(2)
index[key] ||= []
index[key] << item1
end
array2.each do |item2|
items_from_1 = index[item2.first(2)]
if items_from_1 then
found.concat(items_from_1.map { |item1| [item1,item2] })
end
end
found.each do |item1, item2| puts "do something" end
Since you didn't provide any sample data, I didn't test the code.
I hope that helps.

Resources