カメリアの記事

意味があることやないことを綴ります

for 文ではない繰り返し(再帰処理)

ごあいさつ

まず言い訳をさせてもらいたいのですが、記事を投稿する寸前まで「再帰処理」というものを認識していませんでした。再帰処理が十分に有名なものだという認識も当然なかったのです。つまり下の文章は「再帰処理を自分が初めて思いついたみたいにはしゃいで執筆した恥ずかしい足跡」であり、公開することにためらいがあるのですが、それでも役に立つことがあるかもしれない、僕の恥ずかしさより人類への貢献を取るべきだろう、と考え勇気を振り絞って投稿するものです。温かい目で見てやってくださると喜びます。

本文

for 文は繰り返し処理と言われますが、実際には一つの処理が終わるのを待たず次の処理が始まります。下のような場合、 1 秒が経過した後にいっぺんに 1 ~ 3 が出力されます。

for (let i = 0; i < 3; i++) {
  setTimeout(() => {
    console.log(i)
  }, 1000)
}

これは、一つのソースに対して複数の処理を行うには、時として使えないことがあります。例えば下のように「正規表現と置換文字列をセットにした配列があり、これを一つの長い文章に対して適用していく」というような場合です。(下の場合、実際にそれらしい値を得るには Promise.all() などを使用する必要があります)

let word = "長い文章"
let replList = [
  {
    "regx": "a",
    "repl": "1"
  },
  {
    "regx": "b",
    "repl": "2"
  },
  {
    "regx": "c",
    "repl": "3"
  }
]
// for 文ではダメな場合がある。
for (let i in replList) {
  word = word.replace(RegExp(replList[i].regx, "g"), replList[i].repl)
}
console.log(word)

これを解決する方法として下のような「 1 回分の処理を記述した関数が、終了時に自分自身を呼び出す」という方法が考えられます。

let word = "長い文章"
let replList = [
  {
    "regx": "a",
    "repl": "1"
  },
  {
    "regx": "b",
    "repl": "2"
  },
  {
    "regx": "c",
    "repl": "3"
  }
]
new Promise(resolve => {
  let i = 0
  fun(word)
  function fun(input) {
    let work = input.replace(RegExp(replList[i].regx, "g"), replList[i].repl)
    if (i < replList.length - 1) {
      i++
      fun(work) // ここで自分自身を呼んでいる。
    }
    else {
      resolve(work)
    }
  }
})
.then(r => {
  console.log(r)
})