I want sort an array of String by alphabet but force one to the front of the array
p ['Home', 'Contact', 'Profile', 'Jobs', 'Privacy'].sort { |x, y|
if x == 'Privacy'
-1
else
x <=> y
end
}
Gives me ["Contact", "Home", "Jobs", "Privacy", "Profolio"]
instead of ["Privacy", "Contact", "Home", "Jobs", "Profolio"]
Eventually I want to sort an array of objects by name attribute
class Page
include Comparable
def <=> (other)
if self.name == 'Jobs'
-1
else
self.name <=> other.name
end
end
end
[Page.new('Contact'), Page.new('Home'), Page.new('Jobs')...].sort
However overwriting <=> in the Comparable class doesn't work as well
CodePudding user response:
You just need to specify the sort for both x
and y
:
["Home", "Contact", "Profile", "Jobs", "Privacy"].sort { |x, y|
if x == "Privacy" # "Privacy" <=> y
-1 # y is lower
elsif y == "Privacy" # x <=> "Privacy"
1 # "Privacy" is higher
else # the rest
x <=> y # use default sort order
end
}
# => ["Privacy", "Contact", "Home", "Jobs", "Profile"]
Same thing when using Comparable
:
class Page
include Comparable
def initialize name
@name = name
end
def <=> other
if @name == "Privacy"
-1
elsif other == "Privacy"
1
else
@name <=> other
end
end
end
>> [Page.new("Contact"), Page.new("Home"), Page.new("Jobs"), Page.new("Privacy")].sort
=> [#<Page:0x00007f8688624d30 @name="Privacy">, #<Page:0x00007f8688624ec0 @name="Contact">, #<Page:0x00007f8688624e20 @name="Home">, #<Page:0x00007f8688624da8 @name="Jobs">]
One other way I know:
>> ["Home", "Contact", "Profile", "Jobs", "Privacy"]
.partition{|i| i == "Privacy"}
.flat_map(&:sort)
=> ["Privacy", "Contact", "Home", "Jobs", "Profile"]
Ok, I was just wondering:
require "benchmark"
a = (1..100_000).map{ rand(36**8).to_s(36) } ["privacy"]
d = a.dup # because `.delete` modifies the array
Benchmark.bm(15) do |b|
b.report("Delete Sort") { [d.delete("privacy"), *d.sort] }
b.report("Partition Sort") { a.partition{|i| i == "privacy"}.flat_map(&:sort) }
b.report("Sort by String") { a.sort_by {|i| i == "privacy" ? "" : i } }
b.report("Sort") { a.sort {|x, y| if x == "privacy";-1; elsif y == "privacy";1; else x<=>y end } }
b.report("Sort by Array") { a.sort_by {|s| s == "privacy" ? [0, ""] : [1, s] } }
end
user system total real
Delete Sort 0.035848 0.000000 0.035848 ( 0.035891)
Partition Sort 0.045608 0.000044 0.045652 ( 0.045711)
Sort by String 0.068656 0.000018 0.068674 ( 0.068733)
Sort 0.307052 0.000000 0.307052 ( 0.307237)
Sort by Array 0.493351 0.000000 0.493351 ( 0.493606) # making arrays is expensive
CodePudding user response:
Try the following.
arr = ['Home', 'Contact', 'Profile', 'Jobs', 'Privacy']
arr.sort_by { |s| s == 'Privacy' ? [0, ''] : [1, s] }
#=> ["Privacy", "Contact", "Home", "Jobs", "Profile"].
Enumerable#sort_by uses Array#<=> to order arrays. See especially the third paragraph of the latter doc.
Note that the second element of [0, '']
is arbitrary. That's because it is never referenced by sort_by
. For example, [0, { a:1, b:2 }]
or [0]
would work as well.
It's probably faster to use sort_by
than sort
because sort_by
computes a two-element array for each element of the array only once, whereas sort
performs a comparable calculation twice for each pairwise ordering it considers.
Better, in a comment @Alex suggested:
arr.sort_by { |s| s == "Privacy" ? "" : s }
Alternatively, you could write:
a = arr.sort
[a.delete('Privacy'), *a]
#=> ["Privacy", "Contact", "Home", "Jobs", "Profile"]