Related
I write something like this in Ruby:
if a.max == a[0]
brand = b[0]
elsif a.max == a[1]
brand = b[1]
elsif a.max == a[2]
brand = b[2]
elsif a.max == a[3]
brand = b[3]
end
a and b both are unique arrays.
Is there any way to check all if and elsif's in the same condition?
Only one condition for a[0], a[1], a[2] and a[3]?
Array#index might help in cases like these (assuming the size of a and b is the same):
brand = b[a.index(a.max)]
In cases in which the array a might be empty, you will need an additional condition to avoid an error:
index = a.index(a.max)
brand = b[index] if index
Two more ways:
a = [3, 1, 6, 4]
b = [2, 8, 5, 7]
b[a.each_index.max_by { |i| a[i] }]
#=> 5
or
b[a.each_with_index.max_by(&:first).last]
#=> 5
Assuming a and b have the same size, e.g.
a = [2, 5, 8, 1]
b = [:a, :b, :c, :d]
you could combine zip and max:
a.zip(b).max.last # or more explicit: a.zip(b).max_by(&:first).last
#=> :c # or reversed: b.zip(a).max_by(&:last).first
or max_by and with_index:
b.max_by.with_index { |_, i| a[i] }
#=> :c
If your array has multiple maxima, you may want to get the indices of the array that correspond to all the maxima:
a = [10, 12, 12]
b = [:a, :b, :c]
# Compute and store the maximum once, to avoid re-computing it in the
# loops below:
a_max = a.max
idxs = a.each_with_index.select{ |el, idx| el == a_max }.map{ |el, idx| idx }
# or:
idxs = a.each_with_index.map{ |el, idx| idx if el == a_max }.compact
puts "#{idxs}"
# [1, 2]
puts "#{idxs.map{ |idx| b[idx] }}"
# [:b, :c]
I want to compare three arrays transitively to see if there are any shared elements:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
I want to return a matrix that compares the elements transitively. That is, if two arrays share any of the same elements, or if they match with a third record, return 1. Otherwise return 0.
With this example the result should be:
result = [
[1, 1, 1],
[1, 1, 1],
[1, 1, 1]
]
result[0][0] is 1 because if we compare arrs[0] with arrs[0] (compare itself), they share "AAA".
result[0][1] is 1 because if we compare arrs[0] and arrs[1] there are no shared elements, but both arrs[0] & arrs[2] and arrs[1] & arrs[2] return an intersecting element, so we return 1
result[0][2] is 1 because if we compare arrs[0] with arrs[2], they share "AAA"
We repeat the process for all other array combinations in arrs.
It's really not that tricky, you just need to double-map here:
def transitive(arr)
arr.map do |a|
arr.map do |b|
(a & b).any? ? 1 : 0
end
end
end
A more Ruby approach is to use true or false, but 1 and 0 is fine if you can handle the ternary to convert it.
How it works:
arrs = [
["AAA", "", ""],
["", "", "CCC"],
["AAA", "BBB", "CCC"]
]
transitive(arrs)
# => [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
Not a very exciting example. Here's one that has more variety:
arrs = [
%w[ A B C ],
%w[ A D E ],
%w[ D E F ]
]
transitive(arrs)
# => [[1, 1, 0], [1, 1, 1], [0, 1, 1]]
Where that has some misses.
Assumption
We are given an array arr of size n, each element being an array of size m. We wish to construct another n x m array, a, such each element a[i][j] equals 1 (else 0) if all of the following arrays are non-empty:
a[i] & a[i] # non-empty if a is non-empty
a[i] & a[(i+1)%n]
a[(i+1)%n] & a[(i+2)%n]
a[(j-1)%n] & a[j]
This is what I interpret "transitive" to mean. Note that I've assumed the transitive relation "wraps around" from the last to the first element of arr.
Let's consider an example.
arr = [["A", "B", "C"],
["A", "D", "E"],
["D", "E", "F"]]
Suppose we wish to compute a[i][j] of the array a being constructed. This equals 1 (else 0) if the following arrays are all non-empty:
a[1] & a[1] #=> a[1] => ["A", "D", "E"]
a[1] & a[2%3] #=> a[1] & a[2] => ["D"]
a[(i+1)%n] & a[(i+2)%n] #=> a[2] & a[1] => []
Note that had (a[1] & a[2%3]).empty? #=> true, it would not be necessary to compute the third expression (or any following expressions if arr were larger).
For i #=> 0,
a[0,0] = (arr[0] & arr[0]).any?
#=> arr[0].any? #=> true, hence = 1
a[0,1] = (arr[0] & arr[1]).any?
#=> ["A"].any? #=> true, hence = 1
a[0,2] = (arr[0] & arr[1]).any? && (arr[1] & arr[2]).any?
#=> (a[0,1] == 1) && ["D"].any? => true && true => true, hence = 1
For i #=> 1,
a[1,1] = (arr[1] & arr[1]).any?
#=> arr[1].any? #=> true, hence = 1
a[1,2] = (arr[1] & arr[2]).any?
#=> ["D"].any? #=> true, hence = 1
a[1,0] = (arr[1] & arr[2]).any? && (arr[2] & arr[0]).any?
#=> (a[1,2] == 1) && [].any? => true && false => true, hence = 0
For i #=> 2,
a[2,2] = (arr[2] & arr[2]).any?
#=> arr[2].any? #=> true, hence = 1
a[2,0] = (arr[2] & arr[0]).any?
#=> [].any? #=> false, hence = 0
a[2,1] = (arr[2] & arr[0]).any? && (arr[0] & arr[1]).any?
#=> (a[2,0] == 1) && ["A"].any? => false && true => false, hence = 0
Code
require 'set'
def transitive(arr)
n = arr.size
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
Array.new(n) do |i|
Array.new(arr.first.size) { |j| st.include?([i,j]) ? 1 : 0 }
end
end
Example
For arr defined earlier,
transitive(arr)
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]
Explanation
The steps are as follows:
n = arr.size
#=> 3
st = n.times.with_object(Set.new) do |i,st|
(i..i+n-1).each do |j|
if j==i
st << [i,j]
else
jj = j % n
jprev = (j-1) % n
break unless st.include?([i,jprev]) & (arr[jprev] & arr[jj]).any?
st << [i,jj]
end
end
end
#=> #<Set: {[0, 0], [0, 1], [0, 2], [1, 1], [1, 2], [2, 2]}>
st is a set of the transitive elements of arr. This shows that elements of arr with indices [0, 2] (order matters) are transitive, but those with indices [2, 0] are not (because st does not contain [2, 0]). Notice that once [2, 0] it was determined to not be transitive it was not necessary to check [2, 1].
The last step uses the method Array::new:
Array.new(n) {|i| Array.new(arr.first.size) {|j| st.include?([i,j]) ? 1 : 0}}
#=> [[1, 1, 1],
# [0, 1, 1],
# [0, 0, 1]]
I'm using Ruby 1.9.3 and I want to remove values from an array that appear more than once. I have the following:
arr = [1,2,2,3,4,5,6,6,7,8,9]
and the result should be:
arr = [1,3,4,5,7,8,9].
What would be the simplest, shortest Ruby code to accomplish this?
As #Sergio Tulentsev mentioned combination of group_by and select will do the trick
Here you go
arr.group_by{|i| i}.select{|k, v| v.count.eql?(1)}.keys
We can achieve this by array select and count methods
arr.select { |x| arr.count(x) == 1 } #=> [1, 3, 4, 5, 7, 8, 9]
def find_duplicates(elements)
encountered = {}
# Examine all elements in the array.
elements.each do |e|
# If the element is in the hash, it is a duplicate.
if encountered[e]
#Remove the element
else
# Record that the element was encountered.
encountered[e] = 1
end
end
end
I want to remove values from an array that appear more than once.
to check element appear more than once use Array#count
to remove element conditionally use Array#delete_if
below is an example:
> arr.delete_if{|e| arr.count(e) > 1}
#=> [1, 3, 4, 5, 7, 8, 9]
Option2:
> arr.group_by{|e| e}.delete_if{|_,v| v.size > 1}.keys
#=> [1, 3, 4, 5, 7, 8, 9]
First of you need to group elements by itself (which will return key, value pair), then remove such elements which appear more than once(value), and use keys
I would be inclined to use a counting hash.
Code
def single_instances(arr)
arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }.
select { |_,v| v == 1 }.
keys
end
Example
single_instances [1,2,2,3,4,5,6,6,7,8,9]
#=> [1, 3, 4, 5, 7, 8, 9]
Explanation
The steps are as follows.
arr = [1,2,2,3,4,5,6,6,7,8,9]
f = Hash.new(0)
#=> {}
f is created with the method Hash::new with an argument of zero. That means that if f does not have a key k, f[k] returns zero (and does not alter f).
enum = arr.each_with_object(f)
#=> #<Enumerator: [1, 2, 2, 3, 4, 5, 6, 6, 7, 8, 9]:each_with_object({})>
h = enum.each { |e,h| h[e] += 1 }
#=> {1=>1, 2=>2, 3=>1, 4=>1, 5=>1, 6=>2, 7=>1, 8=>1, 9=>1}
g = h.select { |_,v| v == 1 }
#=> {1=>1, 3=>1, 4=>1, 5=>1, 7=>1, 8=>1, 9=>1}
g.keys
#=> [1, 3, 4, 5, 7, 8, 9]
In calculating g, Hash#select (which returns a hash), not Enumerable#select (which returns an array), is executed. I've used an underscore for the first block variable (a key in h) to signify that it is not used in the block calculation.
Let's look more carefully at the calculation of h. The first value is generated by the enumerator enum and passed to the block, and the block variables are assigned values using a process called disambiguation or decomposition.
e, h = enum.next
#=> [1, {}]
e #=> 1
h #=> {}
so the block calculation is
h[e] += 1
#=> h[e] = h[e] + 1 => 0 + 1 => 1
h[e] on the right side of the equality (using the method Hash#[], as contrasted with Hash#[]= on the left side of the equality), returns 1 because h has no key e #=> 1.
The next two elements of enum are passed to the block and the following calculations are performed.
e, h = enum.next
#=> [2, {1=>1}]
h[e] += 1
#=> h[e] = h[2] + 1 => 0 + 1 => 1
Notice that h has been updated.
e, h = enum.next
#=> [2, {1=>1, 2=>1}]
h[e] += 1
#=> h[e] = h[e] + 1 => h[2] + 1 => 1 + 1 => 2
h #=> {1=>1, 2=>2}
This time, because h already has a key e #=> 2, the hash's default value is not used.
The remaining calculations are similar.
Use [Array#difference] instead
A simpler way is to use the method Array#difference.
class Array
def difference(other)
h = other.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
reject { |e| h[e] > 0 && h[e] -= 1 }
end
end
Suppose
arr = [1,2,2,3,4,2,5,6,6,7,8,9]
Note the addition of a third 2.
arr - arr.difference(arr.uniq)
# => [1, 3, 4, 5, 7, 8, 9]
The three steps are as follows.
a = arr.uniq
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9]
b = arr.difference(a)
#=> [2, 2, 6] (elements that appear more than once)
arr - b
# => [1, 3, 4, 5, 7, 8, 9]
I've proposed that Array#diffence be added to the Ruby core, but there seems to be little interest in doing so.
Say I have an array arr = [7,0,4,-7] and I'd like to get the pair of indexes [i, i2] where arr[i] + arr[i2] == 0. In the example, the answer would be [0, 3]. What would be the idiomatic and efficient way to do so?
Here's the best I've gotten so far. I'm sure it's not the best way to do it. Previously I was using two while loops but I feel like this isn't any better.
> nums = [7,0,4,-7]
> nums.each_index do |n1|
(nums.length).times do |n2|
return [n1, n2] if nums[n1] + nums[n2] == 0
end
end
> [0, 3]
The following code will find you all pairs of elements where the sum is zero.
arr = [7,0,4,-7, -4, 5]
zero_sum = arr.combination(2).select { |pair| pair.first + pair.last == 0 }
zero_sum #=> [[7, -7], [4, -4]]
You can then find the indexes of these elements this way:
zero_sum.map { |pair| [arr.index(pair.first), arr.index(pair.last)] } #=> [[0, 3], [2, 4]]
If you need just one pair use method find instead of select:
arr.combination(2)
.find { |first, last| first + last == 0 } #=> [7, -7]
.map { |num| arr.index(num) } # =>[0, 3]
The following method requires only a single pass through the array. It returns all pairs of indices of elements of the array that sum to zero.
Code
def zero_summing_pairs(arr)
processed = {}
arr.each_with_index.with_object([]) do |(n,i),pairs|
processed[-n].each { |j| pairs << [j,i] } if processed.key?(-n)
(processed[n] ||= []) << i
end
end
Examples
zero_summing_pairs [7,0,4,-7]
#=> [[0, 3]]
zero_summing_pairs [7,4,0,7,4,0,-7,-4,-7]
#=> [[2, 5], [0, 6], [3, 6], [1, 7], [4, 7], [0, 8], [3, 8]]
The associated values are as follows.
arr = [7,0,4,-7]
zero_summing_pairs(arr).map { |i,j| [arr[i], arr[j]] }
#=> [[7, -7]]
arr = [7,4,0,7,4,0,-7,-4,-7]
zero_summing_pairs(arr).map { |i,j| [arr[i], arr[j]] }
#=> [[0, 0], [7, -7], [7, -7], [4, -4], [4, -4], [7, -7], [7, -7]]
Explanation
pairs is the array of pairs of indices of values of arr that sum to zero. pairs is the object that is returned by the method.
processed is a hash with keys equal to the values of arr that have been processed by the block. The value of each key k is an array of the indices i of arr that have been processed by the block and for which arr[i] #=> -n. I chose a hash structure for fast key lookup.
The line
(processed[n] ||= []) << i
requires explanation. Firstly, this is shorthand for
processed[n] = (processed[n] || []) << i
If processed has a key n (whose value is not nil), the value of that key on the right side of the above expression is a non-empty array containing indices i for which arr[i] #=> -n, so the above expression reduces to
processed[n] = processed[n] << i
and the index i is added to the array. If processed does not have a key n, processed[n] equals nil, so the expression becomes
processed[n] = (processed[n] || []) << i
= (nil || []) << i
= [] << i
= [i]
In other words, here the value of key n is made an empty array and then i is appended to that array.
Let's now step through the code for
arr = [7,0,4,-7]
processed = {}
enum0 = arr.each_with_index
#=> #<Enumerator: [7, 0, 4, -7]:each_with_index>
We can see the values that will be generated by this enumerator by converting it to an array.
enum0.to_a
#=> [[7, 0], [0, 1], [4, 2], [-7, 3]]
Continuing,
enum1 = enum0.with_object([])
#=> #<Enumerator: #<Enumerator: [7, 0, 4, -7]:each_with_index>:with_object([])>
enum1.to_a
#=> [[[7, 0], []], [[0, 1], []], [[4, 2], []], [[-7, 3], []]]
If you examine the return value for the definition of enum1, you will see that it can be thought of as a "compound" enumerator. The empty arrays (corresponding to the block variable pairs) will be filled in as the calculations are performed.
The first value of enum1 is generated and passed to the block, and the three block variables are assigned values using parallel assignment (aka multiple assignment) and disambiguation (aka decompositon).
(n,i), pairs = enum1.next
#=> [[7, 0], []]
n #=> 7
i #=> 0
pairs #=> []
As
processed.key?(-n)
#=> processed.key?(-7)
#=> false
the first line of the block is not executed. The second line of the block is
(processed[n] ||= []) << i
#=> processed[n]
#=> [i]
#=> [0]
so now
processed
#=> {7=>[0], 0=>[1]}
pairs
#=> []
The remaining three elements generated by enum1 are processed similarly.
(n,i), pairs = enum1.next
#=> [[0, 1], []]
processed.key?(-n)
#=> processed.key?(0)
#=> false
(processed[n] ||= []) << i
#=> (processed[0] ||= []) << 1
#=> [] << 1
#=> [1]
processed
#=> {7=>[0], 0=>[1]}
pairs
#=> []
(n,i), pairs = enum1.next
#=> [[4, 2], []]
processed.key?(-n)
#=> processed.key?(-4)
#=> false
(processed[n] ||= []) << i
#=> (processed[4] ||= []) << 2
#=> [] << 2
#=> [2]
processed
#=> {7=>[0], 0=>[1], 4=>[2]}
pairs
#=> []
(n,i), pairs = enum1.next
#=> [[-7, 3], []]
processed.key?(-n)
# processed.key?(7)
#=> true
processed[-n].each { |j| pairs << [j,i] }
# processed[7].each { |j| pairs << [j,3] }
#=> [0]
(processed[n] ||= []) << i
#=> (processed[-7] ||= []) << 3
#=> [] << 3
#=> [3]
processed
#=> {7=>[0], 0=>[1], 4=>[2], -7=>[3]}
pairs
#=> [[0, 3]]
Notice that the last value generated by enum1 is the first to have a match in processed, so is treated differently than the previous values in the block calculation. Lastly,
(n,i), pairs = enum1.next
#=> StopIteration: iteration reached an end (an exception)
causing pairs to be returned from the block and therefore from the method.
Here is one way to do this, it uses Array#combination, approach similar to other answer by #kallax, but works on combinations of indices instead of combination of elements:
arr = [7,0,4,-7]
(0...arr.size).to_a.combination(2).select {|i| arr[i.first] + arr[i.last] == 0}
#=> [[0, 3]]
Suppose I have an array of sorted inclusive ranges:
a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]
I want as output an array of arrays, each of whose first element is a range and second element its overlapping count, like this:
[[1012..1014, 1], [1016..1016, 1], [1017..1020, 2], [1021..1022, 2], [1023..1035, 1], [1040..1080, 1]]
For example, the range 1017..1020 is included in two ranges 1016..1020 and 1017..1022, so its count would be two.
Code
require 'set'
def range_info(a)
covered_by = a.each_with_object(Hash.new { |h,k| h[k]=Set.new }) { |r,h|
r.each { |n| h[n] << r } }
a.flat_map { |r| r.to_a }.
uniq.
slice_when { |b,c| c > b+1 }.
flat_map { |r| r.to_a.slice_when { |b,c| covered_by[b] != covered_by[c] } }.
flat_map { |enum| enum.to_a.map { |a| [a.first..a.last, covered_by[a.first].size] } }
end
Example
a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]
range_info(a)
#=> [[1012..1014, 1], [1016..1016, 1], [1017..1020, 2], [1021..1022, 2],
# [1023..1035, 1], [1040..1080, 1]]
Explanation
First create the hash covered_by with keys equal to numbers that are covered by at least one range in a, where covered_by[n] equals the set of all ranges in a that cover key n:
covered_by = a.each_with_object(Hash.new { |h,k| h[k]=Set.new }) { |r,h|
r.each { |n| h[n] << r } }
#=> {1012=>#<Set: {1012..1014}>, 1013=>#<Set: {1012..1014}>,
# ...
# 1016=>#<Set: {1016..1020}>, 1017=>#<Set: {1016..1020, 1017..1022}>,
# ...
# 1079=>#<Set: {1040..1080}>, 1080=>#<Set: {1040..1080}>}
See my answer here for an explanation of Hash.new { |h,k| h[k]=[] }, which is similar to Hash.new { |h,k| h[k]=Set.new }.
Next, obtain an array of increasing non-overlapping ranges that cover the same numbers that are covered by one or more ranges in a:
arr = a.flat_map { |r| r.to_a }.uniq.slice_when { |b,c| c > b+1 }
#=> [1012..1014, 1016..1035, 1040..1080]
Next, break each of the ranges in arr into enumerators that will generate arrays of consecutive numbers that are covered by the same ranges in a:
b = arr.flat_map { |r| r.to_a.slice_when { |b,c| covered_by[b] != covered_by[c] } }
#=> [#<Enumerator: #<Enumerator::Generator:0x007fd1ea854558>:each>,
# #<Enumerator: #<Enumerator::Generator:0x007fd1ea8543c8>:each>,
# #<Enumerator: #<Enumerator::Generator:0x007fd1ea854238>:each>]
We can see the elements of b by converting them to arrays:
b.map(&:to_a)
#=> [[[1012, 1013, 1014]],
# [[1016], [1017, 1018, 1019, 1020], [1021, 1022],
# [1023, 1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1032, 1033,
# 1034, 1035]],
# [[1040, 1041, 1042, 1043, 1044, 1045, 1046, 1047, 1048, 1049, 1050,
# 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061,
# 1062, 1063, 1064, 1065, 1066, 1067, 1068, 1069, 1070, 1071, 1072,
# 1073, 1074, 1075, 1076, 1077, 1078, 1079, 1080]]]
Lastly, flat_map these arrays to arrays containing a range and the number of ranges in a that cover all the elements of the range:
c = b.flat_map { |enum| enum.to_a.map { |a| [a.first..a.last, covered_by[a.first].size] } }
#=> [[1012..1014, 1], [1016..1016, 1], [1017..1020, 2], [1021..1022, 2],
# [1023..1035, 1], [1040..1080, 1]]
Here is my take on this problem. It may not be efficient - complexity O(n2) - nonetheless, it is a solution.
My approach to find whether a range is sub-range of another range is to do following steps:
Convert both ranges to array and join then using Array#| operator
Sort the array obtained by combining the two ranges.
If one range is sub-range of another, then, the range that includes the sub-range will be equal to combined sorted array when converted to array using to_a.
Here is an illustration:
r1 = 2..3
r2 = 1..4
p a = r1.to_a | r2.to_a
#=> [2, 3, 1, 4]
p a = a.sort
#=> [1, 2, 3, 4]
p a == r1.to_a
#=> [1,2,3,4] == [2,3]
#=> false
p a == r2.to_a
#=> [1,2,3,4] == [1,2,3,4]
#=> true
Based on the above approach, here is the complete code. Although I am not sure that example list of ranges given in the question has any overlapping ranges, hence, I have taken example of my own.
h = {}
r_a = [1016..1020, 1017..1020, 1021..1035, 1040..1080]
r_a.each {|r| h[r] = 1}
(0...r_a.length).each do |i|
(0...r_a.length).each do |j|
if (i != j)
range_outer = r_a[i]
range_inner = r_a[j]
first,*rest,last = (range_outer.to_a | range_inner.to_a).to_a.sort
combined_range = Range.new(first, last)
if range_inner == combined_range
h[range_outer] += 1
end
end
end
end
p h
#=> {1016..1020=>1, 1017..1020=>2, 1021..1035=>1, 1040..1080=>1}
If you are looking to test all subranges from the supplied ranges, you could try something like this (only accounts for subranges starting from the min value of each original range):
a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]
test_inputs = a.each_with_object([]) do |original, expanded|
original.size.times.each{ |i| expanded << Range.new(original.min, original.min+i) }
end
output = test_inputs.each_with_object([]) do |input, result|
appears = a.select{|x| x.min <= input.min}.select{|x| x.max >= input.max}.count
result << [input, appears]
end
This is my approach to solve your problem. Let
a = [1012..1014, 1016..1020, 1017..1022, 1021..1035, 1040..1080]
Step 1: Flatten this array, then count each element
b = a.map(&:to_a).inject(:+).sort.group_by{|i| i }.map{|k,v| [k,v.count] }
# => [[1012, 1], [1013, 1], [1014, 1], [1016, 1], [1017, 2], [1018, 2], [1019, 2], [1020, 2], [1021, 2], [1022, 2], [1023, 1], ...
Step 2: Add nil as break points
c = b.each_with_index do |e, i|
if e.nil? || b[i+1].nil? then next end
if b[i][0] + 1 != b[i+1][0] || b[i][1] != b[i+1][1] then b.insert(i+1,nil) end
end
# => [[1012, 1], [1013, 1], [1014, 1], nil, [1016, 1], nil, [1017, 2], [1018, 2], [1019, 2], [1020, 2], [1021, 2], [1022, 2], nil, [1023, 1], ...
Step 3: Split obtained array by break points and group them to ranges
d = c.split{|e| e.nil?}.map{|e| [(e.first[0]..e.last[0]), e.first[1]]}
# => [[1012..1014, 1], [1016..1016, 1], [1017..1022, 2], [1023..1035, 1], [1040..1080, 1]]
Update:
Since split is a method from Rails, so I have an alternative with pure Ruby.
Step 1: same as above
Step 2: Split the array into small groups as below
c = []
j = 0
b.each_with_index do |e, i|
if c[j].nil? then c[j] =[] end
c[j] << b[i]
if b[i+1] && (b[i][0] + 1 != b[i+1][0] || b[i][1] != b[i+1][1]) then j+=1 end
end
# p c => [
# [[[1012, 1], [1013, 1], [1014, 1]],
# [[1016, 1]],
# [[1017, 2], [1018, 2], [1019, 2], [1020, 2], [1021, 2], [1022, 2]],
# ...
# ]
Step 3: Convert each group to a range
d = c.map{|e| [(e.first[0]..e.last[0]), e.first[1]]}
# => [[1012..1014, 1], [1016..1016, 1], [1017..1022, 2], [1023..1035, 1], [1040..1080, 1]]
The following solution works under limited occasions: when the minimum value of a range and the maximum value of a range are never identical. (I.e., if there is x..100, then there is no 100..y. Also there is no z..z.)
break_points = a.flat_map{|r| [r.min - 1, r.min, r.max, r.max + 1]}.uniq.sort
a.flat_map do
|r|
break_points
.select{|i| r.min <= i and i <= r.max}
.each_slice(2)
.map{|min, max| min..max}
end
.group_by(&:itself)
.map{|k, v| [k, v.length]}