tags:

views:

222

answers:

6

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?

A: 

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.

Draco Ater
A: 

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.

dimitko
+5  A: 

For the simplest, leanest solution, you could use the new method Enumerable#chunk, coming to you in the next Ruby 1.9.2 release but available now, even in Ruby 1.8.6 with my backports gem:

require 'backports'

a.chunk{|n| n}.map(&:first)
Marc-André Lafortune
I just tried this - excellent. Thanks!
Mike Woodhouse
+4  A: 
a.inject([]){|acc,i| acc.last == i ? acc : acc << i }
Mladen Jablanović
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...
Mike Woodhouse
+1  A: 

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}
glenn jackman
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!
Mike Woodhouse
+1  A: 

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

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