How to order Structs based on two fields

Hi, I've a struct like this:

  KK = Struct.new(:a,:b)

and some entries in an array:

  kk1=KK.new(0,10)
  #<struct KK a=0, b=10>

  kk2=KK.new(0,5)
  <struct KK a=0, b=5>

  kk3=KK.new(2,0)
  #<struct KK a=2, b=0>

  kk4 = KK.new(2,5)
  #<struct KK a=2, b=5>

  array = [kk3, kk2, kk1, kk4]

I need to order the array based:
- Elements with minor :a must be first.
- If two elements have same :a, then order based on higher :b.

The result should be:

  [kk1, kk2, kk4, kk3]

I expected that the following could work:

  array.sort_by{|entry| entry.a or entry.b}

but it generates:

   [kk1, kk2, kk3, kk4]

so obviously it does not work. I'm not very used to Enumerable.sort_by
method, any tip please?

Thanks a lot.

···

--
Iñaki Baz Castillo
<ibc@aliax.net>

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

···

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:

I expected that the following could work:

array.sort_by{|entry| entry.a or entry.b}

but it generates:

[kk1, kk2, kk3, kk4]

so obviously it does not work. I'm not very used to Enumerable.sort_by
method, any tip please?

--
Iñaki Baz Castillo
<ibc@aliax.net>

How about:

array.sort do |i, j|
  r = i.a <=> j.a
  case r
    when 0 r
    else -1 * (i.b <=> j.b)
  end
end

···

On Fri, Jul 1, 2011 at 4:22 PM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

Hi, I've a struct like this:

KK = Struct.new(:a,:b)

and some entries in an array:

kk1=KK.new(0,10)
#<struct KK a=0, b=10>

kk2=KK.new(0,5)
<struct KK a=0, b=5>

kk3=KK.new(2,0)
#<struct KK a=2, b=0>

kk4 = KK.new(2,5)
#<struct KK a=2, b=5>

array = [kk3, kk2, kk1, kk4]

I need to order the array based:
- Elements with minor :a must be first.
- If two elements have same :a, then order based on higher :b.

The result should be:

[kk1, kk2, kk4, kk3]

I expected that the following could work:

array.sort_by{|entry| entry.a or entry.b}

--
Anurag Priyam
http://about.me/yeban/

Try this: array.sort { |x,y| x.a == y.a ? x.b <=> y.b : x.a <=> y.a }
Or overide the <=> method in your struct.

···

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:
> I expected that the following could work:
>
> array.sort_by{|entry| entry.a or entry.b}
>
> but it generates:
>
> [kk1, kk2, kk3, kk4]
>
> so obviously it does not work. I'm not very used to Enumerable.sort_by
> method, any tip please?

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

--
Iñaki Baz Castillo
<ibc@aliax.net>

Structs return classes already (this is why you can inherit from them in the
form `class A < Struct.new(:a,:b)`). You can pass Struct.new a block, and it
will be evaluated within the context of the struct you are creating:

KK = Struct.new(:a,:b) do
  def <=>(kk)
    if a < kk.a
      -1
    elsif a > kk.a
      1
    else
      kk.b <=> b
    end
  end
end

kk1=KK.new(0,10)
kk2=KK.new(0,5)
kk3=KK.new(2,0)
kk4 = KK.new(2,5)
array = [kk3, kk2, kk1, kk4]

# Elements with minor :a must be first.
# If two elements have same :a, then order based on higher :b.
# The result should be:

[kk1, kk2, kk4, kk3] == array.sort # => true

···

On Fri, Jul 1, 2011 at 5:55 AM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:
> I expected that the following could work:
>
> array.sort_by{|entry| entry.a or entry.b}
>
> but it generates:
>
> [kk1, kk2, kk3, kk4]
>
> so obviously it does not work. I'm not very used to Enumerable.sort_by
> method, any tip please?

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

--
Iñaki Baz Castillo
<ibc@aliax.net>

You can do

array.sort_by {|e| [e.a, e.b]}

But you can of course define <=> for a Struct class as well

KK = Struct.new :a,:b do
  include Comparable

  def <=>(o)
    cmp = a <=> o.a
    cmp == 0 ? b <=> o.b : cmp
  end
end

Kind regards

robert

···

On Fri, Jul 1, 2011 at 12:55 PM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:

I expected that the following could work:

array.sort_by{|entry| entry.a or entry.b}

but it generates:

[kk1, kk2, kk3, kk4]

so obviously it does not work. I'm not very used to Enumerable.sort_by
method, any tip please?

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

You can have both:

KK = Struct.new(:a, :b) do
  include Enumerable

  def <=>
    #...
  end
end

Regards,
Florian

···

On Jul 1, 2011, at 12:55 PM, Iñaki Baz Castillo wrote:

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:

I expected that the following could work:

array.sort_by{|entry| entry.a or entry.b}

but it generates:

  [kk1, kk2, kk3, kk4]

so obviously it does not work. I'm not very used to Enumerable.sort_by
method, any tip please?

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

Sorry code must be array.sort { |x,y| x.a == y.a ? -(x.b <=> y.b) : x.a <=>
y.a }

···

2011/7/1 Gunther Diemant <g.diemant@gmx.net>

Try this: array.sort { |x,y| x.a == y.a ? x.b <=> y.b : x.a <=> y.a }
Or overide the <=> method in your struct.

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:
> I expected that the following could work:
>
> array.sort_by{|entry| entry.a or entry.b}
>
> but it generates:
>
> [kk1, kk2, kk3, kk4]
>
> so obviously it does not work. I'm not very used to Enumerable.sort_by
> method, any tip please?

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

--
Iñaki Baz Castillo
<ibc@aliax.net>

Really thanks to all for your responses, I will try them :slight_smile:

···

2011/7/1 Josh Cheek <josh.cheek@gmail.com>:

Structs return classes already (this is why you can inherit from them in the
form `class A < Struct.new(:a,:b)`). You can pass Struct.new a block, and it
will be evaluated within the context of the struct you are creating:

KK = Struct.new(:a,:b) do
def <=>(kk)
if a < kk.a
-1
elsif a > kk.a
1
else
kk.b <=> b
end
end
end

--
Iñaki Baz Castillo
<ibc@aliax.net>

Note that entries must be ordered by smaller :a and, in case of same
:a, by higher :b. So the last "e.b" should be changed to something
else. Trying it right now.

Thanks a lot.

···

2011/7/1 Robert Klemme <shortcutter@googlemail.com>:

array.sort_by {|e| [e.a, e.b]}

--
Iñaki Baz Castillo
<ibc@aliax.net>

You can do

array.sort_by {|e| [e.a, e.b]}

Nice. Little mistake (same as mine). Code should be array.sort_by { |e|
[e.a, -e.b] }

I expected that the following could work:

array.sort_by{|entry| entry.a or entry.b}

but it generates:

  [kk1, kk2, kk3, kk4]

so obviously it does not work. I'm not very used to Enumerable.sort_by
method, any tip please?

Maybe it would be better to use "class KK" rather than an struct, and
then define <=> method for KK class (and include Enumerable module)?

You can do

array.sort_by {|e| [e.a, e.b]}

But you can of course define <=> for a Struct class as well

KK = Struct.new :a,:b do
include Comparable

def <=>(o)
   cmp = a <=> o.a
   cmp == 0 ? b <=> o.b : cmp
end
end

Actually, there's a method to help with exactly this sort of thing (pun intended).

def <=>(o)
   (a <=> o.a).nonzero? || b <=> o.b
end

Take a look at Numeric#nonzero? and you'll find an example that's almost exactly this.

-Rob

Kind regards

robert

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Rob Biedenharn
Rob@AgileConsultingLLC.com http://AgileConsultingLLC.com/
rab@GaslightSoftware.com http://GaslightSoftware.com/

···

On Jul 1, 2011, at 7:18 AM, Robert Klemme wrote:

On Fri, Jul 1, 2011 at 12:55 PM, Iñaki Baz Castillo <ibc@aliax.net> > wrote:

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:

Yeah :slight_smile:

So I've lot of solutions right now, and all of them work :slight_smile:

I'll do some benchmarks and select the fastest one.

Really thanks to all, this community is really impresive.

···

2011/7/1 Gunther Diemant <g.diemant@gmx.net>:

Nice. Little mistake (same as mine). Code should be array.sort_by { |e|
[e.a, -e.b] }

--
Iñaki Baz Castillo
<ibc@aliax.net>

Oh, my mistake. I am sorry. So that would be (for numeric b):

array.sort_by {|e| [e.a, -e.b]}

And then

KK = Struct.new :a,:b do
include Comparable

def <=>(o)
   cmp = a <=> o.a
   cmp == 0 ? o.b <=> b : cmp
end
end

Cheers

robert

···

On Fri, Jul 1, 2011 at 1:22 PM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

2011/7/1 Robert Klemme <shortcutter@googlemail.com>:

array.sort_by {|e| [e.a, e.b]}

Note that entries must be ordered by smaller :a and, in case of same
:a, by higher :b. So the last "e.b" should be changed to something
else. Trying it right now.

--
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/

Very cool :slight_smile:

···

2011/7/1 Rob Biedenharn <Rob@agileconsultingllc.com>:

Actually, there's a method to help with exactly this sort of thing (pun
intended).

def <=>(o)
(a <=> o.a).nonzero? || b <=> o.b
end

Take a look at Numeric#nonzero? and you'll find an example that's almost
exactly this.

--
Iñaki Baz Castillo
<ibc@aliax.net>

I recommend selecting the most straightforward one. Are there any solutions
where you look at them and they are so obvious as to seem mundane? Later
when you look at this code, you'll be appreciative of code like that.

···

On Fri, Jul 1, 2011 at 6:30 AM, Iñaki Baz Castillo <ibc@aliax.net> wrote:

2011/7/1 Gunther Diemant <g.diemant@gmx.net>:
> Nice. Little mistake (same as mine). Code should be array.sort_by { |e|
> [e.a, -e.b] }

Yeah :slight_smile:

So I've lot of solutions right now, and all of them work :slight_smile:

I'll do some benchmarks and select the fastest one.

Really thanks to all, this community is really impresive.

--
Iñaki Baz Castillo
<ibc@aliax.net>

But it would be:

def <=>(o)
   (a <=> o.a).nonzero? || o.b <=> b
end

:slight_smile:

···

2011/7/1 Iñaki Baz Castillo <ibc@aliax.net>:

def <=>(o)
(a <=> o.a).nonzero? || b <=> o.b
end

Take a look at Numeric#nonzero? and you'll find an example that's almost
exactly this.

Very cool :slight_smile:

--
Iñaki Baz Castillo
<ibc@aliax.net>

The efficiency depends on how expensive it is to compute the key of the elements. If the computation is non-trivial, then Array#sort_by would be much faster because it caches the keys before the sort so that no unnecessary computation is done (i.e., the key of each element is computed exactly once).

If, like it is in your case, the computation only involves several Fixnum comparisons, Array#sort_by will most likely be slower as it needs to allocate (cache) the key set prior to the actual sorting.

For example, there should be a difference if the accessors themselves are expensive. Something like:

   def a
     sleep 0.01
     @a
   end

As a side note, this idiom used by Array#sort_by is known as the Schwartzian transform in the Perl world.

···

On 7/1/2011 7:30 AM, Iñaki Baz Castillo wrote:

I'll do some benchmarks and select the fastest one.

Sure, I'll also consider it :wink:

···

2011/7/1 Josh Cheek <josh.cheek@gmail.com>:

I recommend selecting the most straightforward one. Are there any solutions
where you look at them and they are so obvious as to seem mundane? Later
when you look at this code, you'll be appreciative of code like that.

--
Iñaki Baz Castillo
<ibc@aliax.net>

Thanks. However in my case the class is a Struct and I add no
complexity to accessor. In fact I just use:

  KK = Struct.new(:a, :b)
  kk1 = KK.new(10,20)

···

2011/7/1 Su Zhang <su.comp.lang.ruby@gmail.com>:

The efficiency depends on how expensive it is to compute the key of the
elements. If the computation is non-trivial, then Array#sort_by would be
much faster because it caches the keys before the sort so that no
unnecessary computation is done (i.e., the key of each element is computed
exactly once).

If, like it is in your case, the computation only involves several Fixnum
comparisons, Array#sort_by will most likely be slower as it needs to
allocate (cache) the key set prior to the actual sorting.

For example, there should be a difference if the accessors themselves are
expensive. Something like:

def a
sleep 0.01
@a
end

--
Iñaki Baz Castillo
<ibc@aliax.net>