宮水の日記

宮水の日記

主に書評や資格取得について記事を書いています。

RubyでEnum, sort_by, find_indexを使った複数条件下のソート

お仕事で、以下のようなソートを実装する機会がありました。

例として教科書を題材にします。

Text model

教科書は、教科コード、4種類のとあるタイプと、応用/基礎の情報を持っています。

class Text

  enum type: {
    a_type: 'a_type',
    b_type: 'b_type',
    c_type: 'c_type',
  }

  enum subject: { japanese: 'japanese', math: 'math', english: 'english'}

  # 基礎/応用はfalse/trueで表現しています
...
end

仕様

これから実装するのは以下の仕様です。

  • 最初に教科でソートする。
  • 同じ教科の中でtypeごとにソート
  • それでも一緒なら「基礎/応用」でソートする

例えば、
```
{subject: 'english', type: 'c_type', support_advanced: true},
{subject: 'english', type: 'a_type', support_advanced: true},
{subject: 'english', type: 'b_type', support_advanced: true},
{subject: 'english', type: 'b_type', support_advanced: false},
{subject: 'math', type: 'c_type', support_advanced: true},
{subject: 'japanese', type: 'b_type', support_advanced: false},
{subject: 'english', type: 'c_type', support_advanced: true},
```

みたいなデータがきたら、
```
{subject: 'english', type: 'a_type', support_advanced: true},
{subject: 'english', type: 'b_type', support_advanced: true},
{subject: 'english', type: 'b_type', support_advanced: false},
{subject: 'english', type: 'c_type', support_advanced: true},
{subject: 'english', type: 'c_type', support_advanced: false},
{subject: 'math', type: 'c_type', support_advanced: true},
{subject: 'japanese', type: 'b_type', support_advanced: false},

```
みたいにしたいです。

困りポイント

  • enumvalueが数値じゃないので困った...。

結論

こうやって書きます。

  subjects = %w[english, math, japanese]
  types = %w[a_type, b_type, c_type]
  sort_support_advanced = [true, false]

    texts.sort_by do |text|
      [
        subjects.find_index { |subject| text.subject == subject },
        types.find_index { |type| text.type == type },
        sort_support_advanced.find_index { |support_advanced| text.support_advanced == support_advanced }
      ]
    end

解説

配列の番号で順番を指定できます。

find_indexは条件に一致する最初の要素の位置を返します。

  subjects = %w[english, math, japanese]
  types = %w[a_type, b_type, c_type]
  sort_support_advanced = [true, false]

そしてsort_by に、配列のindex(位置情報)を渡してあげると、配列で並べた通りに並べ替えてくれます。

    texts.sort_by do |text|
      [subjects.find_index { |subject| text.subject == subject } ]
    end

ソートした中でさらに別の条件でソートしたい場合、以下のように書くと上から順に並び替えてくれます。

    texts.sort_by do |text|
      [
        subjects.find_index { |subject| text.subject == subject },
        types.find_index { |type| text.type == type },
        sort_support_advanced.find_index { |support_advanced| text.support_advanced == support_advanced }
      ]
    end


以上です。