Insert a number into an ordered array - ruby

I have an array of numbers sorted either in ascending or descending order, and I want to find the index at which to insert a number while preserving the order of the array. If the array is [1, 5, 7, 11, 51] and the number to insert is 9, I would be expecting 3 so I could do [1, 5, 7, 11, 51].insert(3, 9). If the array is [49, 32, 22, 11, 10, 8, 3, 2] and the number to be inserted is 9, I would be expecting 5 so I could do [49, 32, 22, 11, 10, 8, 3, 2].insert(5, 9)
What would be the best/cleanest way to find the index at which to insert 9 in either of these two arrays while preserving the sorting of the array?
I wrote this code that works, but it's not very pretty:
array = [55, 33, 10, 7, 1]
num_to_insert = 9
index_to_insert = array[0..-2].each_with_index.map do |n, index|
range = [n, array[index.next]].sort
index.next if num_to_insert.between?(range[0], range[1])
end.compact.first
index_to_insert # => 3

Wand Maker's answer isn't bad, but it has two problems:
It sorts the entire array to determine whether it's ascending or descending. That's silly when all you have to do is find one element that's not equal to the one before it compare the first and last elements to determine this. That's O(n) O(1) in the worst case instead of O(n log n).
It uses Array#index when it should use bsearch. We can do a binary search instead of iterating over the whole array because it's sorted. That's O(log n) in the worst case instead of O(n).
I found it was clearer to split it into two methods, but you could of course turn it into one:
def search_proc(ary, n)
case ary.first <=> ary.last
when 1 then ->(idx) { n > ary[idx] }
when -1 then ->(idx) { n < ary[idx] }
else raise "Array neither ascending nor descending"
end
end
def find_insert_idx(ary, n)
(0...ary.size).bsearch(&search_proc(ary, n))
end
p find_insert_idx([1, 5, 7, 11, 51], 9)
#=> 3
p find_insert_idx([49, 32, 22, 11, 10, 8, 3, 2], 9)
#=> 5
(I use Range#bsearch here. Array#bsearch works the same, but it was more convenient to use a range to return an index, and more efficient since otherwise we'd have to do each_with_index.to_a or something.)

This is not a good way, but perhaps cleaner since you can use the method insert_sorted(number) on either an ascending or descending array without bothering about the index it will be placed on:
module SortedInsert
def insert_index(number)
self.each_with_index do |element, index|
if element > number && ascending?
return index
end
if element < number && descending?
return index
end
end
length
end
def insert_sorted(number)
insert(insert_index(number), number)
end
def ascending?
first <= last
end
def descending?
!ascending?
end
end
Use it on a array as follows:
array = [2, 61, 12, 7, 98, 64]
ascending = array.sort
descending = array.sort.reverse
ascending.extend SortedInsert
descending.extend SortedInsert
number_to_insert = 3
puts "Descending: "
p number_to_insert
p descending
p descending.insert_sorted(number_to_insert)
puts "Ascending: "
p number_to_insert
p ascending
p ascending.insert_sorted(number_to_insert)
This will give:
Descending:
3
[98, 64, 61, 12, 7, 2]
[98, 64, 61, 12, 7, 3, 2]
Ascending:
3
[2, 7, 12, 61, 64, 98]
[2, 3, 7, 12, 61, 64, 98]
Notes:
The module defines a few methods that will be added to the specific Array object alone.
The new methods provides a sorted array (either ascending/descending) a method insert_sorted(number) which enables to insert the number at sorted position.
In case the position of insertion is required, there is a method for that too: insert_index(number), which will provide the index to which the number needs to be inserted so that the resultant array remains sorted.
Caveat: The module assumes the array being extended is sorted either as ascending or descending.

Here is the simplest way I can think of doing.
def find_insert_idx(ary, n)
is_asc = (ary.sort == ary)
if (is_asc)
return ary.index { |i| i > n }
else
return ary.index { |i| i < n }
end
end
p find_insert_idx([1,5,7,11,51], 9)
#=> 3
p find_insert_idx([49,32,22,11,10,8,3,2], 9)
#=> 5

Related

Remove groups of consecutive numbers from array in Ruby

I have an array:
[1, 2, 3, 6, 8, 9, 10, 23, 34, 35, 36, 45, 50, 51, ...]
I'm trying to remove each group of consecutive numbers so I end up with:
[6, 23, 45, ...]
I am looking for anomalies in serial ids. Does anyone have suggestions?
My initial attempt only checks for the id before each element:
non_consecutive_ids = []
ids.each_with_index do |x, i|
unless x == ids[i-1] + 1
non_consecutive_ids << x
end
end
The thing I think I was missing was to also check to see if the next element in the array is 1 more than the current.
Other option:
array.chunk_while { |i, j| i + 1 == j }.select { |e| e.size == 1 }.flatten
#=> [6, 23, 45]
The good of Enumerable#chunk_while is that it takes two params. The core doc has just an example of a one-by-one increasing subsequence.
You can use select and check the surrounding values:
array.select.with_index{ |x, index| (array[index-1] != x-1) && (array[index+1] != x+1)}

Check if numbers summed in array match input parameter

I'm following Ruby practice problem from a website and I'm completely stuck on figuring out a solution to this problem. Basically, given a function has_sum?(val, arr), return true if any combination of numbers in the array (second parameter) can be added together to equal the first parameter, otherwise return false. So:
has_sum?(5, [1, 2, 3, 4]) # true
has_sum?(5, [1, 2, 6]) # false
I'm completely stuck and not quite sure how to accomplish this... Here's what I have so far.
def has_sum?(val, arr)
arr.each_with_index do |idx, v|
# ??? no idea what to do here except add the current num to the next in the list
end
end
Any help would be greatly appreciated - thanks!
An array can produce a sum when there is a subset of any length that adds up to that sum:
def has_sum?(val, arr)
(arr.size + 1).times
.flat_map { |i| arr.combination(i).to_a }
.any? { |s| s.inject(:+) == val }
end
has_sum?(5, [5])
# => true
has_sum?(5, [1, 2, 3])
# => true
has_sum?(5, [1, 1, 1, 1, 1, 1])
# => true
has_sum?(5, [1, 2, 7])
# => false
This is not very efficient as it generates all the possibilities before testing. This should terminate sooner:
def has_sum?(val, arr)
(arr.size + 1).times.any? { |i|
arr.combination(i).any? { |s| s.inject(:+) == val }
}
end
Even more efficiently, a recursive implementation, with the idea that a sum of an empty array is zero (and has_sum(nonzero, []) should return false); for a larger array, we pop off its head, and see if the sum of the rest of the array is okay if we count, or don't count, the head element. Here, we don't do the useless summing of the whole array over and over again:
def has_sum?(val, arr)
if arr.empty?
val.zero?
else
first, *rest = arr
has_sum?(val, rest) || has_sum?(val - first, rest)
end
end
This solution employs dynamic programming. I assume that zeroes have been removed from the array. If all numbers in the array are positive, we can also remove elements that are larger than the target sum.
Code
def sum_to_target(arr, target)
h = arr.each_index.with_object({}) do |i,h|
v = arr[i]
h.keys.each do |n|
unless h.key?(n+v) # || (n+v > target)
h[n+v] = v
return reconstruct(h, target) if n+v == target
end
end
h[v] = v unless h.key?(v)
return reconstruct(h, target) if v == target
end
nil
end
def reconstruct(h, target)
a = []
loop do
i = h[target]
a.unshift i
target -= i
return a if target == 0
end
a
end
Additional efficiency improvements are possible if arr contains only postive values.1
Examples
#1
sum_to_target [2,4,7,2], 8
#=> [2, 4, 2]
#2
arr = [64, 18, 64, 6, 39, 51, 87, 62, 78, 62, 49, 86, 35, 57, 40, 15, 74, 10, 8, 7]
a = sum_to_target(arr, 461)
#=> [64, 18, 39, 51, 87, 62, 78, 62]
Let's check that.
a.reduce(:+)
#=> 461
#3
a = sum_to_target([-64, 18, 64, -6, 39, 51, -87, 62, -78, 62, 49, 86, 35, 57,
40, 15, -74, 10, -8, -7], 190)
#=> [18, 64, -6, 39, 51, -87, 62, 49]
a.reduce(:+)
#=> 190
#4
arr = 1_000.times.map { rand 1..5_000 }
#=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
# ...
# 521, 1316, 1986, 4099, 1398, 3803, 4498, 4607, 2262, 3941, 4367]
arr is an array of 1,000 elements, each a random number between 1 and 5,000.
answer = arr.sample(500)
#=> [3469, 2957, 1542, 950, 4765, 3126, 3602, 755, 4132, 4281, 2374,
# ...
# 427, 4238, 4397, 2717, 912, 1690, 3626, 169, 3607, 4084, 3161]
answer is an array of 500 elements from arr, sampled without replacement.
target = answer.reduce(:+)
#=> 1_226_020
target is the sum of the elements of answer. We will now search arr for a collection of elements that sum to 1,226,020 (answer being one such collection).
require 'time'
t = Time.now
#=> 2016-12-12 23:00:51 -0800
a = sum_to_target(arr, target)
#=> [3471, 1891, 4257, 2265, 832, 1060, 3961, 875, 614, 2308, 2240, 3286,
# ...
# 3616, 4150, 3222, 3896, 631, 2806, 1932, 3244, 2430, 1443, 1452]
Notice that a != answer (which is not surprising).
a.reduce(:+)
#=> 1226020
(Time.now-t).to_i
#=> 60 seconds
For this last example, methods that use Array#combination would have to wade though as many as
(1..arr.size).reduce(0) { |t,i| t + arr.combination(i).size }.to_f
#~> 1.07+301
combinations.
Explanation
Let
arr = [2,4,7,2]
target = 8
Suppose we temporarily redefine reconstruct to return the hash passed to it.
def reconstruct(h, target)
h
end
We then obtain the following:
h = sum_to_target(arr, target)
#=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}
h is defined as follows.
Given an array of non-zero integers arr and a number n, if n is a key of h there exists an array a containing elements from arr, in the same order, such that the elements of a sum to n and the last element of a equals h[n].
which, admittedly, is a mouthful.
We now use the reconstruct (as defined in the "Code" section) to construct an array answer that will contain elements from arr (without repeating elements) that sum to target.
reconstruct(h, target) #=> [2, 4, 2]
Initially, reconstruct initializes the array answer, which it will build and return:
answer = []
h will always contain a key equal to target (8). As h[8] #=> 2 we conclude that the last element of answer equals 2, so we execute
answer.unshift(2) #=> [2]
The problem is now to find an array of elements from arr that sum to 8 - 2 #=> 6. As h[6] #=> 4, we conclude that the element in answer that precedes the 2 we just added is 4:
answer.unshift(4) #=> [4, 2]
We now need 8-2-4 #=> 2 more to total target. As h[2] #=> 2 we execute
answer.unshift(2) #=> [2, 4, 2]
Since 8-2-4-2 #=> 0 we are finished and therefore return answer.
Notice that 4 precedes the last 2 in arr and the first 2 precedes the 4 in arr. The way h is constructed ensures the elements of answer will always be ordered in this way.
Now consider how h is constructed. First,
h = {}
As arr[0] #=> 2, we conclude that, using only the first element of arr, all we can conclude is:
h[2] = 2
h #=> {2=>2}
h has no key equal to target (8), so we continue. Now consider arr[1] #=> 4. Using only the first two elements of arr we can conclude the following:
h[2+4] = 4
h #=> {2=>2, 6=>4}
and since h has no key 4,
h[4] = 4
h #=> {2=>2, 6=>4, 4=>4}
h still has no key equal to target (8), so we press on and examine arr[2] #=> 7. Using only the first three elements of arr we conclude the following:
h[2+7] = 7
h[6+7] = 7
h[4+7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7}
and since h has no key 7:
h[7] = 7
h #=> {2=>2, 6=>4, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7}
We added four elements to h, but since arr contains only positive numbers, those with keys 9, 13 and 11 are of no interest.
Since h still does not have a key equal to target (8), we examine the next element in arr: arr[3] #=> 2. Using only the first four elements of arr we conclude the following:
h[4+2] = 2
h[6+2] = 2
Here we stop, since 6+2 == target #=> true.
h #=> {2=>2, 6=>2, 4=>4, 9=>7, 13=>7, 11=>7, 7=>7, 8=>2}
Notice that we did not compute h[2+2] = 2 since h already has a key 4. Further, had arr contained additional elements we still would have terminated the construction of the hash at this point.
Had we modified the code to take advantage of the fact that arr contains only positive values, the final hash would have been:
h #=> {2=>2, 6=>2, 4=>4, 7=>7, 8=>2}
If this is still not clear, it might be helpful to run the code for this example with included puts statements (e.g., puts "i=#{i}, h=#{h}, v=#{v}" after the line v = arr[i] in sum_to_target, and so on).
1 The line unless h.key?(n+v) can be changed to unless h.key?(n+v) || (n+v > target) if it is known that the array contains no negative elements. (Doing so reduced the solution time for example #4 by 4 seconds.) One could also compute #all_positive = arr.all?(&:positive?) and then make that line conditional on #all_positive.
I would do nested loops.
for x = 0 to length of array
for y = x + 1 to length of array
if number at x + number at y = sum return true
return false
Basically it will check the sum of each number with each of the numbers after it.
EDIT: this will only sum 2 numbers at a time. If you want to be able to sum any amount of numbers this wont work.

Recursive merge sort in Ruby

I am trying to write a ruby method which performs a merge sort recursively. I have the method working, but It's one of those times where I accidentally got it working so I have no idea WHY it works, and would love to understand how the code I have written works. In psuedocode, the steps I followed look like this.
Split the original array of length n until I have n arrays of length 1
Merge and sort 2 arrays of length m at time to return an array of length m*2
Repeat the step above until I have a single now sorted array of length n
Basically what this looks like to me is a large tree branching out into n branches, with each branch containing an array of length 1. Then I need to take these n branches and somehow merge them back into a single branch within the method.
def merge_sort(arr)
return arr if arr.length == 1
merge(merge_sort(arr.slice(0, arr.length/2)),
merge_sort(arr.slice(arr.length/2, arr[-1])))
end
def merge(arr1, arr2)
sorted = []
begin
less_than = arr1[0] <=> arr2[0]
less_than = (arr1[0] == nil ? 1 : -1) if less_than == nil
case less_than
when -1
sorted << arr1[0]
arr1 = arr1.drop(1)
when 0
sorted << arr1[0]
sorted << arr2[0]
arr1 = arr1.drop(1)
arr2 = arr2.drop(1)
when 1
sorted << arr2[0]
arr2 = arr2.drop(1)
end
end until (arr1.length == 0 && arr2.length == 0)
sorted
end
merge_sort([1,6,3,8,22,3,11,24,54,68,79,80,98,65,46,76,53])
#Returns => [1, 3, 3, 6, 8, 11, 22, 24, 46, 53, 54, 65, 68, 76, 79, 80, 98]
The method I have actually correctly sorts the list, but I am not totally sure how the method combines each branch and then returns the sorted merged list, rather than just the first two length one arrays it combines.
Also, If anyone has ideas for how I can make the merge method prettier to look more like the ruby code I have grown to love please let me know.
Here is my implementation of mergesort in Ruby
def mergesort(array)
return array if array.length == 1
middle = array.length / 2
merge mergesort(array[0...middle]), mergesort(array[middle..-1])
end
def merge(left, right)
result = []
until left.length == 0 || right.length == 0 do
result << (left.first <= right.first ? left.shift : right.shift)
end
result + left + right
end
As you can see, the mergesort method is basically the same as yours, and this is where the recursion occurs so that is what I will focus on.
First, you have your base case: return array if array.length == 1 This is what allows the recursion to work and not go on indefinitely.
Next, in my implementation I have defined a variable middle to represent the middle of the array: middle = array.length / 2
Finally, the third line is where all the work occurs: merge mergesort(array[0...middle]), mergesort(array[middle..-1])
What you are doing here is telling the merge method to merge the mergesorted left half with the mergesorted right half.
If you assume your input array is [9, 1, 5, 4] what you are saying is merge mergesort([9, 1]), mergesort([5, 4]).
In order to perform the merge, you first have to mergesort [9, 1] and mergesort [5, 4]. The recursion then becomes
merge((merge mergesort([9]), mergesort([1])), (merge mergesort([5]), mergesort([4])))
When we recurse again, the mergesort([9]) has reached the base case and returns [9]. Similarly, mergesort([1]) has also reached the base case and returns [1]. Now you can merge [9] and [1]. The result of the merge is [1, 9].
Now for the other side of the merge. We have to figure out the result of merge mergesort([5]), mergesort([4]) before we can merge it with [1, 9]. Following the same procedure as the left side, we get to the base case of [5] and [4] and merge those to get [4, 5].
Now we need to merge [1, 9] with [4, 5].
On the first pass, result receives 1 because 1 <= 4.
On the next pass, we are working with result = [1], left = [9], and right = [4, 5]. When we see if left.first <= right.first we see that it is false, so we return right.shift, or 4. Now result = [1, 4].
On the third pass, we are working with result = [1, 4], left = [9], and right = [5]. When we see if left.first <= right.first we see that it is false, so we return right.shift, or 5. Now result = [1, 4, 5].
Here the loop ends because right.length == 0.
We simply concatenate result + left + right or [1, 4, 5] + [9] + [], which results in a sorted array.
Here is my version of a recursive merge_sort method for Ruby. Which does the exact same as above, but slightly different.
def merge_sort(array)
array.length <= 1 ? array : merge_helper(merge_sort(array[0...array.length / 2]), merge_sort(array[array.length / 2..-1]))
end
def merge_helper(left, right, merged = [])
left.first <= right.first ? merged << left.shift : merged << right.shift until left.length < 1 || right.length < 1
merged + left + right
end
p merge_sort([]) # => []
p merge_sort([20, 8]) # => [8, 20]
p merge_sort([16, 14, 11]) # => [11, 14, 16]
p merge_sort([18, 4, 7, 19, 17]) # => [4, 7, 17, 18, 19]
p merge_sort([10, 12, 15, 13, 16, 7, 19, 2]) # => [2, 7, 10, 12, 13, 15, 16, 19]
p merge_sort([3, 14, 10, 8, 11, 7, 18, 17, 2, 5, 9, 20, 19]) # => [2, 3, 5, 7, 8, 9, 10, 11, 14, 17, 18, 19, 20]

How do I create a subset of an array based on an array of indexes for that array in Ruby

If I have an array like this:
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
and I want to select a subset of that array based on this arbitrary array of indexes:
[0,1,4,7,8,13,14,15,18,19]
with the result being this subset of the first array:
[1,2,5,8,9,14,15,16,19,20]
My question is, how do I make a simple function (1 or 2 lines) out of the array of indexes and the starting array to get the subset?
arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
indexes = [0,1,4,7,8,13,14,15,18,19]
arr.values_at(*indexes) # => [1, 2, 5, 8, 9, 14, 15, 16, 19, 20]
arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
index = [0,1,4,7,8,13,14,15,18,19]
arr.select.with_index{|m,i| m if index.include? i}
#=> [1, 2, 5, 8, 9, 14, 15, 16, 19, 20]
arr = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
index = [0,1,4,7,8,13,14,15,18,19]
arr.each_with_index {|value,index| p value if indexes.include?(index)}

How do I loop through a set of numbers, do some addition and append to an array?

My goal here is to create an array with the sum totals of every combination of 2 numbers on a set of dice. I'm creating the beginning of a loop that adds die1[0] to die2[0..5] before going through die1[1] + die2[0..5] and so on.
I've got this code below and I'm doing something wrong. I want to be able to call specific numbers in the array, such as dieSums[4], and get one number. Any idea what i'm doing incorrectly here?
die1 = [1,2,3,4,5,6]
die2 = [1,2,3,4,5,6]
dieSums = []
count = 0
while count <= 5 do
dieSums << die1[0] + die2[count]
count += 1
puts dieSums[5]
end
A while loop, as you've written it, isn't very Rubyonic. (Rubinic?) A more idiomatic way to iterate over the elements of an array:
#!/usr/bin/ruby
die1 = [1,2,3,4,5,6]
die2 = [1,2,3,4,5,6]
dieSums = []
die1.each do |d1|
die2.each do |d2|
dieSums << d1 + d2
end
end
puts dieSums[5]
Of course, die1 and die2 are identical in this case, so you could replace die2 with die1 and it'd all work out.
You are calling puts dieSums[5] inside the loop. dieSums[5] won't exist until the last iteration. It'll work if you call it outside the loop:
die1 = [1,2,3,4,5,6]
die2 = [1,2,3,4,5,6]
dieSums = []
count = 0
while count <= 5 do
dieSums << die1[0] + die2[count]
count += 1
end
puts dieSums[5] #=> 7
As a side note: notice that you are over-complicating the problem (because you think in imperative terms, take a look at Functional programming). The sum of all possible values for two dice:
>> die = [1,2,3,4,5,6]
>> die.product(die).map { |v1, v2| v1 + v2 }
=> [2, 3, 4, 5, 6, 7, 3, 4, 5, 6, 7, 8, 4, 5, 6, 7, 8, 9, 5, 6, 7, 8, 9, 10, 6, 7, 8, 9, 10, 11, 7, 8, 9, 10, 11, 12]
Call uniq at the end if you don't want repeated values.
Use repeated_combination(2) instead of product if you don't care about the order.
Note that die.product(die) = die.repeated_permutation(2))
Finding all the sums for N dice is almost as simple:
>> die.repeated_permutation(5).map { |values| values.inject(:+) }

Resources