Remove groups of consecutive numbers from array in Ruby - 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)}

Related

is there a Range in Ruby like x-y not x .. y

I'm trying to solve Range Extraction
each time i click Attempt i see that
Test Results:
✘ Expected: "-6,-3-1,3-5,7-11,14,15,17-20", instead got: [-6, -3..1, 3..5, 7..11, 14, 15, 17..20]
so what is the different between -3-1 and -3..1 ? is that a bug ?
this is the first day i write Ruby so i can not judge
this is my code
def solution(list)
result = []
arr = []
list.each.with_index{
|x,index|
arr.push(x)
if index == list.length-1
result.push(arr)
break
end
if list[index + 1] - x != 1
result.push(arr)
arr = []
end
}
final = []
result.each{
|x|
if x.length >= 3
final.push(Range.new(x[0],x[-1]))
else
final.concat(x)
end
}
final
end
puts solution([-6, -3, -2, -1, 0, 1, 3, 4, 5, 7, 8, 9, 10, 11, 14, 15, 17, 18, 19, 20]).inspect
# returns "-6,-3-1,3-5,7-11,14,15,17-20"
Thanks to slice_when, you can write a shorter and more Ruby-ish solution:
def solution(list)
list.slice_when { |x, y| y > x + 1 }.flat_map do |neighbors|
if neighbors.size > 2
"#{neighbors.first}-#{neighbors.last}"
else
neighbors
end
end.join(',')
end
"-6,-3-1,3-5,7-11,14,15,17-20" is a string, [-6, -3..1, 3..5, 7..11, 14, 15, 17..20] is an array of ranges and integers.
You can replace the final line of your solution method to coerce the array to the required format:
final.map do |x|
if x.is_a? Range
[x.min, x.max].join("-")
else
x.to_s
end
end.join(",")
Here are two other ways.
array = [-6, -3, -2, -1, 0, 3, 5, 7, 8, 9, 14, 15, 17]
#1 Use Enumerable#chunk_while
Enumerable#chunk_while (new in Ruby v2.3) is the flip side of Enumerable#slice_when (new in Ruby v2.2), which #Eric used in his answer.
array.chunk_while { |x,y| y == x+1 }.map { |a|
a.size == 1 ? a.first.to_s : "#{ a.first }-#{ a.last }" }.join(",")
#=> "-6,-3-0,3,5,7-9,14-15,17"
Note that
array.chunk_while { |a,b| b == a+1 }.to_a
#=> [[-6], [-3, -2, -1, 0], [3], [5], [7, 8, 9], [14, 15], [17]]
#2 Step through the array
first, *rest = array
rest.each_with_object([[first]]) { |n, arr|
(n == arr.last.last+1) ? (arr.last << n) : (arr << [n]) }.
map { |a| (a.size == 1)? a.first.to_s : "#{ a.first }-#{ a.last }" }.join(",")
#=> "-6,-3-0,3,5,7-9,14-15,17"

Insert a number into an ordered array

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

Ruby: replacing a matching element in a multidimensional array?

Could someone tell me how I can achieve replacing an element in this 2D array? I tried each, include and replace and wasn't able to figure out where I am going wrong. Thank you in advance for any help.
class Lotto
def initialize
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..60)})}
end
def current_pick
#number = rand(1..60).to_s
puts "The number is #{#number}."
end
def has_number
#prints out initial slip
#lotto_slip.each {|x| p x}
#Prints slip with an "X" replacing number if is on slip
#Ex: #number equals 4th number on slip --> 1, 2, 3, X, 5, 6
#lotto_slip.each do |z|
if z.include?(#number)
z = "X"
p #lotto_slip
else
z = z
p #lotto_slip
end
end
end
end
test = Lotto.new
test.current_pick
test.has_number
Let me know if this works out (tried to reduce the variations from 1 to 10 in order to be able to test easier):
class Lotto
def initialize
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..10)})}
end
def current_pick
#number = rand(1..10)
puts "The number is #{#number}."
end
def has_number
#prints out initial slip
#lotto_slip.each {|x| p x}
#Prints slip with an "X" replacing number if is on slip
#Ex: #number equals 4th number on slip --> 1, 2, 3, X, 5, 6
#lotto_slip.each do |z|
if z.include?(#number)
p "#{#number} included in #{z}"
z.map! { |x| x == #number ? 'X' : x}
end
end
#lotto_slip
end
end
test = Lotto.new
test.current_pick
p test.has_number
The problems I saw with your code are:
You don't need the to_s for this line #number = rand(1..60).to_s, else how are you going to compare the numbers produced by the array with an actual string?
You need to re-generate the array instead of re-assigning, that's why I've replaced all of that code with z.map! { |x| x == #number ? 'X' : x} which basically re-generates the entire array.
Not necessary iterate with each, use map:
#lotto_slip = Array.new(5) {Array(6.times.map{rand(1..60)})}
#=> [[25, 22, 10, 10, 57, 17], [37, 4, 8, 52, 55, 7], [44, 30, 58, 58, 50, 19], [49, 49, 24, 31, 26, 28], [24, 18, 39, 27, 8, 54]]
#number = 24
#lotto_slip.map{|x| x.map{|x| x == #number ? 'X' : x}}
#=> [[25, 22, 10, 10, 57, 17], [37, 4, 8, 52, 55, 7], [44, 30, 58, 58, 50, 19], [49, 49, "X", 31, 26, 28], ["X", 18, 39, 27, 8, 54]]

How to refer the index of an element in Array#delete_if

I want to build a custom method Array#drop_every(n) (I know it's monkey patching, I am doing this for a homework), which returns a new array omitting every nth element:
[4, 8, 15, 16, 23, 42].drop_every(2) # [4, 15, 23]
I want to implement it with Array#delete_if, but by referring to the index and not to the element itself, (similar to each_index) something like this:
def drop_every(step)
self.delete_if { |index| index % step == 0 }
end
How do I do this? I don't insist on using delete_if, I also looked at drop_while and reject, other suggestions are welcome.
You can use with_index method that returns enumerator, filter your collection and then get rid of the indexes.
class Array
def drop_every(step)
self.each.with_index.select { |_, index| index % step == 0 }.map(&:first)
end
end
[4, 8, 15, 16, 23, 42].drop_every(2) # => [4, 15, 23]
def drop_every(step)
reject.with_index { |x,i| (i+1) % step == 0 }
end
[4, 8, 15, 16, 23, 42].reject.with_index{|x,i| (i+1) % 2 == 0}
# => [4, 15, 23]
[4, 8, 15, 16, 23, 42].reject.with_index{|x,i| (i+1) % 3 == 0}
# => [4, 8, 16, 23]
You could use the values_at method to selectively filter out indices which you want.
class Array
def drop_every(step)
self.values_at(*(0...self.size).find_all{ |x| (x+1) % step != 0 })
end
end
The answer was accepted while I was typing it. I will post it anyways.
def drop_every step
delete_if.with_index(1){|_, i| i.%(step).zero?}
end
class Array
def drop_every(step)
self.each_slice(step).flat_map{|slice| slice[0..-2]}
end
end
p [4, 8, 15, 16, 23, 42].drop_every(2) #=> [4, 15, 23]
I'd extend the Enumerable mixin instead:
module Enumerable
def drop_every(step)
return to_enum(:drop_every, step) unless block_given?
each.with_index(1) do |o, i|
yield o unless i % step == 0
end
end
end
(1..10).drop_every(3) { |a| p a }
# outputs below
1
2
4
5
7
8
10

Every Other 2 Items in Array

I need a ruby formula to create an array of integers. The array must be every other 2 numbers as follows.
[2, 3, 6, 7, 10, 11, 14, 15, 18, 19...]
I have read a lot about how I can do every other number or multiples, but I am not sure of the best way to achieve what I need.
Here's an approach that works on any array.
def every_other_two arr
arr.select.with_index do |_, idx|
idx % 4 > 1
end
end
every_other_two((0...20).to_a) # => [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
# it works on any array
every_other_two %w{one two three four five six} # => ["three", "four"]
array = []
#Change 100000 to whatever is your upper limit
100000.times do |i|
array << i if i%4 > 1
end
This code works for any start number to any end limit
i = 3
j = 19
x =[]
(i...j).each do |y|
x << y if (y-i)%4<2
end
puts x
this should work
For fun, using lazy enumerables (requires Ruby 2.0 or gem enumerable-lazy):
(2..Float::INFINITY).step(4).lazy.map(&:to_i).flat_map { |x| [x, x+1] }.first(8)
#=> => [2, 3, 6, 7, 10, 11, 14, 15]
here's a solution that works with infinite streams:
enum = Enumerator.new do |y|
(2...1/0.0).each_slice(4) do |slice|
slice[0 .. 1].each { |n| y.yield(n) }
end
end
enum.first(10) #=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
enum.each do |n|
puts n
end
Single Liner:
(0..20).to_a.reduce([0,[]]){|(count,arr),ele| arr << ele if count%4 > 1;
[count+1,arr] }.last
Explanation:
Starts the reduce look with 0,[] in count,arr vars
Add current element to array if condition satisfied. Block returns increment and arr for the next iteration.
I agree though that it is not so much of a single liner though and a bit complex looking.
Here's a slightly more general version of Sergio's fine answer
module Enumerable
def every_other(slice=1)
mod = slice*2
res = select.with_index { |_, i| i % mod >= slice }
block_given? ? res.map{|x| yield(x)} : res
end
end
irb> (0...20).every_other
=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
irb> (0...20).every_other(2)
=> [2, 3, 6, 7, 10, 11, 14, 15, 18, 19]
irb> (0...20).every_other(3)
=> [3, 4, 5, 9, 10, 11, 15, 16, 17]
irb> (0...20).every_other(5) {|v| v*10 }
=> [50, 60, 70, 80, 90, 150, 160, 170, 180, 190]

Resources