11

Ruby 1.8.6

I have an array containing numerical values. I want to reduce it such that sequences of the same value are reduced to a single instance of that value.

So I want

a = [1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3]

to reduce to

[1, 2, 3, 2, 3]

As you can see, Array#uniq won't work in this case.

I have the following, which works:

(a.size - 1).downto(1) { |i| a[i] = nil if a[i - 1] == a[i] }

Can anyone come up with something less ugly?

6 Answers 6

25

For the simplest, leanest solution, you could use the method Enumerable#chunk:

a.chunk(&:itself).map(&:first)

The itself method is Ruby 2.2+. Use {|n| n} if you are stuck in an older Ruby, or my backports gems. It was introduced in Ruby 1.9.2. If you're unlucky enough to be using older rubies, you could use my backports gem and require 'backports/1.9.2/enumerable/chunk'.

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

3 Comments

You don't need x.first, you can simply say .map(&:first)
@DarkMouse You did in 1.8.6 :-) actually. This was relevant 4 years ago, but not much today. I've updated my answer
As of Ruby 2.2, a.chunk(&:itself).map(&:first) works and looks a little nicer.
5
a.inject([]){|acc,i| acc.last == i ? acc : acc << i }

1 Comment

I got to this one as well, once I remembered #inject. It's certainly cleaner than the attempt I posted and it's a lot more "Rubyish". The #chunk thing is hard to beat, though...
1

I can think only of this

a.each_with_index{|item,i| a[i] = nil if a[i] == a[i+1] }.compact

but it is more or less the same.

Comments

1

Unless you are very concerned with the speed that block will calculate at, I would suggest you simply add this line to the end of your block to get the desired output:

a.compact!

That will just remove all the nil elements you introduced to the array earlier (the would-be duplicates), forming your desired output: [1, 2, 3, 2, 3]

If you want another algorithm, here is something far uglier than yours. :-)

require "pp"

a = [1, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 2, 3, 3, 3]

i = 0

while i < a.size do
  e = a[i]
  j = i

  begin
    j += 1
  end while e == a[j]

  for k in i+1..j-1 do
    a[k] = nil
  end

  i = j
end

pp a
a.compact!
pp a

Gives you the output:

[1, nil, nil, 2, nil, 3, nil, nil, nil, 2, nil, nil, 3, nil, nil]
[1, 2, 3, 2, 3]

In my opinion, your code is fine. Just add the a.compact! call and you are sorted.

Comments

1

another solution:

acc = [a[0]]
a.each_cons(2) {|x,y| acc << y if x != y}

or

a.each_cons(2).inject([a[0]]) {|acc, (x,y)| x == y ? acc : acc << y}

1 Comment

I seem to forget about require 'enumerator', which is often a mistake. I bet I could find a dozen places in my code where I should have used #each_cons. Thanks!
1

If the numbers are all single digits 0-9: a.join.squeeze('0-9').each_char.to_a should work.

1 Comment

In my case they aren't, but you've given me the name for the method if I monkey-patch: Array#squeeze

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.