Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to split an array by a condition on adjacent elements into a limited number of partitions

Let's say I have an array of numbers, e.g.

ary = [1, 3, 6, 7, 10, 9, 11, 13, 7, 24]

I would like to split the array between the first point where a smaller number follows a larger one. My output should be:

[[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]

I've tried slice_when and it comes quite close:

ary.slice_when { |i, j| i > j }.to_a
#=> [[1, 3, 6, 7, 10], [9, 11, 13], [7, 24]]

But it also splits between 13 and 7, so I have to join the remaining arrays:

first, *rest = ary.slice_when { |i, j| i > j }.to_a
[first, rest.flatten(1)]
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]

which looks a bit cumbersome. It also seems inefficient to keep comparing items when the match was already found.

I am looking for a general solution based on an arbitrary condition. Having numeric elements and i > j is just an example.

Is there a better way to approach this?

like image 264
Stefan Avatar asked Oct 13 '17 15:10

Stefan


People also ask

How do you split an element in an array?

Example-2: This example uses the splice() method to split the array into chunks of array. This method removes the items from the original array. This method can be used repeatedly to split array of any size.

How do you split an array into two sets?

A Simple solution is to run two loop to split array and check it is possible to split array into two parts such that sum of first_part equal to sum of second_part. Below is the implementation of above idea.

How do you split an array into 4 Subarrays?

Given an array of n non-negative integers. Choose three indices i.e. (0 <= index_1 <= index_ 2<= index_3 <= n) from the array to make four subsets such that the term sum(0, index_1) – sum(index_1, index_2) + sum(index_2, index_3) – sum(index_3, n) is maximum possible.


Video Answer


2 Answers

There's probably a better way to do this but my first thought is...

break_done = false
ary.slice_when { |i, j| (break_done = i > j) unless break_done }.to_a
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]
like image 110
SteveTurczyn Avatar answered Sep 27 '22 17:09

SteveTurczyn


I'm not sure you'll find this more elegant, but it prevents the split-and-rejoin maneuver:

def split(ary)
  split_done = false
  ary.slice_when do |i, j|
    !split_done && (split_done = yield(i, j))
  end.to_a
end

Usage:

ary = [1, 3, 6, 7, 10, 9, 11, 13, 7, 24]    
split(ary){ |i, j| i > j }
#=> [[1, 3, 6, 7, 10], [9, 11, 13, 7, 24]]

Update:

Some may find this variant more readable. #chunk_while is the inverse of #split_when and then I just applied De Morgan's Law to the block.

def split(ary)
  split_done = false
  ary.chunk_while do |i, j|
    split_done || !(split_done = yield(i, j))
  end.to_a
end
like image 31
hoffm Avatar answered Sep 27 '22 17:09

hoffm