Help us understand the problem. What is going on with this article?

[備忘録] [初心者] Rubyの繰り返し文中で配列要素の値を変更する際の書き方

(ruby 2.5.3)

結論

JavaScriptの繰り返し文について、配列要素に変更を加えながら回したい時、

.js
for (var i = 0; i < A.length; i++){
 // 省略:ある条件の元、A[i]の値を変更する
}

同じ雰囲気でRubyで再現するなら、

.rb
A.each_index do |i|
  ## 省略:ある条件の元、A[i]の値を変更する
end

と書くことにする。

背景

JavaScriptで書いた私的なコードをRubyに書き直していた。
下記JavaScriptの関数はその一部で、英文のパーツを配列で渡すと、末尾の記号によって文頭に当たる語の先頭を大文字に変更して結合して出力するというもの。

Upcasing.js
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のメソッドに書き換えた(と思い込んだ)ものが下記。

(fail)Upcasing.rb
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_linesrは、ブロック外でも使用できるが、そもそも元の配列要素とは別に新しく定義される変数であるので、配列要素に変更を加えたいならば、ブロック内でもresult_lines[i]を操作する必要がある。(この変数rはブロックパラメータでは無い。)

普段、Rubyのコードを書くときに、繰り返しのブロック内で値を代入するような場面があまりなかったため、ブロックパラメータのような一時的な箱の概念や、生成されるスコープへの認識が欠けていた。

(※ この備忘録で使用したのはruby 2.5.3です。)

上記コードのようなfor 変数名 in 配列名の形だと、ブロック内で配列要素を指定するためのインデックスが変数に定義されないため、each_indexメソッドを使用して以下のように変更した。
(下記コードは後日投稿の記事内でリファクタリングしました。)

(success)Upcasing.rb
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 |とかならまだ分かり易いかもしれない。

没案.rb
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
RiSE_blackbird
railsなど
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away