Home > Blockchain >  How to Sort Method to Force an Element to the Front of the Array
How to Sort Method to Force an Element to the Front of the Array

Time:09-12

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"]
  •  Tags:  
  • ruby
  • Related