Rails search through array of arrays - ruby-on-rails

I have an array of arrays like so: [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]. I want to check whether the larger array contains arrays of a given number without regard to the animal element of the array. I also do not want to loop through the array because this would become computationally heavy.
So something like #boolean = bigArray.include?([2, *]), except something that actually works...

You have two possible solutions. Detect the element in the array or convert the array into a Hash
1. Use Enumerable#detect, it will stop as soon as it finds a match and return it, nil otherwise.
> a = [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]
=> [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]
> a.detect{ |(n, _)| n == 2 }
=> [2, "cat"]
> a.detect{ |(n, _)| n == 10 }
=> nil
If you want to add this to the Array class like your example, and force it to return a boolean, do this:
class Array
def custom_include?(num)
!!detect{ |(n, _)| num == n }
end
end
Example:
> a.custom_include?(2)
=> true
> a.custom_include?(10)
=> false
2. If you don't care about the colliding keys, you could convert the array into a Hash and see if the key exists.
> a = [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]
=> [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]
> Hash[a][2]
=> "bird"
> Hash[a][10]
=> nil

One very simple way that doesn't involve looping is to convert the structure to a hash. Your arrays happen to be in an ideal format for this:
values = [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]
Hash[values][2] # "bird"
You will lose the first value for each key, but it will serve to show whether a key exists.

We can use this:
arr = [[1, "dog"], [2, "cat"], [2, "bird"], [3, "monkey"]]
arr.assoc(2)
it will return [2, cat]
in array if element is not exist
arr.assoc(10)
returns nil

Related

How to convert array of 3 element arrays in to a hash where key is first 2 elements

My problem is that I need to do efficient lookups of if a 2 element array and their corresponding value is nil. So if I have the following arrays:
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101]
]
I want something like
h = {
[1, 2] => 100,
[3, 4] => nil,
[5, 6] => 101
}
So I can do something like:
error = []
arr2 = [
[1,2],
[3,4],
[7,8]
]
arr2.each do |val|
if h.include?(val)
if h[val] == nil
error << "Value is nil"
else
# Do something
end
else
error << "Key doesn't exist"
end
end
Given that overwriting or ignoring duplicates is acceptable per your comment.
You can use Enumerable#each_with_object to iterate the Array and create a Hash like so
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101],
[1, 2, nil],
]
arr1.each_with_object({}) do |(*first_two,last),obj|
obj[first_two] = last
end
#=> {[1, 2]=>nil, [3, 4]=>nil, [5, 6]=>101}
You can ignore duplicates in a similar fashion
arr1.each_with_object({}) do |(*first_two,last),obj|
obj[first_two] = last unless obj.key?(first_two)
end
#=> {[1, 2]=>100, [3, 4]=>nil, [5, 6]=>101}
Explanation:
each_with_object({}) will pass each element of of arr1 to the block along with an object (a Hash in this case)
(*first_two,last),obj - *first_two will collect everything up to last and obj is our Hash
obj[first_two] = last simple Hash key assignment
each_with_object returns the object (obj Hash in this case)
Update as recommended by #Stefan in ruby >= 2.7 you could also use
arr1.to_h {|*first_two,last| [first_two, last] }
This version will overwrite keys
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101]
]
result = {}
arr1.each { |i| result[i.first(2)] = i.last }
=> {[1, 2]=>100, [3, 4]=>nil, [5, 6]=>101}
You can destructure every subarray during mapping and then convert result to hash with Array#to_h method
arr1 = [
[1, 2, 100],
[3, 4, nil],
[5, 6, 101],
[1, 2, nil],
]
arr1.map { |*first_two, last| [first_two, last] }.to_h
# => {[1, 2]=>nil, [3, 4]=>nil, [5, 6]=>101}
Duplicates will be overwritten
In case if you need for two last values as key:
arr.map { |b| { b.shift => b }.invert }

Is there an alternative to use of the method Array#product? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I am using the method Array#product with several arguments that are large arrays. The receiver is also a large array. Consequently, the resulting array is huge and consumes an excessive amount of memory. I need all the combinations produced but I don't need them in a single array.
I would like to know if there is an alternative to using that method that requires less memory. Computational time is not really an issue.
You can use the block form of product (see https://apidock.com/ruby/Array/product).
For example the following makes my ruby process unresponsive and I have to kill -9 it:
list = Array.new(100_000)
list.product list
However with the block form I can control+c to stop it any time:
list.product(list) do |combination|
puts combination.join(",")
end
Let me begin with the problem of computing the product of two arrays. Later I will address the more general problem.
arr1 = [1,2,3]
arr2 = [4,5,6]
arr1.product(arr2)
#=> [[1, 4], [1, 5], [1, 6], [2, 4], [2, 5],
# [2, 6], [3, 4], [3, 5], [3, 6]]
To reduce memory requirements--that is, avoiding the construction of the above array--you can construct an enumerator.
product_enum = Enumerator.new do |y|
arr1.size.times do |i|
e1 = arr1[i]
arr2.size.times { |j| y << [e1, arr2[j]] }
end
end
#=> #<Enumerator: 3<Enumerator::Generator:0x00...20>:each>
loop do
p product_enum.next
end
[1, 4]
[1, 5]
[1, 6]
[2, 4]
[2, 5]
[2, 6]
[3, 4]
[3, 5]
[3, 6]
To reuse the enumerator execute product_enum.rewind. See Enumerator::new and Enumerator#rewind. Enumerator::new's block variable y is called a yielder. An enumerator can be thought of as a machine that generates values following certain rules.
Ruby's methods Array#combination, Array#permutation and others return enumerators, but for reasons that are unknown to me, Array#product returns an array. (Perhaps a reader can explain why in a comment.)
We can construct an Array method product_enum that returns an enumerator, which you could use for your problem.
class Array
def product_enum(*arr)
Enumerator.new do |y|
self.size.times do |i|
e1 = self[i]
product_enum_recurse(y,[e1],*arr)
end
end
end
def product_enum_recurse(y,a,*arr)
v, *rest = arr
v.size.times do |i|
if rest.empty?
y << [*a, v[i]]
else
product_enum_recurse(y,[*a, v[i]],*rest)
end
end
end
end
e = [1,2].product_enum([3,4], [5,6], [7,8])
#=> #<Enumerator: #<Enumerator::Generator:0x00...0>:each>
loop do
p e.next
end
[1, 3, 5, 7]
[1, 3, 5, 8]
[1, 3, 6, 7]
[1, 3, 6, 8]
[1, 4, 5, 7]
[1, 4, 5, 8]
[1, 4, 6, 7]
[1, 4, 6, 8]
[2, 3, 5, 7]
[2, 3, 5, 8]
[2, 3, 6, 7]
[2, 3, 6, 8]
[2, 4, 5, 7]
[2, 4, 5, 8]
[2, 4, 6, 7]
[2, 4, 6, 8]

Given an array A[] and a number x, check for pair in A[] with sum as x

Given an array A[] and a number x, check for pair in A[] with sum as x. can anyone help me out on this one in rails?
The ruby array #combination method can give you all combinations of array members of a given number of elements.
[1, 2, 3, 4, 5, 6].combination(2).to_a
=> [[1, 2], [1, 3], [1, 4], [1, 5], [1, 6], [2, 3], [2, 4], ... [5,6]]
Then you just want to select the elements where they add up to a given number.
[1, 2, 3, 4, 5, 6]combination(2).to_a.select{|comb| comb[0] + comb[1] == 7}
=> [[1, 6], [2, 5], [3, 4]]
To make it work for a different number of combined elements (e.g. 3 instead of 2) you can do...
[1, 2, 3, 4, 5, 6]combination(3).to_a.select{|c| (c.inject(0) {|sum,x| sum + x}) == 7}
This will work for 2, 3, 4, or any number up to the full array size.
It works by
finding combinations of 3
using `#inject' to sum all the elements of each combination
comparing that sum to the target number
You can easily achieve it by own function as:
def sum_as_x?(ary,x)
num=a.find{|e| ary.include?(x-e)}
unless num
puts "not exist"
else
p [x-num,num]
end
end
a = [1,2,3,4,5]
sum_to_x?(a,9)
>> [5, 4]
sum_to_x?(a,20)
>> not exist

How do I sort two arrays the same way?

I have two arrays:
a = [6, 4, 3]
b = [1, 3, 4]
I call a.sort:
a.sort = [3, 4, 6]
How do I sort array b so the values have the same position to values in array a before the sort?
It would be now:
b = [4, 3, 1]
So that values in b have the same position to values in array a.
You could combine both the arrays into one using the zip method. Once you combine a and b, you would get,
[[6, 1], [4, 3], [3, 4]]
Now sort the arrays, which would sort them based on the first element of each sub-array resulting in,
[[3, 4], [4, 3], [6, 1]]
Now we want to do the reverse of zip to get the first and second elements of each sub-array into a new array. Using transpose, we can get it back in the original form as,
[[3, 4, 6], [4, 3, 1]]
Thankfully using parallel assignment all of this is possible in one line. Here's the full code,
x, y = a.zip(b).sort.transpose
Now x should contain [3, 4, 6], and y should contain [4, 3, 1].
a = [6, 4, 3]
b = [1, 3, 4]
ra, rb = a.zip(b).sort_by(&:first).transpose
# ra => [3, 4, 6]
# rb => [4, 3, 1]
I don't know what you're trying to achieve, and I'm sure others could come up with a more elegant solution, but I would use a Hash instead. Assign the values of a a as the key, and the values of b as the values. You could iterate over a to accomplish this, or just in advance when you're creating this data. The result should be:
$ hash
=> {6 => 1, 4 => 3, 3 => 4}
a0 b0 a1 b1 a2 b2
$ hash.sort
=> [[3, 4], [4, 3], [6, 1]]
Like I said, not super smooth, but I've got turkey hangover...
[a, b].transpose.sort { |x, y| x[0] <=> y[0] }.transpose[1]
=> [4, 3, 1]
or
a, b = [a, b].transpose.sort { |x, y| x[0] <=> y[0] }.transpose

get matching items in arrays

I found another question on here that told how to get the matching items in 2 arrays like this:
matches = array1 & array2
However I have an array of arrays. like:
[[1,2,3,4],[2,3,4,5],[1,3,4,5]]
In this case I want to return 3 and 4 because they are in all three arrays.
How do I go about doing that?
Thank you!
Like this:
a.reduce(:&)
For example:
>> a = [[1,2,3,4],[2,3,4,5],[1,3,4,5]]
=> [[1, 2, 3, 4], [2, 3, 4, 5], [1, 3, 4, 5]]
>> a.reduce(:&)
=> [3, 4]

Resources