Ruby 標準ライブラリ set RDoc rake

Ruby 標準機能・ライブラリ確認シリーズ。
今回はset,RDoc,rake

参考

Set

Set が表すのは、集合。
配列と比べて、要素に順序関係がありません。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
require 'set'

## 配列の場合
a1 = [1,2]
a2 = [2,1]
a3 = [1,2]

p a1 == a2
# => false

p a1 == a3
# => true

# to_set を使うと変換して比較できる
p a1.to_set == a2.to_set
# => true

## Setでオブジェクトを作る場合
s1 = Set.new([1,2])
s2 = Set.new([2,1])
s3 = Set[2,1,3]

p s1 == s2
# => true

# 含む要素が違うので、比較すればfalse
p s1 == s3
# => false


## 要素の追加・削除
s1.add(4)
p s1
# => #<Set: {1, 2, 4}>

# 同じ値を入れても変化しない
s1.add(4)
p s1
# => #<Set: {1, 2, 4}>

# .add .delete は、常に変更されたオブジェクトが返ってくる
p s1.delete(2)
# => #<Set: {1, 4}>

p s1.delete(2)
# => #<Set: {1, 4}>

# .add? .delete? は、変化がない場合にnilを返す
p s1.delete?(1)
# => #<Set: {4}>

p s1.delete?(1)
# => nil

## 要素をまとめて追加・削除

s4 = Set.new(0..3)
s5 = Set.new(2..5)

# .merge .subtractはレシーバーのオブジェクト自体が変更されている
# 返り値として返すのは自身なので注意

# s4 と s5 の要素まとめる
p s4.merge(s5)
# => #<Set: {0, 1, 2, 3, 4, 5}>

# s4 の要素にあるものから s5 に含む要素を取り除く
p s4.subtract(s5)
# => #<Set: {0, 1}>

s5 = Set.new(0..3)
s6 = Set.new(2..5)

# | & - ^ は新たにSetオブジェクトを作る
p s5 | s6
# => #<Set: {0, 1, 2, 3, 4, 5}>
# .union も同じ動き

p s5 & s6
# => #<Set: {2, 3}>
# .intersection も同じ動き

p s5 - s6
# => #<Set: {0, 1}>
# .differencも同じ

p s5 ^ s6
# => #<Set: {4, 5, 0, 1}>
# 対称差という片側にしかないもの同士だけ取り出す

## 集合を複数に分ける
s7 = Set.new(1..10)

# 条件設定してSetで取り出す .divide
p s7.divide { |n| n.odd? }
# => #<Set: {#<Set: {1, 3, 5, 7, 9}>, #<Set: {2, 4, 6, 8, 10}>}>

# 条件設定してHashで取り出す .classify
p s7.classify { |n| n.odd? }
# => {true=>#<Set: {1, 3, 5, 7, 9}>, false=>#<Set: {2, 4, 6, 8, 10}>}

## 集合に関する上位集合や、部分集合の判定ができる いくつかメソッドがあるが、今回は抜粋
s8 = Set.new(["ニンジン","キュウリ","レタス","トマト"])
s9 = Set.new(["キュウリ","レタス"])

p s8.superset?(s9)
# => true

p s8.subset?(s9)
# => false
# s9 は、s8 の部分集合

## Enumerable モジュールの提供するメソッドが使用できる
sa = (1..10).to_set
p sa
# => #<Set: {[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}>

p sa.map {|n| n ** n}
# => [1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489, 10000000000]

p sa.select { |n| n.odd? }
# => [1, 3, 5, 7, 9]

(|の後ろは解釈が上手くいかないようで、シンタックスハイライトがおかしい)

配列で結果を受け取って並べ替えて一致するかなどを試したりしてきましたが、Set を使うのがベストそうでした。
複数の選択肢の組み合わせで、何か行うときの条件のチェックにも Set は有効そうです。
例えば、3 つのオプションを選択できる商品があった時、特定の組み合わせだけ特典が付くなどですね。

RDoc

ソースコードのコメントを RDoc のフォーマットで書くと変換してドキュメントを生成してくれる。
よく見かけてはいたけれども、準拠したコメントを書いていたわけでもないので確認。

tool.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# == rdoc 確認のためのToolクラス
# Author::octo
# Version::0.0.1
# License:MIT
class Tool
attr_accesster :mode

# === initialize
# 初期化
#
def initialize(mode)
@mode = mode
end

# === exec
# 処理の実行
#
def exec
end

end

上記のような、クラスを書いて rdoc tool.rb を実行。
./doc以下にページが生成される。

RDoc の形式でコメント書くようにしていった方いいのだろうなぁ。

Rake

Rakeを改めて学んでみると、使っていない機能が非常に多いことに驚きました。

基本

以下のように rakefile.rb を作成。

rakefile.rb
1
2
3
4
desc 'テスト用タスク1'
task :test_task_1 do
p `test_task_1`
end

以下のように実行する。

1
2
3
4
5
6
7
# タスクの一覧
$ rake -T
rake test_task_1 # テスト用タスク1

# タスクの実行
$ rake test_task_1
"test_task_1"

ここまでの機能は、認識はあったけれどもここからが本番。

依存関係

タスクは事前に行うものがあるなど、依存関係を定義できました。

rakefile.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
desc 'テスト用事前実行タスク1'
task :test_before_task_1 do
p 'test_before_task_1'
end

desc 'テスト用事前実行タスク2'
task :test_before_task_2 do
p 'test_before_task_2'
end

# :test_before_task_1, :test_before_task_2 を実行して test_task_1 を実行するように定義
desc 'テスト用タスク1'
task :test_task_1 => [:test_before_task_1, :test_before_task_2] do
p 'test_task_1'
end
1
2
3
4
5
6
7
8
9
$ rake -T
rake test_before_task_1 # テスト用事前実行タスク1
rake test_before_task_2 # テスト用事前実行タスク2
rake test_task_1 # テスト用タスク1

$ rake test_task_1
"test_before_task_1"
"test_before_task_2"
"test_task_1"

事前に実行してほしいタスクを定義できました。

並列実行タスク

multitaskを使うと、並列にタスクを実行してくれます。

rakefile.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
desc 'テスト用並列実行タスク1'
task :test_parallel_task_1 do
p 'start: test_parallel_task_1'
sleep 3
p 'end: test_parallel_task_1'
end

desc 'テスト用並列実行タスク2'
task :test_parallel_task_2 do
p 'start: test_parallel_task_2'
sleep 3
p 'end: test_parallel_task_2'
end

desc 'テスト用並列実行タスク3'
task :test_parallel_task_3 do
p 'start: test_parallel_task_3'
sleep 3
p 'end: test_parallel_task_3'
end

desc 'テスト用タスク1'
multitask :test_task_2 => [:test_parallel_task_1, :test_parallel_task_2, :test_parallel_task_3] do
p 'test_task_1'
end

実行すると以下のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ rake test_task_2
"start: test_parallel_task_3"
"start: test_parallel_task_1"
"start: test_parallel_task_2"
"end: test_parallel_task_2"
"end: test_parallel_task_3"
"end: test_parallel_task_1"
"test_task_1"

# -j を指定すると並列実行数を指定できる
$ rake test_task_2 -j 1
"start: test_parallel_task_3"
"end: test_parallel_task_3"
"start: test_parallel_task_2"
"end: test_parallel_task_2"
"start: test_parallel_task_1"
"end: test_parallel_task_1"
"test_task_1"

並列実行ができました。

ファイルタスク

rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 読み込み対象ファイル指定
FILE_NAME = 'src.txt'

desc 'ファイル作成'
file FILE_NAME do |task|
p 'create'
p task.needed?
open FILE_NAME, 'w' do |f|
f.puts 'hogehoge'
end
end

desc 'テスト用タスク1'
task :test_task_1 => [FILE_NAME] do
p 'test_task_1'
open FILE_NAME, 'r' do |f|
p f.read
end
end

実行すると、次のようになります。

1
2
3
4
5
6
7
8
9
10
11
# 1回目
$ rake test_task_1
"create"
true
"test_task_1"
"hogehoge\n"

# 2回目
$ rake test_task_1
"test_task_1"
"hogehoge\n"

file で設定したファイルが無ければ定義したタスクが呼ばれ、あればスキップします。

クリーンタスク

処理の途上で作成した最終的に不要なファイルなどを削除したいといった、掃除用のタスクです。

1
2
3
4
5
require 'rake/clean'

FILE_NAME = 'src.txt'

CLEAN.include(FILE_NAME)

require 'rake/clean'を記述すると次のようになります。

1
2
3
4
5
6
7
8
9
# rake clean clobberというタスクが増えている
$ rake -T
rake clean # Remove any temporary products
rake clobber # Remove any generated files
...
...

$ rake clean
# src.txt が削除される

ディレクトリタスク

rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
DIST_DIR = 'dist'
DIST_FILE = 'dist.txt'

desc '出力用ディレクトリ作成'
directory DIST_DIR

desc 'テスト用タスク1'
task :test_task_1 => [DIST_DIR] do
p 'test_task_1'
open "#{DIST_DIR}/#{DIST_FILE}", 'w' do |f|
f.puts "dist"
end
end

実行すると次のようになります。

1
2
3
4
5
6
7
8
$ rake -T
rake dist # 出力用ディレクトリ作成
rake test_task_1 # テスト用タスク1

$ rake test_task_1
mkdir -p dist
"test_task_1"
# dist/dist.txtが作成されている

ルール

主にファイルの記述を動的に定義できます。

rakefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
FILE_NAME = 'src.txt'

rule '.txt' do |task|
p 'create'
p task.name
p task.needed?
open task.name, 'w' do |f|
f.puts task.name
end
end

desc 'テスト用タスク1'
task :test_task_1 => [FILE_NAME] do
p 'test_task_1'
open FILE_NAME, 'r' do |f|
p f.read
end
end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# タスク一覧にはruleで定義したものは出てこない
$ rake -T
rake test_task_1 # テスト用タスク1

# 1回目
$ rake test_task_1
"create"
"src.txt"
true
"test_task_1"
"src.txt\n"

# 2回目
$ rake test_task_1
"test_task_1"
"src.txt\n"

namespace

こちらはよく見かけるnamespace

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace :test_tasks do

desc 'テスト用事前実行タスク1'
task :test_before_task_1 do
p 'test_before_task_1'
end

desc 'テスト用事前実行タスク2'
task :test_before_task_2 do
p 'test_before_task_2'
end

# :test_before_task_1, :test_before_task_2 を実行して test_task_1 を実行するように定義
desc 'テスト用タスク1'
task :test_task_1 => [:test_before_task_1, :test_before_task_2] do
p 'test_task_1'
end

end

タスク一覧を確認すると次のようになります。

1
2
3
4
5
6
7
8
9
10
11
# namespace使用前
$ rake -T
rake test_before_task_1 # テスト用事前実行タスク1
rake test_before_task_2 # テスト用事前実行タスク2
rake test_task_1 # テスト用タスク1

# namespace使用後
$ rake -T
rake test_tasks:test_before_task_1 # テスト用事前実行タスク1
rake test_tasks:test_before_task_2 # テスト用事前実行タスク2
rake test_tasks:test_task_1 # テスト用タスク1

タスクのグループ化がされるようになります。


rake タスクを Rails の開発中に使うことは、比較的多くありますが、知らなかったことが多数ありました。
やはり標準機能・ライブラリを確認していくのは大事ですね。

ではでは。