Is There a Natural_Sort_By Method for Ruby

Is there a natural_sort_by method for Ruby?

As long as files are always named "file #", you could do

files.sort_by{|f| f.name.split(" ")[1].to_i }

This splits on the space, and grabs the number to do the sorting.

natural sort an array of hashes in Ruby

First of all, your my_array is JavaScript/JSON so I'll assume that you really have this:

my_array = [
{"id" => "some-server-1", "foo" => "bar"},
{"id" => "some-server-2", "foo" => "bat"},
{"id" => "some-server-10", "foo" => "baz"}
]

Then you just need to sort_by the numeric suffix of the 'id' values:

my_array.sort_by { |e| e['id'].sub(/^some-server-/, '').to_i }

If the "some-server-" prefixes aren't always "some-server-" then you could try something like this:

my_array.sort_by { |e| e['id'].scan(/\D+|\d+/).map { |x| x =~ /\d/ ? x.to_i : x } }

That would split the 'id' values into numeric and non-numeric pieces, convert the numeric pieces to integers, and then compare the mixed string/integers arrays using the Array <=> operator (which compares component-wise); this will work as long as the numeric and non-numeric components always match up. This approach would handle this:

my_array = [
{"id" => "some-server-1", "foo" => "bar"},
{"id" => "xxx-10", "foo" => "baz"}
]

but not this:

my_array = [
{"id" => "11-pancakes-23", "foo" => "baz"},
{"id" => "some-server-1", "foo" => "bar"}
]

If you need to handle this last case then you'd need to compare the arrays entry-by-entry by hand and adjust the comparison based on what you have. You could still get some of the advantages of the sort_by Schwartzian Transform with something like this (not very well tested code):

class NaturalCmp
include Comparable
attr_accessor :chunks

def initialize(s)
@chunks = s.scan(/\D+|\d+/).map { |x| x =~ /\d/ ? x.to_i : x }
end

def <=>(other)
i = 0
@chunks.inject(0) do |cmp, e|
oe = other.chunks[i]
i += 1
if(cmp == 0)
cmp = e.class == oe.class \
? e <=> oe \
: e.to_s <=> oe.to_s
end
cmp
end
end
end

my_array.sort_by { |e| NaturalCmp.new(e['id']) }

The basic idea here is to push the comparison noise off to another class to keep the sort_by from degenerating into an incomprehensible mess. Then we use the same scanning as before to break the strings into pieces and implement the array <=> comparator by hand. If we have two things of the same class then we let that class's <=> deal with it otherwise we force both components to String and compare them as such. And we only care about the first non-0 result.

How to sort file names in ruby?

Eric's answer is great way of doing a Natural Sort Order for the digits only in file names. Works if all file names have the same prefix.

If you want to add a second element (for example, file names that do not have digits in them) you can great a multi-element sort_by be creating a list:

filenames = ["file1.txt", "file11.txt", "file12.txt", "file2.txt", "file3.txt","file.txt", "File.txt"]
filenames.sort_by{ |name| [name[/\d+/].to_i, name] }
=> ["File.txt", "file.txt", "file1.txt", "file2.txt", "file3.txt", "file11.txt", "file12.txt"]

The two element of the sort_by implements:

  1. Integer value of digits if any are found with a regex name[/\d+/].to_i then
  2. Name if no digits or same digits name.

More robustly, you can split the entire string by digits and convert each to an int:

> "abc123def456gh".split(/(\d+)/).map{ |e| Integer(e) rescue e}
=> ["abc", 123, "def", 456, "gh"]

So your Natural Sort becomes:

arr.sort_by{ |s| s.split(/(\d+)/).map{ |e| Integer(e) rescue e}}

So now names and numbers (even multiples names and numbers) are handled correctly:

> arr = ["file1.txt", "file11.txt", "file12.txt", "file2.txt", "file3.txt", "gfile10.txt", "gfile1.txt", "gfile.txt", "file.txt", "afile.txt","afile10.txt","afile2.txt" ]
> arr.sort_by{ |s| s.split(/(\d+)/).map{ |e| Integer(e) rescue e}}
=> ["afile2.txt", "afile10.txt", "afile.txt", "file1.txt", "file2.txt", "file3.txt", "file11.txt", "file12.txt", "file.txt", "gfile1.txt", "gfile10.txt", "gfile.txt"]

Ruby how to override ruby sort_by

class Array
def natural_sort_by
sort_by{|x| MyModule.naturalize_str(yield(x))}
end
end

or

class Array
def natural_sort_by &block
sort_by{|x| MyModule.naturalize_str(block.call(x))}
end
end

Is there a method to limit/clamp a number?

Ruby 2.4.0 introduces Comparable#clamp:

523.clamp(0, 100)        #=> 100

How to sort chapter numbers like 1.1.1, 1.2.1, 1.2.46, etc.?

In Ruby, Arrays are ordered lexicographically, so the easiest way would be to convert these into Arrays and then sort them:

chapters = %w[1.1.1 1.1.2 1.2.1 1.3.1 1.3.2 1.3.3 10.42.64]

chapters.sort_by {|chapter| chapter.split('.').map(&:to_i) }
# => ["1.1.1", "1.1.2", "1.2.1", "1.3.1", "1.3.2", "1.3.3", "10.42.64"]

Of course, the real solution would be to use objects instead of slugging around arrays of strings of numbers. After all, Ruby is an object-oriented language, not an arrays-of-strings-of-numbers-oriented language:

class ChapterNumber
include Comparable

def initialize(*nums)
self.nums = nums
end

def <=>(other)
nums <=> other.nums
end

def to_s
nums.join('.')
end

alias_method :inspect, :to_s

protected

attr_reader :nums

private

attr_writer :nums
end

chapters = [ChapterNumber.new(1, 1, 1), ChapterNumber.new(1, 1, 2),
ChapterNumber.new(1, 2, 1), ChapterNumber.new(1, 3, 1),
ChapterNumber.new(1, 3, 2), ChapterNumber.new(1, 3, 3),
ChapterNumber.new(10, 42, 64)]

chapters.sort
# => [1.1.1, 1.1.2, 1.2.1, 1.3.1, 1.3.2, 1.3.3, 10.42.64]

ruby aasm gem: event name change, but permitted method still ask for old event name

You will need to restart your rails console/server once you have made changes to the state machine events.



Related Topics



Leave a reply



Submit