default eye-catch image.

Redshift テーブル設計のベストプラクティス

どのようにテーブル設計するとパフォーマンスを得られるか. 公式がベストプラクティスを用意している. Redshiftのベストプラクティスが先にあってER図が後なのか、 ER図に対してベストプラクティスを適用するのか、 実際は行ったり来たりするようなイメージ. ER図とは別に何を考慮すべきなのか読み進めていく. [arst_toc tag=\"h4\"] ソートキー テーブル作成時に1つ以上の列をソートキーとして設定できる. 設定するとソートキーに準じたソート順でディスクに格納される. ソートキーに関するベストプラクティスは以下の通り. 最新のデータを得たい場合はタイムスタンプ列をソートキーにする. 1つの列に対してwhere句による範囲指定or等価指定をおこなう場合はその列をソートキーにする. ディメンションテーブルを頻繁に結合する場合は結合キーをソートキーにする. ファクトテーブルを中心にディメンションテーブルが4つある構造があるとする. ファクトテーブルにはディメンションテーブルのPKが入り関連している. また、ファクトテーブルに日付カラムがあり、常に最新のレコードが欲しいとする. ベストプラクティスによると、 各テーブルの各カラムに以下のようにソートキーを設定する. 分散スタイル クエリの実行を複数のクラスタ(コンピューティングノード、スライス)で実行するために、 それらに1)データを配信して 2)計算させて 3)結合、集計する というステップが必要になる。 最後のステップ3を達成するために、データの再び配ることが必要となる。 全体として最適となるように、1),2),3)の効率を高める必要があるが、 あらゆるデータ、条件について同じ戦略で最高の効率を得ることはできず、 設計者が戦略を指定するパラメタとなっている。 この戦略を分散スタイルと呼んでいる. 分散スタイルとして以下の3通りが用意されている. 各々だけ読むとさっぱり意味がわからないが、結局のところ再分散のコストをいかに減らすか、 というところに着目すると合点がいく. EVEN 分散 特定の列に含まれている値と関係なくラウンドロビンで複数のスライス間で行を分散させる. テーブルが結合に関与していない場合や、キー分散、ALL分散のどちらが良いかわからない場合に指定する. キー分散 キー分散のキーとは結合キーのこと. 特定の列に含まれている値に従って複数のスライスに行を分散させる.キーが同じということは「同じデータ」であり「同じデータ」達を同じスライスに分散させる意味がある. 共通の列からの一致する値が同じスライスにまとめられるよう努力する. ALL 分散 テーブル全体のコピーが全てのノードに分散される. EVEN分散、キー分散によってテーブルの一部が各ノードに配置されているときにALL分散を行うことでテーブルが関与しているあらゆる結合で全ての行が確実にコロケーションされる. 何が嬉しいのかわかりづらいが、EVEN分散やキー分散では、クエリ実行に伴って、再び必要なデータをコピーする(再分散する)必要が発生する可能性が生じる.ALL分散であればその可能性がなくなる. AUTO 分散 (デフォルト) テーブルデータのサイズに基づいて最適な分散スタイルを割り当てる. まず小さなテーブルにALL分散を設定し,テーブルが大きくなるとEVEN分散に切り替える. 分散スタイルを明示的に設定しないとAUTO分散になる. まず、ファクトテーブル関連する1つのテーブルの共通の列に基づいて分散させる. 関連するテーブルの選び方の観点は大きさで最もレコード数が大きいテーブルを選択する. 以下の構造では、ファクトテーブルとディメンションテーブル1が dim1_keyというキーを使って結合している. そこで, ファクトテーブルのdim1_key、ディメンションテーブル1のdim1_keyを分散キーとして採用する.(緑) ここまでで、dim1_keyの値が一致するレコードが同じスライスにコロケーションされる. キー分散に使うキーは1組のみ. 残りのテーブルについてはEVEN分散かALL分散を用いる. 選び方は上記の通り. テーブルのサイズが小さいのであれば、ALL分散により再分配の可能性がなくなり選びやすい. 圧縮エンコーディング 通常のRDBのように行方向の固まりを記録する場合、各列の値は型や値の傾向がまちまちであるため、 一様に圧縮しようとしても高い圧縮率を得られない. 対して、列方向の固まりを記録する場合、各列の型は同じだし値の傾向が似ていることが多いため、 高い圧縮率を得られる可能性がある. ただし、値の傾向により圧縮アルゴリズムを選択する必要がある. 公式で挙げられているアルゴリズム. 結局試してみないとわからない、というのはある. (Zstandard強すぎないか?) raw エンコード 圧縮をおこなわない. ソートキーが設定されているときはrawエンコードが設定される BOOLEAN、REAL,DOUBLE PRECISION型もraw. AZ64 エンコード Amazon 独自の圧縮エンコードアルゴリズム より小さなデータ値のグループを圧縮し、並列処理に SIMD (Single Instruction Multiple Data) 命令を使用する 数値、日付、および時刻データ型のストレージを大幅に節約する バイトディクショナリエンコード バイトディクショナリエンコード ディスク上の列値のブロックごとに、一意の値の個別のディクショナリを作成する 列に含まれる一意の値の数が制限されている場合に非常に効果的 列のデータドメインが一意の値 256 個未満である場合に最適 CHAR 列に長い文字列が含まれる場合に特に空間効率が高まる VARCHAR 列に対しては、LZO などの BYTEDICT 以外のエンコードを使用する デルタエンコード 列内の連続する値間の差を記録することにより、データを圧縮 日時列にとって非常に有用 差が小さいときに特に有用 LZO エンコード 非常に長い文字列を格納する CHAR および VARCHAR 列、特に製品説明、ユーザーコメント、JSON 文字列などの自由形式テキストに適している Mostly エンコード 列のデータ型が、格納された大部分の値で必要なサイズより大きい場合に有用. たとえば、INT2 列などの 16 ビット列を 8 ビットストレージに圧縮できる ランレングスエンコード 連続して繰り返される値を、値と連続発生数 (実行の長さ) から成るトークンに置き換える データ値が連続して繰り返されることが多いテーブルに最適 たとえば、テーブルがこれらの値でソートされている場合など Text255 および Text32k エンコード 同じ単語が頻繁に出現する VARCHAR 列を圧縮する場合に有用 Zstandard エンコード 多様なデータセット間で非常にパフォーマンスのいい高圧縮比率を提供 製品説明、ユーザーのコメント、ログ、JSON 文字列など、長さがさまざまな文字列を保存する CHAR および VARCHAR 列に対して有用 圧縮エンコーディングをテストするためには、 各アルゴリズムで差が出るように大量のデータを用意する必要がある. 公式には、テストするために大量のデータを用意することは難しいので デカルト積ででっち上げる手法が案内されている. 例えば、こんな感じにデータをでっちあげる. create table cartesian_venue( venueid smallint not null distkey sortkey, venuename varchar(100), venuecity varchar(30), venuestate char(2), venueseats integer ); insert into cartesian_venue select venueid, venuename, venuecity, venuestate, venueseats from venue, listing; このうち、venunameに対して各エンコーディングアルゴリズムを適用して格納するデータを作る. create table encodingvenue ( venueraw varchar(100) encode raw, venuebytedict varchar(100) encode bytedict, venuelzo varchar(100) encode lzo, venuerunlength varchar(100) encode runlength, venuetext255 varchar(100) encode text255, venuetext32k varchar(100) encode text32k, venuezstd varchar(100) encode zstd); insert into encodingvenue select venuename as venueraw, venuename as venuebytedict, venuename as venuelzo, venuename as venuerunlength, venuename as venuetext32k, venuename as venuetext255, venuename as venuezstd from cartesian_venue; 知りたいことは、encodingvenueの各列で実際に使われているディスク容量. 以下のようにして各列で使用される1 MBのディスク ブロック数を比較するらしい. rawが203に対してBYTEDICTが10. つまりBYTEDICTにより20:1の圧縮率を得られた例. select col, max(blocknum) from stv_blocklist b, stv_tbl_perm p where (b.tbl=p.id) and name =\'encodingvenue\' and col < 7 group by name, col order by col; col | max -----+----- 0 | 203 1 | 10 2 | 22 3 | 204 4 | 56 5 | 72 6 | 20 (7 rows) まとめ 公式のベストプラクティスを追ってみた. 面倒だけれども結構力技で出来ているなという印象. 与えられたスタースキーマからある程度決まったやり方でパラメタを選択できそう. 実データでやったら迷うことは必至w ソートキーと分散スタイルの選択は分散コストに影響する. 圧縮エンコーディングの選択はディスクストレージに影響する. 理解したら実際に試行錯誤していくしかないイメージ.

default eye-catch image.

Amazon Redshift概要 パフォーマンス総論

概要 各論に入る前に総論。 MPPでクエリ実行するために必要な制限事項を設計/実装するために、 とりあえず、何故その制限事項が必要なのかを理解しておく必要がありそう。 [arst_toc tag=\"h4\"] 並列処理 MPP(Massively Prallel Processing). シンプルで安価なプロセッサを多数集積して一台のコンピュータとする手法. 各ノードの各コアは、同じコンパイル済みクエリセグメントをデータ全体の一部分に対して実行する。 テーブルの行をコンピューティングノードに分配し分散処理する。 列指向 通常のアプリケーションとデータウェアハウスではクエリで取得したいデータが異なる. 通常のアプリケーションは行の大方の列が欲しい一方で、データウェアハウスは行の中の一部の列が欲しい。 データウェアハウスが1行の全ての列を取得する方式を使用すると、ほとんどの列は無駄になってしまう. ある行の1列にだけ関心がある状況で15行分欲しい場合、行指向であれば100回、列指向であれば5回のディスクI/O。 データ圧縮 列指向で同じ列のデータを取る場合、同じ列に入るデータの乱れ方には傾向があるはずなので、データ圧縮が効きやすい。 データ圧縮によりディスクI/Oがさらに減少する。 クエリオプティマイザ MPP対応のクエリオプティマイザ。複数のコンピューティングノードで並列処理するための最適化が走る。 結果のキャッシュ リーダーノートでキャッシュする必要性があるクエリと結果をキャッシュする。 サイズの大きなクエリ結果セットはキャッシュしない。 キャッシュするか否かは、キャッシュ内のエントリ数とAmazon Redshiftクラスターのインスタンスタイプが含まれる。

default eye-catch image.

NumPy vector operations, universal functions, matplotlib, 3項演算, 次元削減

universal functions ndarrayの全ての要素に対して基本的な計算を実行する。 以下オペランドが1つの単項universal functions。 abs,sqrt,square,exp,log,sign,ceil,floor,rint,modf,isnan,sin,cos,arcsin,arccosなどがある。 array = np.arange(10) print(array) # [0 1 2 3 4 5 6 7 8 9] sqrt = np.sqrt(array) print(sqrt) # [0. 1. 1.41421356 1.73205081 2. 2.23606798 # 2.44948974 2.64575131 2.82842712 3. ] exp = np.exp(array) print(exp) # [1.00000000e+00 2.71828183e+00 7.38905610e+00 2.00855369e+01 # 5.45981500e+01 1.48413159e+02 4.03428793e+02 1.09663316e+03 # 2.98095799e+03 8.10308393e+03] 以下、オペランドが2つの2項universal functions。 いずれかのうち最大の値を残すmaximum()。 add,subtract,divide,power,maximum,minimum,copysign,greater,lessなどがある。 x = np.random.randn(10) y = np.random.randn(10) print(x) # [ 1.3213258 0.12423666 -1.45665939 -1.49766467 -0.6129116 2.00056744 # -0.00816571 0.63247747 0.29497652 0.80000291] print(y) # [-0.76739214 0.95151629 0.03208859 0.40641677 0.82635027 1.01773826 # 0.75601178 0.25200147 1.59929321 0.6251983 ] z = np.maximum(x,y) print(z) # [1.3213258 0.95151629 0.03208859 0.40641677 0.82635027 2.00056744 # 0.75601178 0.63247747 1.59929321 0.80000291] [mathjax] matplotlibにndarrayを引数として渡せば簡単にプロットできる。 (z=sqrt{x^2+y^2})をプロットしてみる。 import numpy as np import matplotlib.pyplot as plt points = np.arange(-5,5,0.01) xs,ys = np.meshgrid(points,points) z = np.sqrt(xs**2 +ys**2) plt.imshow(z, cmap=plt.cm.gray) plt.colorbar() plt.title(\"Image plot\") plt.show() 3項演算子 where マスクの論理値に従って2つのndarrayのうちいずれかの値を選択してリストに書く。 3項演算子を使ってPythonのlistに入れる方法は以下。 xa,xbはndarrayだが最終的なr1はPythonオブジェクト。 import numpy as np xa = np.array([1,2,3,4,5]) xb = np.array([6,7,8,9,10]) cnd = np.array([True,True,False,False,False]) r1 = [(x if c else y) for x,y,c in zip(xa,xb,cnd)] print(r1) 対して、ndarrayに対して直に3項演算子を実行するwhereがある。 import numpy as np xa = np.array([1,2,3,4,5]) xb = np.array([6,7,8,9,10]) cnd = np.array([True,True,False,False,False]) r2 = np.where(cnd,xa,xb) print(r2) 数学関数,統計関数,次元削減 (n)次のndarrayをある軸について集計して(n-1)次のndarrayにする。 集計方法としていくつかの数学関数、統計関数が用意されている。 以下5x4(2次)のndarrayについて、それぞれの列について平均を取り4列(1次)のndarrayにしている。 さらに列の平均を取りスカラーにしている。 import numpy as np ary = np.random.randn(5,4) print(ary) # [[-1.84573174 1.84169514 1.43012623 -0.5416877 ] # [-1.03660701 0.63504086 -0.12239017 -0.77822113] # [ 0.1711323 -0.16660851 -0.7928288 1.17582814] # [-0.29302267 -0.23316282 1.70611457 0.53870384] # [-0.46513289 -1.12207588 0.01930695 0.49635739]] print(ary.mean(axis=0)) # [-0.6938724 0.19097776 0.44806576 0.17819611] print(ary.mean(axis=1)) # [ 0.22110048 -0.32554436 0.09688078 0.42965823 -0.26788611] print(ary.mean()) # 0.030841804893752683

default eye-catch image.

ブロック操作系まとめ

全然網羅できてないけどブロック操作系まとめ。 [arst_toc tag=\"h3\"] スコープを作ってコレクションを操作できるのはそうだとして、 ブロック内の評価値をまとめたものがブロックの評価結果となるところがポイント。 コードがぐっと短くなって気分が良い。 基本的なeach 最も基本的な配列、ハッシュのeach。配列、ハッシュをレシーバとしてeachメソッドを呼び出す。 評価結果はレシーバ自身。 arrays = [100, 200, 300] rets = arrays.each do |value| p value end # 100 # 200 # 300 # [100, 200, 300] p rets # [100, 200, 300] hashes = {a1:\"a1\", a2:\"a2\", a3:\"a3\"} rets = hashed.each do |value| p value end # [a1:\"a1\", a2:\"a2\", a3:\"a3\"] # {a1: \"a2\"} # {a2: \"a2\"} # {a3: \"a3\"} # [a1:\"a1\", a2:\"a2\", a3:\"a3\"} p rets # [a1:\"a1\", a2:\"a2\", a3:\"a3\"} each_with_index 配列の順序数を付けることができる。 arrays = [100, 200, 300] arrays.each_with_index do |value,index| p \"#{index}_#{value}\" end # \"0_100\" # \"1_200\" # \"2_300\" # [100, 200, 300] each_key, each_value ハッシュのキーのみ、値のみをブロックで使う場合は以下。 キー、値の両方を使える制限無しの状態でどちらかを使う、というのではなく、 初めからキー、値のどちらを使うかを宣言して、それだけを使うという拘りビリティ。 hashes = {a1:\"a1\", a2:\"a2\", a3:\"a3\"} rets = hashes.each_key do |key| p key end # :a1 # :a2 # :a3 # {:a1=>\"a1\", :a2=>\"a2\", :a3=>\"a3\"} rets2 = hashes.each_value do |value| p value end # \"a1\" # \"a2\" # \"a3\" # {:a1=>\"a1\", :a2=>\"a2\", :a3=>\"a3\"} upto, downto ループを回して、制御変数をインクリメント、デクリメントするケースには専用の構文を使う。 Rubyにはインクリメント演算子は無いし、腐ってもforの外で用意した変数に対して+1を自己代入したりしない。 何より、英語の構文と同じところが気持ちが良い。 100.upto(103) do |i| p i end # 100,101,102,103 103.downto(100) do |j| p j end # 103,102,101,100 times n回の構文,times。 3.times do |i| p i end # 0,1,2 map mapは、レシーバの各要素分繰り返す。ブロックの評価値は各要素に対応する値となり、新たな配列が返る。 eachだと、評価結果がレシーバ自身であるためレシーバを加工して返す用途に使えないが、 mapを使うと、ブロックの応答として加工済みの配列を返せるので、制御構文がグッと短くなって気持ちが良い。 あくまでもレシーバの個数繰り返すので、ブロックでnilを返すと、評価結果の該当要素がnilになる。 arrays = [1, 2, 3] rets = arrays.map do |i| \"values_#{i}\" end [\"values_1\", \"values_2\", \"values_3\"] rets2 = arrays.map do |i| \"values_#{i}\" if i <3 end ["values_1", "values_2", nil] ただ、順序数付きのmap_with_indexはこのような動きをしない。 arrays = [1, 2, 3] rets = arrays.map_with_index do |index,value| \"#{index}_#{value}\" end p rets # {評価結果なし} select 前述の通り、mapはレシーバの個数分繰り返すため、ブロック内の最終評価値がnilとなった場合、 評価値の該当要素がnilになる。 そうではなく、nilでない評価結果をまとめて返したい場合はselectを使う。 rets3 = arrays.select do |i| \"values_#{i}\" if i <3 end ["values_1", "values_2"] まだまだ全然網羅できてないけど、とりあえず終了。

default eye-catch image.

procとlambda

[arst_toc tag=\"h3\"] Procとcallメソッド 以下では、get_closureメソッドにより、クロージャオブジェクトを生成している。 その際初期値を設定している。 クロージャオブジェクトのcallメソッドによりクロージャを実行する。 初期値を設定した変数initial_valueはクロージャのスコープなので、 クロージャの外からは一切存在を管理する必要がない。 def get_closure initial_value Proc.new{|up| initial_value += up} end closure = get_closure(100) closure.call(10) # 110 closure.call(20) # 130 proc内でのreturn proc内でreturnすると、procの呼び出し元のスコープを抜ける。 procの呼び出し位置で処理が止まるため、そのあとの処理は実行されない。 以下callerというメソッド内でprocを実行するが、 procがreturnすることで、proc.callの実行後callerのスコープを抜ける。 なので2は評価されない。 この動きは後のlambdaと異なる。 def caller proc = Proc.new{ return 1 } proc.call 2 end Procオブジェクトをブロックに変換 作っておいたクロージャオブジェクトをメソッド実行時にブロックとして使用することができる。 メソッドの引数として、&を付与してProcオブジェクトを渡す。 def func hoge hoge + yield end closure = Proc.new {5} func(10, &closure) # 15 ブロックをProcオブジェクトに変換 ブロックをProcオブジェクトとして受けることができる。 def func2 hoge, &proc hoge + proc.call end func2(100) do 200 end # 300 lambda lambdaもprocを返すが、Proc.newとは異なる動きをする。 procの実行時にreturnするとprocの呼び出し元のスコープを抜けていた。 lambdaは実行時にreturnしてもlambdaの呼び出し元のスコープを抜けない。 下の例でlambdaが返したprocが途中で終わっても callerはそこで抜けず、proc.callの後が実行される。 def caller proc = lambda{ return 1 } proc.call 2 end lambdaの別の書き方 以下のように格好良く書ける。他の言語の同様の構文に似せた感じ。 lmd2 = ->(hoge,fuga) { hoge + fuga } lmd2.call(100,200) # 300

default eye-catch image.

ブロック(クロージャ)とスコープ

[arst_toc tag=\"h3\"] Ruby固有のスコープの扱いについて追記。 ブロックとスコープ メソッド呼び出しの直後にブロックを指定することができる。 例えば、以下においてメソッドfuncを呼び出す際に引数1と同時にdoから始まりendで終わるブロックを指定している。 ブロックの中ではx1という変数に2を代入していて、それがブロックの評価値となる。 ブロック付きで呼び出されたメソッドの中では、ブロックの評価値をyeildという特殊変数で受け取ることができる。 つまり、メソッドfuncの中では、引数y1=1と、ブロックの評価値2の和の評価が行われる。 def func y1 y1 + yield end # 3 func (1) do x1 = 2 end なお、ブロックはdo..endだけでなく、{..}でも書ける。 Rubyの掟的には、複数行のブロックはdo..end、1行のブロックは{..}とのこと。 func (1) { x1 = 2 } ちなみに、ブロックの中と外はスコープが異なる。 ブロックの中で確保した変数x1は、スコープの外からは見えない。 ブロックの中で確保した変数はブロックの評価が完了した時点で破棄される。 func (1) { x1 = 2 } p x1 # Exception 破棄されるのはブロックで初めて確保した変数であって、 ブロックの外部の変数をブロック内で使用した場合は、 ブロック内での操作がブロック外の変数に影響する。 例えば、以下のように予めブロックの外で変数x1を確保しておき、 ブロックにより加算するという状況で、ブロック内のx1は紛れもなくブロック外のx1。 x1はブロックにより束縛される(キリっ)。 ブロックの評価後に評価すると、確かにブロック内の加算が行われた後。 x1 = 10 func (1) { x1 += 2 } # 13 p x1 # 12 Rubyの特殊なスコープ事情 変数の束縛について追記。他の言語では動きそうな以下のコードはRubyでは動かない。 Rubyではオブジェクトやメソッドの壁を超えて変数を参照することはできない。 (Railsではインスタンス変数を使って超えちゃってるけど..) fuga = 20 def myfunc p fuga end ブロックを使うことで初めて実行時の環境(コンテキスト,文脈)を共有できる。 ここで一般化して良いのかわからないけども、 処理の生成時の変数を束縛するものをクロージャというらしい。 実行時の環境を動的に使えて便利だな、ぐらいの認識だったけど、 変数の束縛がポイント。 ブロックの引数 ブロック、つまりクロージャの引数の定義方法と使いかたは以下の通り。 クロージャ内の先頭で引数を|で囲む。かなり特殊に見える。慣れだろうか。 def func2 x2, y2 x2 + yield(y2,30) end p func2(10,20){|x,y| x+y} # 60 メソッド側でブロックが指定されたか調べる どんなケースでそんな使いかたをするのか.. メソッド側で呼び出し時にブロックが指定されてか否かを調べることができる。 def func3 x3,y3 return x2 + yield(y2,30) if block_given? 0 end p func3(10,20){|x,y| x+y} # 60 p func3(10,20) # 0

default eye-catch image.

範囲リテラル、case式、while式

[arst_toc tag=\"h3\"] 範囲を表すリテラルが言語仕様として準備されている。 範囲リテラル 範囲クラスはRangeクラスのインスタンス。 \"..\"により開始、終了の両方を含む範囲を表現する。\"...\"だおt終了は含まれない。 def func for var in 1..10 do p var end end func # 1 2 3 4 5 6 7 8 9 10 def func2 for var in 1...10 do p var end end func2 # 1 2 3 4 5 6 7 8 9 包含と同値 include?により範囲に値が含まれるかどうかを調べられる。 (1..5).include?(3) # true (1..5).include?(6) # false なお、===演算子も包含関係を表す。 (1..5) === 3 # true (1..5) === 10 # false ==演算子は同値関係を表す。 (1..5) == (1..5) # true (1..5) == 3 # true 添字演算子 配列の添字演算子に範囲を指定することができる。何番目から何番目という表現はかなり直感的。 a = [ 1, 2, 3 ,4 ,5] p a[1..2] # [2..3] p a[1,3] # 文字列の添字演算子にも範囲を指定することができる。部分文字列を範囲で表現できる。直感的。 a = \"abcdef\" p a[1..2] # bc case式 phpなど他の言語のswitch文。case when.. と書く様子。 でもなぜswitchではいけなかったのだろうか。 case式自体の評価値はcase式内で最後に評価された値となる。 def hoge a case a when 10 then p \"first\" when 20 then p \"second\" else p \"third\" end end hoge 10 # \"first\" hoge 20 # \"second\" hoge 30 # \"third\" whenで何が行なわれているかというと、===演算子によって同値の判断がされている。 なので、whenにRangeを指定すると、===演算子によって包含関係が評価される。 while式 phpのwhileと同等。条件が真である間繰り返す。 i = 0 while (0..5) === i do p i i += 1 end # 1 2 3 4 5 phpなどにもあるように、ループの末尾にwhileを書くことができる。 最初の1回は必ず実行される例のやつ。 後に置くので\"後置while\"という名前がついている。 i=0 begin p i i+=1 end while (1..4) === 1 # 0 1 2 3 4 後置whileとは別に、後に置くが\"修飾詞\"として使う書き方がある。 最初の1回はwhileが評価されるところが後置ifと異なる。 i=0 p i += 1 while (0..4) === i # 1 2 3 4 5 until式 条件が真になるまで繰り返す(真になったら抜ける)、専用の条件式。 Rubyは条件式を書く時に否定文を書く必要がない。(たぶん) i=0 until i===5 do p i i+=1 end # 0 1 2 3 4

default eye-catch image.

ハッシュリテラル

要素の順序性がある場合に使う配列と、順序性がない場合に使うハッシュ。 Rubyのハッシュの言語仕様について。 [arst_toc tag=\"h3\"] 基本的な書き方 PHPと同じようにキーとして文字列を使う場合は以下の通り。 a = {\"hoge1\" => 100, \"hgoe2\" => 200, \"hoge3\" => 300} a[\"hoge1\"] # 100 a[\"hoge2\"] # 200 a[\"hoge4\"] # nil キーとしてシンボルを使う場合は以下の通り。Rubyっぽくなる。 Ruby1.9以降はこちらを使う。 b = {:hoge1 => 100, :hoge2 => 200, :hoge3 => 300} b[:hoge1] # 100 b[:hoge2] # 200 b[:hoge4] # nil Hash.new()による生成 Hash.new()により配列の初期値として新たに確保した変数を設定できる。 Array.new()と同様に、全ての要素として同じオブジェクトを設定する。 a = Hash.new(100) a[:hoge1] # 100 a[:hoge1].object_id # 12345 a[:hoge2] # 100 a[:hoge2].object_id # 12345 []による生成 面倒くさいことに、キーと値が交互に現れる配列からハッシュを生成できる。 その書き方は以下の通り。 a = Hash[:hoge1, 100, :hoge2, 200, :hoge3, 300] # [:hoge1=>100,:hoge2=>200,:hoge3=>300] b = [[:hoge1,100],[:hoge2,200],[:hoge3,300]].to_h # [:hoge1=>100,:hoge2=>200,:hoge3=>300] 関数の引数、波カッコの省略 関数にハッシュを引数として与える場合、ハッシュを構成する波カッコを省略できる。 というか、引数の数が合わなかったときに末尾の処理の一つである様子。 ハッシュ引数の波カッコの種略を引数の先頭にもってくると(末尾の引数でないと)、例外が発生する。 def func a, b, c c end func 100,200,:hoge1=>300,:hoge2=>400 # {:hoge1=>300,:hoge2=>400} func :hoge1=>100,:hoge2=>200,300,400 func :hoge1=>100,:hoge2=>200,300,400 # 例外 キーワード引数 Rubyっぽい書き方。キーワード引数。 キーワードにシンボルで名前をつけることができる。関数を呼び出す時の可読性が向上する。 def func hoge1:, hoge2:, hoge3 hoge1 + hoge2 + hoge3 end func 100,200,300 # 600

default eye-catch image.

配列、配列演算、繰り返し

本日の素振り。 [arst_toc tag=\"h3\"] 配列 配列はArrayクラスのインスタンス。 a = Array.new(3) # [nil,nil,nil] b = Array.new(2,\"hoge\") # [\"hoge\",\"hoge\"] 配列は[]で表す。 a = [10,20,30] # [10,20,30] a[1] # 20 b = [20,true,\"hoge\"] # [10,true,\"hoge\"] b[2] # \"hoge\" b[3] # nil %記法で配列生成 配列の要素が文字列である場合に限って、%W、%wによって文字列を配列化できる。 デリミタは半角スペース、%Wでダブルクォート、%wでシングルクォート(なし)。 自動的にダブルクォート、シングルクォートで括るため、 文字列の配列を作るためにダブルクォート、シングルクォートで汚すことを防げる。 p = %W(hoge fuga) # [\"hoge\",\"fuga\"] q = %w(hoge fuga) # [\'hoge\',\'fuga\'] joinで配列結合 配列と配列を結合して新しい配列を作るのではなく、要素を文字列として結合する。 p = [100,200,300] # [100,200,300] p.join # \"100200300\" p.join(\"_\") # \"100_200_300\" 配列の生成 Array.new()を使うと同じオブジェクトを持つ配列を作れる。 要素分の変数が確保されるのではなく、指定した値を持つ変数が1つ確保されて並ぶ。 a = Array.new(2,\"hoge\") # [\"hoge\", \"hoge\"] a.object_id # 4032 b.object_id # 4032 a[0].replace(\'fuga\') # [\"fuga\", \"fuga\"] a.object_id # 4032 b.object_id # 4032 Array.new(){}だと要素数分の変数が確保されて並ぶ。 a = Array.new(2){\'hoge\'} # [\"hoge\", \"hoge\"] a[0].object_id # 4033 a[1].object_id # 4034 範囲外アクセス 配列の大きさを超えてアクセスしたとき、例外とならず、アクセスしたところまで配列が広がる。 最初の位置から作った最後の位置までnilで埋められる。 a = Array.new(3){\"hoge\"} # [\"hoge\",\"hoge\",\"hoge\"] a[5] = \"fuga\" # [\"hoge\",\"hoge\",\"hoge\",nil,nil,\"fuga\"] 負の添え字アクセス 何が何でもコード行を削減したい意思! 配列に負の添え字でアクセスすると\"末尾から数えた位置\"という意味になる。 a = [\"hoge1\", \"hoge2\", \"hoge3\"] # [\"hoge1\", \"hoge2\", \"hoge3\"] a[-1] # \"hoge3\" 配列のスライス phpのarray_slice()に相当するやつ。指定したインデックスから何個分というやつ。 a = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] a[2,3] # [2, 3, 4] 以下のように指定したインデックスから何個分を置き換えられる。 a = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] a[1,2] = \"hoge\" # [0, \"hoge\", 3, 4, 5, 6] b = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] b[1,2] = \"hoge\", \"fuga\" # [0, \"hoge\",\"fuga\", 3, 4, 5, 6] c = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] c[1,2] = \"hoge1\", \"hoge2\", \"hoge3\" # [0, \"hoge1\",\"hoge2\",\"hoge3\", 3, 4, 5, 6] 複数の戻り返却と多重代入 複数の値を返す関数を定義する。複数の値を返すように見えるが配列が返っている。 def test return \"hoge1\", \"hoge2\", \"hoge3\" end a, b, c = test # [\"hoge1\", \"hoge2\", \"hoge3\"] a # \"hoge1\" b # \"hoge2\" c $ \"hoge3\" 戻りとしてネストした配列を返すことができて、同じ構造の変数に代入できる。(多重代入) def test2 return [[\"hoge1\", \"hoge2\"], \"hoge3\"] end (x,y),z = test2 # [[\"hoge1\",\"hgoe2\"], \"hoge3\"] +、-演算子 左オペランドと右オペランドの要素の加算、左オペランドから右オペランドの減算 a = [\"hoge1\",\"hoge2\"] # [\"hoge1\",\"hoge2\"] b = [\"hoge2\",\"hoge3\"] # [\"hoge2\",\"hoge3\"] a + b # [\"hoge1\",\"hoge2\",\"hoge2\",\"hoge3\"] a - b # [\"hoge1\"] b - a # [\"hoge3\"] 減算の場合、左オペランドに重複があるときは全て削除される x = [\"hoge1\",\"hoge1\",\"hoge2\"] # [\"hoge1\",\"hoge2\",\"hoge3\"] y = [\"hoge1\"] x - y # [\"hoge2\",\"hoge3\"] *演算子 右オペランドが数値の場合は左オペランドをその回数繰り返す。 右オペランドが文字列の場合はjoinと同じ。 a = [1,2,3] a * 2 # [1,2,3,1,2,3] for式,each式 PHPのforeachに近いforとeach。forは期待と異なりループの中と外はスコープが同じ。 for内の変数をfor外でアクセスできる。 for value in [100,200,300] inner_loop = 500 end inner_loop + 100 # 600 対してeachはループ内とループ外でスコープが異なりeach内の変数はeach外で未初期化。 [100,200,300].each do inner_look2 = 500 end inner_loop # undefined variable

default eye-catch image.

symbol、参照、破壊的メソッド

[arst_toc tag=\"h3\"] シンボル 文字列そのものに意味はなく、単純にラベルとして文字列を扱いたいというときシンボルを使う。 シンボルのポイントは、文字の並びが同じであれば(同値であれば)同一であること。 hoge1 = :hoge1 p hoge1 # 524328 hoge2 = :hoge1 p hoge2 # 524328 hoge3 = :hoge3 p hoge3 # 324648 文字列からシンボルに変換できる。 hogeA = \"hoge1\".to_sym p hogeA.object_id # 524328 値の同値性と同一性 演算子==は、例えば2つの文字列の値が等しいかどうか(同値であるか)を比較する。 一方で、2つの変数のobject_idが等しいかどうか(同一であるか)を比較するために、 equal?メソッドを利用する。 hoge_x = \"hoge1\" hoge_y = \"hoge1\" p hoge_x == hoge_y # true p hoge_x.equal? hoge_y # false オブジェクトと参照 変数を宣言すると(つまりリテラルを指定すると)オブジェクトへの参照が与えられる。 以下の場合、\"hoge\"という一つのオブジェクトをhoge1、hoge2が参照する形になる。 hoge1 = \"hoge\" hoge2 = hoge1 hoge1.equal? hoge2 # true 片方の変数について値を変更した場合、その変更に対応するオブジェクトが新たに作られ、 新しいオブジェクトへの参照が作られる。 hoge1 = \"hoge\" hoge2 = hoge1 hoge2 = hoge2 + \"fuga\" p hoge1 # hoge p hoge2 # hogefuga hoge1.equal? hoge2 # false 実引数と仮引数の参照 関数に実引数として渡した変数と関数内で利用できる仮引数の参照は同じ。 ただし関数内で仮引数に対して処理すると、別の値が確保され、仮引数はその値への参照となる。 x = 100 x.object_id # 435123 def func x p x.object_id # 435123 x = 200 p x.object_id # 249821 end 破壊的メソッドと参照 変数の値を変更したときに参照を変えずに値を変更するメソッドを破壊的メソッドという。 慣例的に破壊的メソッドの末尾には「!」をつける。 破壊的メソッドと、代入時に別変数を確保して参照を変更するメソッドはしばしば同名で、 「!」により区別することがある。 残念ながら徹底されておらず、破壊的メソッドなのに「!」がなかったり逆もある。 w = \"hogehoge\" w.chop # \"hogehog\" w # \"hogehoge\" w.chop! # \"hogehog\" w # \"hogehog\"