How to swap variables inside of a nested array? - ruby-on-rails

I have 2 arrays that I've zipped together and now I'm trying to swipe values at even positions.
So this is what I've tried:
a = [1, 2, 3, 4]
b = [111, 222, 333, 444]
c = a.zip(b)
# Now c is equal to: [[1, 111], [2, 222],[3, 333],[4, 444]]
c.map.with_index do |item, index|
a = item[0]
b = item[1]
if index%2 == 0
a, b = b, a
end
end
What I would like to have:
c = [[1, 111], [222,2], [3, 333],[444, 4]]
But it's still not working, is there a better solution ? Or how could I fix mine to make it work ?
EDIT:
I've realized that I could probably just use the ".reverse" method to swap the element. But I still can't manage to make it work.

Perhaps try:
c.map.with_index do |item, index|
index%2 != 0 ? item.reverse : item
end
=> [[1, 111], [222, 2], [3, 333], [444, 4]]

I would probably go with
a = [1, 2, 3, 4]
b = [111, 222, 333, 444]
a.zip(b).each_with_index do |item, idx|
item.reverse! if idx.odd?
end
#=>[[1, 111], [222, 2], [3, 333], [444, 4]]
zip as you did and reverse! just the items where the index is odd.
Other options include:
a.map.with_index(1) do |item,idx|
[item].insert(idx % 2, b[idx -1])
end
#=>[[1, 111], [222, 2], [3, 333], [444, 4]]
Here we use with_index starting with 1 and then use the modulo method to determine if the item in b should be placed at index 0 or index 1.
Or
a.zip(b).tap {|c| c.each_slice(2) {|_,b| b.reverse!}}
#=>[[1, 111], [222, 2], [3, 333], [444, 4]]
Here we zip a and b as your example did then we take the sub Arrays in groups of 2 and reverse the second Array using reverse! which will modify the Array in place.

Related

How to count consecutive numbers in an array?

If I have an array:
array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
I want to be able to identify consecutive matching numbers that have a length of greater than 3. And map the starting index of the consecutive numbers. An example output for the above array would be:
consecutive_numbers = [
{starting_index: 1, value: 2, length: 4},
{starting_index: 10, value: 3, length: 4},
{starting_index: 14, value: 2, length: 7}
]
The values can be the same, but the consecutive series' must be mutually exclusive. See that there are 2 hashes with a value of 2, but their starting indexes are different.
My attempt so far... looks like this:
array.each_cons(3).with_index.select{|(a,b,c), i|
[a,b,c].uniq.length == 1
}
but that will returns:
[[[2, 2, 2], 1], [[2, 2, 2], 2], [[1, 1, 1], 7], [[3, 3, 3], 10], [[3, 3, 3], 11], [[2, 2, 2], 14], [[2, 2, 2], 15], [[2, 2, 2], 16], [[2, 2, 2], 17], [[2, 2, 2], 18]]
But that returns overlapping results.
array.each_with_index.
chunk(&:first).
select { |_,a| a.size > 3 }.
map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
The steps are as follows.
e = array.each_with_index.chunk(&:first)
#=> #<Enumerator: #<Enumerator::Generator:0x00005b1944253c18>:each>
We can convert this enumerator to an array to view the elements it will generate and pass to its block.
e.to_a
#=> [[1, [[1, 0]]],
# [2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
# [5, [[5, 5], [5, 6]]],
# [1, [[1, 7], [1, 8], [1, 9]]],
# [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
# [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]]
Continuing,
c = e.select { |_,a| a.size > 3 }
#=> [[2, [[2, 1], [2, 2], [2, 3], [2, 4]]],
# [3, [[3, 10], [3, 11], [3, 12], [3, 13]]],
# [2, [[2, 14], [2, 15], [2, 16], [2, 17], [2, 18], [2, 19], [2, 20]]]]
c.map { |n,a| { starting_index: a.first.last, value: n, length: a.size } }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
This is another way.
array.each_with_index.with_object([]) do |(n,i),arr|
if arr.any? && arr.last[:value] == n
arr.last[:length] += 1
else
arr << { starting_index: i, value: n, length: 1 }
end
end.select { |h| h[:length] > 3 }
#=> [{:starting_index=> 1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
You can chunk_while each pair of elements are equal:
p array.chunk_while { |a, b| a == b }.to_a
# [[1], [2, 2, 2, 2], [5, 5], [1, 1, 1], [3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 2]]
You select the arrays with 3 or more elements.
After that, with then, you can yield self, so you have access to the array of arrays, which you can use to get the starting_index:
[1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].chunk_while(&:==).then do |this|
this.each_with_object([]).with_index do |(e, memo), index|
memo << { starting_index: this.to_a[0...index].flatten.size, value: e.first, length: e.size }
end
end.select { |e| e[:length] > 3 }
# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
For the starting_index, you get the elements to the current index (non inclusive), flatten them, and get the total of elements.
The value, as each array in the array has the same elements, can be anything, the length, is the length of the current array in the "main" array.
This is another option..
Zipping the array (Enumerable#zip) with its indexes by Endles Ranges
Calling Enumerable#slice_when (another flavour of chunk_while)
Mapping (Enumerable#map) to the required Hash
Finally rejecting (Enumerable#reject) hashes if length is greater than 3
array
.zip(0..)
.slice_when { |a, b| a.first != b.first }
.map { |a| { starting_index: a.first.last, value: a.first.first, length: a.size } }
.reject { |h| h[:length] < 3 }
#=> [{:starting_index=>1, :value=>2, :length=>4}, {:starting_index=>7, :value=>1, :length=>3}, {:starting_index=>10, :value=>3, :length=>4}, {:starting_index=>14, :value=>2, :length=>7}]
Well, the most obvious (and probably the fastest) way is iterate over an array and count everything manually:
array = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2]
array_length_pred = array.length.pred
consecutive_numbers = []
starting_index = 0
value = array.first
length = 1
array.each_with_index do |v, i|
if v != value || i == array_length_pred
length += 1 if i == array_length_pred && value == v
if length >= 3
consecutive_numbers << {
starting_index: starting_index,
value: value,
length: length
}
end
starting_index = i
value = v
length = 1
next
end
length += 1
end
p consecutive_numbers
# [{:starting_index=>1, :value=>2, :length=>4},
# {:starting_index=>7, :value=>1, :length=>3},
# {:starting_index=>10, :value=>3, :length=>4},
# {:starting_index=>14, :value=>2, :length=>7}]
You could work with strings instead.
Here, I coerce the array into a string:
input_sequence = [1,2,2,2,2,5,5,1,1,1,3,3,3,3,2,2,2,2,2,2,2].join
I use a regex to group consecutive characters:
groups = input_sequence.gsub(/(.)\1*/).to_a
#=> ["1", "2222", "55", "111", "3333", "2222222"]
Now I can search for the groups as substrings within the input string:
groups.map do |group|
{
starting_index: input_sequence.index(group),
value: group[0].to_i,
length: group.length
}
end.reject { |group| group[:length] <= 3 }
#=> [{:starting_index=>1, :value=>2, :length=>4},
{:starting_index=>7, :value=>1, :length=>3},
{:starting_index=>10, :value=>3, :length=>4},
{:starting_index=>14, :value=>2, :length=>7}]
There's room for improvement here -- I'm creating lots of intermediate objects for one -- but I thought I would offer a different approach.

How to optimize the solution for Two_sum code in ruby

I am woking on the solution for the following question.
Given an array of integers, return indices of the two numbers such that they add up to a specific target.
You may assume that each input would have exactly one solution.
Example:
Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].
This is the solution submitted in ruby after referring the C++ code http://leetcodeunlock.com/2016/05/20/leetcode-1-two-sum-easy/ .
def two_sum(nums, target)
hash = {}
arr = []
nums.each_with_index do |value,index|
y = target - value
if(hash.find{|key,val| key == value})
arr << hash[value]
arr << index
return arr
else
hash[y] = index
end
end
end
My submission failed with the message : Time limit exceeded. Can anyone point out the mistake and help me optimise the code?
nums = [2, 7, 11, 15]
target = 9
# this will find all combinations of 2 elements that add up to 9
results = (0...nums.size).to_a.combination(2).select { |first, last| nums[first] + nums[last] == target }
results.first #=> [0, 1]
Explanation of some parts of the code:
# Get indexes of all elements of nums array
(0...nums.size).to_a #=> [0, 1, 2, 3]
# Generate all combinations of indexes of each 2 elements
(0...nums.size).to_a.combination(2).to_a #=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
I have modified the line
if(hash.find{|key,val| key == value})
to
if(hash.key?(value))
to find if a specific key is present in the hash and this solved the issue.
Code
def sum_to_num(arr, num)
return [num/2, num/2] if num.even? && arr.count(num/2) > 1
a = arr.uniq.
group_by { |n| (2*n-num).abs }.
find { |_,a| a.size > 1 }
a.nil? ? nil : a.last
end
This method requires three or four passes through the array, if num is even, one to count the instances of num/2, one to remove duplicate values, one to group_by and one to find the pair of numbers that sum to the desired total. It therefore should be much faster than methods that evaluate every pair of the array's elements, particularly as the size of the array is increased.
Examples
sum_to_num [2, 11, 7, 15], 9
#=> [2, 7]
sum_to_num [2, 5, 2, 6, 1, -5, 4], 10
#=> [6, 4]
sum_to_num [2, 7, 11, -7, 15], 0
#=> [7, -7]
sum_to_num [2, 7, 11, 7, 15], 14 #???
sum_to_num [2, -7, 11, -7, 15], -14 #???
sum_to_num [2, 7, 11, 15], 17
#=> [2, 15]
sum_to_num [2, -11, 8, 15], 4
#=> [-11, 15]
sum_to_num [2, -11, 8, 15], -3
#=> [-11, 8]
sum_to_num [2, -11, 8, 15], 100
#=> nil
Explanation
Assume x and y sum to num. Then
2*x-num + 2*y-num = 2*(x+y) - 2*num
= 2*num - 2*num
= 0
meaning that 2*x-num and 2*y-num are either both zero or they have the opposite signs and the same absolute value. Similarly, if 2*x-num and 2*y-num sum to zero, then
2*x-num + 2*y-num = 0
2*(x+y) - 2*num = 0
meaning that n+m = num (which is hardly surprising considering that 2*x+num is a linear transformation.
Suppose
arr = [2, 5, 2, 6, 1, -5, 4]
num = 10
then
if num.even? && arr.count(num/2) > 1
#=> if 10.even? && arr.count(5) > 1
#=> if true && false
#=> false
Therefore, do not return [5,5].
b = arr.uniq
#=> [2, 5, 6, 1, -5, 4]
c = b.group_by { |n| (2*n-num).abs }
#=> {6=>[2], 0=>[5], 2=>[6, 4], 8=>[1], 20=>[-5]}
a = c.find { |_,a| a.size > 1 }
#=> [2, [6, 4]]
return nil if a.nil?
# do not return
a.last
#=> [6, 4]
I was doing this challenge for fun and wrote a cleaned up ruby solution.
def two_sum(nums, target)
hash = {}
nums.each_with_index { |number, index| hash[number] = index }
nums.each_with_index do |number, index|
difference = target - number
if hash[difference] && hash[difference] != index
return [index, hash[difference]]
end
end
end
# #param {Integer[]} nums
# #param {Integer} target
# #return {Integer[]}
def two_sum(nums, target)
length = nums.length
for i in 0..length
j = i+1
for a in j..length
if j < length
if nums[i] + nums[a] == target
return [i, a]
end
end
j+=1
end
end
[]
end
Well this is my way of solving this
def two_sum(nums, target)
nums.each_with_index do |value, index|
match_index = nums.find_index(target - value)
return [index, match_index] if match_index
end
nil
end
The above has the advantage that it stops execution when a match is found and so hopefully won't time out. :)

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 does Ruby's map method work in this case?

I got the mistake when I want to add doubled values to an array:
arr = [1,2,3]
def my_mistake(arr)
result = Array.new
arr.map { |element| result << element * 2 }
end
#=> [[2, 4, 6], [2, 4, 6], [2, 4, 6]]
def solution(arr)
arr.map { |element| element * 2 }
end
#=> [2,4,6]
However, come back to my mistake and the definition of map method in Ruby.
Invokes the given block once for each element of self. Creates a new array containing the values returned by the block.
I think my_mistake method has to return [[2], [2, 4], [2, 4, 6]] but it doesn't.
Everyone can explain this case for me ?
The resulting array will contain three occurrences of the same reference to the same array, as result is the result of the expression result << element * 2. So the result of the map is (kind of) [result, result, result]. These all point to the same content, which is the content of result at the end of the process ([2, 4, 6]).
What you expected would be achieved if you clone the array at each point, so that every resulting element would point to a different array, and each addition would not affect the previously computed arrays:
arr.map { |element| (result << element * 2).clone }
=> [[2], [2, 4], [2, 4, 6]]
.map returns the last evaluated expression, so no need for the result << part there. Here's something that worked for me:
def my_mistake(arr)
result = [] # '= []' is same like '= Array.new', look-up "literal constructors in Ruby"
new_arr = [] # same like new_arr = Array.new
until arr.empty?
new_arr << arr.shift # we add each element of arr, one by one, starting from the beginning
output << new_arr.map { |e| e * 2} # we calculate *2 for each element
end
return result
end
p my_mistake(arr) #=> [[2], [2, 4], [2, 4, 6]]
If you're nto sure how this works, try putting "p output" after the 6th line:
def my_mistake(arr)
output = []
new_arr = []
until arr.empty?
new_arr << arr.shift
output << new_arr.map { |e| e * 2}
p output
end
return output
end
my_mistake(arr)
The program will print:
[[2]]
[[2], [2, 4]]
[[2], [2, 4], [2, 4, 6]]

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

Resources