ruby find the index of the next available number - ruby

I have a find_num method that returns the index of a specified number in an ordered array, e.g.
find_num(6, [1, 4, 6, 9, 13]) #=> 2
however my spec also requires that if the number is not available it finds the position of the next highest number so ...
find_num(8, [1, 4, 6, 9, 13]) #=> 3
as 9 is the next available number.
Having trouble implementing this... I have thought of iterating through the whole array but I am told to take into account the array could be large...

You can pass a block to index and it ...
[...] returns the index of the first object for which the block returns true. Returns nil if no match is found.
Examples:
[1, 4, 6, 9, 13].index { |n| n >= 6 } #=> 2
[1, 4, 6, 9, 13].index { |n| n >= 8 } #=> 3
[1, 4, 6, 9, 13].index { |n| n >= 15 } #=> nil
Because this requires the array to be ordered, you can also use bsearch_index which performs a binary search.

you can also find a index of any element in array like this.
2.1.8 :040 > [1, 4, 6, 9, 13].index(6)
=> 2
2.1.8 :041 > [1, 4, 6, 9, 13].index(15)
=> nil

def find_num(n,a)
a.each_with_index.to_a.sort_by(&:first).find { |nbr,_| nbr >= n }.last
end
find_num(6, [1, 4, 6, 9, 13])
#=> 2
find_num(8, [1, 4, 6, 9, 13]) #=> 3
#=> 3
The steps for
n = 8
a = [1, 4, 6, 9, 13]
are as follows.
b = a.each_with_index.to_a
#=> [[1, 0], [4, 1], [6, 2], [9, 3], [13, 4]]
c = b.sort_by(&:first)
#=> [[1, 0], [4, 1], [6, 2], [9, 3], [13, 4]]
d = c.find { |nbr,_| nbr >= n }
#=> [9, 3]
d.last
#=> 3

Related

Repeating a loop when it reaches the end

I am trying to conceptualize the iteration of two loops
numbers_array = [1,2,3,4,5,6,7,8,9,10]
add_to_array = [1,2,3,4]
While the numbers_array iterates, add_to_array iterates simultaneously adding both elements together at the same time. The caveat is once add_to_array reaches the end, it starts over adding its element to the next index in numbers_array. So at numbers_array[4] we would be adding add_to_array[0] then adding numbers_array[5] to add_to_array[1] and so on. This process would repeat until we reach the end of the numbers_array.
Any input would be greatly appreciated!
You are looking for Enumerable#zip and Enumerable#cycle:
numbers_array = [1,2,3,4,5,6,7,8,9,10]
#⇒ [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
add_to_array = [1,2,3,4]
#⇒ [1, 2, 3, 4]
numbers_array.zip(add_to_array.cycle)
#⇒ [[1, 1], [2, 2], [3, 3], [4, 4], [5, 1],
# [6, 2], [7, 3], [8, 4], [9, 1], [10, 2]]
Now do whatever you want with the array returned. E.g. to reduce the zipped result summing elements, map ro Enumerable#sum:
numbers_array.zip(add_to_array.cycle).map(&:sum)
#⇒ [2, 4, 6, 8, 6, 8, 10, 12, 10, 12]
It works by using the % operator to cycle through the indexes.
numbers_array = [1,2,3,4,5,6,7,8,9,10]
add_to_array = [1,2,3,4]
numbers_array.map.with_index do |n, i|
n + add_to_array[i % add_to_array.length]
end
A cool method that's similar, if you didn't want to start over at the next array, would be .zip
https://apidock.com/ruby/Array/zip
add_to_array.zip(*numbers_array.each_slice(add_to_array.size)).
map { |a| a.sum { |e| e.to_i } }
#=> [16, 20, 13, 16]
e.to_i is needed to convert nil values to zeros. See NilClass#to_i.
Another option:
numbers_array.map { |e| e + add_to_array.rotate!.last }
# => [2, 4, 6, 8, 6, 8, 10, 12, 10, 12]
Drawback: add_to_array is mutated by rotate!

How can i avoid using the same element in my two sum solution

So I am trying to get a solution to my two sum problem and I am stuck, I need to print the indices for the elements which add up to the target and my solution will return an element twice if it is one half of the target
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[num], idx]
end
end
end
So I don't think the problem is related to the number being 1/2 of the target, it just seems to be "if a solution is found, it returns the same index twice". For instance, using the sample set [2, 7, 11, 15]
two_sum([2, 7, 11, 15], 14) # => [2, 7, 11, 15]
So, 7 is half of 14, which is the target, and instead of returning the index 1 twice, as you suggest it would, it returns the original input array (the result of nums.each_with_index. However, if we try passing a target of 9, it behaves as you describe:
two_sum([2, 7, 11, 15], 9) # => [1, 1]
The reason for this, is because of the line:
return [num_hash[num], idx]
you have already set num into the num_hash (num_hash[num] = idx) and then you are returning both the idx and num_hash[num], which is also idx. So what you want to do is:
return [num_hash[target - num], idx]
and then to 'fix' all the elements being returned when no result is found, just return [] at the end of the method:
def two_sum(nums, target)
num_hash = Hash.new(0)
nums.each_with_index do |num,idx|
num_hash[num] = idx
if num_hash.key?(target - num) && target % num != 0
return [num_hash[target - num], idx]
end
end
[]
end
and now:
two_sum([2, 7, 11, 15], 14) # => []
two_sum([2, 7, 11, 15], 9) # => [0, 1]
Note: you also have a problem with the code where, if you have the same number twice, it doesn't find the answer:
two_sum([2, 7, 11, 7, 15], 14) # => []
left for you to figure out, just wanted to point this out to you.
You can use the method Array#combination to advantage here.
def two_sum(nums, target)
nums.each_index.to_a.combination(2).select { |i,j| nums[i] + nums[j] == target }
end
two_sum([2, 7, 11, 15], 14)
#=> []
two_sum([2, 7, 11, 15], 9)
#=> [[0, 1]]
two_sum([2, 4, 7, 5], 9)
#=> [[0, 2], [1, 3]]
two_sum([2, 2, 2, 2], 4)
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
two_sum([2, 4, 7, 5], 8)
#=> []
For
nums = [2, 4, 7, 5]
target = 9
the steps are as follows.
a = nums.each_index
#=> #<Enumerator: [2, 4, 7, 5]:each_index>
We can see the elements that will be generated by this enumerator by converting it to an array.
b = a.to_a
#=> [0, 1, 2, 3]
Next,
c = b.combination(2)
#=> #<Enumerator: [0, 1, 2, 3]:combination(2)>
c.to_a
#=> [[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]]
The rest is straightforward as select merely selects those pairs of indices passed to it (i,j) whose corresponding values, num[i] and num[j], sum to target.
I think what you want is ...
return [num_hash[target-num], idx]

Algorithm: Factor Combinations

I'm working on the following algorithm from Leetcode:
Numbers can be regarded as product of its factors. For example,
8 = 2 x 2 x 2;
= 2 x 4.
Write a function that takes an integer n and return all possible combinations of its factors.
Note:
You may assume that n is always positive.
Factors should be greater than 1 and less than n.
Examples:
input: 1
output:
[]
input: 37
output:
[]
input: 12
output:
[
[2, 6],
[2, 2, 3],
[3, 4]
]
input: 32
output:
[
[2, 16],
[2, 2, 8],
[2, 2, 2, 4],
[2, 2, 2, 2, 2],
[2, 4, 4],
[4, 8]
]
Here's the code that I have thus far:
def get_factors(n)
factors = []
(2...n).each do |candidate|
if n % candidate == 0
factors << [candidate, (n/candidate)]
get_factors(n/candidate).each do |factor_set|
factors << [candidate] + factor_set
end
end
end
factors
end
This code works really well, but doesn't handle duplicates (e.g [3, 2, 2] will be inserted along with [2, 2, 3]). I tried using a Set with the following code,
def get_factors(n)
seen = Set.new
factors = []
(2...n).each do |candidate|
if n % candidate == 0 && !seen.include?(candidate)
factors << [candidate, (n/candidate)]
get_factors(n/candidate).each do |factor_set|
factors << [candidate] + factor_set
end
end
seen << (n/candidate)
end
factors
end
but that only works to solve some test cases and not others. I'm not sure how to go about ensuring no duplicates in an efficient way? The really inefficient way is to generate some sort of hash value for each array depending on it's elements (and not dependent on order), and while this would work, there definitely should be a better way. Any ideas?
I think always going forward is a good policy (i.e when checking, say, with 5, do not check with 2, 3, 4 etc). That way, searching for duplicates can be eliminated.
Since the algorithm already uses a lot of time, I don't see a problem sorting each answer and removing duplicates. This requires no proof to ensure it works, which the answer provided by mac does.
Code
require 'prime'
def get_factors(n)
primes, nbr = Prime.prime_division(n).transpose
powers = nbr.map { |m| (0..m).to_a }
powers.shift.
product(*powers).
map { |pows| primes.zip(pows).reduce(1) { |t,(pr,po)| t * (pr**po) } }.
sort
end
The array returned includes 1 and n (which are factors of n). If those values should be excluded, replace .sort with .sort - [1, n].
Examples
get_factors(24)
#=> [1, 2, 3, 4, 6, 8, 12, 24]
get_factors(64)
#=> [1, 2, 4, 8, 16, 32, 64]
get_factors(90)
#=> [1, 2, 3, 5, 6, 9, 10, 15, 18, 30, 45, 90]
Explanation
Consider
n = 60
The steps are as follows.
a = Prime.prime_division(30)
#=> [[2, 2], [3, 1], [5, 1]]
Ergo, the primes of 30 are 2, 3 and 5, and
60 = 2**2 * 3**1 * 5**1
See Prime::prime_division. Continuing,
primes, nbr = a.transpose
#=> [[2, 3, 5], [2, 1, 1]]
primes
#=> [2, 3, 5]
nbr
#=> [2, 1, 1]
powers = nbr.map { |m| (0..m).to_a }
#=> [[0, 1, 2], [0, 1], [0, 1]]
This means that each factor will be the product of 0, 1 or 2 2's, 0 or 1 3's and 0 or 1 5's.
b = powers.shift
#=> [0, 1, 2]
powers
#=> [[0, 1], [0, 1]]
c = b.product(*powers)
#=> [[0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1],
# [1, 1, 0], [1, 1, 1], [2, 0, 0], [2, 0, 1], [2, 1, 0], [2, 1, 1]]
d = c.map { |pows| primes.zip(pows).reduce(1) { |t,(pr,po)| t * (pr**po) } }
#=> [1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
d.sort
#=> [1, 5, 3, 15, 2, 10, 6, 30, 4, 20, 12, 60]
Consider now the calculation of d. The 10th element of c that is passed to the block is [2, 0, 1]. The block calculation for that element is as follows.
pows = [2, 0, 1]
e = primes.zip(pows)
#=> [[2, 2], [3, 0], [5, 1]]
e.reduce(1) { |t,(pr,po)| t * (pr**po) }
#=> 20
The reduce calculation is equivalent to
2**2 * 3**0 * 5**1
#=> 4 * 1 * 5 => 20
The calculations for the other values of c passed to the block are similar.
A simple way is to replace the last line of your method with
factors.map(&:sort).uniq
which sorts all the subarrays and then eliminates duplicates.

comparing 2 arrays in every position

So what Im trying to accomplish is write a (shorter) condition that makes sure each element is different from the other array. This is confusing but I hope this example clears it up.
array = [1, 2, 3]
new_array = array.shuffle
until array[0] != new_array[0] &&
array[1] != new_array[1] &&
array[2] != new_array[2]
new_array = array.shuffle
end
So what Im doing is making sure that every single element/index pair does not match in the other array.
# [1, 2, 3] => [3, 1, 2] yayyyy
# [1, 2, 3] => [3, 2, 1] not what I want because the 2 didnt move
Is there a better way to do what I want to do? Ive looked up the .any? and .none? but I cant seem to figure out how to implement them. Thanks!
I would do this:
array.zip(new_array).all? { |left, right| left != right }
Here are two approaches that do not involve repeated sampling until a valid sample is obtained:
Sample from the population of valid permutations
Construct the population from which you are sampling:
array = [1, 2, 3, 4]
population = array.permutation(array.size).reject do |a|
a.zip(array).any? { |e,f| e==f }
end
#=> [[2, 1, 4, 3], [2, 3, 4, 1], [2, 4, 1, 3], [3, 1, 4, 2], [3, 4, 1, 2],
# [3, 4, 2, 1], [4, 1, 2, 3], [4, 3, 1, 2], [4, 3, 2, 1]]
Then just choose one at random:
10.times { p population.sample }
# [4, 3, 1, 2]
# [3, 4, 1, 2]
# [3, 4, 1, 2]
# [4, 3, 1, 2]
# [2, 1, 4, 3]
# [2, 1, 4, 3]
# [4, 1, 2, 3]
# [2, 1, 4, 3]
# [4, 3, 1, 2]
# [3, 4, 1, 2]
Sequentially sample for each position in the array
def sample_no_match(array)
a = array.each_index.to_a.shuffle
last_ndx = a[-1]
a.dup.map do |i|
if a.size == 2 && a[-1] == last_ndx
select = a[-1]
else
select = (a-[i]).sample
end
a.delete(select)
array[select]
end
end
10.times.each { p sample_no_match(array) }
# [2, 4, 3, 1]
# [4, 3, 1, 2]
# [2, 1, 3, 4]
# [1, 3, 4, 2]
# [1, 3, 2, 4]
# [1, 3, 2, 4]
# [1, 4, 3, 2]
# [3, 4, 2, 1]
# [1, 3, 4, 2]
# [1, 3, 4, 2]
I have been unable to prove or disprove that the second method produces a random sample. We can, however, determine relative frequencies of outcomes:
n = 500_000
h = n.times.with_object(Hash.new(0)) { |_,h| h[sample_no_match(array)] += 1 }
h.keys.each { |k| h[k] = (h[k]/(n.to_f)).round(4) }
h #=> {[1, 2, 3, 4]=>0.0418, [2, 1, 3, 4]=>0.0414, [1, 4, 2, 3]=>0.0418,
# [3, 4, 2, 1]=>0.0417, [4, 3, 2, 1]=>0.0415, [3, 1, 4, 2]=>0.0419,
# [2, 3, 1, 4]=>0.0420, [4, 2, 3, 1]=>0.0417, [3, 2, 1, 4]=>0.0413,
# [4, 2, 1, 3]=>0.0417, [2, 1, 4, 3]=>0.0419, [1, 3, 2, 4]=>0.0415,
# [1, 2, 4, 3]=>0.0418, [1, 3, 4, 2]=>0.0417, [2, 4, 1, 3]=>0.0414,
# [3, 4, 1, 2]=>0.0412, [1, 4, 3, 2]=>0.0423, [4, 1, 3, 2]=>0.0411,
# [3, 2, 4, 1]=>0.0411, [2, 4, 3, 1]=>0.0418, [3, 1, 2, 4]=>0.0419,
# [4, 3, 1, 2]=>0.0412, [4, 1, 2, 3]=>0.0421, [2, 3, 4, 1]=>0.0421}
avg = (h.values.reduce(:+)/h.size.to_f).round(4)
#=> 0.0417
mn, mx = h.values.minmax
#=> [0.0411, 0.0423]
([avg-mn,mx-avg].max/avg).round(6)
#=> 0.014388
which means that the maximum deviation from the average was only 1.4% percent of the average.
This suggests that the second method is a reasonable way of producing pseudo-random samples.
Initially, the first line of this method was:
a = array.each_index.to_a
By looking at the frequency distribution for outcomes, however, it was clear that that method did not produce a pseudo-random sample; hence, the need to shuffle a.
Here's one possibility:
until array.zip(new_array).reject{ |x, y| x == y }.size == array.size
new_array = array.shuffle
end
Note, though, that it will break for arrays like [1] or [1, 1, 1, 2, 3], where the number of instances of 1 exceeds half the size of the array. Recommend Array#uniq or similar, along with checking for arrays of sizes 0 or 1, depending on how trustworthy your input is!

Ruby, perform operation on an array and return the new array, aswell as "changes"

I am looking for a way to perform a certain operation (for instance delete_if) on an array and return both the deleted elements, and the remaining elements.
For example
a = [1,2,3,4,5,6,7,8,9,10]
a.delete_if {|x| x.even? } #=> [[1, 3, 5, 7, 9]]
But what I am looking for is something like
a = [1,2,3,4,5,6,7,8,9,10]
a.some_operation #=> [[1,3,5,7,9],[2,4,6,8,10]]
How would I go about doing this?
Using Enumerable#partition:
a = [1,2,3,4,5,6,7,8,9,10]
a.partition &:even?
# => [[2, 4, 6, 8, 10], [1, 3, 5, 7, 9]]
The first element of the Enumerable#partition return value contains the elements that are evaluated to true in the block. So you need to use odd? to get what you want.
a.partition &:odd?
# => [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]
You might be looking for something like this:
a = [1,2,3,4,5,6,7,8,9,10]
a.group_by { |x| x.even? }.values
#=> [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]

Resources