Forum: Ruby Sorting numbers as strings

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
D877dec36ab3027ff7c9016e83cc1a4f?d=identicon&s=25 Jack Bauer (realmadrid2727)
on 2009-05-18 16:26
I'm trying to sort some strings containing numbers. The strings
themselves can't be changed (they're items being pulled from a DB.) This
is an example of some of the things I need to sort. First is how I
wanted them sorted:

FastEthernet0/1
FastEthernet0/10
FastEthernet0/11
FastEthernet0/12
FastEthernet0/13
FastEthernet0/2
FastEthernet0/3
FastEthernet0/4
FastEthernet0/5
FastEthernet0/6
FastEthernet0/7
FastEthernet0/8
FastEthernet0/9
...

I need to get it like this:
FastEthernet0/1
FastEthernet0/2
FastEthernet0/3
FastEthernet0/4
FastEthernet0/5
FastEthernet0/6
FastEthernet0/7
FastEthernet0/8
FastEthernet0/9
FastEthernet0/10
FastEthernet0/11
FastEthernet0/12
FastEthernet0/13

Then they could turn into FastEthernet1/1, etc. Also, the name doesn't
really matter as it could have FastEthernet, another name, or none at
all and just be 0/1 or whatever.

I'm guessing something like a regex to strip non-numbers so
FastEthernet0/1 becomes 01 then sort numerically, but that wouldn't help
if I have FastEthernet0/1, GigabitEthernet0/1, and FastEthernet0/2 since
it would come out in an incorrect order (01, 01, 02 instead of 01, 02,
01 because of the F and G alphabetical sort.)

Any ideas of the best way to go about this?
D877dec36ab3027ff7c9016e83cc1a4f?d=identicon&s=25 Jack Bauer (realmadrid2727)
on 2009-05-18 16:27
Jack Bauer wrote:
> First is how I wanted them sorted:



Sorry I meant to say that the first version is how they're CURRENTLY
being sorted and the second version is how I WANT them sorted.
7cee9dd780723c5fa0f06a242338e57c?d=identicon&s=25 Douglas Seifert (Guest)
on 2009-05-18 16:39
(Received via mailing list)
Maybe something like this:

irb> a = ["FastEthernet0/1", "FastEthernet0/10", "FastEthernet0/11",
"FastEthernet0/12", "FastEthernet0/13", "FastEthernet0/2",
"FastEthernet0/3", "FastEthernet0/4", "FastEthernet0/5",
"FastEthernet0/6",
"FastEthernet0/7", "FastEthernet0/8", "FastEthernet0/9"]
irb> a.sort_by{|s| n,i = s.split('/'); [n, i.to_i]}
=> ["FastEthernet0/1", "FastEthernet0/2", "FastEthernet0/3",
"FastEthernet0/4", "FastEthernet0/5", "FastEthernet0/6",
"FastEthernet0/7",
"FastEthernet0/8", "FastEthernet0/9", "FastEthernet0/10",
"FastEthernet0/11", "FastEthernet0/12", "FastEthernet0/13"]
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-05-18 16:41
(Received via mailing list)
2009/5/18 Jack Bauer <realmadrid2727@yahoo.es>:
> FastEthernet0/2
> FastEthernet0/1
> FastEthernet0/12
> 01 because of the F and G alphabetical sort.)
>
> Any ideas of the best way to go about this?

16:39:42 Temp$ ruby19 srt.rb
FastEthernet0/1
FastEthernet0/2
FastEthernet0/3
FastEthernet0/4
FastEthernet0/5
FastEthernet0/6
FastEthernet0/7
FastEthernet0/8
FastEthernet0/9
FastEthernet0/10
FastEthernet0/11
FastEthernet0/12
FastEthernet0/13
16:40:24 Temp$ cat srt.rb
puts DATA.sort_by {|s| s.scan(/\d+/).map {|x| x.to_i} }
__END__
FastEthernet0/1
FastEthernet0/10
FastEthernet0/11
FastEthernet0/12
FastEthernet0/13
FastEthernet0/2
FastEthernet0/3
FastEthernet0/4
FastEthernet0/5
FastEthernet0/6
FastEthernet0/7
FastEthernet0/8
FastEthernet0/9
16:40:28 Temp$

Kind regards

robert
D877dec36ab3027ff7c9016e83cc1a4f?d=identicon&s=25 Jack Bauer (realmadrid2727)
on 2009-05-18 17:01
Douglas Seifert wrote:
> Maybe something like this:
> irb> a.sort_by{|s| n,i = s.split('/'); [n, i.to_i]}

Thanks to both of you guys. The one I quoted above looks similar to what
I was doing, I just thought it wasn't working because I'm using
pagination and it's ordering each page rather than the whole result and
then paginating.
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2009-05-18 17:38
(Received via mailing list)
On May 18, 2009, at 10:40 AM, Robert Klemme wrote:

>> FastEthernet0/12
>>
>> FastEthernet0/10
>> FastEthernet0/1 becomes 01 then sort numerically, but that wouldn't
> puts DATA.sort_by {|s| s.scan(/\d+/).map {|x| x.to_i} }
<snip/>
>
> Kind regards
>
> robert
> --
> remember.guy do |as, often| as.you_can - without end
> http://blog.rubybestpractices.com/

If you do need to consider things like "FastEthernet..." v.
"GigabitEthernet...", then perhaps you want something like this which
breaks the original string into digits and non-digits and converts the
digits to be integers.

puts DATA.sort_by {|s| s.scan(/\d+|\D+/).map {|x| x =~ /\d+/ ?
x.to_i : x } }

-Rob

Rob Biedenharn    http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-05-18 19:02
(Received via mailing list)
On 18.05.2009 17:37, Rob Biedenharn wrote:

> If you do need to consider things like "FastEthernet..." v.
> "GigabitEthernet...", then perhaps you want something like this which
> breaks the original string into digits and non-digits and converts the
> digits to be integers.
>
> puts DATA.sort_by {|s| s.scan(/\d+|\D+/).map {|x| x =~ /\d+/ ?
> x.to_i : x } }

Good point!  Here's an interesting variant:

puts DATA.sort_by {|s| s.scan(/(\d+)|\D+/).map { $1 ? $1.to_i : $& } }

Kind regards

  robert
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2009-05-19 02:55
(Received via mailing list)
On May 18, 2009, at 1:01 PM, Robert Klemme wrote:

>
> puts DATA.sort_by {|s| s.scan(/(\d+)|\D+/).map { $1 ? $1.to_i : $& } }
>
> Kind regards
>
>   robert
> --
> remember.guy do |as, often| as.you_can - without end
> http://blog.rubybestpractices.com/

Interesting, but "wrong" in that it doesn't sort the way the OP wanted.
Actually, since all the regexp applications have been applied by #scan
before the #map happens, the values of $1 and $& are effectively
constants and no sorting happens at all.

However, that did inspire me to make my version a little better.

puts DATA.sort_by {|s| s.scan(/(\d+)|(\D+)/).map {|(n,s)| s ||
n.to_i } }

I'd rather make the variables local than invoke the Perlish Regexp
globals (even if they did were assigned in the block the way you
expected).  It could be even more readable if (n,s) were replaced with
(digits,nondigits), but it looks OK to me with n and s.

-Rob

Rob Biedenharn    http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-05-19 08:15
(Received via mailing list)
On 19.05.2009 02:55, Rob Biedenharn wrote:
>> Good point!  Here's an interesting variant:
>>
>> puts DATA.sort_by {|s| s.scan(/(\d+)|\D+/).map { $1 ? $1.to_i : $& } }

> Interesting, but "wrong" in that it doesn't sort the way the OP wanted.
> Actually, since all the regexp applications have been applied by #scan
> before the #map happens, the values of $1 and $& are effectively
> constants and no sorting happens at all.

Aaargh!  Yes, you are completely right.

> However, that did inspire me to make my version a little better.
>
> puts DATA.sort_by {|s| s.scan(/(\d+)|(\D+)/).map {|(n,s)| s ||
> n.to_i } }
>
> I'd rather make the variables local than invoke the Perlish Regexp
> globals (even if they did were assigned in the block the way you
> expected).  It could be even more readable if (n,s) were replaced with
> (digits,nondigits), but it looks OK to me with n and s.

Absolutely agree, I try to use local variables whenever possible.
Although I recently learned that $1 etc. are local to the current stack
frame!  I did not knew that before and it certainly makes their use a
lot safer.

Thanks for catching my mistake!

Kind regards

  robert
Ca515d239bf09ffe970af1da4174eb03?d=identicon&s=25 Lars Christensen (Guest)
on 2009-05-19 14:14
(Received via mailing list)
On Mon, May 18, 2009 at 4:26 PM, Jack Bauer <realmadrid2727@yahoo.es>
wrote:
> I'm trying to sort some strings containing numbers.

http://sourcefrog.net/projects/natsort/natcmp.rb
D877dec36ab3027ff7c9016e83cc1a4f?d=identicon&s=25 Jack Bauer (realmadrid2727)
on 2009-05-19 17:23
You guys are great. I went with Bob's (pick one, hah) since it ended up
being moderately faster than the method I was originally using.
2ee728d97a0460dc285eeee54f4d093d?d=identicon&s=25 Johan Holmberg (Guest)
on 2009-05-19 23:51
(Received via mailing list)
On Tue, May 19, 2009 at 5:24 PM, Jack Bauer <realmadrid2727@yahoo.es>
wrote:
> You guys are great. I went with Bob's (pick one, hah) since it ended up
> being moderately faster than the method I was originally using.
> --

Here is a variant similar to the other solutions you already got:

    puts arr.sort_by {|s| s.split(/(\d+)/).each_with_index.map {|x,i|
i.odd? ? x.to_i : x }}

Works with Ruby 1.9.

/Johan Holmberg
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2009-05-20 06:25
(Received via mailing list)
On May 19, 2009, at 5:51 PM, Johan Holmberg wrote:
> i.odd? ? x.to_i : x }}
>
> Works with Ruby 1.9.
>
> /Johan Holmberg


Are you trying to say that you believe some part of a solution that
Jack already accepted won't work in Ruby 1.9? (Because they should
work fine.)

-Rob

Rob Biedenharn    http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
2ee728d97a0460dc285eeee54f4d093d?d=identicon&s=25 Johan Holmberg (Guest)
on 2009-05-20 08:05
(Received via mailing list)
On Wed, May 20, 2009 at 6:24 AM, Rob Biedenharn
<Rob@agileconsultingllc.com> wrote:
>>
>
> -Rob
>

No, not at all. I tried to say that I believed *my* variant only
worked in 1.9 (and maybe 1.8.7). (I should have said "Only works with
Ruby 1.9").

Sorry for the misunderstanding.

/Johan Holmberg
This topic is locked and can not be replied to.