Pi Horse
Pi Horse

Reputation: 2430

Sorting an array in Ruby (Special Case)

I have an array in Ruby which has values as follows

xs = %w(2.0.0.1
2.0.0.6
2.0.1.10
2.0.1.5
2.0.0.8)

and so on. I want to sort the array such that the final result should be something like this :

ys = %w(2.0.0.1
2.0.0.6
2.0.0.8
2.0.1.5
2.0.1.10)

I have tried using the array.sort function, but it places "2.0.1.10" before "2.0.1.5". I am not sure why that happens

Upvotes: 2

Views: 848

Answers (2)

tokland
tokland

Reputation: 67860

Using a Schwartzian transform (Enumerable#sort_by), and taking advantage of the lexicographical order defined by an array of integers (Array#<=>):

sorted_ips = ips.sort_by { |ip| ip.split(".").map(&:to_i) }

Can you please explain a bit more elaborately

  1. You cannot compare strings containing numbers: "2" > "1", yes, but "11" < "2" because strings are compared lexicographically, like words in a dictionary. Therefore, you must convert the ip into something than can be compared (array of integers): ip.split(".").map(&:to_i). For example "1.2.10.3" is converted to [1, 2, 10, 3]. Let's call this transformation f.

  2. You could now use Enumerable#sort: ips.sort { |ip1, ip2| f(ip1) <=> f(ip2) }, but check always if the higher abstraction Enumerable#sort_by can be used instead. In this case: ips.sort_by { |ip| f(ip) }. You can read it as "take the ips and sort them by the order defined by the f mapping".

Upvotes: 18

LPD
LPD

Reputation: 2883

Split your data into chunks by splitting on '.'. There is no standard function to do it as such so you need to write a custom sort to perform this.

And the behaviour you said about 2.0.1.10 before 2.0.1.5 is expected because it is taking the data as strings and doing ASCII comparisons, leading to the result that you see.

arr1 = "2.0.0.1".split('.')
arr2 = "2.0.0.6".split('.')

Compare both arr1 and arr2 element by element, for all the data in your input.

Upvotes: 1

Related Questions