LoginSignup
8
8

More than 5 years have passed since last update.

Rによるラベル付きループ

Posted at

この記事の内容は誤っているかもしれません。

そもそも、Rでforを使うのはダメ、というのは嘘。多分ベクトル化に関して誤解があります。

applyはただのラッパで、実際はforをつかうよ、の理由を書こうと思ったんだけど、@a_bickyさんがapplyの解説記事を書いてくれたので、そっちを見て下さい。とにかく、Rでもforは使うんです。

R の apply 徹底解説

Rでは多重ループがよく出てくる。

多次元配列とか使うと出てきます。当たり前です。まあなんでもいいんですが、多重ループ途中で条件によって全ループ脱出したい、という場合があるかもしれません。

例えばC系だと、gotoを使うか、フラグを使うかします。以下の例は、trinoの走り書きっていうブログからお借りしました。ありがとう。

for( int z = 0; z < iSizeZ; ++z ){
    for( int y = 0; y < iSizeY; ++y ){
        for( int x = 0; x < iSizeX; ++x ){
            if( ... ){
                goto LABEL;
            }
        }
    }
}
LABEL:

とか

bool bEnd = false;
for( int z = 0; z < iSizeZ; ++z ){
    for( int y = 0; y < iSizeY; ++y ){
        for( int x = 0; x < iSizeX; ++x ){
            if( ... ){
                bEnd = true;
            }
            if( bEnd ){ break; }
        }
        if( bEnd ){ break; }
    }
    if( bEnd ){ break; }
}

とか。あと、関数を使うのもあり。

void Loop()
{
    for( int z = 0; z < iSizeZ; ++z ){
        for( int y = 0; y < iSizeY; ++y ){
            for( int x = 0; x < iSizeX; ++x ){
                if( ... ){
                    return;
                }
            }
        }
    }
}

Rではフラグを使うか、関数を作るか、tryでエラー起こすか、というのが手ですが、

  1. フラグ面倒。バグ入りがち。
  2. 関数はあり。Rはどこでも関数が定義できるので、なおアリ。でもスコープの問題があって、関数の中の副作用(代入とかね)はデフォで関数内しか効かない。
  3. エラーtryは、予定外のエラーも拾ってパスしちゃうから、なし。一応stop(e)で例外投げて、分岐すればOKかもだけど、多重ループ脱出のために条件分岐してエラー投げて、受け取った側でエラーの中身見て条件分岐、とか、ないわ。

Rによる多重ループの脱出

前回の記事で書いたとおり、シグナリング機構を使うと多重ループが実装できる。

やってることは以下の通り、

  1. 関数(._.())に多重ループ式を渡す。
  2. シグナル埋め込む(.Internal(.addRestart(…)))。
  3. 式を評価する関数を評価する。
  4. 多重ループ式中では、ある条件でシグナルを生成する(.V.)。
  5. 式を評価する関数の評価が停止して、呼び出し環境に戻る。
  6. 関数を抜ける。

という感じです。プロミスとして渡された式を評価してるので、式の環境は呼び出し元の環境になっています。つまりグローバルから呼び出したら、グローバルの変数の書き換えも可能ということ。要するに、スコープ気にしないで普通の式みたいに使える、ということ。

ラベル付きループ

Java(など)にはラベル付きループというものがあって、多重ループの中のどのループまで抜けるか指定できる。これは便利。こういうのね。

で、これをRで(あとmatlabでも)使えるといいなあ、と思っていたんだけど、多重ループ脱出のことを考えたら出来るような気がして、書いてみたらできた。CRANから使えます。

CRAN - Package labeledLoop

これフル脱出。

> library(labeledLoop)
> A %._.% for (i in 1:3) {
+   B %._.% for (j in 4:6) {
+       C %._.% for (k in 7:9) {
+           if (i == 2 && j == 5 && k == 8) {
+               print(c(i, j, k))
+               ._.(A)
+           }
+       }
+   }
+ }
[1] 2 5 8

真ん中を抜ける。

> A %._.% for (i in 1:3) {
+   B %._.% for (j in 4:6) {
+       C %._.% for (k in 7:9) {
+           if (k == 8) {
+               print(c(i, j, k))
+               ._.(B)
+           }
+       }
+   }
+ }
[1] 1 4 8
[1] 2 4 8
[1] 3 4 8

という感じです。

実装についてのメモ

ソースコードはgithubにおいてあります。

パッケージとは言っても、中身は%._.%という演算子と._.という関数があるだけ。あわせて10行くらいです。

`%._.%` <- function(label, statement) {
  label <- substitute(label)
  if (!is.character(label)) label <- deparse(substitute(label))
  arg <- list(as.name("statement"), label)
  names(arg) <- c("expr", label)
  do.call("withRestarts", arg)
  invisible()
}

`._.` <- function(label = ".") {
  label <- substitute(label)
  if (!is.character(label)) label <- deparse(substitute(label))
  invokeRestart(label)
}

%._.%は左辺にラベル、右辺に式をとる演算子です。ラベルを受けフラグとしてリスタートを仕込んでます(withRestarts)。それだけ。

._.はラベルを引数にする関数。リスタートを発行します。それだけ。

withRestartsは任意の仮引数の「名前」で、invokeRestartのフラグを判定するんだけど、一番面倒なのが、動的に渡されるラベルを使って引数を作るところ。まあeval(parse(text)))しちゃえばいいんですが・・・。

まとめ

というわけで、@a_bickyさんのいうように、Rではfor使ったほうがいい場面が結構あります。そんな時に使えるライブラリかもしれません。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8