新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

学習記録:Ruby、Hadoop

[Ruby] Rubyの概要、言語仕様

内容:1章 Rubyの概要

Rubyについて
# クラスベース
class Sample
  def say
    puts 'Hello, World!'
  end
end

sample = Sample.new
sample.say

# 手続き型
puts 'Hello, World!'
  • 全てがオブジェクト
    • プリミティブ型は存在しない
    • 1やtrue/falseなどもオブジェクトで、メソッドを呼び出すことが可能
p 1.to_s
p true.to_s
    • true/falseやnilと言ったオブジェクト(疑似変数)にもクラスが存在
    • Objectクラスの子孫になる(Objectクラスにも親クラスが存在する)
Rubyを使ってみよう
#hello.rb
puts 'hello, world!'
  • 上記のプログラムをrubyコマンドで実行
% ruby hello.rb
hello, world!
% ruby -e 'puts "hello, world!"'
hello, world!
  • irb:REPLと呼ばれる対話形式での動作確認コマンド
$ irb
irb(main):001:0> puts 'hello, world!'
hello, world!
=> nil
コーディングスタイル
  • クラス・モジュール名
    • 先頭は大文字でなければならない
    • キャメルケース(複合語はひと綴りにして要素の先頭を大文字にする)が推奨
  • メソッド
    • 全て小文字で書く
    • 区切りに"_"を用いるスネークケースが慣例
  • 真偽を返すメソッド
    • 末尾に?を付ける
    • (他言語ではis_xxxなどと書くことが多い)
  • 変数名
    • メソッド名と同様、小文字のスネークケースが慣例
  • コードブロック
    • do...endも{..}も書ける
    • 慣習では複数行の記述はdo...endを、一行で書ききってしまう場合は{...}を使用
    • (厳密には{...}とdo...endでは優先度が異なるので使い分けを意識する必要)
# 複数行
1.upto(2) do |n|
  puts n
end

# 1行
1.upto(2) { |n| puts n }

2章 Rubyの基礎

Hello, Ruby.
# hello.rb
def hello(names)
  names.each do |name|
    puts "HELLO, #{name.upcase}"
  end
end

rubies = ['MRI', 'jruby', 'rubinius']

hello(rubies) # HELLO, MRI HELLO, jruby...と表示
  • def メソッド名(仮引数...) ...end
  • 変数名 = 式
  • 文字列はシングル・ダブルクォートのどちらでも表現できる
    • ただし、ダブルクォートでは#{...}で囲んだRubyのコードを埋め込むことが出来る
    • ex) "result: #{2 + 2}"は"result: 4"という文字列になる
    • upcaseメソッドは文字列を大文字にする
  • メソッドの呼び出し方
  • レシーバ.メソッド
    • names.eachもメソッド呼び出し
    • Rubyでは、配列の繰り返しやループにはfor文よりこのようなメソッド呼び出しを良く使う
  • メソッド名(実引数...) do |変数名| ... end
    • doとendで囲まれた処理はブロックと呼ばれ、メソッドの呼び出し時に一つだけ渡すことのできる引数の一種
    • ブロックを渡されたメソッドは、受け取ったブロックを任意のタイミングで実行できる
    • eachメソッドは、配列の要素数と同じ数だけブロックを実行する
    • ||で囲まれた変数は、ブロックの仮引数
    • eachメソッドに渡したブロックは、配列の要素を引数として受け取る
    • ブロックは繰り返しだけでなく、様々な用途に使用
File.open "README.md" do |file|
  puts file.read
end
  • helloメソッドはレシーバなしで呼び出せている
    • 定義式の外(トップレベ)で書かれたメソッドは、グローバルなサブルーチンのように使用できる
    • つまり、
      • レシーバの記述をしない
      • (いわゆる関数のように)どこからでもグローバルに呼び出し可能
  • メソッドの戻り値
    • メソッドで最後に評価された式の値が戻り値になる
    • returnを書く必要なし
    • returnを書くとそこで処理が終わる(というか終わらせたい場合に使う)、戻り値も返せる
  • 行は改行で区切る
    • 1行に複数の式を書きたい場合にはセミコロンを使えるが、普通は書かない
変数と定数
  • ローカル変数
    • 先頭が小文字か"_"で始まる
    • ブロック、メソッド定義、クラス・モジュール定義、トップレベルのいずれかをスコープとする
foo = 'foo in top level...'

def display_foo
  puts foo
end

puts foo # foo in top level...と表示
display_foo  # NameErrorが発生
  • 定数
    • 大文字アルファベットで始まる必要
    • メソッド内での定数の定義は不可能
    • 再代入は警告が出るが出来てしまうので注意
FOO = 'foo'

puts FOO

FOO = 'hoge'

puts FOO

FOO = 'uwaa'

puts FOO
$ ruby const.rb 
foo
const.rb:5: warning: already initialized constant FOO
const.rb:1: warning: previous definition of FOO was here
hoge
const.rb:9: warning: already initialized constant FOO
const.rb:5: warning: previous definition of FOO was here
uwaa
条件分岐と真偽値
  • ifに与えた条件式が真なら、ifの中の処理が実行される
    • 文字列や数値を条件式で与えることも可能
    • falseとnil以外の全ての値は真として扱われる
    • elsifでつなぐ
n = 2

if n.zero?
  puts '0...'
elsif n.even?
  puts 'odd...'
elsif n.odd?
  puts 'odd...'
end
クラス
class Ruler
  def length=(val)
    @length = val
  end

  def length
    @length
  end
end

ruler = Ruler.new

ruler.length = 50
puts ruler.length
  • Rubyではインスタンス変数に代入を行うメソッド名にはイコールを付ける
    • こうすることでruler.length = 50のように代入のように記述できる
    • 実際はattr_accessor: インスタンス変数名 と記述すると、Ruler#length=とRuler#lengthが自動で定義される
class Ruler
  attr_accessor :length
end

ruler = Ruler.new

ruler.length = 50
puts ruler.length
class Ruler
  attr_accessor :length
  def display_length
    # Ruler#lengthの戻り値を出力する                                                                                                   
    puts length
  end
  def set_default_length
    # length=30ではlengthというローカル変数が設定されてしまう                                                                          
    self.length = 30
  end
end

ruler = Ruler.new

ruler.set_default_length
ruler.display_length
  • クラス変数
    • クラスとそのインスタンスをスコープにした変数
    • 先頭が"@@"で始まる
  • 継承
class Parent
  def hello
    puts 'hello, parent!'
  end
end

class Child < Parent
  def hello
    super
    puts 'hello, child!'
  end
end

c = Child.new
c.hello

# hello, parent!とhello, child!が順に表示される
モジュール
  • インスタンスできないクラスのようなもの
    • moduleキーワードを用いて定義
    • モジュール内には別のモジュールやクラスを定義できるので、名前空間として利用可能
    • ネストしたクラスやモジュールは"::"を用いて参照可能
主な組み込みクラス
  • 数値(Numeric)
    • 整数(整数、16進数、2進数、8進数)
      • "_"で区切ることも可能、0x/0b/0で○進数表記も
    • 浮動小数点数
  • 文字列(String)
    • シングルクォートまたはダブルクォートで記述
    • ""内では#{...}を用いた式展開が可能
    • 複数行の文字列の表現には<<を用いたピアドキュメントを使う
  • シンボル(Symbol)
    • 先頭にコロンを付けた文字はシンボルリテラルになる
  • 配列(Array)
    • ブラケット([])の中に要素をカンマ区切りで記述
    • 参照にはhoge[0]のように0から始まる添字を指定
    • 添字に負の値を指定すると、末尾から逆順に数えた要素を返す
    • 配列のサイズを超えた位置に要素を格納すると、間に自動でnilが入る
  • Hash
    • 連想配列
    • {キー => 要素} と記述
    • 参照にはcolors['blue']のようにキーを指定
    • キーには読み書きのしやすさからシンボルがよく使われる
#ruby 1.9以前
colors = {:red => 'ff0000', :green => '00ff00'}
colors[:red]

#ruby 1.9以降
colors = {red: 'ff0000', green: '00ff00'}
  • 範囲(Range)
    • 「1から5まで」のような範囲を表す範囲オブジェクトが存在
    • 1..5 -> 末尾の5を範囲に含む
    • 1...5 -> 末尾の5を範囲に含まない
    • 日時や文字列も指定可能
  • 正規表現(Regexp)
    • /pattern/のように両端をスラッシュで囲んで表現
    • パターンマッチには===や=~を使う
    • ===は文字列がマッチするか真偽値で返す
    • =~は最初にマッチした位置を整数で返す
  • 手続きオブジェクト(Proc)
    • 関数をオブジェクトとして表現
    • Proc.newにブロックを渡すことで手続きオブジェクトを生成
    • Proc#callを呼び出すと手続きが実行される
様々な代入式
  • 多重代入
a, b = 1, 2
a # => 1
b # => 2
    • 右辺が左辺より多い場合、余った要素は無視される
    • 配列からの多重代入
a,b = [1,2,3]
a # => 1
b # => 2
    • 変数の交換にも使える
a,b = b,a
例外
  • 例外処理にはrescue節を使用
begin
  1 / 0 #ZeroDivisionError                                                                                                             
rescue ZeroDivisionError
  puts 'zero割...'
end
外部ファイルの読み込み
  • requireで指定ファイルを読み込む
    • クラスやモジュール単位でファイルを分ける場合に使う
require '/path/to/library.rb'   # /path/to/library.rbを読み込む
require './library'                    # rubyの実行ディレクトリから探す 
require 'library'                       # $LOAD_PATHから探す
組み込み変数
  • 疑似変数
    • self, true, false, nil
    • __FILE__, __LINE__, __ENCODING__
  • 組み込み変数
    • グローバル変数がいくつか組み込みで用意
    • $stdout, $> :標準出力
    • $SAFE:現在のスレッドのセーフレベル
    • $VERBOSE:冗長メッセージフラグ
    • etc...

[Hadoop] MapReduceの活用例

内容:2章 MapReduceアプリケーションの活用例

  • MapReduceでどんな処理が実現できるのか?
    • 単純化すると、1) Mapフェーズでデータにキーを付与
    • 2) Shuffleで同じキーの付いたデータをまとめる
    • 3) Reduceで同じキーの付いたデータを処理
ブログのアクセス数集計
  • 問題設定
    • ブログサービスで、ブログごとの閲覧社数をカウントしたい
    • 閲覧要求はWebサーバに一日単位でアクセスログに記録
    • ログには閲覧されたブログのIDと要求元のIPアドレス、正しい要求かなどが含まれる
    • 注意点1:正しい閲覧要求のみをカウントする(存在しないブログの閲覧要求など)
    • 注意点2:重複なく1度だけカウントする(同じブログの複数記事を行き来しても、カウントは1度のみ)
  • アプローチ
    • 不正な閲覧要求のフィルタリング
    • 同じブログに対する同じIPアドレスからのリクエストは重複除去
  • MapReduceでの実現
    • 1) アクセスログにフィルタリングと重複除去を実行
    • 2) カウントを実行してブログごとのアクセス回数を解析
  1. アクセスログにフィルタリングと重複除去を実行
    • Mapフェーズでアクセスログの閲覧要求1件1件に対してmap関数でフィルタリングを行う
    • 「閲覧を要求されたブログIDと要求元IPの組み合わせ」をキー、「1」をバリューとする
    • (バリューを1にしておくと、あるユーザが同じブログに何回アクセスしたかをカウントできる)
    • Shuffleフェースでは同じキーをもつデータは一つに集約されるので、重複が除去される
    • Reduceフェーズでは、入力されたキーを分解し、<ブログID>, のキーバリューを出力
  2. カウントを実行してブログごとのアクセス回数を解析
    • Mapフェーズでは先ほどのジョブで出力されたブログIDとIPアドレスのキーバリューが出力
    • Shuffleフェーズでキーごと=ブログIDごとにアクセスしたIPアドレスが集約される
    • Reduceでは集約に含まれるIPアドレスの数をカウントすれば完成
似ている人を見つける
  • 問題設定
    • SNSサイトで、ユーザのプロフィールや行動記録など、ユーザごとの属性情報を記録
    • これを利用して、あるユーザと同じ傾向を持つユーザを「友達」として推薦
  • アプローチ
    • 特徴を計算するためのアルゴリズムが存在するものとする
    • 全てのユーザの特徴を計算後、同じ特徴を持つユーザごとにグループを作成
    • グループ内から友達を推薦
  • MapReduceでの実現方法
    • <特徴>,<ユーザ>のキーバリューを作成
    • Shuffleフェーズで特徴ごとにグループ化される
    • グループ内から友達を推薦
検索エンジンインデックスの作成
  • 問題設定
    • 特定の文書に含まれている単語を検索キーワードとして、文書を検索
    • あらかじめ文書から単語を抽出し、その単語を含む文章リストを記録したインデックスを作成しておく
  • アプローチ
    • キーワードとそのキーワードを含む文書のリストが対になったデータを作れば良い
    • まず各文書に含まれるキーワードを抽出
    • 各キーワードと文書の対を生成
    • キーワードごとにそれらをマージ
  • MapReduceでの実現方法
    • Mapフェーズで、文書に含まれるキーワードごとに、キーワードと文書の対を生成
    • 文書からキーワードの抽出には、形態素解析アルゴリズムなどを使用
    • <キーワード>, <文書>のキーバリューを出力
    • Shuffleでキーワードごとに集約
画像データの分散処理
  • map関数だけでも単純な分散処理が可能
  • 問題設定
    • あるWebサービスでは、日本中のあらゆる場所を360度撮影し、画像と地図データをマッピング
    • 人やナンバープレートなどの個人情報にモザイクをかける必要
    • 画商処理自体には識別する技術があるとして、写真データが膨大なので並列処理を行いたい
  • アプローチ
    • 各画像に施す処理は同じ
    • 集約処理は不要
    • とりあえず処理系をたくさん並べればOK
  • MapReduceでの実現方法
    • map関数は、入力となるデータ間で独立に処理が可能で、各データに対して同じ処理を行うのが得意
    • map関数一回の呼び出しにつき画像データを1つ渡し、画像処理を実行
まとめとポイント
  • map関数とreduce関数を適切に使い分ける
    • mapは処理対象データのひとつひとつが渡される、順序は制御できない
    • フィルタリングなどの集約処理に先立った前処理に向いている
    • reduceはキーにひもづいた複数のデータが渡される、キーでソートされている
    • あるまとまりをもったデータを必要とする処理や順序を考慮する必要がある処理を実現
    • ある分類ごとの合計や平均値計算などの集約処理や、時系列データの処理向き
  • 集約単位や分類の軸を考慮し、中間データのキーを選択する
    • Shuffleでキーごとにまとめられるので
  • 複雑な処理は複数の単純なMapReduceジョブに分割する
  • mapだけで処理が完結するか検討