3

I have array that looks like

[nil, nil, nil, nil, 5, 6, 7, 8, 9, 10, 11, nil, nil, nil, nil, nil, 17, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 50, 51]

I want to "unflatten" it, so it'd look like

[[5,6,7,8,9,10,11],[17], [50,51]]

What would be the easiest way to achieve that?

1
  • 2
    @CarySwoveland You're right. Bad move by me. Will wait for more answers and mark most interesting Commented Aug 10, 2015 at 16:27

3 Answers 3

4
arr.slice_before(&:nil?).map(&:compact).reject(&:empty?)

Just out of curiosity, O(N):

arr.inject([[]]) do |memo, e| 
  e ? memo.last << e : (memo << [] unless memo.last.empty?)
  memo
end

Or, with modern ruby goodness:

arr.each_with_object([[]]) do |e, memo| 
  e && memo.last << e || (memo << [] unless memo.last.empty?)
end

And, totally out of curiosity (it’s a joke, please, do not use):

JSON.parse arr.to_json
              .gsub(/\D{2,}/, '],[')
              .gsub(/\A\D+/, '[[')
              .gsub(/\D+\z/, ']]')
Sign up to request clarification or add additional context in comments.

4 Comments

Nice one. I tried the second approach but couldn't get it into such an elegant form.
@CarySwoveland I added the most sophisticated method.
Here's another curiosity. If the elements are nil or integers, we could write arr.join(' ').strip.split(/\s{2,}/).map { |s| s.split(/\s/).map(&:to_i) }.
@CarySwoveland Good catch!
3

I thought about

a.slice_when { |e1, e2| e1 != e2 && e2.nil? }.map(&:compact)
# => [[5, 6, 7, 8, 9, 10, 11], [17], [50, 51]]

5 Comments

I love Enumerable#slice_when. I believe we got it with Ruby 2.2.
@CarySwoveland Yes, it is from 2.2.2.
There is Enumerable#chunk_while, which I proposed, coming in Ruby 2.3: bugs.ruby-lang.org/issues/10769.
@sawa, that will be a welcome addition. I've been thinking of proposing the addition of a method Array#difference. Could you give me a link to where you proposed chunk_by, so I can see the procedure?
Cary, I misstyped the name. It is chunk_while. The change is here. Cary and Arup: chunk_while is the negation of slice_when.
1

We can use Enumerable#chunk for this:

arr = [nil, nil, nil, nil, 5, 6, 7, 8, 9, 10, 11, nil, nil, nil, nil, nil, 17,
       nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 50, 51]

arr.chunk(&:nil?).reject(&:first).map(&:last)
  #=> [[5, 6, 7, 8, 9, 10, 11], [17], [50, 51]] 

The steps:

enum = arr.chunk(&:nil?)
  #=> #<Enumerator: #<Enumerator::Generator:0x007fd303042440>:each> 

We can examine the elements of the enumerator enum by converting it to an array:

enum.to_a
  #=> [[true,  [nil, nil, nil, nil]],
  #    [false, [5, 6, 7, 8, 9, 10, 11]],
  #    [true,  [nil, nil, nil, nil, nil]],
  #    [false, [17]],
  #    [true,  [nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil]],
  #    [false, [50, 51]]] 

Two more steps:

a = enum.reject(&:first)
  #=> [[false, [5, 6, 7, 8, 9, 10, 11]],
  #    [false, [17]],
  #    [false, [50, 51]]] 
a.map(&:last)
  #=> [[5, 6, 7, 8, 9, 10, 11], [17], [50, 51]] 

Edit: Thanks to @Stefan, today I learned something new about the way chunk handles nils. As he suggests, we can simplify this to:

arr.chunk { |e| e && false }.map(&:last)
  #=> [[5, 6, 7, 8, 9, 10, 11], [17], [50, 51]]

Well, maybe that's not exactly what he said, but it works just as well. (My variant is more of a head-scratcher.)

1 Comment

A slight variation: a.chunk { |e| e && true }.map(&:last)

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.