How to convert an array of number into ranges [closed] - ruby-on-rails

Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions asking for code must demonstrate a minimal understanding of the problem being solved. Include attempted solutions, why they didn't work, and the expected results. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I'm looking to convert an array of numbers in sequence with gaps into an array of multiple ranges in Ruby.
Each range should be determined the gaps in the sequence:
[1,2,3,5,6,8,9,10,11,12]
The expected result would be:
[1-3, 5-6, 8-12]
I haven't been able to come up with any good ideas for tackling the problem. How can I go about solving this?

I would do as below using Enumerable#slice_before :
a = [1,2,3,5,6,8,9,10,11,12]
prev = a[0]
p a.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map{|b,*,c| c ? (b..c) : b }
# >> [1..3, 5..6, 8..12]

a = [1,2,3,5,8,9,10,11,12]
b = [a.first-1] + (a.first..a.last).to_a - a + [a.last + 1]
# => [0, 4, 6, 7, 13]
b.each_cons(2).with_object([]) {|(i,j), c| c << (i+1..j-1) if j > i+1}
# => [1..3, 5..5, 8..12]
Alternatively,
b = [a.first] + ((a.first..a.last).to_a - a).flat_map {|e| [e-1,e+1]}+[a.last]
# => [1, 3, 5, 5, 7, 6, 8, 12]
b.each_slice(2).map {|f,l| l >= f ? f..l : nil}.compact
# => [1..3, 5..5, 8..12]
Note: b.each_slice(2).to_a # => [[1, 3], [5, 5], [7, 6], [8, 12]]

Another solution:
class Array
def to_range_array
res = [ Range.new(first,first) ]
self[1..-1].sort.each{|item|
if res.last.max == (item -1)
res << Range.new(res.pop.min, item)
else
res << Range.new(item, item)
end
}
res
end
end
array = [ 1,2,3,5,6,8,9,10,11,12 ]
p array.to_range_array #[1..3, 5..6, 8..12]
My assumptions:
The ranges are based on the sorted array.
[ 9,10,11,12, 1,2,3,5,6,8] should return the same result as [ 1,2,3,5,6,8,9,10,11,12 ].
Without the sort-command you get [9..12, 1..3, 5..6, 8..8].
Single values result in a one-value range (e.g. 8..8)

a = [1,2,3,5,6,8,9,10,11,12]
b = ((a.first..a.last+1).to_a - a).unshift(-(2**(0.size * 8 -2)))
# => [-4611686018427387904, 4, 7, 13]
c = a.slice_before {|i| i > b.first ? b.shift : false}.to_a
# => [[1, 2, 3], [5, 6], [8, 9, 10, 11, 12]]
c.map {|e| (e.first..e.last)}
# => [1..3, 5..6, 8..12]

I much prefer all the variants using Enumerable#slice_before, but here is an alternate solution using Enumerable#each_cons:
def array_to_ranges(arr)
return [] if arr.empty?
seq, i = [[arr[0]]], 0
arr.each_cons(2) { |x,y| y-x == 1 ? seq[i] << y : seq[i+=1] = [y] }
seq.map { |range| range[0]..range[-1] }
end
Note that if any singletons are present in seq it will turn those into ranges as well. For example, if arr = [1, 2, 3, 5, 7, 8] array_to_ranges(arr) returns [1..3, 5..5, 7..8]. An empty array is returned if the input is an empty array.

Related

How to improve memory usage when generating a contiguous permutation

I have code that needs to generate a contiguous permutation:
(1..n).flat_map {|x| array.map {|y| (x..y) unless x > y } }.compact
Which outputs:
[[1], [1, 2], [1, 2, 3], [1, 2, 3, 4], [1, 2, 3, 4, 5], [2], [2, 3], [2, 3, 4], [2, 3, 4, 5],
[3], [3, 4], [3, 4, 5], [4], [4, 5], [5]]
It works really well with low sizes of n, but when I have n = 100000 I run out of memory. Is there a way to improve this but keeping them contiguous?
I need to perform a reject! iterating over poisonous and allergic arrays:
array = (1..n)
permutations = array.flat_map {|x| array.map {|y| (x..y) unless x > y } }.compact
poisonous.each_with_index do |x, i|
permutations.reject! { |y| y.include?(x) && y.include?(allergic[i]) }
end
The problem:
poisonous = [3,4,6]
allergic = [5,6,7]
These numbers can't be together:
3 -> 4
4 -> 6
6 -> 7
combinations = [[1], [3,4], [4,5]]
So, [3,4] is not a valid combination.
I was able to solve the memory issue by using less variables as possible and doing calculations directly while generating the permutation, although processing time has increased a bit, it is using a lot less memory (dropped from 800mb to 32mb approximately). I'm open for suggestions to improve it even more.
counter = 0
(1..n).each {|x|
(1..n).each {|y|
counter += 1 if !(x > y) && !poisonous.each_with_index.select {|poison, i| (x..y).include?(poison) && (x..y).include?(allergic[i])}.any?
}
}
The following computes the number of "clean combinations". As no large arrays are produced it has modest memory requirements.
require 'set'
def clean_combos(n, poisonous, allergic)
arr = (1..n).to_a
bad_allergies = poisonous.zip(allergic).to_h
arr.sum do |m|
arr.combination(m).sum do |combo|
combo_set = combo.to_set
bad_allergies.any? do |poison, allergy|
combo_set.include?(poison) && combo_set.include?(allergy)
end ? 0 : 1
end
end
end
n = 10
poisonous = [3,4,6]
allergic = [5,6,7]
clean_combos(n, poisonous, allergic)
#=> 479
bad_allergies is found to equal {3=>5, 4=>6, 6=>7}.
This concludes that, for the array [1, 2,..., 10], there are 479 combinations of elements of size between 1 and 10 such that, for each combination, 3 and 5 are not both included, and neither are 4 and 5, and 6 and 7.
See Array#zip, Array#to_h, Array#sum, Array#combination, Hash#any? and Set#include?. Array#to_set is added when include 'set' is executed.
I've converted each combo to a set to speed lookups.
Some tweaks might improve efficiency, so experimentation may be called for. This may depend on the size of the array poisonous (and of allergic) relative to n.

Distribute items into containers in twos - Rails

I have a list of 10 items -- it is an array of hashes.
[{ id: 1, name: 'one'}, { id: 2, name: 'two' } .. { id: 10, name: 'ten' }]
I also have a random number of containers -- let's say 3, in this case. These containers are hashes with array values.
{ one: [], two: [], three: [] }
What I want to do, is iterate over the containers and drop 2 items at a time resulting in:
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}]
}
Also, if the item list is an odd number (11), the last item is still dropped into the next container.
{
one: [{id:1}, {id:2}, {id:7}, {id:8}],
two: [{id:3}, {id:4}, {id:9}, {id:10}],
three: [{id:5}, {id:6}, {id:11}]
}
note: the hashes are snipped here so it's easier to read.
My solution is something like this: (simplified)
x = 10
containers = { one: [], two: [], three: [] }
until x < 1 do
containers.each do |c|
c << 'x'
c << 'x'
end
x -= 2
end
puts containers
I'm trying to wrap my head around how I can achieve this but I can't seem to get it to work.
Round-robin pair distribution into three bins:
bins = 3
array = 10.times.map { |i| i + 1 }
# => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
array.
each_slice(2). # divide into pairs
group_by. # group into bins
with_index { |p, i| i % bins }. # round-robin style
values. # get rid of bin indices
each(&:flatten!) # join pairs in each bin
Completely different approach, stuffing bins in order:
base_size, bins_with_extra = (array.size / 2).divmod(bins)
pos = 0
bins.times.map { |i|
length = 2 * (base_size + (i < bins_with_extra ? 1 : 0)) # how much in this bin?
array[pos, length].tap { pos += length } # extract and advance
}
# => [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]]
If you absolutely need to have this in a hash,
Hash[%i(one two three).zip(binned_array)]
# => {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
The lovely (but likely not as performant) solution hinted at by Stefan Pochmann:
bins.times.with_object(array.to_enum).map { |i, e|
Array.new(2 * (base_size + (i < bins_with_extra ? 1 : 0))) { e.next }
}
This is just to show a different approach (and I would probably not use this one myself).
Given an array of items and the containers hash:
items = (1..10).to_a
containers = { one: [], two: [], three: [] }
You could dup the array (in order not to modify the original one) and build an enumerator that cycles each_value in the hash:
array = items.dup
enum = containers.each_value.cycle
Using the above, you can shift 2 items off the array and push them to the next container until the array is emtpy?:
enum.next.push(*array.shift(2)) until array.empty?
Result:
containers
#=> {:one=>[1, 2, 7, 8], :two=>[3, 4, 9, 10], :three=>[5, 6]}
You can use Enumerable#each_slice to iterate over a range from 0 to 10 in 3s and then append to an array of arrays:
containers = [
[],
[],
[]
]
(1...10).each_slice(3) do |slice|
containers[0] << slice[0]
containers[1] << slice[1]
containers[2] << slice[2]
end
p containers
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]

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. :)

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]]

Remove redundant or duplicate tuples in Ruby array

Imagine the following Ruby array:
[9, 9, 5, 5, 5, 2, 9, 9]
What's the easiest way of removing redundant tuples, producing an output like the following:
[9, 5, 2, 9]
uniq is not correct because it's examining the entire array. The ordering of the input is important and must be kept. Is there a straightforward approach to this?
Thanks!
I'd do using Enumerable#chunk
2.0.0-p0 :001 > a = [9, 9, 5, 5, 5, 2, 9, 9]
=> [9, 9, 5, 5, 5, 2, 9, 9]
2.0.0-p0 :002 > a.chunk { |e| e }.map(&:first)
=> [9, 5, 2, 9]
I would do it like
b = [];
a.each { |n| b << n if b.last != n }
and b is the result
only one array scan is needed
I like Arup's answer best, but in case you want a method that is compatible with versions that don't have chunk you can do
a = [9, 9, 5, 5, 5, 2, 9, 9]
a.inject([a[0]]) { |b,c| b.last == c ? b : b << c }
# => [9, 5, 2, 9]
This is my version:
a.each_with_object([]) { |el, arr| arr << el if arr.last != el }
#=> [9, 5, 2, 9]
For those who land on this question looking to remove "redundant" values, the OP is trying to remove "repeated consecutive" values, not "redundant" or "duplicate" values and used the wrong word. They are different situations.
For clarification, removing redundant or duplicate values would be:
asdf = [9, 9, 5, 5, 5, 2, 9, 9]
asdf.uniq # => [9, 5, 2]
Or:
asdf & asdf # => [9, 5, 2]
Or:
require 'set'
asdf.to_set.to_a # => [9, 5, 2]
And, yes, I know the OP is asking for a different result. This is to show the answer for the question that was asked, NOT what what would meet the desired output. For that see the selected answer.
This is to show how you could use an enumerator directly, with the methods Enumerator#next and Enumerator#peek.
def purge_conseq_dups(arr)
return arr if arr.empty?
enum = arr.to_enum
a = []
loop do
e = enum.next
a << e unless e == enum.peek
end
a << arr.last
end
asdf = [9, 9, 5, 5, 5, 2, 9, 9]
purge_conseq_dups(asdf) #=> [9, 5, 2, 9]
When e is the last element of the enumerator enum, enum.peek raises a StopInteration exception which is rescued by Kernel#loop, which responds by breaking out of the loop. At that point all that remains is to append the last element of arr to a.
We could write a << e rather than a << arr.last, provided we initialize e prior to the loop (e.g., e = nil) so that the variable will be in scope in the last line.

Resources