LabVIEWを触ったことがない方に向けて、それなりのプログラムが書けるようになるところまで基本的な事柄を解説していこうという試みです。
シリーズ11回目としてループ処理の続きです。
この記事は、以下のような方に向けて書いています。
- WhileループとForループの注意点は?
- 前のループの値を使いたいけれどどうすればいい?
もし上記のことに興味があるよ、という方には参考にして頂けるかもしれません。
なお、前回の記事はこちらです。
ループの使用についておさらい
前回、ループの種類にはWhileループとForループがあるということを見てきました。ループの止め方に違いがあり、
- Whileループ:指定した終了の条件が満たされるまで繰り返し続ける
- Forループ:あらかじめ指定した回数が終わるまで繰り返し続ける
という違いがあるのでした。
プログラムの組み方の大筋はどちらも変わらないので、Forループを例におさらいしてみます。
1秒に一回、一つの乱数を表示させるループを10回繰り返すプログラムを作ってみてください。
前回ForループはWhileループのおまけみたいな形でしか紹介していなかったですが、書けますか?例えば以下のようなプログラムが考えられます。
もちろん、ループ回数の部分(カウント端子への配線)は定数でなくても制御器でも構いません。また、反復端子に反復数といった表示器をつけていなくてももちろんプログラムは動きます。
実行結果の例を上の図右に載せていますが、これは実行後の結果であるのにもかかわらず、反復数が9になっています。これは、「反復端子は0から数え始める」からですね。(たまにここを勘違いするとループの回数そのものと1差ができてプログラムが動作しないことがあるので気を付ける必要があります)
さて、ここまでは前回のおさらいでした。では、次に本題に入っていきます。
サイコロを100回振ってそれらの平均を求めるプログラムを作ってみます。これは前回の内容では作れない内容になるのでまずこれをするためのテクニックを上でおさらいしたプログラムを例に以下説明していきます。
まず、乱数はForループの中に置いたまま、乱数の値をForループの外に出すためにワイヤをForループの枠右側に繋げます。
なぜいきなり外に出そうとするか、訳が分からないと思いますが、とりあえず枠右側につけると、Forループの境界部分が四角になると思います。
説明上、先に用語を出した方が便利なのでここで正体を明かすと、この四角いアイコンは「出力トンネル」の中の「自動指標付け有効」状態といえます。
出力トンネル、は何となく意味が分かると思います。Forループから値が出る部分、というイメージですが、自動指標付け有効の方については何か思い当たることありませんか?
種明かしの前に、この自動指標付け有効の出力トンネルで右クリックして、表示器を作成してみてください。するとフロントパネルに見覚えのある表示器が表れると思います。
自動「指標」付け、という言葉でピンときた方もいると思いますが、作成された表示器は配列表示器となっています。
これはForループの性質で、「Forループから出た値は(デフォルトでは)ループ回数の番号が指標番号となった配列として出力トンネルから出る」というものになっています。
試しに、上のプログラムを動かしてみると、1秒に1回乱数の表示器から出た値が、ループが終わると配列に順番に並ぶ様子が確認できるはずです。
これはかなり便利な性質です。勝手に一つ一つの値をForループ終了後に配列にして返してくれるので。この性質を使うと、例えば「3の倍数を3から順に10こ配列に入れて用意したい」という場合に、いちいち配列定数に値を入れて用意しなくても一瞬で用意できます。
なお、例えば上の図のブロックダイアグラムで、Forループを出口トンネルを介して突き抜ける前後で、ワイヤの太さが変わっていることに気づくでしょうか?
このワイヤの太さは、データの種類を表しており、細い線が単一のデータ(スカラーと呼びます)、少し太いのが1次元の配列(自動指標付け有効から出てスカラーが配列になったということです)を表します。ちなみに、2次元以降の配列だともう少し太いワイヤとなります。
さて、この自動指標付け有効の概念がわかったら、サイコロを100回振ってその平均を出すというプログラムも組めるはずです。
乱数を1~6にするには、6をかけてから整数にする必要があります。これには数値パレットの「切り上げ整数化」が便利そうです。また、平均を出す関数は「数学」パレットの「確率&統計」の中にあるのですが、Ctrl + Spaceで出るクイックドロップで探すのが便利だと思います。
プログラムの一例を以下に載せますね。
サイコロを振った際の出る目の期待値は3.5なので、試行回数(ループ回数)を増やすほど、3.5に近づくのが分かると思います。
WhileループとForループの性質
さて、一通りWhileループとForループの使い方を見てきた今、改めてWhileループとForループの違いを見てみます。実は、ループの止め方以外にも細かい違いがいくつかあります。
- ループの実行回数について
- トンネルの仕様について
以下、それぞれの内容についてみていきます。
ループの実行回数について
WhileループとForループでは、ループの実行回数の仕様に違いがあります。具体的には
- Whileループは必ず1回は実行される
- Forループは実行されないことがある
という違いがあります。
シンプルに、Whileループの条件端子にTRUEを、Forループのカウント端子に0を配線したプログラムを組みます。違いが分かるようにそれぞれのループに乱数を置いて、Forループの方はその乱数の値をForから出して表示させます。
これを実行すると、Whileループの中にある乱数の値が表示器に表れます。Whileループの反復端子が0のままプログラムが終了しているので、1回しか実行されていないことが分かります。
一方でForループの方は一回も実行されていません。for乱数やfor反復には0と表示されていますがこれらはデフォルトの値です。乱数の値をfor結果配列で見ても、一つも値が入っていないことから、Forループは本当に一回も実行されていない状態になっています。
Forループをあえて実行回数0にする意味はないと思います(何かの演算の結果の数値をカウント端子につないでその数値がたまたま0になることはあり得るかもしれないですが)。一方で、Whileループの条件端子にTRUEを入れるのは実は意味のあるプログラムになります。ただこの内容は少しアドバンスな内容になるので今回は割愛します。
トンネルの仕様について
続いてトンネルの仕様についてです。先に結論から言うと、
- Whileループはデフォルトで自動指標付けが無効
- Forループはデフォルトで自動指標付けが有効
という違いがあります。
Forループの自動指標付けについては既に扱ってきました。一方で、Whileループの時には扱っていませんでしたよね?ではWhileループでもForループの時と同様なプログラムを新規で用意してみます。
Whileループの場合、ループの枠とワイヤの交点である出力トンネルは、Forループの時のそれとは少し見た目が変わり、塗りつぶされた四角になっています。
これは、自動指標付けが無効な出力トンネルとなっています。文字通り、自動指標付けがされないので、ループの一番最後の値一つだけがループから出てきます。試しに右クリックで表示器を作成すると配列ではなく単体のスカラーになっていることが分かると思います。
WhileとForループの違いでわざわざ「デフォルトで」と書いたのは、これらの設定が変更できるからです。ループごとに変更を変える必要はあるのですが、Forループに対して自動指標付けを無効にしたり、Whileループに対して自動指標付けを有効にすることもできます。
下の図ではWhileループの出力トンネルの設定を変えていますが、Forループも全く同様に操作できます。
デフォルトの設定が違う理屈としては、whileループはユーザーの好きなタイミングでループを止めることができるので最後に計算された値を使用することが多いのに対し、Forループはループの回数が決まっているので配列用のメモリを確保しやすいために自動的に指標付けを行うようになっているから、なようです。
トンネルの種類
なお、WhileループでもForループでも、出口トンネルを右クリックして表示される選択肢にある「最後の値」、「指標」以外に「連結」という物があります。これはループの中で既に配列を扱っているときに、ループごとのデータを全て連結して大きな配列にする場合に使用しますが、今はまぁ気にしなくていいです。
どっちかというとその下の「条件」の方が覚えがいがあるかもしれません。この条件を押すと、トンネルの下にハテナマークがつきます。ハテナマークが緑色になっていますが、緑といえばブールデータタイプです。つまり、このハテナにはTRUEやFALSEといったブールデータを配線することになります。
条件、という名前が表す通り、ここには毎ループごとに「配列に含めていいかを条件分岐で判定する」コードの結果を配線します。
動作を確認してみます。例えば、Forループの中に乱数を置いて、値を取り出すのですが、「0.5よりも大きい値しか出さない」という形で組んだとします。さらに、それが何回目の配列で起きたかも表すためにもともとの配列(条件を設定していない配列)と反復端子も組み合わせてみます。
これを実行すれば、条件で指定した値だけを取り出せていることが分かると思います。
Forループは配列と相性がいい
トンネルの話が出たのですが、今まで見たのは出力トンネルしか扱っていませんでした。
出力があるということは入力も当然あります。そして入力トンネルの仕組みを見るとForループと配列の相性がよいことを知ることができます。次のブロックダイアグラムを見てください。
このプログラム、実行できるでしょうか?何回ループを回していいかのカウント端子が配線されていないですよね。ですがプログラムはエラーなく動作できます。
実は、Forループに対して配列を入力用に配線した場合、Forループはその配列の要素の数分だけ回り、かつ入力トンネルから毎回一つずつ要素を取り出して実行します。これは本当に便利な機能です。
例えば上記の図のプログラムを動かした場合に結果を見てみます。
毎ループごとに、配列の各要素を1から(指標0から)順番に取りだし、そこに処理を加えて次のループへ、を繰り返し、すべての要素に対して処理を終えたら配列のデータを得られます。
ただしこのような書き方をするときのポイントとして「複数の指定があった場合には、一番数が小さいものが採用される」ということに注意する必要があります。例えば下のような場合を考えます。
カウント端子では5が指定されていて配列1はサイズが3、配列2はサイズが2の場合、Forループは2回しか回りません。このときの動作の結果がどうなるか、実際にやってみると
はい、一番短い配列に入っていた数の分だけForループが回り、サイズが3の配列の最後の要素はなかったかのように何にもされていません。そのため、このようなサイズの異なる配列をForループに複数入力する際には注意する必要があります。
前のループの値を使用するには(シフトレジスタ)
WhileループとForループについて重要な内容として、最後にシフトレジスタの話をして終わります。
繰り返しの処理の中で、「一つ前のループで出た結果を次のループに持ち越したい」ということが良くあります。前の結果にさらに処理を加える、とか前の結果を元に次の処理を変える、みたいなことですね。
これは今までのループの説明では出てこなかった「シフトレジスタ」によって実現できます。この機能の出し方は、ループの枠組みを右クリックして「シフトレジスタ」を選択すると出てきます。
一つシフトレジスタを用意すると、ループの左右に一対で表れます。イメージとしては、右側のシフトレジスタに渡された「あるループの結果の値」をループの枠をぐるっと反時計回りに回って左側のシフトレジスタから取り出して次のループで使用できるようになる感じです。
試しに下のようなプログラムを用意してみます。新規のviで作ってみます。
このプログラム、実行してみてください。待機の関数でループの速度を制限しているので、数値の表示器に表れる各ループごとの値が確認できるはずです。一つずつ増えていますよね?これはループ間でシフトレジスタを介してデータを受け渡している状態です。
さて、実行し終わったときに表示器には4という数値が出て終了しています。面白いのは、このプログラムを何度も実行させた時です。止まったプログラムを再度動かしてみます。その結果が下の図です。
表示器に示された数が4ではなくなっています。また次に実行して、実行ごとに結果が変わっていきます。まるで、前の実行結果から再開しているみたい・・・
そう、その通り、続きから再開しているんです。前のプログラム実行の最後の結果を、次の実行の最初のシフトレジスタからの値として使用しているんです。
シフトレジスタそのものは、ある意味「メモリ」の機能を持っています。このメモリは、LabVIEW自体を消すまで残ります。(より正確には、このシフトレジスタがあるプログラムがLabVIEWのメモリにロードされてい続ける限り残っているようですが)。
例えばLabVIEWを一度終了してから再度このプログラムを実行するとまた最初から始めることができます。
いやいや、毎回実行結果を同じにしたい、あるいは、毎回特定の値がループの最初のシフトレジスタから出るようにしたい、そんな場合には、シフトレジスタの左に定数や制御器をつけます。
試しに制御器に0を入れて実行します。何度実行しても結果は変わらないですよね?このようにシフトレジスタに左から値を渡して、ループの始まりのときの値を指定することをシフトレジスタの初期化といいます。
シフトレジスタの初期化を行うことで、毎回ループの始まりの値を指定することができるわけですが、敢えて初期化を行わないパターンがあります。シフトレジスタが値を保持してくれる機能を利用するというプログラムの書き方ですね。こちらについては少しアドバンスな内容になるのでここでは扱いません。
なお、シフトレジスタについては、一度作成した入力および出力トンネルをシフトレジスタに変換することも可能です(逆も然りです)。既に作ったプログラムを途中から変更することも簡単なので試してみてください。
シフトレジスタでフィボナッチ
一つ前のループの値が使えることが分かりました。でも、他にこんな疑問を持つ方がいるかもしれません。「前の値ではなく、前の前の値をとることはできないの?」
できます。シフトレジスタを複数積み重ねることで実現できます。これをするには、左側のシフトレジスタを下に引っ張ってやります。
これはロケット鉛筆のような仕組みになっています(最近の方にはロケット鉛筆って伝わらないかもしれないですが)。
図のようにしている場合、下の段からは「前の前の値」が出てきます。上の段からは「前の値」が出てきます。もっと多く「前の前の前の・・・・」と用意することもできます。
シフトレジスタの値をたくさん扱うのは、例えばなにか測定値の移動平均を出すのに役立ちます。移動平均、というのは「ある点からさかのぼって何点かの値の平均」を毎回計算した結果の集まりですね。
こちらについてはNIサンプルファインダにサンプルがあるので一度確認してみるといいと思います。
では、ループの記事の集大成として、フィボナッチ数列がN(任意の値、制御器で指定)よりも大きくなるのは何項目か求めるプログラムを作ってみてください。
フィボナッチ数列を聞いたことがない方のために、フィボナッチ数列は、文字で表すと
「二つ前の値と一つ前の値を足して今の値とする」
というルールで数字を並べてできる数字の列です。ルール上、最初の数値と2番目の数値はあらかじめ決めておく必要があります。私は数学者ではないので厳密に言うとどうかは知りませんが、例えば最初の数値は0、次の数値は1とすると、数値の列はこのように並ぶはずです。
0、1、1、2、3、5、8、13、21、34・・・
この並びの最初の0を一項目、次の1を二項目、その次の1を三項目、次の2を四項目・・・と数えることにします。
うまくできましたか?プログラムの一例を以下に示しますね。
され、これでループについての紹介は以上です。結構内容が多いかと思った方もいるかもしれません。でもプログラムを作る以上、ループを使うのは避けて通れないと思います。
使い分けなどは難しくないと思いますが、ループの止め方以外の注意点も意識しないと意図した動作にならないことがあるので、色々試しに書いてみて慣れることをオススメします。
また、ループに関連して重要なタイミング関数の使い方についても改めて確認をしておくことをオススメします。関連記事を書きました。
次回は、ループと親和性の高いチャート、グラフについて扱っていこうと思います。視覚的にデータを見せる際にこれらを使うのですが、似て非なる二つの表示方法をどう使い分けるのかを中心に紹介しようと思います。
もしよろしければ次の記事も見ていってもらえると嬉しいです。
ここまで読んでいただきありがとうございました。
コメント