Sort the array with reference to another array - arrays

I have two different arrays. Let's say:
a = [1, 2, 13, 4, 10, 11, 43]
b = [44, 23, 1, 4, 10, 2, 55, 13]
Now I have to sort the array b by referring to the array a. I tried the following solution:
lookup = {}
a.each_with_index do |item, index|
lookup[item] = index
end
b.sort_by do |item|
lookup.fetch(item)
end
But I'm getting the KeyError: key not found: 44 error. Can anyone help me find a solution?
Expected output is [1, 2, 13, 4, 10, 23, 44, 55].

Comparing arrays checks the first value, if it's equal goes to the second value and so on. Hence this will compare by the order of occurrence in a and then by the actual value for the ones not in a:
b.sort_by { |e| [a.index(e) || a.size, e] }
To keep O(nlogn), you could:
ai = a.each_with_index.to_h
b.sort_by { |e| [ai[e] || a.size, e] }

Related

How to get the indices of subarray in large array

I have two arrays as follows:
a = [1,2,3,4,5,6,7,8,9,10]
b = [3,5,8,10,11]
I want to find the index of subarray in main array if a number is present. The expected output is:
res = [2,4,7,9]
I have done as follows:
[3,5,8,10,11].each do |_element|
res_array = []
if [1,2,3,4,5,6,7,8,9,10].find_index(_element).present?
res_array << (header_array.find_index(_element)
end
res_array
end
But I think there is a better approach to do this.
If performance matters (i.e. if your arrays are huge), you can build a hash of all number-index pairs in a, using each_with_index and to_h:
a.each_with_index.to_h
#=> {1=>0, 2=>1, 3=>2, 4=>3, 5=>4, 6=>5, 7=>6, 8=>7, 9=>8, 10=>9}
A hash allows fetching the values (i.e. indices) for the numbers in b much faster (as opposed to traversing an array each time), e.g. via values_at:
a.each_with_index.to_h.values_at(*b)
#=> [2, 4, 7, 9, nil]
Use compact to eliminate nil values:
a.each_with_index.to_h.values_at(*b).compact
#=> [2, 4, 7, 9]
or alternatively slice and values:
a.each_with_index.to_h.slice(*b).values
#=> [2, 4, 7, 9]
b.map { |e| a.index(e) }.compact
#⇒ [2, 4, 7, 9]
or, more concise:
b.map(&a.method(:index)).compact
Here is another simpler solution,
indxs = a.each_with_index.to_h
(a&b).map{|e| indxs[e]}
All the answers so far traverse all of a once (#Stefan's) or traverse all or part of a b.size times. My answer traverses part or all of a once. It is relatively efficient when a is large, b is small relative to a and all elements in b appear in a.
My solution is particularly efficient when a is ordered in such a way that the elements of b typically appear towards the beginning of a. For example, a might be a list of last names sorted by decreasing frequency of occurrence in the general population (e.g., ['smith', 'jones',...]) and b is a list of names to look up in a.
a and b may contain duplicates1 and not all elements of b are guaranteed to be in a. I assume b is not empty.
Code
require 'set'
def lookup_index(a, b)
b_set = b.to_set
b_hash = {}
a.each_with_index do |n,i|
next unless b_set.include?(n)
b_hash[n] = i
b_set.delete(n)
break if b_set.empty?
end
b_hash.values_at(*b)
end
I converted b to a set to make lookups comparable in speed to hash lookups (which should not be surprising considering that sets are implemented with an underlying hash). Hash lookups are very fast, of course.
Examples
a = [1,2,3,4,5,6,7,8,9,10,8]
b = [3,5,8,10,11,5]
Note that in this example both a and b contain duplicates and 11 in b is not present in a.
lookup_index(a, b)
#=> [2, 4, 7, 9, nil, 4]
Observe the array returned contains the index 4 twice, once for each 5 in b. Also, the array contains nil at index 4 to show that it is b[4] #=> 11 that does not appear in a. Without the nil placeholder there would be no means to map the elements of b to indices in a. If, however, the nil placeholder is not desired, one may replace b_hash.values_at(*b) with b_hash.values_at(*b).compact, or, if duplicates are unwanted, with b_hash.values_at(*b).compact.uniq.
As a second example suppose we are given the following.
a = [*1..10_000]
b = 10.times.map { rand(100) }.shuffle
#=> [30, 62, 36, 24, 41, 27, 83, 61, 15, 55]
lookup_index(a, b)
#=> [29, 61, 35, 23, 40, 26, 82, 60, 14, 54]
Here the solution was found after the first 83 elements of a were enumerated.
1 My solution would be no more efficient if duplicates were not permitted in a and/or b.

How do I remove the beginning elements of my array only if the first element satisfies a condition?

In Ruby, let's say I have an array of ordreed, unique numbers
[0, 1, 2, 4, 6, 8, 10]
If the first element of the array is zero, how do I remove all the elements from teh beginning of the array that are consecutive, starting wiht zero? That is, in the above example, I would want to remove "0", "1", and "2" leaving me with
[4, 6, 8, 10]
But if my array is
[1, 2, 3, 10, 15]
I would expect the array to be unchanged because the first element is not zero.
You could use a mix of drop_while and with_index to only remove the first matching elements:
[0, 1, 2, 4, 6, 8, 10].drop_while.with_index{|x, i| x == i}
# [4, 6, 8, 10]
[1, 1, 2, 4, 6, 8, 10].drop_while.with_index{|x, i| x == i}
# [1, 1, 2, 4, 6, 8, 10]
Note that the second and third elements don't get deleted in the second example, even though they're equal to their indices.
Drop elements, as long as they are equal to their index:
a=a.drop_while.with_index{|e,i| e==i}
Sounds like you're trying to delete entities if they match their idx (provided the first idx is 0). Try this:
if array.first == 0
new_array = array.reject.each_with_index{ |item, idx| item == idx }
end
Although this will only work with ordered arrays of unique numbers, if you're not sure that they are then include: array = array.sort.uniq
You could do:
x = -1
while my_array.first == x + 1 do
x = my_array.shift
end
Note that array.shift is the same as array.pop except that it works from the start of the array.
If I understand you right, then it can be one of possible solutions:
def foo(array)
if array.first.zero?
array.keep_if.with_index { |e, ind| e != ind }
else
array
end
end
> foo([0, 1, 2, 5, 6, 7])
#=> => [5, 6, 7]
> foo([1, 2, 3])
#=> [1, 2, 3]
In short form:
a[0] == 0 ? a[3..-1] : a
In longer form:
if a.first == 0
a[3..(a.size)]
else
a
end

iterate over array to find int over 10 and add those two digits together

Trying to iterate over and array and for any digit 10 or higher, split those digits and add them together for instance: 10 > "1" "0" > 1.
I am able to iterate through the array and achieve that. however, it returns nil instead of the digits < 9.
def over_ten_sum
#splits the numbers over 10 into seperate digit and sums them
square_odd.map do |num|
if num > 9
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
end
end
end
With a value of [6, 4, 10, 2, 14, 7, 8, 4, 6, 7, 18, 4] it returns:
=> [nil, nil, [1], nil, [5], nil, nil, nil, nil, nil, [9], nil]
I am trying to have the output be
[6, 4, 1, 2, 5, 7, 8, 6, 7, 9, 4]
Just not seeing the disconnect here. Thank you in advance for any insights.
Suppose you were to write
[1, 2, 3].map { |n| }
#=> [nil, nil, nil]
An array of nils is returned because map returns nil for n if n is not assigned a value in the block. Similarly,
[1, 2, 3].map { |n| 2*n if n > 1 }
#=> [nil, 4, 6]
which is very similar to the problem with the OP's code. If one doesn't want nils in the array returned one simply needs to map each element of the array into a non-nil value:
[1, 2, 3].map { |n| n > 1 ? 2*n : n }
#=> [1, 4, 6]
Now let's look at the line
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
If num = 34, this returns [7], which, except for the fact that 7 is in an array, is correct. On the other hand, if num = 134 the expression returns [4, 4] (i.e., [1+3, 4]), which I don't expect is what is wanted. If, however, the numbers always have two digits, the above expression is the same as:
num[0].to_i + num[1].to_i
which is much simpler.1 To make it more general you need to write something like the following2:
def over_nine_sum(arr)
arr.map { |n| n > 9 ? n.to_s.each_char.reduce(0) { |t,s| t + s.to_i } : n }
end
over_nine_sum [12, 5, 71, 3]
#=> [3, 5, 8, 3]
See Enumerable#reduce (aka inject).
#JörgWMittag noted (see comment) that the sum of the digits of a single-digit number (0-9) is the same as the number itself, so there is no need to treat those numbers differently. We may therefore write
def sum_digits(arr)
arr.map { |n| n.to_s.each_char.reduce(0) { |t,s| t + s.to_i } }
end
sum_digits [12, 5, 71, 3]
#=> [3, 5, 8, 3]
As #steenslag's suggested in a comment, this can be simplified to
def sum_digits(arr)
arr.map { |n| n.digits.sum }
end
which uses the methods Integer#digits and Array#sum (both new in Ruby v2.4).
Consider the steps (for the first version of sum_digits above) when n = 34:
n.to_s.each_char.reduce(0) { |t,s| t + s.to_i }
#=> 34.to_s.each_char.reduce(0) { |t,s| t + s.to_i }
#=> "34".each_char.reduce(0) { |t,s| t + s.to_i }
Now reduce initializes the block variable t (the "memo", which is returned) to zero and passes the first digit of "34" to the block and assigns it to the block variable s:
t = 0
s = "3"
The block calculation is:
t + s.to_i
#=> 0 + "3".to_i
#=> 3
which is the updated value of t. Next,
s = "4"
t + s.to_i
#=> 3 + "4".to_i
#=> 3 + 4
#=> 7
1. Another problem is that if square_odd is a local variable, Ruby will raise an "undefined variable or method" exception when it evaluates it.
2. n.to_s.each_char.reduce(0)... is preferable to n.to_s.chars.reduce(0)... because chars returns a temporary array whereas each_char returns an enumerator.
Remove the if:
def over_ten_sum
#splits the numbers over 10 into seperate digit and sums them
square_odd.map do |num|
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
end.flatten
end
#=> [6, 4, 1, 2, 5, 7, 8, 4, 6, 7, 9, 4]
What was wrong? if num > 9 left out every other number from being treated and nothing was returned, so you got nil each time. To make it clearer, check the following code:
def over_ten_sum
#splits the numbers over 10 into seperate digit and sums them
square_odd.map do |num|
if num > 9
num.to_s.chars.each_slice(2).map { |num_1, num_2| num_1.to_i + num_2.to_i }
else
num
end
end.flatten
end
#=> [6, 4, 1, 2, 5, 7, 8, 4, 6, 7, 9, 4]
As you can see, the result is the same, because else send num back as it is when it is not greater than 9.

How do I compare elements of separate arrays at specific indexes?

I have two arrays, I want to return the larger number from the same position in each array.
def get_larger_numbers(a, b)
c = []
count = 0
while count < 10 #assumes there are less than 10 elements in an array, not an ideal solution.
if a[count] > b[count]
c << a[count]
elsif b[count] > a[count]
c << b[count]
else #if numbers are the same
c << a[count]
end
count+= 1
end
return c
end
a = [13, 64, 15, 17, 88]
b = [23, 14, 53, 17, 80]
should return:
c == [23, 64, 53, 17, 88]
Clearly, my code doesn't work, what's the best way to refer to increasing index positions?
Also interested to know simpler ways of doing this.
Your code isn't working because of the static 10 you have as the length. Instead I suggest you make your code more dynamic with regards to how often you loop.
def get_larger_numbers(a,b)
c = []
[a.length, b.length].min.times do |i|
if a[i] > b[i]
c << a[i]
else
c << b[i]
end
end
c
end
a = [13, 64, 15, 17, 88]
b = [23, 14, 53, 17, 80]
get_larger_numbers(a,b)
#=> [23, 64, 53, 17, 88]
This solution assumes that if the arrays are not equal size, you want to throw the rest away.
Okay... Here's what you should do:
def get_larger_numbers(a, b)
c = [] #declare empty array for answer
for i in 0...(a.length < b.length ? a.length : b.length) #see EDIT note
c << (a[i] > b[i] ? a[i] : b[i])
end
c #this is an implicit return
end
a = [13, 64, 15, 17, 88]
b = [23, 14, 53, 17, 80]
puts get_larger_numbers(a,b)
This'll do a for loop that'll run from 0 to the length of a. Yes, it assumes that they're the same length. I figure this is what you want.
Anyway, there's a simple ternary that compares the value of each element in both arrays, one index at a time.
It'll push the bigger value to the c array, leaving you with the greater values in the c array to be returned.
EDIT: Added the ternary expression so that for loops through only the smaller array, because comparing with nil (which is what is at any n index beyond the array, presumably) would raise an error.
A compact solution would be:
def get_larger_numbers(a, b)
return a.zip(b).map{|x, y| (x >= y) ? x : y } # Return optional, added for clarity
end
a = [13, 64, 15, 17, 88]
b = [23, 14, 53, 17, 80]
p get_larger_numbers(a, b)
Note that this assumes the input arrays are of the same length. If arrays are of unequal length, you can truncate to the length of the shorter array, or pad the end with the unpaired elements of the larger array. The current code will throw an error, letting you know you've hit this unspecified case.
As for how it works, the zip pairs the elements of the two arrays, so a.zip(b) becomes:
[[13, 23], [64, 14], [15, 53], [17, 17], [88, 80]]
It then loops over the array with map to produce a new array, passing each pair into the block, which returns the larger of the two elements to fill the output array.
Assuming the two arrays are the same size, simply:
def largest_by_position(a,b)
a.zip(b).map(&:max)
end
largest_by_position([13, 64, 15, 17, 88], [23, 14, 53, 17, 80])
#=> [23, 64, 53, 17, 88]
Alternatively, make the operative line:
[a,b].transpose.map(&:max)
For equal-size arrays a and b, Enumerable#zip and Array#transpose always have this yin and yang relationship:
a.zip(b) == [a,b].transpose #=> true

Replace nil elements in array with values from two other arrays

I have three arrays: g, a and b. g has two groups of one or more consecutive nil values. The first (second) group contains a.size (b.size) nils. I wish to replace each nil in the first (second) group with the corresponding element of a (b). For example, if:
g = [1, 2, nil, nil, nil, 3, nil, nil, nil, nil]
a = [55, 45, 56]
b = [100, 200, 300, 400]
I wish g to become:
[1, 2, 55, 45, 56, 3, 100, 200, 300, 400]
How can I do that?
The []= method can assign like this, one of its variants is
foo[insertion_point, length] = values
(there is also a version that takes a range instead of this pair of values)
g = [1,2,nil,nil,nil,3,nil,nil,nil,nil]
a = [55,45,56]
b = [100,200,300,400]
g[2, a.size] = a
g #=> [1, 2, 55, 45, 56, 3, nil, nil, nil, nil]
g[6, b.size] = b
g #=> [1, 2, 55, 45, 56, 3, 100, 200, 300, 400]
As long as the gaps are the same size as the arrays you are inserting, you wouldn't need to do anything about the nils
g.map!{|e| e || a.shift || b.shift}
# => [1, 2, 55, 45, 56, 3, 100, 200, 300, 400]
I set up an enumurator and pull items from it whenever necessary. Either of these extends easily to more input lists
itr = (a + b).to_enum
g.map!{ |e| e.nil? || itr.next }
or in this case [a, b] can be replaced with a more general tree
itr = [a, b].flatten.to_enum
g.map!{ |e| e.nil? || itr.next }
I think you can do it in this way:
g.insert(6, *b); // inserts b into 6 index
g.insert(2, *a); // inserts a into 2 index
g.compact! // removes the nil elements
Note that we are inserting in the reverse way so as to not disturb the indexes.

Resources