Array into ranges of consecutive numbers [closed] - ruby

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 am trying to make a Ruby function that converts an array of unique numbers into ranges of consecutive numbers.
[1, 2, 3, 5, 6, 8, 9] => [(1..3), (5..6), (8..9)]
It doesn't seem too hard, but I want to know if there's better way.

How is this using Enumerable#slice_before?
ar = [1, 2, 3, 5, 6, 8, 9]
prev = ar[0]
p ar.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map{|a| a[0]..a[-1]}
# >> [1..3, 5..6, 8..9]
ar = [1, 2, 3, 5, 6,7, 8, 9,11]
prev = ar[0]
p ar.slice_before { |e|
prev, prev2 = e, prev
prev2 + 1 != e
}.map{|a| a[0]..a[-1]}
# >> [1..3, 5..9, 11..11]

This is something I wrote a while back when dealing with IP address ranges:
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]
puts ary.join(', ')
ap ary.to_ranges
ary = [1, 2, 4, 8, 5, 6, 7, 3, 9, 11, 12, 10]
puts ary.join(', ')
ap ary.to_ranges
end
Pass true to to_ranges and it will not convert individual elements into one-element ranges.

Here's a solution to the same question you're asking. The linked code does a bit more work than you require (the numbers don't need to be sorted, or consecutive), but it'll do the trick. Or, you could use this code, suggested by #NewAlexandria :
class Array
def to_ranges
compact.sort.uniq.inject([]) do |r,x|
r.empty? || r.last.last.succ != x ? r << (x..x) : r[0..-2] << (r.last.first..x)
end
end
end

Related

Extract items between 2 numbers in Ruby

Problem:
Given an array of numbers in Ruby, return the groups of numbers that appear between 1 and 2.
The numbers 1 and 2 do not appear in between other 1's and 2's (there are no subsets of subsets).
Example 1
input: [1, 3, 2, 1, 4, 2]
output: [[1, 3, 2], [1, 4, 2]]
Example 2
input: [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
output: [ [1, 3, 2], [1, 5, 6, 7, 8, 7, 5, 2], [1, -400, 2] ]
My hunch is to use a combination of #chunk and #drop_while or a generator.
Thanks in advance.
This is an option using [Enumerable#slice_when][1]:
ary1 = [1, 3, 2, 1, 4, 2]
ary2 = [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
For example:
stop = [1, 2]
ary2.slice_when{ |e| stop.include? e }
.each_slice(2).map { |a, b| b.unshift(a.last) if b }
.reject { |e| e.nil? || (e.intersection stop).empty? }
#=> [[1, 3, 2], [1, 5, 6, 7, 8, 7, 5, 2], [1, -400, 2]]
Other option
More verbose but clearer, given the input:
input = %w(b a b c a b c a c b c a c a)
start = 'a'
stop = 'b'
Using Enumerable#each_with_object, why not use the good old if then else?:
tmp = []
pickup = false
input.each_with_object([]) do |e, res|
if e == start
pickup = true
tmp << e
elsif pickup && e == stop
tmp << e
res << tmp
tmp = []
pickup = false
elsif pickup
tmp << e
end
end
#=> [["a", "b"], ["a", "b"], ["a", "c", "b"]]
[1]: https://ruby-doc.org/core-2.7.0/Enumerable.html#method-i-slice_when
Sounds like an interview question. I'll explain the simplest algorithm I can think of:
You loop through the array once and build the output as you go. When you encounter 1, you store it and the subsequent numbers into another temporary array. When you encounter 2, you put the array in the output array. The edge cases are:
another 1 after you start building the temporary array
a 2 when you don't have a temporary array
First case is easy, always build a new temp array when you encounter a 1. For the second one, you have to check whether you have any items in your temporary array and only append the temp array to your output if it's not empty.
That should get you started.
You could use chunk and Ruby's flip-flop operator:
input = [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
input.chunk { |i| true if i==1..i==2 }.each { |_, ary| p ary }
Output:
[1, 3, 2]
[1, 5, 6, 7, 8, 7, 5, 2]
[1, -400, 2]
For all people wanting to take a walk on the beach but for obvious reasons can't:
class Flipflop
def initialize(flip, flop) #flip and flop being boolean-returning lambdas
#state = false
#flip = flip
#flop = flop
end
def flipflop(x) #logic taken from The Ruby Programming Language page 111
if !#state
result = #flip[x]
if result
#state = !#flop[x]
end
result
else
#state = !#flop[x]
true
end
end
end
ff = Flipflop.new( ->(x){x == 1}, ->(x){x == 2} )
input = [0, 1, 3, 2, 10, 1, 5, 6, 7, 8, 7, 5, 2, 3, 1, -400, 2, 12, 16]
res = input.select{|el| ff.flipflop(el) }.slice_before(1) #an Enumerator
p res.to_a
# =>[[1, 3, 2], [1, 5, 6, 7, 8, 7, 5, 2], [1, -400, 2]]
For strings, ff = Flipflop.new( ->(x){x.chomp == "BEGIN"}, ->(x){x.chomp == "END"} ) or something like that should work.
Since you commented and added that you are actually reading a file, I deleted my old answer (which was faulty anyways, as #Stefan pointed out) and cam up with this. You can paste this in a file and run it, the DATA IO contains everything that appears after __END__. In your application you would replace it with your File.
class Chunker
BEGIN_INDICATOR = "BEGIN"
END_INDICATOR = "END"
def initialize(io)
#io = io
end
def each
return enum_for(:each) if !block_given?
chunk = nil
while !io.eof? do
line = io.readline.chomp
if line == BEGIN_INDICATOR
chunk = []
chunk << line
elsif line == END_INDICATOR
chunk << line
yield chunk.freeze
chunk = nil
elsif chunk
chunk << line
end
end
end
private
attr_reader :io
end
chunker = Chunker.new(DATA)
chunker.each do |chunk|
p chunk
end
# or, thanks to the `return enum_for(:each) if !block_given?` line:
chunker.each.with_index do |chunk, index|
p "at #{index} is #{chunk}"
end
__END__
ignore
BEGIN
some
thing
END
BEGIN
some
other
thing
END
maybe ignore as well
ยดยดยด
You could enhance it to throw EOF when `each` is called multiple times or whatever suits your needs.

Find all max of elements of an array [duplicate]

This question already has answers here:
Returning all maximum or minimum values that can be multiple
(3 answers)
Closed 8 years ago.
Suppose I have a array, namely arr: [1, 2, 3, 4, 8, 8], and I want to find all max elements in this array:
arr.allmax # => [8, 8]
Is there a built-in method combinations to solve this? I don't like to monkey patch as I am doing now:
class Array
def allmax
max = self.max
self.select { |e| e == max }
end
end
Monkey patch is not a good idea, I could just do:
some_array.select { |e| e == some_array.max }
and it will work as allmax. Thanks for all answers and comments for inspirations.
Here's a fun way to do it.
arr.sort!.slice arr.index(arr[-1]) || 0..-1
Sort the array, then find the leftmost index of the array which matches the rightmost index of the array, and take the subslice that matches that range (or the range 0..-1 if the array is empty).
This one is kind of fun in that it requires no intermediate arrays, though it does mutate the input to achieve the one-liner.
Here is one way :
2.1.0 :006 > arr = [1, 2, 3, 4, 8, 8]
=> [1, 2, 3, 4, 8, 8]
2.1.0 :007 > arr.group_by { |i| i }.max.last
=> [8, 8]
2.1.0 :008 >
Here is a method :-
def all_max(arr)
return [] if arr.empty?
arr.group_by { |i| i }.max.last
end
Another way:
def all_max(arr)
return [] if arr.empty?
mx = arr.max
[mx] * arr.count { |e| e == mx }
end
all_max([1, 2, 3, 4, 8, 8])
#=> [8, 8]
To construct the array in a single pass, you could do this:
arr.each_with_object([]) do |e,a|
if a.empty?
a << e
else
case e <=> a.first
when 0 then a << e
when 1 then a.replace([e])
end
end
end

Why am I having trouble creating a custom array method that returns a hash with indices of matching array values? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 9 years ago.
Improve this question
I'm trying to monkey patch the Array class with a method that, when passed something like [1, 3, 4, 3, 0, 3, 1], will return a hash with { 1 => [0, 6], 3 => [1, 3, 5] }, where the key is the number we are matching to, and the value is an array with the indices of all matches.
Here's the code I have so far. I can't tell why it's returning something like {1=>[0, 2, 3, 1, 2, 0], 3=>[0, 2, 3, 1, 2, 0], 0=>[0, 2, 3, 1, 2, 0]}:
class Array
def dups
matches = {}
matches_index = []
self.each do |i|
self[(i_index + 1)..-1].each_with_index do |j, j_index|
matches_index << j_index if self[i] == self[j]
end
matches[i] = matches_index
end
matches.keep_if { |key, value| value.length > 1 }
end
end
This is an even shorter version:
class Array
def dups
each_index.group_by{|i| self[i]}.select{|k,v| v.size > 1}
end
end
Your code looks very complicated (and very imperative). A functional approach is way easier:
class Array
def dups
grouped_indexed = each_with_index.group_by(&:first)
Hash[grouped_indexed.map { |x, gs| [x, gs.map(&:last)] }]
end
end
Is this still too complicated? well, yes, but that's because the core is missing some basic abstractions like map_by:
require 'facets'
xs = [1, 3, 4, 3, 0, 3, 1]
xs.each_with_index.to_a.map_by { |x, i| [x, i] }
#= {1=>[0, 6], 3=>[1, 3, 5], 4=>[2], 0=>[4]}
To be honest, even that one-liners is too verbose. With the right abstractions we should be able to write something like this:
require 'awesome_functional_extensions'
xs = [1, 3, 4, 3, 0, 3, 1]
xs.with_index.group_by_pair
#= {1=>[0, 6], 3=>[1, 3, 5], 4=>[2], 0=>[4]}
To improve on Stas S already excellent solution:
class Array
def dups
(self.each_index.group_by {|i| self[i]}).keep_if{|k, v| v.size > 1}
end
end
Which results in an array of only duplicates.
class Array
def dups
matches = {}
self.each_with_index do |v, i|
matches.has_key?(v) ? matches[v] << i : matches[v] = [i]
end
matches
end
end
x = [1, 3, 4, 3, 0, 3, 1]
puts x.dups
Yet another version:
class Array
def dups
to_enum
.with_index
.group_by(&:first)
.select{|_, a| a.length > 1}
.each_value{|a| a.map!(&:last)}
end
end

checking if 2 numbers of array add up to Input number in ruby [closed]

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 am new to ruby on rails .
I was programming ruby and want to try checking if 2 numbers of array add up to Input number in ruby.
eg,
array A[]= {3, 1, 8, 11, 5, 7}
given integer say N = 6
answer will be 1,5.
I know how to program it in java,C++ but i am stuck in ruby coding,
Can anyone please help me.Thanks in advance
You can use Array#combination:
ary = [3, 1, 8, 11, 5, 7]
n = 6
ary.combination(2).detect { |a, b| a + b == n }
#=> [1, 5]
combination(2) creates an array of all combinations of length 2, i.e. [3,1], [3,8], [3,11] etc.
detect { |a, b| a + b == n } returns the first pair with sum n
You can use find_all instead of detect to return all pairs with sum n.
a = [3, 1, 8, 11, 4, 5, 7, 2]
> a.combination(2).select {|i| i.inject(:+) == 6 }
#=> [[1, 5], [4, 2]]
a = [3, 1, 8, 11, 5, 7]
p a.combination(2).find{|i| i.inject(:+) == 6}
# >> [1, 5]

Split array by consecutive sequences [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
Given an array of integer such as:
array = [1,2,3,5,6,7,20,21,33,87,88,89,101]
This array contains k consecutive subsequences (in this case k = 6), such as "1,2,3" and "87,88,89". How do I get an array containing arrays of these k subsequences?
For the above example these would be:
[[1,2,3], [5,6,7], [20,21], [33], [87,88,89], [101]]
The unshifted 0 is a dummy. It could be any number.
[1, 2, 3, 5, 6, 7]
.unshift(0)
.each_cons(2).slice_before{|m, n| m + 1 < n}.map{|a| a.map(&:last)}
# => [[1, 2, 3], [5, 6, 7]]
EDIT:
If you are using rails, there is an easy way, using the active support split method
def sort_it(arr)
(arr.first..arr.last).to_a.split {|i| !arr.include?(i) }.select &:present?
end
or in Ruby
def sort_it(arr)
tmp, main = [], []
arr.each_with_index do |x, i|
if arr[i-1]
if arr[i-1] + 1 == x
tmp << x
else
main << tmp unless tmp.empty?
tmp = [x]
end
else
tmp << x
end
end
main << tmp
main
end
> a = [1, 3, 5, 6, 8, 9, 11, 12, 13, 14, 16, 27]
> sort_it(a)
=> [[1], [3], [5, 6], [8, 9], [11, 12, 13, 14], [16], [27]]
def seq_sort(array)
finished = [array.first], i = 0
array.each_cons(2) do |(a,b)|
if b - a == 1
finished[i] << b
else
finished[i += 1] = [b]
end
end
finished
end
> seq_sort([1,2,38,39,92,94,103,104,105,1002])
=> [[1,2], [38, 39], [92], [94], [103, 104, 105], [1002]]

Resources