(ruby 2.5.3)
結論
JavaScriptの繰り返し文について、配列要素に変更を加えながら回したい時、
for (var i = 0; i < A.length; i++){
// 省略:ある条件の元、A[i]の値を変更する
}
同じ雰囲気でRubyで再現するなら、
A.each_index do |i|
## 省略:ある条件の元、A[i]の値を変更する
end
と書くことにする。
背景
JavaScriptで書いた私的なコードをRubyに書き直していた。
下記JavaScriptの関数はその一部で、英文のパーツを配列で渡すと、末尾の記号によって文頭に当たる語の先頭を大文字に変更して結合して出力するというもの。
function Upcasing(result_lines) {
result_lines[0] = result_lines[0].replace(/^[a-z]/, result_lines[0].charAt(0).toUpperCase() );
var break_flg = false;
const break_sign = { ".":true, "!":true, "?":true, ",":false };
for (var i = 0; i < result_lines.length; i++) {
if (break_flg === true) {
result_lines[i] = result_lines[i].replace(/^[a-z]/, result_lines[i].charAt(0).toUpperCase() );
}
if ( break_sign[result_lines[i].slice(-1)] === true) {
break_flg = true;
} else {
break_flg = false;
}
}
return result_lines.join(" ");
}
console.log(Upcasing(["oh,", "yeah!", "hello!"]));
// 実行結果
// Oh, yeah! Hello!
// <= "hello!"だったものが、直前の"!"によって、次の語の先頭が大文字になっている
そのままRubyのメソッドに書き換えた(と思い込んだ)ものが下記。
def Upcasing(result_lines)
result_lines[0] = result_lines[0].capitalize
break_flg = false
break_sign = { "."=>true, "!"=>true, "?"=>true, ","=>false }
for r in result_lines
if break_flg == true
r = r.capitalize
end
if break_sign[r.slice(-1)] == true
break_flg = true
else
break_flg = false
end
end
joined_line = result_lines.join(" ")
return joined_line
end
print Upcasing(["oh,", "yeah!", "hello!"])
## 実行結果
## Oh, yeah! hello! <= "hello!"の"h"が大文字になっていない
for式
はeachメソッド
のようにスコープを作成しないのでfor r in result_lines
のr
は、ブロック外でも使用できるが、そもそも元の配列要素とは別に新しく定義される変数であるので、配列要素に変更を加えたいならば、ブロック内でもresult_lines[i]
を操作する必要がある。(この変数r
はブロックパラメータでは無い。)
普段、Rubyのコードを書くときに、繰り返しのブロック内で値を代入するような場面があまりなかったため、ブロックパラメータのような一時的な箱の概念や、生成されるスコープへの認識が欠けていた。
(※ この備忘録で使用したのはruby 2.5.3です。)
上記コードのようなfor 変数名 in 配列名
の形だと、ブロック内で配列要素を指定するためのインデックスが変数に定義されないため、each_indexメソッド
を使用して以下のように変更した。
(下記コードは後日投稿の記事内でリファクタリングしました。)
def Upcasing(result_lines)
result_lines[0] = result_lines[0].capitalize
break_flg = false
break_sign = { "."=>true, "!"=>true, "?"=>true, ","=>false }
result_lines.each_index do |i|
if break_flg == true
result_lines[i] = result_lines[i].capitalize
end
if break_sign[result_lines[i].slice(-1)] == true
break_flg = true
else
break_flg = false
end
end
joined_line = result_lines.join(" ")
return joined_line
end
print Upcasing(["oh,", "yeah!", "hello!"])
## 実行結果
## Oh, yeah! Hello!
(今回については、繰り返しブロック内で定義した変数をその外で使用する用事が無いので、for式
かeachメソッド
かによるスコープの生成有無による全体への影響は生じない。)
所感
変数名を省略して文量を減らせるのはブロックパラメータを利用する大きな利点だと思うが、繰り返し過程での当該の配列要素の変更がありきだと、あまり使うことはないのかもしれない。
下記の没案のようにeach_with_indexメソッド
で値もインデックスもブロックパラメータに渡して、現在の要素を参照する部分のみで使用するなども考えたが、書き間違えとかが増えそうな気がして止めた。
変数名が| r |じゃなくて、| current_element |とかならまだ分かり易いかもしれない。
result_lines.each_with_index do |current_element, i|
if break_flg == true
result_lines[i] = current_element.capitalize
end
if break_sign[current_element.slice(-1)] == true
break_flg = true
else
break_flg = false
end
end