0

I have an array "sizes" that look like this:

[#<OPTIONVALUE ID: 5, NAME: "M">,
#<OPTIONVALUE ID: 6, NAME: "M/L">, 
#<OPTIONVALUE ID: 7, NAME: "XS/S">]

Consider the values of attribute NAME. The array is sorted: M, M/L, XS/S.

But the sort order should look like this:

@sizes_sort_order = ["XS", "XS/S", "S", "S/M", "M", "M/L", "L", "L/XL", "XL"]

applied to the former array the order of the elements should look like this:

[#<SPREE::OPTIONVALUE ID: 7, NAME: "XS/S">,
#<SPREE::OPTIONVALUE ID: 5, NAME: "M">,
#<SPREE::OPTIONVALUE ID: 6, NAME: "M/L">]

def sizes
  @sizes ||= grouped_option_values_by_option_type[Spree::OptionType.find_by!(name: 'size')]
  @sizes_sort_order = ["XS", "XS/S", "S", "S/M", "M", "M/L", "L", "L/XL", "XL"]
  @sizes.map { # sort after @size_sort_order }
end

How can i achieve to get the elements in the array sorted after @sizes_sort_order ?

1

3 Answers 3

3

You can use Enumerable#sort_by

my_array.sort_by {|x| @sizes_sort_order.index(x.name) }
Sign up to request clarification or add additional context in comments.

Comments

2

You can include the Comparablemodule to get a natural sort for the objects.

http://ruby-doc.org/core-2.2.3/Comparable.html

The Comparable mixin is used by classes whose objects may be ordered. The class must define the <=> operator, which compares the receiver against another object, returning -1, 0, or +1 depending on whether the receiver is less than, equal to, or greater than the other object.

class Size
  include Comparable

  SIZES = ["XS", "XS/S", "S", "S/M", "M", "M/L", "L", "L/XL", "XL"]

  attr_reader :name
  def initialize(id, name)
    @id = id
    @name = name
  end

  def <=>(b)
    SIZES.index(name) <=> SIZES.index(b.name)
  end
end

a = Size.new(5, 'M')
b = Size.new(6, 'M/L')
c = Size.new(7, 'XS/S')

print [a, b, c].sort

[#<Size:0x007f8e910458e0 @id=7, @name="XS/S">, #<Size:0x007f8e910459a8 @id=5, @name="M">, #<Size:0x007f8e91045930 @id=6, @name="M/L">]

Comments

1

This approach involves more steps than ones that employ sort or sort_by, but for larger arrays it may be faster, as no sorting--which is relatively expensive--is involved.

Code

def reorder_by_size(instances, size_order)
  instances.each_with_object({}) { |inst, h| h.update(inst.name=>inst) }.
    values_at(*(size_order & (instances.map { |s| s.name })))
end

Example

First let's create an array of instances of

class Sizes
  attr_reader :name

  def initialize(id, name)
    @id = id
    @name = name
  end
end

like so:

instances = [Sizes.new(5,'M'), Sizes.new(6,'M/L'), Sizes.new(7, 'XS/S')]
  #=> [#<Sizes:0x007fa66a955ac0 @id=5, @name="M">,
  #    #<Sizes:0x007fa66a955a70 @id=6, @name="M/L">,
  #    #<Sizes:0x007fa66a955a20 @id=7, @name="XS/S">]

Then

reorder_by_size(instances, @sizes_sort_order)
 #=> [#<Sizes:0x007fa66a01dfc0 @id=7, @name="XS/S">,
 #    #<Sizes:0x007fa66a86fdb8 @id=5, @name="M">,
 #    #<Sizes:0x007fa66a8404f0 @id=6, @name="M/L">] 

Explanation

For instances as defined for the example, first create an array of sizes in the desired order:

names = @sizes_sort_order & (instances.map { |s| s.name })
  #=> ["XS/S", "M", "M/L"]

Important: the doc for Array#& states, "The order is preserved from the original array.".

Now we can create the desired reordering without sorting, by creating a hash with keys the sizes and values the instances, then use Hash#values_at to extract the instances in the desired order.

instances.each_with_object({}) { |inst, h|
  h.update(inst.name=>inst) }.values_at(*names)
  #=> [#<Sizes:0x007fa66a01dfc0 @id=7, @name="XS/S">,
  #    #<Sizes:0x007fa66a86fdb8 @id=5, @name="M">,
  #    #<Sizes:0x007fa66a8404f0 @id=6, @name="M/L">] 

The last operation involves the following two steps.

h = instances.each_with_object({}) { |inst, h| h.update(inst.name=>inst) }
  #=> {"M" => #<Sizes:0x007fa66a955ac0 @id=5, @name="M">,
  #    "M/L" => #<Sizes:0x007fa66a955a70 @id=6, @name="M/L">,
  #    "XS/S" => #<Sizes:0x007fa66a955a20 @id=7, @name="XS/S">} 
h.values_at(*names)
  #=> h.values_at(*["XS/S", "M", "M/L"])
  #=> h.values_at("XS/S", "M", "M/L")
  #=> [#<Sizes:0x007fa66a955a20 @id=7, @name="XS/S">,
  #    #<Sizes:0x007fa66a955ac0 @id=5, @name="M">,
  #    #<Sizes:0x007fa66a955a70 @id=6, @name="M/L">] 

1 Comment

wow, thanks a lot Cary! For this case i stick to the sort_by solution but for larger arrays I'll choose yours. Awesome explanation by the way.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.