# Compact or uniq that takes a block like sort?

**URL:** <https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239>\
**Category:** Ruby\
**Created:** [January 7, 2008, 1:00pm UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239 "2008-01-07T13:00:36Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![Bil\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/848f3c/32.png) [@Bil\_K](https://www.ruby-forum.com/u/Bil_K)\
**Post date:** [January 7, 2008, 1:00pm UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239/1 "2008-01-07T13:00:36Z")

</div>

Hi,

I’m looking for an elegant way to do the following:

Given an sorted array of (X,Y) pairs where Y is strictly  
increasing, i.e., no repeated Ys.

Compact pairs with repeated Xs, keeping the pair with  
the highest Y value.

E.g.,

require ‘test/unit’

class TestConsolidation \< Test::Unit::TestCase  
def test\_should\_remove\_repeated\_Xs\_but\_save\_one\_with\_highest\_Y  
initial = [[ 1, 12], [3, 15], [3, 17], [3, 22], [7, 45]  
]  
desired = [[ 1, 12], [3, 22], [7, 45] ]  
assert\_equal desired, initial.consolidate  
end  
end

class Array  
def consolidate  
# Too embarrassed to show current hack.  
end  
end

My least surprise inclination had me researching #uniq and #compact  
to see if they took a block like #sort.

Thanks,

---

<div class="post-metadata">

**Author:** ![Bil\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/848f3c/32.png) [@Bil\_K](https://www.ruby-forum.com/u/Bil_K)\
**Post date:** [January 7, 2008, 1:15pm UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239/2 "2008-01-07T13:15:54Z")

</div>

2008/1/7, Bil K. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> I’m looking for an elegant way to do the following:  
> (…)

Bil, this makes the test pass:

class Array  
def consolidate  
Hash[\*flatten].sort  
end  
end

Regards,  
Pit

---

<div class="post-metadata">

**Author:** ![Bil\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/848f3c/32.png) [@Bil\_K](https://www.ruby-forum.com/u/Bil_K)\
**Post date:** [January 7, 2008, 4:46pm UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239/3 "2008-01-07T16:46:00Z")

</div>

2008/1/7, Bil K. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid):

> E.g.,
> 
> class Array  
> def consolidate  
> # Too embarrassed to show current hack.  
> end  
> end
> 
> My least surprise inclination had me researching #uniq and #compact  
> to see if they took a block like #sort.

This is easy with #inject:

ar.inject([]) do |res, (x,y)|  
if res.empty? || res.last[0] != x  
res \<\< [x,y]  
else  
res.last[1] = y  
end  
res  
end

Cheers

robert

---

<div class="post-metadata">

**Author:** ![Bil\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/848f3c/32.png) [@Bil\_K](https://www.ruby-forum.com/u/Bil_K)\
**Post date:** [January 7, 2008, 6:20pm UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239/4 "2008-01-07T18:20:48Z")

</div>

Pit C. wrote:

> end  
> That certainly is elegant!

Maybe Bil wanted to preserve the monotonicity?

class Array  
def consolidate  
Hash[\*flatten].sort\_by {|x,y| y}  
end  
end

p [[5,1] , [3,2] ].consolidate # ==\> [[5, 1], [3, 2]]

---

<div class="post-metadata">

**Author:** ![Bil\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/848f3c/32.png) [@Bil\_K](https://www.ruby-forum.com/u/Bil_K)\
**Post date:** [January 12, 2008, 5:15pm UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239/5 "2008-01-12T17:15:39Z")

</div>

On Jan 7, 10:45 am, Robert K. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid) wrote:

> This is easy with #inject:

Hey, thanks everyone for the help!

I would have replied sooner, but my USENET feed dropped your replies  
and I only now thought to look at [groups.google.com](http://groups.google.com).

Thanks again,

---

<div class="post-metadata">

**Author:** ![Bil\_K](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/b/848f3c/32.png) [@Bil\_K](https://www.ruby-forum.com/u/Bil_K)\
**Post date:** [January 8, 2008, 11:56am UTC](https://www.ruby-forum.com/t/compact-or-uniq-that-takes-a-block-like-sort/125239/6 "2008-01-08T11:56:21Z")

</div>

A more general solution:

require ‘set’

class Array  
def uniq(&blk)  
blk ||= lambda {|x| x}  
already\_seen = Set.new  
uniq\_array = []

self.each\_with\_index do |value, i|  
x = blk.call(value)  
unless already\_seen.include? x  
already\_seen \<\< x  
uniq\_array \<\< value  
end  
end  
uniq\_array  
end  
end

and

class Array  
def consolidate  
reverse.uniq {|a| a.first}.reverse  
end  
end

Of course, the value that you keep for uniq is kinda arbitrary.

Dan

- Hide quoted text -
