はじめに:列挙型について
C言語には列挙型(enum)があります。列挙型を使うと0から始まる連番を定数として管理できます。
#include <stdio.h>
enum
{
E_COLOR_RED, // 0
E_COLOR_YELLOW, // 1
E_COLOR_GREEN, // 2
};
int main()
{
printf("E_COLOR_RED = %d\n", E_COLOR_RED);
printf("E_COLOR_YELLOW = %d\n", E_COLOR_YELLOW);
printf("E_COLOR_GREEN = %d\n", E_COLOR_GREEN);
return 0;
}
実行結果
E_COLOR_RED = 0
E_COLOR_YELLOW = 1
E_COLOR_GREEN = 2
REDとYELLOWの間にBLUEを追加した場合、自動的に採番し直されます。
#include <stdio.h>
enum
{
E_COLOR_RED,
E_COLOR_BLUE, // Add
E_COLOR_YELLOW,
E_COLOR_GREEN,
};
int main()
{
printf("E_COLOR_RED = %d\n", E_COLOR_RED);
printf("E_COLOR_BLUE = %d\n", E_COLOR_BLUE);
printf("E_COLOR_YELLOW = %d\n", E_COLOR_YELLOW);
printf("E_COLOR_GREEN = %d\n", E_COLOR_GREEN);
return 0;
}
実行結果(1、2だったYELLOWとGREENはそれぞれ2、3に変わる)
E_COLOR_RED = 0
E_COLOR_BLUE = 1
E_COLOR_YELLOW = 2
E_COLOR_GREEN = 3
Javaにも列挙型はある
列挙型はC言語のみならず、いろいろな言語で実装されています。たとえば以下はJavaで列挙型を使うコード例です。
public class Sample {
enum Color {
RED, // 0
YELLOW, // 1
GREEN // 2
}
public static void main(String[] args) {
System.out.println("Color.RED = " + Color.RED.ordinal());
System.out.println("Color.YELLOW = " + Color.YELLOW.ordinal());
System.out.println("Color.GREEN = " + Color.GREEN.ordinal());
}
}
実行結果
Color.RED = 0
Color.YELLOW = 1
Color.GREEN = 2
C# にもある
C# でもほぼ同じコードが書けます。
using System;
public class Sample
{
enum Color
{
RED, // 0
YELLOW, // 1
GREEN // 2
}
public static void Main(string[] args)
{
Console.WriteLine("Color.RED = {0}", (int)Color.RED);
Console.WriteLine("Color.YELLOW = {0}", (int)Color.YELLOW);
Console.WriteLine("Color.GREEN = {0}", (int)Color.GREEN);
}
}
実行結果
Color.RED = 0
Color.YELLOW = 1
Color.GREEN = 2
でもRubyには列挙型がない!
しかし、Rubyには列挙型がありません。もしやるとするなら、愚直に定数を定義することになります。
RED = 0
YELLOW = 1
GREEN = 2
puts "RED = #{RED}"
puts "YELLOW = #{YELLOW}"
puts "GREEN = #{GREEN}"
しかし、これだとBLUEを追加したときにYELLOWとGREENの修正も必要になります。
RED = 0
+BLUE = 1
-YELLOW = 1
-GREEN = 2
+YELLOW = 2
+GREEN = 3
次のように工夫すれば修正量を少し減らすことができますが、あまりスマートではありません。
RED = 0
BLUE = RED + 1
YELLOW = BLUE + 1
GREEN = YELLOW + 1
そこで、enumっぽく連番を定義できるgemを作ってみた
そこで、Rubyでもenumっぽく連番を定義できるgemを作ってみました。
seq_as_enum というgemです。
インストール方法はふつうのgemと同じです。
gem install seq_as_enum
このgemはこんなふうに使います。
require 'seq_as_enum'
class Sample
extend SeqAsEnum
# seq_as_enumメソッドで連番を定義
seq_as_enum :RED, :YELLOW, :GREEN
# すると、以下のような定数が定義される
# RED = 0
# YELLOW = 1
# GREEN = 2
# 上で定義した連番を使用する
def self.main
puts "RED = #{RED}"
puts "YELLOW = #{YELLOW}"
puts "GREEN = #{GREEN}"
end
end
Sample.main
実行結果
RED = 0
YELLOW = 1
GREEN = 2
このgemを使えば、REDとYELLOWの間にBLUEを追加するときも必要最小限の修正で済みます。
require 'seq_as_enum'
class Sample
extend SeqAsEnum
# REDとYELLOWの間にBLUEを追加する
seq_as_enum :RED, :BLUE, :YELLOW, :GREEN
def self.main
puts "RED = #{RED}"
puts "BLUE = #{BLUE}"
puts "YELLOW = #{YELLOW}"
puts "GREEN = #{GREEN}"
end
end
Sample.main
実行結果(1、2だったYELLOWとGREENはそれぞれ2、3に変わる)
RED = 0
BLUE = 1
YELLOW = 2
GREEN = 3
応用的な使い方
このgemにはいくつか応用的な使い方があります。
初期値を変える
0以外の値からスタートしたい場合はinit
オプションを使います。
seq_as_enum :RED, :YELLOW, :GREEN, init: 10
# 以下の定数を定義したのと同じ
# RED = 10
# YELLOW = 11
# GREEN = 12
文字列で連番を作る
A, B, Cのような連番を作ることもできます。
seq_as_enum :RED, :YELLOW, :GREEN, init: 'A'
# 以下の定数を定義したのと同じ
# RED = 'A'
# YELLOW = 'B'
# GREEN = 'C'
prefixを付ける
prefix
オプションでprefixを付けることもできます。
seq_as_enum :RED, :YELLOW, :GREEN, prefix: :COLOR
# 以下の定数を定義したのと同じ
# COLOR_RED = 0
# COLOR_YELLOW = 1
# COLOR_GREEN = 2
Javaのenumのように型.定数の形式にする
data_as
オプションを付けると、Javaのenumのように型.定数の形式で連番が定義できます。
seq_as_enum :RED, :YELLOW, :GREEN, data_as: :Color
# 型.定数の形式で連番が定義できる
Color.RED #=> 0
Color.YELLOW #=> 1
Color.GREEN #=> 2
利用例
以下のコードは、HTMLをパースしてテーブル内の各列の内容を画面に出力するコード例です。
各列には以下のようなデータが入力されているものとします。
- 0番目 = 名前
- 1番目 = 住所
- 2番目 = 電話番号
ここでは td[0]
td[1]
td[2]
のように列番号をハードコードする代わりに、seq_as_gem で定義した定数を使って td[NAME]
td[ADDRESS]
td[PHONE]
のようにして各列の値を読み取っています。
require 'seq_as_enum'
require 'nokogiri'
# パース対象のHTML
html = <<HTML
<table>
<thead>
<tr>
<th>名前</th><th>住所</th><th>電話</th>
</tr>
</thead>
<tbody>
<tr>
<td>Alice</td><td>東京</td><td>090-1234-5678</td>
</tr>
</tbody>
</table>
HTML
class TableParser
extend SeqAsEnum
# 連番を定数として定義
seq_as_enum :NAME, :ADDRESS, :PHONE
def self.parse(html)
doc = Nokogiri::HTML(html)
doc.css('tbody tr').each do |tr|
td = tr.css('td').map(&:text)
# td[0]、td[1]、td[2]のように列番号をハードコードする代わりに定数を使う
puts "name: #{td[NAME]}, address: #{td[ADDRESS]}, phone: #{td[PHONE]}"
end
end
end
TableParser.parse(html)
#=> name: Alice, address: 東京, phone: 090-1234-5678
まとめ
というわけで、この記事ではRubyで列挙型(enum)っぽく連番を定義できる、seq_as_enum というgemを紹介してみました。
「なんでRubyには列挙型がないんだー!」と困っていた方はこちらのgemを試してみてください。