Sorting a struct based on a reference array in ruby

Viewed 71

I have a struct definition as

Player = Struct.new(:name, :level)

And a sample array

levels = [ 'level-1','level-2','beta','alpha','level4','level7']

I want to sort the struct in a way that the members appear in the increasing order of the levels mentioned eg

[#<struct Player name="Ryan", level="beta">,
 #<struct Player name="Chris", level="alpha">,
  #<struct Player name="Tom", level="level4">,
 #<struct Player name="Edward", level="level1">,
  #<struct Player name="Drew", level="level7">]

becomes

 #[<struct Player name="Edward", level="level1">,
 #<struct Player name="Ryan", level="beta">,
 #<struct Player name="Chris", level="alpha">,
  #<struct Player name="Tom", level="level4">,
  #<struct Player name="Drew", level="level7">]

How do I go about doing that in ruby.

I tried

arr.sort_by { |e| [ levels.index(e.level)] }

ArgumentError: comparison of Array with Array failed
from (pry):79:in `sort_by'

2 Answers

You've almost got it. The result of the sort_by block should be what you want to compare against. You want to use the index in levels, not an Array which contains the index in levels.

arr.sort_by { |e| levels.index(e.level) }

And make sure you've got your level strings correct. You may wish to use constants to avoid typos.

Here is a solution that has a computational complexity close to O(n), "close" because hash lookups are not quite constant time.

We are given:

Player = Struct.new(:name, :level)

arr = [["Tom", "level4"], ["Ryan", "beta"], ["Chris", "alpha"],
       ["Drew", "level4"]].map { |a| Player.new(*a) }
  #=> [#<struct Player name="Tom",   level="level4">,
  #    #<struct Player name="Ryan",  level="beta">,
  #    #<struct Player name="Chris", level="alpha">,
  #    #<struct Player name="Drew",  level="level4">]

Notice that Tom and Drew have the same level.

levels = ['level-1','level-2','beta','alpha','level4','level7']

Then

    arr.group_by(&:level).values_at(*levels).flatten.compact
  #=>[#<struct Player name="Ryan",  level="beta">,
  #   #<struct Player name="Chris", level="alpha">,
  #   #<struct Player name="Tom",   level="level4">,
  #   #<struct Player name="Drew",  level="level4">]

The steps are as follows:

h = arr.group_by(&:level)
  #=> {"level4"=>[#<struct Player name="Tom",   level="level4">,
  #               #<struct Player name="Drew",  level="level4">],
  #    "beta"  =>[#<struct Player name="Ryan",  level="beta">],
  #    "alpha" =>[#<struct Player name="Chris", level="alpha">]}
a = h.values_at(*levels)
  #=> [nil,
  #    nil,
  #    [#<struct Player name="Ryan",  level="beta">],
  #    [#<struct Player name="Chris", level="alpha">],
  #    [#<struct Player name="Tom",   level="level4">,
  #     #<struct Player name="Drew",  level="level4">],
  #    nil]
b = a.compact
  #=>  [[#<struct Player name="Ryan",  level="beta">],
  #     [#<struct Player name="Chris", level="alpha">],
  #     [#<struct Player name="Tom",   level="level4">,
  #      #<struct Player name="Drew",  level="level4">]]
c = b.flatten
  #=>  [#<struct Player name="Ryan",  level="beta">,
  #     #<struct Player name="Chris", level="alpha">,
  #     #<struct Player name="Tom",   level="level4">,
  #     #<struct Player name="Drew",  level="level4">]
Related