10

I have an array and I'd like to select only the elements between two specified values.

For example, I have an array that looks like this:

a = ["don't want", "don't want", "Start", "want", "want", "Stop", "don't want", "Start", "want", "Stop", "don't want"]

I want to call a method on the a array that captures the elements between "Start" and "Stop" (that includes the "Start" and "Stop" elements). The resulting array should look like this:

[["Start", "want", "want", "Stop"], ["Start", "want", "Stop"]]

I could not find a method like this, so I tried writing one:

class Array
  def group_by_start_and_stop(start = "Start", stop = "Stop")
    main_array = []
    sub_array = []

    group_this_element = false

    each do |e|
      group_this_element = true if e == start

      sub_array << e if group_this_element

      if group_this_element and e == stop
        main_array << sub_array
        sub_array = []
        group_this_element = false
      end
    end

    main_array
  end
end

This works, but it seems perhaps unnecessarily verbose to me. So I have two questions: Does a similar method already exist? If not, are there ways to make my group_by_start_and_stop method better?

2
  • What about nested delimiters (['Start', 'want', 'Start', 'want', 'Stop', 'Stop'])? Is that a Start/Stop inside another Start/Stop, overlapping intervals, or invalid? Commented Nov 10, 2012 at 19:22
  • In that case, I think I'd want everything between the "Start" and the first "Stop". Then it would start again. So, given your example, I'd want the result to be: [["Start", "want", "Start", "want", "Stop"]] Commented Nov 10, 2012 at 19:30

5 Answers 5

7

That's the perfect example where a flip-flop is useful!

a.select {|i| true if (i=="Start")..(i=="Stop")}.slice_before("Start").to_a

Not super known feature, but pretty cool nonetheless! Use it together with slice_before, and you get exactly what you want!

Sign up to request clarification or add additional context in comments.

4 Comments

Greate decision! a.select {|i| true if (i=="Start")..(i=="Stop") } would be enough
I like that condition, didn't know that was possible! still, the result is not the expected result, You still have to present it as an array of arrays.
OK! I fixed that with slice_before.
Thanks for all of the suggestions. I found that this one worked very well.
1
a.each_with_index.select{|s, i| s.eql?("Start") or s.eql?("Stop")}
                 .map{|s, i| i}
                 .each_slice(2)
                 .map{|s, f| a[s..f]}

Comments

1
a.inject [] do |m, e|
  if e == 'Start'
    @state = true
    m << []
  end
  m.last << e if @state
  @state = false if e == 'Stop'
  m
end

Comments

0
a.join('^').scan(/Start.*?Stop/).map { |x| x.split('^') }
=> [["Start", "want", "want", "Stop"], ["Start", "want", "Stop"]]

The symbol ^ seems to be rare so maybe it won't mix with another original symbols.

Comments

0

:dummy below is to ensure that the first chunk of the return from slice_before is what you do not want.

p [pres = :dummy, *a].slice_before{|e|
  prev, pres = pres, e
  prev == "Stop" or pres == "Start"
}
.select.with_index{|_, i| i.odd?}
# => => [["Start", "want", "want", "Stop"], ["Start", "want", "Stop"]]

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.