Ruby decrementing numeric array to start end of range array

I have an array of numbers as shown below:

[11, 12, 13, 14, 19, 20, 21, 29, 30, 33]

      

I would like to reduce this array to:

[[11,14], [19,21], [29,30], [33,33]]

      

Determine the subsequent numbers in the array and hit only the beginning and end of its ranges.

How to do it?

+3


source to share


5 answers


Really some problem is being solved to give an example of a method slice_before

in the ruby โ€‹โ€‹docs:

a = [0, 2, 3, 4, 6, 7, 9]
prev = a[0]
p a.slice_before { |e|
  prev, prev2 = e, prev
  prev2 + 1 != e
}.map { |es|
  es.length <= 2 ? es.join(",") : "#{es.first}-#{es.last}"
}.join(",")

      



In your case, you need to tweak it a bit:

a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
prev = a[0]
p a.slice_before { |e|
  prev, prev2 = e, prev
  prev2 + 1 != e
}.map { |es|
  [es.first, es.last]
}

      

+4


source


Here's another way, using an enumerator with Enumerator # next and Enumerator #peek . It works for any collection that implements succ

(aka next

).

code

def group_consecs(a)
  enum = a.each
  pairs = [[enum.next]]
  loop do
    if pairs.last.last.succ == enum.peek
      pairs.last << enum.next 
    else
      pairs << [enum.next]
    end
  end
  pairs.map { |g| (g.size > 1) ? g : g*2 }
end

      

Note that Enumerator # peek throws an exception StopInteration

if the enumerator is enum

already at the end when called enum.peek

, This exception is handled by the core> loop, which breaks the loop.



<strong> Examples

a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
group_consecs(a)
  #=> [[11, 12, 13, 14], [19, 20, 21], [29, 30], [33, 33]]

a = ['a','b','c','f','g','i','l','m']
group_consecs(a)
  #=> [["a", "b", "c"], ["f", "g"], ["i", "i"], ["l", "m"]]

a = ['aa','ab','ac','af','ag','ai','al','am']
group_consecs(a)
  #=> [["aa", "ab", "ac"], ["af", "ag"], ["ai, ai"], ["al", "am"]]

a = [:a,:b,:c,:f,:g,:i,:l,:m]
group_consecs(a)
  #=> [[:a, :b, :c], [:f, :g], [:i, :i], [:l, :m]]

      

Create an array of seven date objects for the example, then group consecutive dates:

require 'date'
today = Date.today
a = 10.times.map { today = today.succ }.values_at(0,1,2,5,6,8,9)
  #=> [#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
  #    #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>]
group_consecs(a)
  #=> [[#<Date: 2014-08-07 ((2456877j,0s,0n),+0s,2299161j)>,
  #     #<Date: 2014-08-08 ((2456878j,0s,0n),+0s,2299161j)>,
  #     #<Date: 2014-08-09 ((2456879j,0s,0n),+0s,2299161j)>
  #    ],
  #     [#<Date: 2014-08-12 ((2456882j,0s,0n),+0s,2299161j)>,
  #      #<Date: 2014-08-13 ((2456883j,0s,0n),+0s,2299161j)>
  #    ],
  #    [#<Date: 2014-08-15 ((2456885j,0s,0n),+0s,2299161j)>,
  #     #<Date: 2014-08-16 ((2456886j,0s,0n),+0s,2299161j)>
  #    ]]

      

+3


source


This is the code I wrote for a project a while ago:

class Array
  # [1,2,4,5,6,7,9,13].to_ranges       # => [1..2, 4..7, 9..9, 13..13]
  # [1,2,4,5,6,7,9,13].to_ranges(true) # => [1..2, 4..7, 9, 13]
  def to_ranges(non_ranges_ok=false)
    self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
      if (non_ranges_ok)
        pairs.first[0] == pairs.last[0] ? pairs.first[0] : pairs.first[0] .. pairs.last[0]
      else
        pairs.first[0] .. pairs.last[0]
      end
    }
  end
end

if ($0 == __FILE__)
  require 'awesome_print'

  ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
  ary.to_ranges(false) # => [1..2, 4..7, 9..9, 12..13]
  ary.to_ranges(true) # => [1..2, 4..7, 9, 12..13]

  ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
  ary.to_ranges(false) # => [1..12]
  ary.to_ranges(true) # => [1..12]

end

      

It's easy to change this to return start / end pairs:

class Array
  def to_range_pairs(non_ranges_ok=false)
    self.sort.each_with_index.chunk { |x, i| x - i }.map { |diff, pairs|
      if (non_ranges_ok)
        pairs.first[0] == pairs.last[0] ? [pairs.first[0]] : [pairs.first[0], pairs.last[0]]
      else
        [pairs.first[0], pairs.last[0]]
      end
    }
  end
end

if ($0 == __FILE__)
  require 'awesome_print'

  ary = [1, 2, 4, 5, 6, 7, 9, 13, 12]
  ary.to_range_pairs(false) # => [[1, 2], [4, 7], [9, 9], [12, 13]]
  ary.to_range_pairs(true) # => [[1, 2], [4, 7], [9], [12, 13]]

  ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
  ary.to_range_pairs(false) # => [[1, 12]]
  ary.to_range_pairs(true) # => [[1, 12]]

end

      

+1


source


Here's an elegant solution:

arr = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]
output = []

# Sort array
arr.sort!

# Loop through each element in the list
arr.each do |element|

    # Set defaults - for if there are no consecutive numbers in the list
    start = element
    endd = element

    # Loop through consecutive numbers and check if they are inside the list
    i = 1
    while arr.include?(element+i) do

        # Set element as endd
        endd = element+i

        # Remove element from list
        arr.delete(element+i)

        # Increment i
        i += 1

    end

    # Push [start, endd] pair to output
    output.push([start, endd])

end

      

0


source


[Edit: Ha! I didn't understand this question. In your example for an array

a = [11, 12, 13, 14, 19, 20, 21, 29, 30, 33]

      

you specified the desired array of pairs:

[[11,14], [19,21], [29,30], [33,33]]

      

which correspond to the following offsets in a

:

[[0,3], [4,6], [7,8], [9,9]]

      

Matching pairs span the first 4 items, the next 3 items, then the next 2 items, and the next item (coincidentally, obviously). I thought that you needed such pairs, each with an interval less than the previous one, and the interval of the first one - as much as possible. If you take a quick look at my examples below, my guess may be clearer. Looking back, I don't know why I didn't get the question correctly (I should have looked at the answers), but there you have it.

Despite my mistake, I will leave it as I found an interesting problem and had the opportunity to use a quadratic formula in the solution.

Tide]

This is how I will do it.

code

def pull_pairs(a)
  n = ((-1 + Math.sqrt(1.0 + 8*a.size))/2).to_i
  cum = 0
  n.downto(1).map do |i|
    first = cum
    cum += i
    [a[first], a[cum-1]]
  end
end

      

<strong> Examples

a = %w{a b c d e f g h i j k l}
  #=> ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l"]
pull_pairs(a)
  #=> [["a", "d"], ["e", "g"], ["h", "i"], ["j", "j"]]

a = [*(1..25)]
  #=> [ 1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13,
  #    14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
pull_pairs(a)
  #=> [[1, 6], [7, 11], [12, 15], [16, 18], [19, 20], [21, 21]]

a = [*(1..990)]
  #=> [1, 2,..., 990]
pull_pairs(a)
  #=> [[1, 44], [45, 87],..., [988, 989], [990, 990]]

      

Explanation

We will first calculate the number of value pairs in the array that we will be producing. We are given an array (expressed algebraically):

a = [a0,a1,...a(m-1)]

      

where m = a.size

.

Given n > 0

the array being created:

[[a0,a(n-1)], [a(n),a(2n-2)],...,[a(t),a(t)]]

      

These elements cover the first n+(n-1)+...+1

elements a

. Since this is an arithmetic function, the sum is n(n+1)/2

. Hence,

t = n(n+1)/2 - 1

      

Now t <= m-1

, so we maximize the number of pairs in the output array by choosing the largest n

one such that

n(n+1)/2 <= m

      

which is the float solution for n

quadratic:

n^2+n-2m = 0

      

rounds to an integer which

int((-1+sqrt(1^1+4(1)(2m))/2)

      

or

int((-1+sqrt(1+8m))/2)

      

Let's pretend that

a = %w{a b c d e f g h i j k l}

      

Then m (=a.size) = 12

, therefore:

n = int((-1+sqrt(97))/2) = 4

      

and the desired array would be:

[['a','d'],['e','g'],['h','i'],['j','j']]

      

Once calculated, n

building an array of pairs is straightforward.

0


source







All Articles