Quantcast
Channel: ループタグが付けられた新着記事 - Qiita
Viewing all 91 articles
Browse latest View live

SVGループアニメーションの作り方

$
0
0

SVGループアニメーションの作り方

鳥が羽ばたくとか、人が歩くとか、繰り返しの動作は多い。
繰り返しアニメーションは、アニメの基本である。
SVG を使ったループアニメーションの作成方法について考察する。なお、ここでは、JavaScript は使わず、SVGだけで実現する方法について述べる。

SVG SMIL アニメーション

SVG(Scalable Vector Graphics)は、ベクトル図形を表現する方法と思っている人も多いと思うが、実は、アニメーションを表現する機能をもっている。

現在では、Internet Explorer を除く、ほとんどのブラウザが、SVG SMIL アニメーションをサポートしており、iPhone や、Android でもプラグインなしで再生できる。どのブラウザで再生できるかは、ここを見よう

図形1つの繰り返し

SVGは、円を書いて動かすだけなら簡単である。まず、コードをみていただこう。

loop01a.gif

<svg>
<circle fill="blue" cx="150" cy="100" r="50" />
<circle fill="orange" cy="100" r="20" >
<animate attributeName="cx" from="50" to="250" dur="5s" repeatCount="indefinite" />
</circle>
</svg>

青い玉の手前をオレンジの玉が何回も横切ることになる。

  • この動作を確かめるには、CodePenを使うとよい。このサイトを Chrome / Firefox/ Safari などで開き、上の「New Pen」ボタンをクリックし、HTMLの中に、上のSVGのソースをペーストするとアニメーションが見える。ソースを修正するとより理解が進むだろう。
  • 簡単に説明すると、fillは色、cx,cy は中心座標、r は半径。
  • <animate がアニメーションの指定で、attributeNameで指定された cx(中心のX座標) を、5秒(dur="5s")で、50から250まで(from="50" to="250")変化させる。repeatCount="indefinite" が無限ループ設定だ。

立体的にまわる(玉が2つ)

次に、青い玉のまわりを立体的に回るようにしてみよう。
下のように2つの玉を組み合わせれば、立体的に回るように見える。
loop01b.gif

<svg>
<circle fill="orange" cy="100" r="20">
<animate attributeName="cx" from="250" to="50" dur="5s" repeatCount="indefinite" />
</circle>
<circle fill="blue" cx="150" cy="100" r="50" />
<circle fill="orange" cy="100" r="20">
<animate attributeName="cx" from="50" to="250" dur="5s" repeatCount="indefinite" />
</circle>
</svg>

立体的にまわる(玉が1つ)

では、一つの玉が回るようにするにはどうしたらよいか。
その場合、一つの玉が動いている間、もう一つの玉を消しておかないといけないので、もはや、repeatCount="indefinite" は使えない。
2つの玉に、ラベル(id="o1", id="o2")をつけて、
もう片方の玉の動作が終了(o1.end、o2.end)したら
スタート(begin)するようにするしかない。
loop01c.gif

<svg>
<circle fill="orange" cx="-50" cy="100" r="20">
<animate id="o1" begin="0;o2.end" attributeName="cx" from="250" to="50" dur="5s" />
</circle>
<circle fill="blue" cx="150" cy="100" r="50" />
<circle fill="orange" cx="-50" cy="100" r="20">
<animate id="o2" begin="o1.end" attributeName="cx" from="50" to="250" dur="5s" />
</circle>
</svg>

では、これがもっと複雑なアニメーションになったらどうするのか?
このように、id=xxx と、begin=xxx.end をつけていく方法では、図形の数が増え、動きが複雑になると、id の数が半端でなくなる。さらに、間に別のコマを挿入したくなったとき、idのつなぎかえが発生することになり、大変なことになる。

相対時間で指定するループアニメーション

これを解決するために、以下のように、ダミーの <rect> ループをもうけ、それに対する相対時間で動作を指定する方法を考えたので紹介する。コードは以下のようになる。この方法であれば、id=xxxは、ループアニメーション1つに対して、1つ定義すればよい。内部の動作は、相対時間で指定する。多くの時間は、各図形に対して共通なので、もし修正が発生しても、文字列の一括置換で容易に変更できる。

<svg>
<rect>
<animate id="o1" begin="0;o1.end" dur="10s" attributeName="visibility" from="hide" to="hide"/>
</rect>
<circle fill="orange" cx="-50" cy="100" r="20">
<animate begin="o1.begin" attributeName="cx" from="250" to="50" dur="5.05s"/>
</circle>
<circle fill="blue" cx="150" cy="100" r="50" />
<circle fill="orange" cx="-50" cy="100" r="20">
<animate begin="o1.begin+5s" attributeName="cx" from="50" to="250" dur="5.05s"/>
</circle>
</svg>

  • <rect>が10秒間隔(dur="10s")を作るダミーの図形である。id="o1" begin="0;o1.end" によって10秒ごとに起動する。"0;o1.end"は起動条件が、0秒もしくは、o1が終了したときの2つあることを意味する。
  • attributeName="visibility" from="hide" to="hide"の部分は、ほかの命令でもよいが、とりあえず、見えない属性を設定している。
  • id="o1"が10秒ごとに起動するので、玉は、o1と同時(begin="o1.begin")もしくは、o1の5秒後(begin="o1.begin+5s")にスタートさせる。
  • 玉が移動する時間を5.05秒(dur="5.05s")としているのは、5秒きっちりにするとブラウザによってちらつきが生じるためだ。

SVGループアニメーションの例

この方法を用いたループアニメーションの例を、CodePenにあげた。(こちら Komori's SVG Animation)IE以外なら、ほぼ見えるはずである。IE は SVG のSMILアニメーションをサポートしていないため、静止画にしかならない。

SVGループアニメーションが作れるエディタ

Ubuntu, もしくは、Raspberry Pi で動作するエディタ「9va-pi」がここからダウンロードできる。Raspberry Pi は 4000円程度で購入できるし、Ubuntu は、中古のXPパソコンを入手すれば、OSは無料である。Windows版「9va-win」, [Macintosh版「9va-mac」 もあり。


関連記事

9VAeきゅうべえ のご紹介

  1. 無料ソフトでアニメを作ってみよう(9VAe きゅうべえ)
  2. インストール方法
  3. よくある質問

JAVA掛け算の九九の答えが全部表示されるようにしなさい

$
0
0

▪️問1 掛け算の九九の答えが全部表示されるようにしなさい
▪️方法①

public static void test13() {
        for(int i = 1; i < 10; i++) {
            for(int j = 1; j < 10; j++) {
            int k = i * j;
            System.out.print(i + "×" + j + "=" + k);
            System.out.println("");

        }
            System.out.println("");
        }
    }

▪️方法②

public static void test12() {
        for(int i = 1; i < 10; i++) {
            for(int j = 1; j < 10; j++) {
            System.out.print(i + "×" + j + "=" + i * j);
            System.out.println("");

        }
            System.out.println("");
        }
    }

▪️方法①実行結果
Marimo-no-MacBook-Air:JAVA marimo$ java test21
1×1=1
1×2=2
1×3=3
1×4=4
1×5=5
1×6=6
1×7=7
1×8=8
1×9=9

2×1=2
2×2=4
2×3=6
2×4=8
2×5=10
2×6=12
2×7=14
2×8=16
2×9=18

3×1=3
3×2=6
3×3=9
3×4=12
3×5=15
3×6=18
3×7=21
3×8=24
3×9=27

4×1=4
4×2=8
4×3=12
4×4=16
4×5=20
4×6=24
4×7=28
4×8=32
4×9=36

5×1=5
5×2=10
5×3=15
5×4=20
5×5=25
5×6=30
5×7=35
5×8=40
5×9=45

6×1=6
6×2=12
6×3=18
6×4=24
6×5=30
6×6=36
6×7=42
6×8=48
6×9=54

7×1=7
7×2=14
7×3=21
7×4=28
7×5=35
7×6=42
7×7=49
7×8=56
7×9=63

8×1=8
8×2=16
8×3=24
8×4=32
8×5=40
8×6=48
8×7=56
8×8=64
8×9=72

9×1=9
9×2=18
9×3=27
9×4=36
9×5=45
9×6=54
9×7=63
9×8=72
9×9=81

Rubyのループ中,とある条件では,処理を行わないには

$
0
0

ループ処理中に,ある条件のときだけは処理を行わず
次のデータを処理したい.

Rubyの前によく使っていたVBAでだっら
(gotoは使ってはいけないと習ったが)
ラベルと go to を使って 

For i = 1 To 10
    if cht = "A" then goto skip
    ...
    skip:
Next

としていたが,Rubyでは

for i in 1..10 # next はここにジャンプ
  next if i==5
  ...
end

としたら,iが5のときのみ処理を飛ばせると知った.

たとえば,コメント行を読み飛ばしたいときに使える.

「Rubyレシピブック」にも書いてあるのだが,かなりの間
気がつかないでいた.

Bashでいろいろループする

$
0
0

配列をハードコーディングしてループ

items=(
    "altair"
    "betelgeuse"
    "canopus"
)

for item in "${items[@]}" ; do
    echo "[ ${item} ]"
done

ハードコーディング パターン2

items[0]="altair"
items[1]="betelgeuse"
items[2]="canopus"

for item in "${items[@]}" ; do
    echo "[ ${item} ]"
done

連番を生成してループ

for i in {1..10} ; do
    echo ${i}
done
for i in `seq 1 10`; do
    echo ${i}
done

ファイル一覧をループ

for file_name in * ;do
    echo ${file_name}
done
files=(`ls -1 somedir/`)
for file_name in "${files[@]}"; do
    echo ${file_name}
done

引数をすべてループ

for arg; do
  echo ${arg}
done
for arg in "$@"; do
  echo ${arg}
done
$ ./hoge.sh altair betelgeuse canopus

Thanks to: @akinomyoga, @mpyw

ファイルを読み込んで1行づつループ

while read line ; do
    echo ${line}

done < ${DATAFILE}

二次元表

#!/usr/bin/env bash

# iOS 公開用に PNG 画像のアイコンサイズを変更するスクリプト

sizes=(
"29 29"
"40 40"
"50 50"
"57 57"
"58 29@2x"
"60 60"
"72 72"
"76 76"
"80 40@2x"
"87 29@3x"
"100 50@2x"
"114 57@2x"
"120 60@2x"
"144 72@2x"
"152 76@2x"
"167 83.5@2x"
"180 60@3x"
)

if [ ! "${1}" ]; then
    echo "usage: ${0} <image-file>"
    exit 1
fi

bn=`basename ${1}`
bn=${bn%.*}
DN=`dirname ${1}`
for size in "${sizes[@]}"; do
    s=(${size})
    out_file=${bn}_${s[1]}.png
    echo ${s[0]} ${out_file}
    # sips --resampleWidth ${s[0]} --out ${out_file} ${1}
    convert -resize ${s[0]}x ${1} ${out_file}
done

ループの考え方

$
0
0

なぜ ここの数字は 2 なのか、とか コメント書くのが めんどくさいので
Qiita に書いて プログラムのコメントには 参照:参照:、ばっかり書くことにする。

画像が16枚ならんでいるとしよう。
Gazo

わたしの感覚では 左上から 0、1、2 …… と番号が振ってあると分かりやすいんだが、UnityのTexture2D は 左下が原点と言って聞かん坊なので それに合わせることにする。ループ・カウンターは

Yが 3から -1の手前、
Xが 0から 4の手前、

まで回せばいい。C#風に書くなら

// オヌヌメの書き方
for( int y = 3; -1 < y; y-- ){
    for( int x = 0; x < 4; x++ ){
    }
}

だろうか。ここで、 次のようには書かない という考え方がある。

// わたしが避ける書き方
for( int y = 3; 0 <= y; y-- ){
    for( int x = 0; x <= 3; x++ ){
    }
}

違い

書き方A 書き方B
3から-1の手前まで 3から0まで
0から4の手前まで 0から3まで

これは、数字をなんだと思ってるかによって 違ってくる。

指を使って 1、2、3 と数えるときは 書き方B が分かりやすい。
Gazo

これで十分じゃないか、と考える人は
Gazo
3時に待ち合わせしましょう、という時に

Gazo
3時29分にやってくる。

3時に待ち合わせといったら 3時00分だろう! というのは 解釈の1つだと思うんだが 口に出すと怒られるので言わない。

一応もうひとつ例を出しておこう。
Gazo
ここで 青い日と 赤い日 は、休日ではなく遊ぶ日 だ。ここで仕事の締め切りが17日なら、仕事はいつまでに終わらせればいいだろうか。

17日に締め切りといったら 17日00時00分だろう!

20日の朝6時30分 とか答えていいのは インディーズ・ゲーム開発者だけであって わたしの感覚的には 17日いっぱいの17時30分ぐらいかな、と思うんだが これも 解釈の1つだろう。

つまり 指には、
Gazo
3.99 とか無いのだ。かっこよく言うと 離散的だ。

むしろ
Gazo
くそ小学生から見れば 数字に途切れなんか見えない。

りんご1つと りんご1つを足すと りんご2つというのは りんごのサイズはみなだいたい同じという 算数を教えたい教師の都合のようなもので、
Gazo
くそ小学生には りんごが2つであることに どういう意味があるのか分からない。

むしろ
Gazo
りんごは 沼の下で1本の根につながっているかもしれない。ワンチャンある。算数が大嫌いだ。

つまり これらの方法は 1点を指そう、としているところに問題がある。
Gazo

オヌヌメの方法は 閾値(しきいち)を使うことだ。

Gazo
この図が言わんとしていることは、0時から、3時は含まないその手前だ。

日本語にすると 3時より手前で待ち合わせしよう、ということになる。

じゃあ、待ち合わせ場所に 0時に来てもいいし、2時59分59秒に来てもいいことが分かる。

あのカレンダーで言うなら、
Gazo

日本語で言うなら 18日より手前が締め切りだ、 ということになる。

17日の23時59分59秒までオッケーだろう。実際 イベントの締め切り日の23時45分から Webサイトを開いてフォームにキータイピングを開始する時間ぎりぎり平気症の人もいるだろう。

じゃあ、23時59分59秒までオッケー でいいじゃないか、という人もいるが 23時59分59秒999ナノ秒はオッケーなのか?ダメなのか? という疑問が晴れない。解釈の1つだ。

数字は どんなにピンポイントで示しても 小さな端数 が気になってしまう。
18日の手前まで という閾値を用いた方法は 小さな端数 を気にしなくてよくなる 良い方法だ。
Gazo
くそ小学生も 一時的におとなしくなるだろう。

これからは もう こう書きたくなるだろう。

// オヌヌメの書き方
for( int y = 3; -1 < y; y-- ){
    for( int x = 0; x < 4; x++ ){
    }
}

この症状が ひどくなってくると、こんな書き方をしたりもする。

// 我流
for( int tickY = THIS_Y; NEXT_Y < tickY; tickY-- ){
    for( int tickX = THIS_X; tickX < NEXT_X; tickX++ ){
    }
}

tick というのは チックタック時計が進んでいくことだが、刻んでいる。図で示すと……。

Gazo
例えば THIS_X が 10 で、NEXT_X が 18 ということになる。tickX は 10から17だ。

この方法の利点の1つとして、NEXT_X を THIS_X に移せば、そのまま次のディビジョンが始められることだ。ディビジョンという言葉が難しければ、セカンド・シーズンとか 次の区画とか そんな意味だ。

Gazo
ホリデーから始まるディビジョンとか 嫌なものを見た。

で、このことの何が利点なのかというと、
Gazo
開始地点だけ示せば いいわけだ。これが閾値を使う利点。

閾値を使わない場合、
Gazo
開始地点と、終了地点の2つを毎回 用意することになる。 これが 煩わしい。 開始地点があれば、終了地点も兼ねるのだから。

再掲しておこう。

// オヌヌメの書き方
for( int y = 3; -1 < y; y-- ){
    for( int x = 0; x < 4; x++ ){
    }
}

// わたしが避ける書き方
for( int y = 3; 0 <= y; y-- ){
    for( int x = 0; x <= 3; x++ ){
    }
}

お絵描きに疲れたので いったん休憩して またこの続きに記事を続けたい。

RGBは 255,255,255 とは限らない

ところで 月面宙返り3回転ひねりをしていいだろうか。
Gazo

バグが取れんなぁ、と思って たんたんとログ出力する内容を広げていたんだが、RGB値を見てみると 0.5019608 とか出ていた。

128 を 255 で割ると 0.50196078431…… なので、何が起こったのが想像に難くない。

どうも、Unityは何でもかんでも数字を 0.0~1.0 に normalization したいのかもしれない。おっちゃん、泣けてくるほっほ。

コメントに一言 書いていてくれれば こんな記事 書かずにさっさと 255 掛けてたんだが……。

一応 あとで 記事は続ける。

バグを取ること

Visual Studio 2015 のC#のライブラリで書いたコードを UnityのC#ライブラリで書いたコードに移植したつもりなんだが、
Gazo
生成物を比較してみると 部分的に同じで、部分的に異なっている。

処理の途中の生成物を出力してみるのもいいかもしれない。

コピペ・プログラマーもこんな気分だろう。おっ、いけるいける、と思って Rectangle を Rect、Bitmap を Texture2D に置き換えたら コードは張り替えられてコンパイルも通るのに 処理内容の詳細が違うわけだ。

ぶっちゃけ Y座標 に関わるところが ごっそりおかしく、その他に ちょいちょい違うところがある程度なので 見た目の相違ほどのひどさもないと思うが。

あとで 記事は続ける。

ループのことなんかどうでもよくなってきた

不具合の探し方なんだが、
Gazo
左がバグった出力結果、右が正常な出力結果。

数字を見てみると -1.04 ずれているようだ。

そもそも この数字が何かというと、Unity の Offset Y に入れる値なんだが、元の画像では1ピクセルであっても、Unityの画面上では グリッド1つ 100ピクセルだったり、ゲーム・オブジェクトのサイズや、拡縮のスケールが加味されていたりして なんだか分からない計算結果になっている。

まあ、ここでは 100倍して -104ピクセルずれている、と見ていい。

で、エラーが起こっている個所は 4つごとに1つ飛ばしになっているが、
立ち、跳ね、走り、屈み、その他 を1セットで 4キャラ分あるんだが、立ち、跳ね、走り、屈み が全部間違ってますよ、ということだ。

Gazo
17 というのは 左上を0として17枚目、ちょうど赤い枠が描かれている画像だが、ここで 赤い枠の場所を読み間違えているというものだ。

で、1スライス 128x128ピクセルサイズで、グリッドは 8ピクセル単位。
出力された数字は 128x128ピクセルの枠の中心から相対的に全部 縦-104ピクセルずれているというのを示していて、104ピクセルも上にずれているのだろうか。

このタイル画像は 1024x1024 サイズで、Texture2Dは左下原点ということで、Yは1023~-1の手前までループするようにしている。

あ、違う

Yは上に行くほど数字が増えるんだから、-104ずれているんだったら 下に104ずれているんだった。

例えば -128 ずれてるんだったら 分かりやすかった。1スライス分、1段下を見ているということだ。

128 - 104 = 24

この 24 は何か。確かに、赤い箱を見ると グリッド3セル分、24 ピクセルの厚みがあるが……、ん?

Gazo

24 下にずれているって?

Gazo

正しい値の -4 ピクセルというのは、画像の中心から上に 4ピクセル上がったところに 赤枠の中心Y があるということなんだが、
(注・この記事で全部間違っているが、正しい値は 4 ピクセルであって、画像の中心から上に 4ピクセル上がったところに赤枠があるというのは合っている。 -4 ピクセルという数字もあるがこれは別の画像の赤枠だ。この記事を読む上で特に困らないだろう)

例えば

Gazo

そもそも 8 ピクセル上にずれていて、-1段目から検知を開始していて、3段目を調べると その1段下に赤い枠があって検出してしまっていて、その赤枠の中心位置を求めるときに Rect の center.y プロパティーを拾っているんだが 縦幅の半分が中心位置とすると プラス値になっていて 座標は 折り紙を半分折り返したように上に上がってしまう、

と考えてみる。うーん、苦しい。

あ、直った

Gazo

center プロパティーは、プラス、マイナスが逆なんじゃないか、と思って自分で書き直したんだが、

Gazo

直った。

あっ、そう……。

よく分からんバグだった。

プログラムのコメントを楽しようと思ってQiitaに書いているんだが

Texture2Dは左下原点の座標で、Rectは左上原点の座標なんで、あとはよろしく、みたいなコメントでいいのだろうか。
Gazo
上図赤い枠が Texture2D。いわゆる画像。左下が原点でYは上にいくほど増える。緑色の枠が Rect。いわゆる矩形サイズ。左上が原点でYは下にいくほど増える。

例えば こんな座標があるとき、
Gazo

Texture2D では Yは上にいくほど増えていくので、
Gazo
そうか、上に行くほど増えていくんだな、と思うわけなんだが

Rect では Yは下にいくほど増えていくので、
Gazo
縦幅の半分を足せば ちょうど これぐらい。

そうですか。

じゃあ、まあ これで。

Yをループで回そう。

で、Yをループで回していくんだが、例えば 1024x1024 の画像なら、1023から-1の手前まで読み取っていけばいい。

ソースコードの中では、2つの -1 が出てくる。これが何かなんだが……。
Gazo

1024 から 1 引いて 1023 にして、
0 から 1 引いて -1 にしている。

3時59分59秒999ナノ秒 の理屈から言えば 1023.99999 にしなくていいのか という発想にもなるんだが、画像は 1ピクセルより小さな端数は出てこないのでこれでいい。

最後に画像を 貼っ付けておく。
Gazo

これで記事を終わる。

JavaScriptでループの練習をする

$
0
0

N予備校の章

第1章 08.JavaScriptのループ

はじめに

●この単元をやりきるのに1時間かかった。

分かったこと、できるようになったこと

●JavaScriptでループ(繰り返し)を行うにはfor文を使う

for(var i=0; i<100; i++){
  }

●例題の「1から10万まで数えてみよう」を答えを見ずに解けた。

感想

●練習問題の「Fizz Buzzを実装してみよう」が途中で煮詰まり、解けなかったのが悔しかった。

終わりに

●今回の単元で体験入学でできる範囲が全部おわった。復習を兼ねて、まとめを作ってみようと思う。

ループにまつわる高速化の話

$
0
0

プログラムを高速化したいときは、時間計測結果を元に戦略を立てるのが前提であるべきだと思うけど、傾向としてループ部分がクリティカルな状況はよくある。

1回だけ呼ばれる処理を100ms高速化するより、
100万回呼ばれる処理を0.01ms高速化する方が効果が大きくてハードルが低い(ことが多い)

平たく言えばこういうのをどう高速化するかの話。

for(auto &&obj: objects) {
    obj.update();
}

実際にこう書くと速くなるというよりは高速化手法のバリエーションの話だと思って欲しい。
これ意味ないとか逆効果とかの指摘歓迎します。

ループ内を高速化するアプローチ

Object::update内を速くするのが大前提なんだけど、一般的な議論は難しいのでよくある状況を書いとく。

フラグチェックを外に出す

もしこんなコードがあったら

void Object::update() {
    if(this->shouldUpdate()) {
        // updateの内容
    }
}
for(auto &&obj: objects) {
    obj.update();
}

これの方が無駄な関数呼び出しがない分速い(ことが多い)

void Object::update() {
    // updateの内容
}
for(auto &&obj: objects) {
    if(obj.shouldUpdate()) {
        obj.update();
    }
}

加えてshouldUpdate関数をinline指定しておくと速い(ことがある)

inline bool Object::shouldUpdate() {
    return is_awake;
}

実体を使う

下記は前者よりも後者の方が、ポインタのデリファレンス分有利。
実体で良いならそもそもポインタを使う設計にしてないと思うので、使う場面はほぼないとは思う。

std::vector<Object*> objects;
for(auto &&obj: objects) {
    obj->update();
}
std::vector<Object> objects;
for(auto &&obj: objects) {
    obj.update();
}

ループ対象を厳選するアプローチ

shouldUpdateみたいなフラグがあるとしたら、それがfalseのやつはループから除外すれば良い。

  • フラグチェックのコストが高い
  • sleep状態(updateを呼ぶ必要がない)にして良いオブジェクト数が多い
  • awakeとsleepの切り替わりが頻繁でない

など、フラグチェックよりもコンテナへの出し入れの方がコストが安い場合は有効(なことが多い)

ObjectArray awakeObjects, sleepObjects;

void update() {
    for(auto &&obj: awakeObjects) {
        obj.update();
    }
}
void sleep(Object &obj) {
    awakeObjects.remove(obj);
    sleepObjects.add(obj);
}
void awake(Object &obj) {
    awakeObjects.add(obj);
    sleepObjects.remove(obj);
}

ループ自体にかかるコストを減らすアプローチ

ループ内のコストが十分安いと、ループ自体にかかるコストが無視できなくなってくる。
例えばよくある

std::vector<Object> objects;
for(int i = 0; i < objects.size(); i++) {
    objects[i].update();
}

みたいなコードだとObject::update()の他に

  • vec.size()の呼び出し
  • ivec.size()の戻り値との比較
  • iのインクリメント
  • vector要素へのランダムアクセス

がループ回数分呼ばれるので、このあたりに無駄がないか考えてみると良い。

コンテナを選ぶ

[C++] STLの型の使い分け
この記事でほぼ話は終わってるので読むと良いと思います。

イテレーションの速度に関してだけいうなら、vectordequeを使っとくと良いと思う。

一度だけで良い処理はループから出す

これより

for(std::size_t i = 0; i < objects.size(); ++i) {}
for(auto it = std::begin(objects); it != std::end(objects); ++it) {}

これの方が速い(ことが多い)

for(std::size_t i = 0, end = objects.size(); i < end ++i) {}
for(auto it = std::begin(objects), end = std::end(objects); it != end; ++it) {}

よくあるこれも

for(int y = 0; y < image.getHeight(); ++y) {
    for(int x = 0; x < image.getWidth(); ++x) {
    }
}

こうすると速くなる(ことが多い)

int w = image.getWidth();
int h = image.getHeight();
for(int y = 0; y < h; ++y) {
    for(int x = 0; x < w; ++x) {
    }
}

ループ回数を減らすアプローチ

これは半分冗談だけど、発想としてはあっても良いと思う。

ループ内をひらく

for(std::size_t i = 0, end = objects.size(); i < num; i += 4) {
    objects[i].update();
    objects[i+1].update();
    objects[i+2].update();
    objects[i+3].update();
}

ループ頻度を減らす

static std::size_t counter = 0;
const std::size_t skip_length = 4;
for(std::size_t i = counter%skip_length, end = objects.size(); i < end; i += skip_length) {
    object[i].update();
}
++counter;

再掲

これ意味ないとか逆効果とかの指摘歓迎します。

[PHP]while文に対してdo while文を使うメリット

$
0
0

while文の例

<?php
$i = 0;
while($i < 10){
    echo $i;
    $i ++;
}
// => 0123456789

まず\$iに0を代入し、while内で、$iが10になるまで出力されている。

do while文の例

<?php
$i = 0;
do{
    echo $i;
    $i++;
}while($i < 10);
// => 0123456789

こちらはdo while文。whileよりも先に、$iが出力されている。

たとえば$i=100なら

<?php
$i = 0;
while($i < 10){
    echo $i;
    $i ++;
}
// => (表示されない)

while文の場合、\$iが100なので、while文の1週目から「 \$i < 10 」を満たしているので、while内が処理されず、$iの出力がされない。

<?php
$i = 100;
do{
    echo $i;
    $i++;
}while($i < 10);
// => 100

do while文の場合、whileが最後に来ているので、1週目の$iが出力される。


N重ループの書き方

$
0
0

はじめに

 本記事はプログラミング初心者向けにfor文の入れ子構造を簡潔に書く方法を紹介するものです。競技プログラミングを念頭に置きながら書いていますが、競技プログラミングに限らず様々なコードを書く場面で役に立つテクニックだと思うので是非覚えておいてください。
 初心者にありがちな良くない書き方として過度に深いネスト構造があります。具体例として、ABC080のC問題を解く際、for文の入れ子を10回書いても間違いではありませんがコードの書き方として望ましくないのは明らかでしょう。
ABC080C問題
この書き方の問題は主に二つで、一つ目はコードを書くにしても読むにしてもとても辛いという点、二つ目はN重ループのNの値がコンパイルの時点で確定していなければ困ってしまう点です。個人の感覚にもよるところですがfor文の入れ子は3重が限度だと思います。それ以上にfor文を繰り返したい場合、簡潔なコードを書くための工夫を考えるべきでしょう。

問題設定

 今回は「5桁の4進数を00000から33333まで順に出力する」という問題を通して多重for文の書き方を二つ紹介します。

いけないやり方

for文の入れ子を5回繰り返します

ダメな例
include<iostream>
using namespace std;

typedef long long LL;

//ここからメイン
int main(void) {
    LL i, j, k, n, m;

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            for (k = 0; k < 4; k++) {
                for (n = 0; n < 4; n++) {
                    for (m = 0; m < 4; m++) {
                        cout << i << j << k << n << m << endl;
                    }
                }
            }
        }
    }

    return 0;
}

正しい出力は得られますが桁数が増えると大変です。

やり方1

n進数を管理するクラスを使って繰り返しを行います

やり方1
#include<iostream>
#include<vector>
#include<string>

using namespace std;

typedef long long LL;
typedef vector<LL> VLL;


//n進数を管理するクラス
class N_Number {
public:
    N_Number(LL n, LL keta) {
        this->N_Shinsuu = n;

        VLL temp(keta, 0);
        this->numbers = temp;

    }

    //数を足す
    void plus(LL a) {
        this->numbers[0] += a;
        LL size = this->numbers.size();
        for (LL i = 0; i < size; i++) {
            if (i + 1 < size) {
                this->numbers[i + 1] += this->numbers[i] / this->N_Shinsuu;
            }
            this->numbers[i] %= this->N_Shinsuu;
        }
    }

    //全ての桁が同じ数字になっていればその数字を返す。それ以外の場合は -1 を返す
    LL check() {
        LL a = this->numbers[0];

        for (LL i = 0; i < this->numbers.size(); i++) {
            if (this->numbers[i] != a)return -1;
        }

        return a;
    }

    LL getNumber(LL keta) {
        return this->numbers[keta];
    }

    LL getKeta() {
        return this->numbers.size();
    }

    LL getShinsuu() {
        return this->N_Shinsuu;
    }

    void setNumber(LL keta, LL number) {
        if (0 <= number && number < this->getShinsuu()) {
            if (0 <= keta && keta < this->getKeta()) {
                this->numbers[keta] = number;
                return;
            }
        }

        cout << "er" << endl;
    }

    void setAllNumbers(LL number) {
        LL size = this->getKeta(), i;
        for (i = 0; i < size; i++) {
            this->setNumber(i, number);
        }
    }

private:
    VLL numbers;
    LL N_Shinsuu;
};

//ここからメイン
int main(void) {
    N_Number i(4, 5);

    while (true)
    {
        string s;
        for (LL j = i.getKeta() - 1; j >= 0; j--) {
            s += to_string(i.getNumber(j));
        }

        cout << s << endl;

        if (i.check() == i.getShinsuu() - 1)break;
        i.plus(1);
    }

    return 0;
}

ネストが深くなりませんし桁数が変わっても簡単に対応できます。

やり方2

再帰を使います。

やり方2
#include<iostream>
#include<vector>
#include<string>

using namespace std;

typedef long long LL;
typedef vector<LL> VLL;

//再帰で繰り返しを行う
void ForCout(LL n, LL keta, VLL numbers) {
    if (numbers.size() == keta) {
        string s;
        for (LL i = 0; i < numbers.size(); i++) {
            s += to_string(numbers[i]);
        }
        cout << s << endl;
        return;
    }

    for (LL i = 0; i < n; i++) {
        VLL temp = numbers;
        temp.push_back(i);
        ForCout(n, keta, temp);
    }
}

//ここからメイン
int main(void) {

    VLL numbers;
    ForCout(4, 5, numbers);

    return 0;
}

再帰は少し難しいかもしれませんが便利な概念なので練習しましょう。

さいごに

 今回はネストが深くなりすぎない多重ループの書き方を紹介しました。最初に紹介した問題とは別に今回の内容を練習できる問題を一つ記事の最後に置いておきますので、一度自分で書いてみることをお勧めします。また、間違いや直した方がいい点がありましたら指摘して頂けると幸いです。
ABC119C問題

jQueryでeachを使う

$
0
0

jQueryオブジェクトを複数取得した後、ループ処理をしたいのにいつも忘れてしまうeach文の使い方をまとめました。

スキップはreturn true、ブレイクはreturn falseがポイントです。

each文で取得するHTMLはこちら

<ul class="array_test">
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
  <li>item4</li>
  <li>item5</li>
</ul>

javascriptはこのように記述します。

$('ul.array_test li').each(function(index, element){
  console.log(index + ':' + $(element).html());
});
実行結果
0:item1
1:item2
2:item3
3:item4
4:item5

スキップする(return true)

$('ul.array_test li').each(function(index, element){
  if(index == 3){
    return true;
  }
  console.log(index + ':' + $(element).html());
});
実行結果
0:item1
1:item2
2:item3
4:item5

途中でブレイク(return false)

$('ul.array_test li').each(function(index, element){
  if(index == 3){
    return false;
  }
  console.log(index + ':' + $(element).html());
});
実行結果
0:item1
1:item2
2:item3

C#におけるループ処理の速度 ~条件/演算子編~

$
0
0

この記事にはミスがあります。
自身への戒めとして残しているだけであり、参考になるものではありません。

↓ミスを踏まえてテストし直した改訂版を投稿しました!↓
【改訂版】C#におけるループ処理の速度 ~条件/演算子編~

(今度はミスがないといいなぁ)

概要

プログラミングにおいて最もボトルネックとなりやすいのが、ループ処理です。
なので、ループ処理の速度向上に役立つ知識を記述していきます。
環境やループ内の処理によっても違いが出るので、あくまでも参考程度に考えてください。

テスト環境
プロセッサ   :Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.41 GHz
実装メモリ(RAM):32.0GB
システム    :64ビットオペレーティングシステム
言語      :C# 7.3 .NET Framework 3.5
ツール     :Microsoft Visual Studio 2017
         

テスト内容

ループ内で System.Console.WriteLine() メソッドを用いて、連続した100万件の数字を出力します。
使用するテストデータは String[] testData; に格納されています。
時間の計測は System.Diagnostics.Stopwatch を使用し、10回分の平均値を結果として算出しています。

100万件のテストデータを作成するロジック
String testData = new String[1000000];
for(Int32 i = 0, len = testData.Length; i < len; i++ )
{
    testData[i] = i.ToString();
}
時間を計測する方法
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Reset();
sw.Start();

/* ループ処理 */

sw.Stop();
/* ElapsedMilliseconds メンバから経過したミリ秒を取得 */
Int64 res = sw.ElapsedMilliseconds;

ループ条件による違い

まずはループの条件による速度の違いです。
一番修正しやすい部分ではないでしょうか。
testData.Lengthプロパティを直接条件に使用する方法と、testData.Lengthプロパティを変数にキャッシュして条件に使用する方法を検証しています。

結果

結果から記載します。
詳細は後述を参照してください。

条件 経過時間(ミリ秒) 1ループあたり
プロパティを使用 18,070 0.0181
キャッシュを使用 14,609 0.0146

キャッシュした方が 3,461ミリ秒(3.461秒) 早いことが分かりました。
1ループあたり 0.0035ミリ秒 の差ですので殆ど誤差ではありますが、数千万回や数億回という膨大なループの際は効果が実感できそうですね。
大きなデータを扱う際や、ミリ秒単位での高速な処理を要求されている場合はキャッシュしてからのループが良いでしょう。

プロパティを使用

配列の長さ(ListCollection の場合は Count)を示すプロパティを使用してループ条件にする場合です。

処理
/* i < testData.Length の部分に注目 */
for (Int32 i = 0; i < testData.Length; i++)
{
    System.Console.WriteLine(testData[i]);
}

結果:18,070 ミリ秒(18.07 秒)
   1ループあたり0.0181ミリ秒

キャッシュを使用

配列の長さ(ListCollection の場合は Count)を変数にキャッシュしてからループ条件にする場合です。

処理
/* len = testData.Length の部分に注目 */
for (Int32 i = 0, len = testData.Length; i < len; i++)
{
    System.Console.WriteLine(testData[i]);
}

結果:14,609 ミリ秒(14.61 秒)
   1ループあたり0.0146ミリ秒

インクリメント/デクリメントの違い

インクリメント(i++ など、1加算する演算子)、またはデクリメント演算子(i-- など、1減算する演算子)による違いを検証していきます。

結果

結果から記載します。
詳細は後述を参照してください。

演算子 経過時間(ミリ秒) 1ループあたり
i++ 16,463 0.0165
++i 14,241 0.0142
i-- 14,338 0.0143
--i 14,721 0.0147

演算子を後方に置く場合、i++ よりも i-- の方が 2,125ミリ秒(2.125秒) 早いことが分かります。
また、演算子は前方に置いた場合、++ii++ から 2,222ミリ秒(2.222秒) 早くなっていますが、--ii-- から 383ミリ秒(0.383秒) 遅くなっています。
100万件による検証結果なので、383ミリ秒は完全に誤差と考えても良いでしょう。
++i--i の差も 97ミリ秒(0.097秒) と誤差。
なので、基本的には インクリメントよりもデクリメントの方が早いものの、演算子を前方に置く場合はその差はなくなると考えるべきでしょう。
効果が大きいのは、インクリメント演算子を前方に置く ++i だと分かるので、ループ時はデクリメントを使うか、演算子を前方に置きましょう。
ただし、たまにバグの原因となるので、演算子を前方に置いた場合と後方に置いた場合の動きの違いについてはしっかり把握しておきましょう。
C#におけるインクリメント/デクリメント演算子の扱い

インクリメント

演算子が後方にある場合

インクリメントの演算子が後方にある場合(つまり i++)です。
何だかんだでこれを使っている人が多いのではないでしょうか。

処理
/* 後述するデクリメントとの差異を厳密にするため、キャッシュ方式を採用 */
for (Int32 i = 0, len = testData.Length; i < len; i++)
{
    System.Console.WriteLine(testData[i]);
}

結果:16,463 ミリ秒(16.46 秒)
   1ループあたり0.0165ミリ秒

演算子が前方にある場合

インクリメントの演算子が前方にある場合(つまり ++i)です。
慣れている人は結構使う場面があるかもしれませんね。

処理
/* 後述するデクリメントとの差異を厳密にするため、キャッシュ方式を採用 */
for (Int32 i = 0, len = testData.Length; i < len; --i)
{
    System.Console.WriteLine(testData[i]);
}

結果:14,241 ミリ秒(14.24 秒)
   1ループあたり0.0142ミリ秒

デクリメント

演算子が後方にある場合

デクリメントの演算子が後方にある場合(つまり i--)です。

処理
/* キャッシュしているようなものなので、
上記インクリメントもキャッシュ方式を採用しています */
for (Int32 i = testData.Length - 1; i >= 0; i--)
{
    System.Console.WriteLine(testData[i]);
}

結果:14,338 ミリ秒(14.34 秒)
   1ループあたり0.0143ミリ秒

演算子が前方にある場合

デクリメントの演算子が前方にある場合(つまり --i)です。

処理
/* キャッシュしているようなものなので、
上記インクリメントもキャッシュ方式を採用しています */
for (Int32 i = testData.Length - 1; i >= 0; --i)
{
    System.Console.WriteLine(testData[i]);
}

結果:14,721 ミリ秒(14.72 秒)
   1ループあたり0.0147ミリ秒

シリーズ

上から順に書いていく予定です

  • ステートメント編
  • 多重ループ編
  • 小テクニック集

Kali-Linuxでログイン画面の無限ループから抜け出した方法

$
0
0

はじめに

Kali-Linuxの環境設定中に、ログイン画面でユーザーとパスワードを入れた後、黒い画面がしばらく表示されたのち、ログイン画面に戻るという無限ループが発生しました。根本的な原因はよくわかっていないのですが、以下の対処をしたところ抜け出すことができたので、記録しておきます。

環境

ホストOS:Windows10
ゲストOS:Kali-Linux2019.1
仮想化:VirtualBox6.0

発生した直前の状況

Kali-Linuxの環境設定で、パッケージの更新をするために以下を実行しました。
1. apt-get update
2. apt-get upgrade

1.はすぐに終了したのですが、2.は実行に時間がかかっていたのでしばらく放置していました。30分ほどたって画面を見ると、ログイン画面が表示されていました。その後、ユーザ・パスワードを入力するも無限ループしてしまいました。

本件、「ハッキング・ラボのつくりかた」(翔泳社/IPUSIRON著)の作業中に発生したもので、Twitterでつぶやいたところ、著者のIPUSIRON様ご本人からこのページをご紹介いただきました。また、他にも何名かの方からアドバイスのコメントをいただき感謝の限りです。

原因

根本的な原因はよくわかっていません。(私が勉強不足なこともあり、今後わかったら追記するかもしれません。)

対処方法

1.ログイン画面が表示されている状態で、[Ctl]+[Alt]+[F1~F6]を押して、仮想コンソールを表示する。今回は、F1は使えなかったので、F2を使いました。

2.仮想コンソールでログインユーザとパスワードの入力を促されるので、rootユーザーでログイン。

3.ご紹介いただいた記事に従って以下を実行。

#パッケージリストの更新
apt-get update
#パッケージの更新
apt-get upgrade

ここでエラーになって「dpkg was interrupted, you must run 'dpkg --configure -a ' to correct the problem.」と表示されたので、言われるがままに以下を実行。

#パッケージの再設定
dpkg --configure -a
#cinnamon-control-center-dataのインストール
apt-get install cinnamon-control-center-data
#gdm3のバージョン確認
gdm3 --version
#gdm3のインストール
apt-get --reinstall install gdm3
#gnomeのインストール
apt-get --reinstall install gnome
#gnome-shellのインストール
apt-get --reinstall install gnome-shell
#gdm3の再設定
dpkg-reconfigure gdm3
#再起動
reboot

ここまでやったところ、無事、ログインの無限ループから抜け出すことができました。Twitterでアドバイスをくださった皆様、ありがとうございました

参考にしたサイト

Kali Linux Login Loop
http://www.kalitut.com/2018/06/kali-linux-login-loop-solved.html

【改訂版】C#におけるループ処理の速度 ~条件/演算子編~

$
0
0

概要

プログラミングにおいて最もボトルネックとなりやすいのが、ループ処理です。
なので、ループ処理の速度に関する事を調べて記述していきます。
環境やループ内の処理によっても違いが出るので、あくまでも参考程度に考えてください。
この記事は C#におけるループ処理の速度 ~条件/演算子編~ の改訂版です。
上記記事にミスがあったため、調べ直した結果をこちらに書いていきます。

テスト環境
プロセッサ   :Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz 3.41 GHz
実装メモリ(RAM):32.0GB
システム    :64ビットオペレーティングシステム
言語      :C# 7.3 .NET Framework 3.5
ツール     :Microsoft Visual Studio 2017

テスト内容

10億回または1億回のループを行い、ループ内で System.Int64 型の変数に添え字を加算しているだけの処理です。
System.Console.WriteLine() で出力しても良かったんですが、そもそも時間がかかって面倒なのと、コンソールのバッファが作られているか、そもそも文字列は参照を使っている等の理由でテストに向いていないと考えたため、なるべくプリミティブな型のみを使用するように心がけました。
時間の計測は System.Diagnostics.Stopwatch を使用し、10回分の平均値を結果として算出しています。

時間を計測する方法
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Reset();
sw.Start();

/* ループ処理 */

sw.Stop();
/* ElapsedMilliseconds メンバから経過したミリ秒を取得 */
long res = sw.ElapsedMilliseconds;

演算子による速度の違い

forループの再初期化式での演算子の使い方を6つのケースで見ていきます。
インクリメントとデクリメントの前置・後置、+=代入演算子と-=代入演算子です。

結果

結果から記載します。
テストコードや解説は後述を参照してください。

※2019/04/19
 何故かテスト結果の1の位が抜けていたのを修正

演算子 経過時間(ミリ秒)
i++ 1999
++i 1999
i+=1 1999
i-- 1999
--i 1999
i-=1 2000

すべてのテストでほぼ同じ結果が出ました。
唯一のズレも1ミリ秒なので完全に誤差と考えて良いでしょう。
では、何故同じ結果となるのでしょうか?

同じ結果となる理由

コンパイル結果を見てみれば一目瞭然です。

コンパイル前後
/* C#コード */
using System;
public class C {
    private static void PostInc()
    {
        for(int i = 0; i < 1000000000; i++)
        {
        }
    }

    private static void PreInc()
    {
        for(int i = 0; i < 1000000000; ++i)
        {
        }
    }

    private static void SubInc()
    {
        for(int i = 0; i < 1000000000; i+=1)
        {
        }
    }
}

/* 中間言語(CIL) */
.class private auto ansi '<Module>'
{
} // end of class <Module>

.class public auto ansi beforefieldinit C
    extends [mscorlib]System.Object
{
    // Methods
    .method private hidebysig static 
        void PostInc () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 17 (0x11)
        .maxstack 2
        .locals init (
            [0] int32
        )

        IL_0000: ldc.i4.0
        IL_0001: stloc.0
        // sequence point: hidden
        IL_0002: br.s IL_0008
        // loop start (head: IL_0008)
            IL_0004: ldloc.0
            IL_0005: ldc.i4.1
            IL_0006: add
            IL_0007: stloc.0

            IL_0008: ldloc.0
            IL_0009: ldc.i4 1000000000
            IL_000e: blt.s IL_0004
        // end loop

        IL_0010: ret
    } // end of method C::PostInc

    .method private hidebysig static 
        void PreInc () cil managed 
    {
        // Method begins at RVA 0x2070
        // Code size 17 (0x11)
        .maxstack 2
        .locals init (
            [0] int32
        )

        IL_0000: ldc.i4.0
        IL_0001: stloc.0
        // sequence point: hidden
        IL_0002: br.s IL_0008
        // loop start (head: IL_0008)
            IL_0004: ldloc.0
            IL_0005: ldc.i4.1
            IL_0006: add
            IL_0007: stloc.0

            IL_0008: ldloc.0
            IL_0009: ldc.i4 1000000000
            IL_000e: blt.s IL_0004
        // end loop

        IL_0010: ret
    } // end of method C::PreInc

    .method private hidebysig static 
        void SubInc () cil managed 
    {
        // Method begins at RVA 0x2090
        // Code size 17 (0x11)
        .maxstack 2
        .locals init (
            [0] int32
        )

        IL_0000: ldc.i4.0
        IL_0001: stloc.0
        // sequence point: hidden
        IL_0002: br.s IL_0008
        // loop start (head: IL_0008)
            IL_0004: ldloc.0
            IL_0005: ldc.i4.1
            IL_0006: add
            IL_0007: stloc.0

            IL_0008: ldloc.0
            IL_0009: ldc.i4 1000000000
            IL_000e: blt.s IL_0004
        // end loop

        IL_0010: ret
    } // end of method C::SubInc

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20ad
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method C::.ctor

} // end of class C

/* JITによるコンパイル後 */
C..ctor()
    L0000: ret

C.PostInc()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: xor eax, eax
    L0005: inc eax
    L0006: cmp eax, 0x3b9aca00
    L000b: jl L0005
    L000d: pop ebp
    L000e: ret

C.PreInc()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: xor eax, eax
    L0005: inc eax
    L0006: cmp eax, 0x3b9aca00
    L000b: jl L0005
    L000d: pop ebp
    L000e: ret

C.SubInc()
    L0000: push ebp
    L0001: mov ebp, esp
    L0003: xor eax, eax
    L0005: inc eax
    L0006: cmp eax, 0x3b9aca00
    L000b: jl L0005
    L000d: pop ebp
    L000e: ret

中間言語やJITのコンパイル後の部分に注目して貰えれば、まったく同じコードが生成されている事が分かります。
最近のコンパイラは結構賢くて、多少ロスがあるコードを組んでも自動的に最適化してくれるんですね。
当然ですが最適化されていなければ(まずそんな環境に出くわす事はないとは思いますが)内部で行っている処理が違うので、速度にも差が出ることになります。
ですが、その辺りまで詳しくやると記事の情報がとっちらかってしまうので、気になった人は自分で調べてみてください。

テストコード

演算子の速度の違いを調べるテストコード
using System;
using System.Diagnostics;

namespace C
{
    class Program
    {
        static void Main (string[] args)
        {
            long sum;
            long[] result = new long[6];
            Stopwatch sw = new Stopwatch();
            /* 10回分の平均速度を求める */
            for (int loop = 0 ; loop < 10 ; loop++)
            {
                /* i++ */
                sum = 0;
                sw.Reset();
                sw.Start();
                for (int i = 0 ; i < 1000000000 ; i++)
                {
                    sum += i;
                }
                sw.Stop();
                result[0] += sw.ElapsedMilliseconds;

                /* ++i */
                sum = 0;
                sw.Reset();
                sw.Start();
                for (int i = 0 ; i < 1000000000 ; ++i)
                {
                    sum += i;
                }
                sw.Stop();
                result[1] += sw.ElapsedMilliseconds;

                /* i += 1 */
                sum = 0;
                sw.Reset();
                sw.Start();
                for (int i = 0 ; i < 1000000000 ; i += 1)
                {
                    sum += i;
                }
                sw.Stop();
                result[2] += sw.ElapsedMilliseconds;

                /* i-- */
                sum = 0;
                sw.Reset();
                sw.Start();
                for (int i = 999999999 ; i >= 0 ; i--)
                {
                    sum += i;
                }
                sw.Stop();
                result[3] += sw.ElapsedMilliseconds;

                /* --i */
                sum = 0;
                sw.Reset();
                sw.Start();
                for (int i = 999999999 ; i >= 0 ; --i)
                {
                    sum += i;
                }
                sw.Stop();
                result[4] += sw.ElapsedMilliseconds;

                /* i -= 1 */
                sum = 0;
                sw.Reset();
                sw.Start();
                for (int i = 999999999 ; i >= 0 ; i -= 1)
                {
                    sum += i;
                }
                sw.Stop();
                result[5] += sw.ElapsedMilliseconds;
            }
            Console.WriteLine("i++:" + result[0] / 10);
            Console.WriteLine("++i:" + result[1] / 10);
            Console.WriteLine("i+=1:" + result[2] / 10);
            Console.WriteLine("i--:" + result[3] / 10);
            Console.WriteLine("--i:" + result[4] / 10);
            Console.WriteLine("i-=1:" + result[5] / 10);
            Console.ReadKey();
        }
    }
}

条件式の書き方による速度の違い

ループ処理は、ループ条件の書き方によって速度に差が出ることがあります。
例として配列を使用したループ処理でテストしてみました。
1つは配列のLengthプロパティを用いる方法、2つ目はそのLengthプロパティをローカル変数にキャッシュする方法、最後にループ回数をリテラルで指定する方法です。
配列の要素数の上限の問題で、このテストは1億回のループとなっています。

結果

結果から記載します。
テストコードや解説は後述を参照してください。

演算子 経過時間(ミリ秒)
プロパティ 359
キャッシュ 319
リテラル 335

1億回のループで最大でも40ミリ秒の差ですから誤差のようなものですが、速度は違うようです。
プロパティを用いる方法が遅い理由は、単純に呼び出しに時間がかかるからですね。
1億回ループを行うという事は、条件式の部分は1億回実行されます。
なので、配列変数を呼び出す時間、そして配列のLengthプロパティを呼び出す時間だけ遅くなってしまいます。
逆にキャッシュが早い理由は、ローカル変数を1つ呼び出すだけで済むため、プロパティよりも早くなります。
リテラルがキャッシュよりも遅い理由ですが、それはコンパイルしたコードを見てみれば分かります。

リテラルがキャッシュよりも遅い理由

コンパイル結果を見てみれば遅くなる理由は分かります。
ですが、すみませんが何故そのようなコードを生成するのかは私が調べた限りでは分かりませんでした。
分かる方がいらっしゃればコメントお願いします。

コンパイル前後
/* C#コード */
using System;
class C
{
    static void UseProperty (int[] dataArray)
    {
        long sum = 0;
        for (int i = 0 ; i < dataArray.Length ; i++)
        {
            sum += dataArray[i];
        }
    }

    static void UseCache (int[] dataArray)
    {
        long sum = 0;
        for (int i = 0, len = dataArray.Length ; i < len ; i++)
        {
            sum += dataArray[i];
        }
    }

    static void UseLiteral (int[] dataArray)
    {
        long sum = 0;
        for (int i = 0 ; i < 100000000 ; i++)
        {
            sum += dataArray[i];
        }
    }
}

/* 中間言語(CIL) */
.class private auto ansi '<Module>'
{
} // end of class <Module>

.class private auto ansi beforefieldinit C
    extends [mscorlib]System.Object
{
    // Methods
    .method private hidebysig static 
        void UseProperty (
            int32[] dataArray
        ) cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 25 (0x19)
        .maxstack 3
        .locals init (
            [0] int64,
            [1] int32
        )

        IL_0000: ldc.i4.0
        IL_0001: conv.i8
        IL_0002: stloc.0
        IL_0003: ldc.i4.0
        IL_0004: stloc.1
        // sequence point: hidden
        IL_0005: br.s IL_0012
        // loop start (head: IL_0012)
            IL_0007: ldloc.0
            IL_0008: ldarg.0
            IL_0009: ldloc.1
            IL_000a: ldelem.i4
            IL_000b: conv.i8
            IL_000c: add
            IL_000d: stloc.0
            IL_000e: ldloc.1
            IL_000f: ldc.i4.1
            IL_0010: add
            IL_0011: stloc.1

            IL_0012: ldloc.1
            IL_0013: ldarg.0
            IL_0014: ldlen
            IL_0015: conv.i4
            IL_0016: blt.s IL_0007
        // end loop

        IL_0018: ret
    } // end of method C::UseProperty

    .method private hidebysig static 
        void UseCache (
            int32[] dataArray
        ) cil managed 
    {
        // Method begins at RVA 0x2078
        // Code size 27 (0x1b)
        .maxstack 3
        .locals init (
            [0] int64,
            [1] int32,
            [2] int32
        )

        IL_0000: ldc.i4.0
        IL_0001: conv.i8
        IL_0002: stloc.0
        IL_0003: ldc.i4.0
        IL_0004: stloc.1
        IL_0005: ldarg.0
        IL_0006: ldlen
        IL_0007: conv.i4
        IL_0008: stloc.2
        // sequence point: hidden
        IL_0009: br.s IL_0016
        // loop start (head: IL_0016)
            IL_000b: ldloc.0
            IL_000c: ldarg.0
            IL_000d: ldloc.1
            IL_000e: ldelem.i4
            IL_000f: conv.i8
            IL_0010: add
            IL_0011: stloc.0
            IL_0012: ldloc.1
            IL_0013: ldc.i4.1
            IL_0014: add
            IL_0015: stloc.1

            IL_0016: ldloc.1
            IL_0017: ldloc.2
            IL_0018: blt.s IL_000b
        // end loop

        IL_001a: ret
    } // end of method C::UseCache

    .method private hidebysig static 
        void UseLiteral (
            int32[] dataArray
        ) cil managed 
    {
        // Method begins at RVA 0x20a0
        // Code size 27 (0x1b)
        .maxstack 3
        .locals init (
            [0] int64,
            [1] int32
        )

        IL_0000: ldc.i4.0
        IL_0001: conv.i8
        IL_0002: stloc.0
        IL_0003: ldc.i4.0
        IL_0004: stloc.1
        // sequence point: hidden
        IL_0005: br.s IL_0012
        // loop start (head: IL_0012)
            IL_0007: ldloc.0
            IL_0008: ldarg.0
            IL_0009: ldloc.1
            IL_000a: ldelem.i4
            IL_000b: conv.i8
            IL_000c: add
            IL_000d: stloc.0
            IL_000e: ldloc.1
            IL_000f: ldc.i4.1
            IL_0010: add
            IL_0011: stloc.1

            IL_0012: ldloc.1
            IL_0013: ldc.i4 100000000
            IL_0018: blt.s IL_0007
        // end loop

        IL_001a: ret
    } // end of method C::UseLiteral

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x20c7
        // Code size 7 (0x7)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [mscorlib]System.Object::.ctor()
        IL_0006: ret
    } // end of method C::.ctor

} // end of class C

/* JITによるコンパイル後 */
; Desktop CLR v4.7.3324.00 (clr.dll) on amd64.

C..ctor()
    L0000: ret

C.UseProperty(Int32[])
    L0000: xor eax, eax
    L0002: xor edx, edx
    L0004: mov r8d, [rcx+0x8]
    L0008: test r8d, r8d
    L000b: jle L0022
    L000d: movsxd r9, edx
    L0010: mov r9d, [rcx+r9*4+0x10]
    L0015: movsxd r9, r9d
    L0018: add rax, r9
    L001b: inc edx
    L001d: cmp r8d, edx
    L0020: jg L000d
    L0022: ret

C.UseCache(Int32[])
    L0000: xor eax, eax
    L0002: xor edx, edx
    L0004: mov r8d, [rcx+0x8]
    L0008: test r8d, r8d
    L000b: jle L0022
    L000d: movsxd r9, edx
    L0010: mov r9d, [rcx+r9*4+0x10]
    L0015: movsxd r9, r9d
    L0018: add rax, r9
    L001b: inc edx
    L001d: cmp edx, r8d
    L0020: jl L000d
    L0022: ret

C.UseLiteral(Int32[])
    L0000: sub rsp, 0x28
    L0004: xor eax, eax
    L0006: xor edx, edx
    L0008: test rcx, rcx
    L000b: jz L0030
    L000d: cmp dword [rcx+0x8], 0x5f5e100
    L0014: jl L0030
    L0016: movsxd r8, edx
    L0019: mov r8d, [rcx+r8*4+0x10]
    L001e: movsxd r8, r8d
    L0021: add rax, r8
    L0024: inc edx
    L0026: cmp edx, 0x5f5e100
    L002c: jl L0016
    L002e: jmp L004d
    L0030: cmp edx, [rcx+0x8]
    L0033: jae L0052
    L0035: movsxd r8, edx
    L0038: mov r8d, [rcx+r8*4+0x10]
    L003d: movsxd r8, r8d
    L0040: add rax, r8
    L0043: inc edx
    L0045: cmp edx, 0x5f5e100
    L004b: jl L0030
    L004d: add rsp, 0x28
    L0051: ret
    L0052: call 0x7ffdb9ec2660
    L0057: int3

このように、リテラルを用いたループ処理では比較や代入などが何度も行われています。
単純に処理のステップが多いため、リテラルを用いたループ処理はキャッシュを用いたループ処理よりも遅くなっているようです。
基本的にはリテラルの方が早いはずなのですが……分かる方のコメントをお待ちしています。

albireo様のコメントより  2019/04/22 追記
どうやらリテラルの場合は、それが配列の要素数の範囲に収まるかがコンパイル時点では判断出来ないため、
それをループ時にインデックスが配列の範囲外ではないかのチェックを行っている事で速度に差が出てしまっているようです。
これはループに用いる配列を、ループと同じメソッド内で作成しても変わらず、コンパイラはインデックスの範囲外チェックが必要だと判断してしまいました。
上手く回避する方法が見つかれば追記しますが、現状はキャッシュを使用する方法が一番早そうです。

テストコード

条件式の速度の違いを調べるテストコード
using System;
using System.Diagnostics;

namespace C
{
    class Program
    {
        static void Main (string[] args)
        {
            /* ループ処理に使用する配列を作成する */
            int[] dataArray = new int[100000000];
            for (int i = 0 ; i < 100000000 ; i++)
            {
                dataArray[i] = i;
            }

            /* 10回分の平均速度を求める */
            long sum;
            long[] result = new long[3];
            Stopwatch sw = new Stopwatch();
            for (int loop = 0 ; loop < 10 ; loop++)
            {
                /* 配列のプロパティをそのまま使う */
                sw.Reset();
                sw.Start();
                UseProperty(dataArray);
                sw.Stop();
                result[0] += sw.ElapsedMilliseconds;

                /* 配列のプロパティを変数にキャッシュして使う */
                sw.Reset();
                sw.Start();
                UseCache(dataArray);
                sw.Stop();
                result[1] += sw.ElapsedMilliseconds;

                /* リテラルを使用する */
                sw.Reset();
                sw.Start();
                UseLiteral(dataArray);
                sw.Stop();
                result[2] += sw.ElapsedMilliseconds;
            }
            Console.WriteLine("プロパティ:" + result[0] / 10);
            Console.WriteLine("キャッシュ:" + result[1] / 10);
            Console.WriteLine(" リテラル:" + result[2] / 10);
            Console.ReadKey();
        }

                /* Lengthプロパティを用いたループ処理 */
        static void UseProperty (int[] dataArray)
        {
            long sum = 0;
            for (int i = 0 ; i < dataArray.Length ; i++)
            {
                sum += dataArray[i];
            }
        }

                /* Lengthプロパティをローカル変数にキャッシュしたループ処理 */
        static void UseCache (int[] dataArray)
        {
            long sum = 0;
            for (int i = 0, len = dataArray.Length ; i < len ; i++)
            {
                sum += dataArray[i];
            }
        }

        /* リテラルを用いたループ処理 */
        static void UseLiteral (int[] dataArray)
        {
            long sum = 0;
            for (int i = 0 ; i < 100000000 ; i++)
            {
                sum += dataArray[i];
            }
        }
    }
}

あとがき

今回は速度の違いについて書きましたが、個人的には「とにかく早ければ良い」という考えは危険だと思っています。
大抵の場合、速いコードほど読みにくいからです(程度の問題ではありますが)。
そして、読みにくいコードというのは得てしてバグの温床になりやすい。
なので、本当に必要な時以外は読みやすいコードを優先した方が良いと思います(これが一番難しい……)。
この記事でも、そこまで読みにくくはならないだろうという程度の小手先しか紹介していません。
本当に速くしたいのならメモリについての知識が必須なので、興味があれば調べてみてください。
あくまでもこの記事はちょっとしたお役立ち知識として参考にしていただけると幸いです。
また、私自身も勉強中の身のため、間違いを見つけたらコメントで指摘していただけるとありがたいです。

request をループ処理する

$
0
0

http request をすると response があるまで少々の時間がかかります。
しかし、node.js では response を待たずに次の処理に進んでしまいます。
そのため、response を待って処理するためには、promise または callback を利用して非同期処理を行う必要があります。

node.js には request-promise モジュールがあり、こちらを使用することにより簡単に非同期処理を行うことができます。
request-promise については過去に記事を書きましたのでこちらをご参照ください。
Callback 使いから Promise マスターにクラスチェンジするぞ!

と、諸先輩方には釈迦に説法なお話なのですが、自分の理解の定着のために書かせていただきました。
オレ、しっかり反復する。大事('ω')

さて、前置きはこのくらいにして、本題に入りましょう。

ID から名前を取得して表示するプログラム

  1. ID から name を検索する API』を request する
  2. response を取得したら IDname を表示する
  3. 1~2 を ID リストの分だけ繰り返す

こんなループをするプログラムにしたいと思います。

for や while を使ったループで request すると…?

単純にループさせるだけなら forwhile を使えば実現します。

request-loop.js
const idList = ["sample001@shop","sample002@shop","sample003@shop"];
class setOptions {
    constructor(id) {
        // API を request するためのオプションをセット(内容は割愛)
    }
}
for(let i = 0; i < idList.length; i++){ // 3 の処理
    request(new setOptions(idList[i])).then((response) => { // 1 の処理
        console.log(idList[i] + " : " + response); // 2 の処理
    }).catch((error) => { console.log(error) }); 
}

しかし、実行結果がこうなります。

sample002@shop : サンプル次郎
sample001@shop : サンプル太郎
sample003@shop : サンプル四郎

順番がバラバラですね。
結局、forwhile を使う場合には非同期処理になるため、response を待たずに次の request を行ってしまうためです。
for がやっている実際の処理は、こんな感じなのでしょう。

for部分の実際の処理
request(new setOptions(idList[0])).then((response) => {
    console.log(idList[0] + " : " + response);
}).catch((error) => { console.log(error) }); 
request(new setOptions(idList[1])).then((response) => {
    console.log(idList[1] + " : " + response);
}).catch((error) => { console.log(error) }); 
request(new setOptions(idList[2])).then((response) => {
    console.log(idList[2] + " : " + response);
}).catch((error) => { console.log(error) }); 
}

せっかくの request-promise でも、別々に呼び出してたら意味ありませんよね。
結局、非同期処理されて、順番は守られません。

本当は、こうしたいのです。

こんな処理がしたい!
request(new setOptions(idList[0]))
.then((response) => {
    console.log(idList[0] + " : " + response);
    return request(new setOptions(idList[1]));
.then((response) => {
    console.log(idList[1] + " : " + response);
    return request(new setOptions(idList[2]));
.then((response) => {
    console.log(idList[2] + " : " + response);
}).catch((error) => { console.log(error) }); 

さて、これをループ処理にするには、どうしたら良いのでしょうか?

再起処理でループさせる

再起処理とは「自分自身を呼び出す処理が書かれている関数を呼び出すこと」です。
この方法であれば、response を待って処理することができます。

request-loop.js
const idList = ["sample001@shop","sample002@shop","sample003@shop"];
class setOptions {
    constructor(id) {
        // API を request するためのオプションをセット(内容は割愛)
    }
}
function loop(i){
    request(new setOptions(idList[i]))
    .then((response) => {
        console.log(idList[i] + " : " + response);
        i++;
        if(i < idList.length) loop(i); // ここでループする
    }).catch((error) => { errorArart(error) });    
}
loop(0);
sample001@shop : サンプル太郎
sample002@shop : サンプル次郎
sample003@shop : サンプル四郎

API で取得した値を利用して、順番通り表示することができました!(*‘∀‘)やったね!

おわりに

ここまでお付き合いいただきありがとうございました。

肝心の再起処理の部分がさらっと終わってしまったのですが、できたときは凄く嬉しかったです YO!('Д')
いや、ほんと。for 文でなんとかできないか四苦八苦していました。
まだまだ勉強が足りません。精進せな。

今回、メインではなかったので API 部分は割愛したのですが、今回の話を踏まえた上で、次回は LINEWORKS の API でやる方法を解説してみたいと思います。

ではまた!(^^)/

参考にさせていただきましたm(_ _)m

再帰処理とは (分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典)

Microsoft Flow:文字列を区切ってループ処理

$
0
0

仕様

  • 1. カンマ区切りの文字列を変数に格納。(変数の初期化により、変数宣言)
  • 2. 「split」関数で、カンマ区切り。(戻り値は、配列)
  • 3. 「Apply to each」で、上記のカンマ区切りした各値をOneDriveのファイルに書き出す。

pr1.png

  • 4.書き出すファイル名は、文字列結合の関数「concat」を使用し、以下の形式とする。「f_」は接頭辞。    - 形式:f_[区切り値] 

pr2.png

コードの補足

変数の参照

変数の参照は、「variables('変数名')」。

split
split(variables('test_var'), ',')

「Apply to each 」の各値の参照

「item()」関数で、ループの各値を参照。

concat
concat('f_', item())

rubyのブロック処理における配列関連のメソッドメモ

$
0
0

チェリー本を参考にした。このあたりよく忘れるのでメモっておく

each

普通のfor文と同じ役割

num = [1,2,3,4]
sum = 0
num.each do |n|
  sum += n
end
print sum #=> 10

each_with_index

eachについて配列のインデックスも扱うことができる

num = [1,2,3,4]
num.each_with_index do |i,n|
  p "#{i}:#{n}"
end
#出力は以下の通り
#0:1
#1:2
#2:3
#3:4

map

ある配列の要素に対する処理結果を別配列に返す

num = [1,2,3,4]
new_num = []
new_num = num.map do |n|
  n * 10
end
print new_num #=> [10,20,30,40]

エイリアスメソッドであるcollectも同様の使い方が可能

select/find_all

ある配列の要素について、条件に一致しているものを返す

num = [1,2,3,4]
new_num = num.select do |n|
  n.odd? #nが奇数のものをnew_numへ格納
end
print new_num  #=> [1,3]

エイリアスメソッドであるfind_allも同様の使い方が可能

reject

ある配列の要素について、条件に一致していないものを返す
要するにselect/find_allの反対

num = [1,2,3,4]
new_num = num.select do |n|
  n.odd? #nが奇数でないものをnew_numへ格納
end
print new_num  #=> [2,4]

find/detect

ある配列について、戻り値が真かつ最初の要素を返す

num = [1,2,3,4]
new_num = num.find do |n|
  n.odd?
end
print new_num #=> 3

エイリアスメソッドであるdetectも同様の使い方が可能

inject/reduce

ある値を初期値として配列各要素を足しこむ。eachの拡張か。

num = [1,2,3,4]
sum = num.inject(0) do |result,n|
  result += n #初期値0に各要素をすべて足しこむ。初期値が異なれば結果も異なる。
end
print sum #=> 10

当然ながら文字列の結合処理にも利用可能
エイリアスメソッドであるreduceも同様の使い方が可能

その他

map/collectとinject/reduceの思想の違いらしい。エイリアスというくらいなので多分map処理を名前変えてcollectとかしてんだろうな。要するに実装上の違いはないと思うがどうなのか。。。
https://magazine.rubyist.net/articles/0038/0038-MapAndCollect.html#map-%E3%81%A8-collect-%E3%81%AE%E7%99%BA%E6%83%B3%E3%81%AE%E9%81%95%E3%81%84

java Scannerループ入力

$
0
0

この記事の目的

 いつも忘れるので自分用メモ

内容

 JavaでScannerで入力させて、希望の数値、文字列以外が入力されるまで入力ループしたい。例外処理は考えないでとりあえず処理だけのメモ。

コード

rupe.java
Scanner scan = new Scanner(System.in);
while(true){
  int num = scan.nextInt();
  switch(num){
    case // 入力してほしい数値:
      // 処理内容
      break;
    default:
      continue;
  }
  break;
}

pythonの少し複雑なループ

$
0
0

初心者です。どのようにループを掛ければいいかわかりません。
アドバイスをいただければ幸いです。

データ a = [250, 300 , 500]
データ b = [[100,150,300],[50,60,80],[400,500,300]]

やりたいこと
250*100 + 300*50 + 500*400  合計24000
250*50 + 300*60 + 500*80 合計70500
250*400 + 300*500 + 500*300 合計400000

以上の計算式をループ等を使ってやりたいのですが
いろいろ試したのですが、どうまわしていいか
わかりません。
分かる方よろしくお願いいたします。

PHPの繰り返し処理大全

$
0
0

PHP7.3時代の話です。
PHP8や9のころには、また別の結論になっているかもしれません。

最初に結論

・全要素繰り返しはforeach
・途中で打ち切るのはwhile/for
・それ以外はいらん

繰り返し処理一覧

foreach

PHPのforeachは非常に優秀です。
あらゆる反復可能な値を繰り返し処理することができます。

$arr = [1, 2, 3];
foreach($arr as $val){
  echo $val; // 順に1, 2, 3
}

PHPの配列は順序付きリストなので、順番も保証されます。
また、キーと値を両方とも取れるので、inとofどっちだったっけとか悩む必要もありません。

$arr = [3 => 1, 2 => 2, 1 => 3];
foreach ($arr as $key => $val) {
    echo $key, $val; // 31, 22, 13 順番が変わったりはしない
}

オブジェクトにも使えます。

class Test{
    public $a = 1;
    protected $b = 2;
    private $c = 3;

    public function loop(){
        foreach($this as $val){
            echo $val;
        }
    }
}

$test = new Test();
foreach($test as $val){
    echo $val; // 1
}

$test->loop(); // 1,2,3

オブジェクトに使った場合、可視なプロパティが順にアクセスされます。
すなわち外部からはpublicプロパティしか見えず、内部からならprotectedやprivateも見えるということです。

さらに他言語では御法度の、ループ中で自身や別要素を削除するという芸当がPHPでは可能です。

$array = range(0, 10);

foreach ($array as $k => $v) {
    unset($array[$k - 1]);
}

var_dump($array); // [10=>10]

ループ中で、前回ループの値を削除しています。
他言語でこんなことをやるとどんな動作になるかわかったものではありませんが、PHPでは全要素をきちんとループした上で、最終的に$arrayの値は想定通りになります。
配列をforeachした時点で配列のコピーが作成されるので、ループ内で変なことをしでかしてもループ自体には影響が及ばないように対策されているのです。
おかげで何も考えずにfilterが実装できます。

$array = [1, 2, 3, 4, 5, 6];

// 偶数以外削除
foreach ($array as $k => $v) {
    if ($v % 2) {
        unset($array[$k]);
    }
}

var_dump($array); // [2, 4, 6]

while

条件がtrueっぽい値であるかぎり、ループを繰り返します。

$i = 1;
while ($i <= 10) {
    echo $i;
    $i++;
}

foreachとの使い分けですが、foreachは配列などの全要素にアクセスする用途に使います。
whileは何らかの条件でループを脱出する用途に使います。

// 30回ループしたら脱出
$i = 0;
while ($i < 30) {
    echo $i;
    $i++;
}

// 30秒経ったら中断
while(true){
    sleep(1);

    if(time() - $_SERVER['REQUEST_TIME'] > 30){
        break;
    }
}

// 標準入力を出力
while( ($line = fgets(STDIN)) !== false){
    echo $line;
}

うっかり使うと無限ループになるので使用は避けましょう
いや嘘。
whileを使うべきところにforやforeachを使ったりはせず、適切に使い分けましょう。

for

whileで多くの場合必要となる『初期値設定』『カウント処理』を最初から文法に組み込んだものです。

// 30回ループしたら脱出
for ($i = 0; $i < 30; $i++) {
    echo $i;
}

whileではループ前後に書かざるをえなかった定型処理がひとつの文にまとまったことで、わかりやすくなって見た目もすっきりしました。

whileは常にforで書き換え可能なので、実はwhileは必ずしも存在する必要はありません。
しかし初期値設定などが必要ない簡易的なループはwhileで書いた方がわかりやすいので、forとwhileは処理内容によって使い分けるとよいでしょう。

// forを使うほどではない
for(; true; ){
    sleep(1);

    if(time() - $_SERVER['REQUEST_TIME'] > 30){
        break;
    }
}

PHPでは、配列要素などにアクセスする用途でforを使用する理由はありません。

$array = [1, 2, 3];

for ($key = 0; $key < count($array); $key++) {
    echo $key, $array[$key]; // 01, 12, 23
}

$array = [0 => 1, 2 => 3]; // 死
$array = ['answer' => 42]; // 死

歯抜けのない純粋配列にしか使用できず、うっかり連想配列や歯抜けのある配列にforでアクセスすると死にます。
要素に対するアクセスには必ずforeachを使いましょう。

forの存在価値はループのためではなく、条件分岐のためにあります。

for(; time() - $_SERVER['REQUEST_TIME'] <= 30; sleep(1));

条件部分やループカウント部分にあらゆる処理を詰め込むことでforの本文を空にすることもできますが、見づらくなるだけなので止めましょう。

do - while

条件のチェックが最後に行われるということ以外はwhileと同じです。
つまり、たとえ条件が偽でも必ず一回だけは実行されるwhileということです。

$i = 100;
do {
    echo $i++;
} while ($i < 30);

正直、whileではなくこちらを使わなければならないシーンを思いつきません。

関数型関数

PHPには組み込みでarray_walkarray_reducearray_productといった、関数型のように書ける関数が多数用意されています。
これらを使うことで反復処理をやめ、関数型プログラミングっぽい記述にすることが可能になります。

$sum = 0;
foreach($array as $val){
    $sum += $val;
}
echo $sum;

echo array_sum($array); // ↑と同じ

が、基本的にこれらを使う必要はないです。
上記のようなわかりやすい例はむしろ例外で、PHPでは文法の都合上、大抵の処理は関数型で書くと却ってわかりづらくなります。

以下はPHPで高速オシャレな配列操作を求めてより借用した例です。

// 0~10000のうち、偶数だけを抽出して自乗し、結果が20を超えるものを足しあわせよ

// 関数型
echo array_sum(
    array_filter(
        array_map(
            function ($v) {
                return $v ** 2;
            },
            array_filter(range(0, 10000), function ($v) {
                return $v % 2 === 0;
            })
        ),
        function ($v) {
            return $v > 20;
        }
    )
);

// 普通に書く
for ($sum = $v = 0; $v <= 10000; ++$v) {
    if ($v % 2){ continue; }
    $v **= 2;
    if ($v <= 20){ continue; }
    $sum += $v;
}
echo $sum;

明らかに普通に書いた方がわかりやすいですね。
そもそも関数型の例でよく出てくる『○○を抽出する』みたいな処理は、PHPであればSQL発行する時点で絞っておけって話ですし。

filter_varなど使いこなすと色々楽しいこともできるのですが、実用的かと言われると首が傾いてしまいますね。
もちろん関数型で書いてもわかりやすい場合もありますが、別にforeachで書いたところで可読性も大してかわらないので、なら最初から全部foreachで書いた方が手っ取り早いです。

反復処理の定義

オブジェクトに対するループ処理の挙動を、PHPでは任意に定義可能です。
上のほうでオブジェクトをforeachするとpublicプロパティが順番に出てくると言いましたが、それはデフォルトの動作であって、やろうと思えば変更できるということです。

Iteratorインターフェイス

反復処理実装の基本です。
Iteratorインターフェイスをimplementsして各メソッドを実装します。

class Test implements Iterator
{
    public $dummy = '出てこない';
    private $data1 = [1, 2, 3];
    private $data2 = [4, 5, 6];
    private $current = 0;

    /**
     * @Override
     * 現在の値を返す
     * @return mixed 現在の値
     */
    public function current(){
        return $this->data1[$this->current] ?? $this->data2[$this->current - 3] ?? null;
    }

    /**
     * @Override
     * 現在のキーを返す
     * @return string|int 現在のキー
     */
    public function key(){
        return $this->current;
    }

    /**
     * @Override
     * ポインタを次に移動する
     */
    public function next(){
        $this->current++;
    }

    /**
     * @Override
     * ポインタを初期化する
     */
    public function rewind(){
        $this->current = 0;
    }

    /**
     * @Override
     * 現在のポインタが有効か
     * @return boolean 有効ならtrue
     */
    public function valid(){
        return isset($this->data1[$this->current]) ?: isset($this->data2[$this->current - 3]);
    }
}

$test = new Test();
foreach ($test as $key => $val) {
    echo $key, '=>', $val; // [0=>1, 1=>2, 2=>3, 3=>4, 4=>5, 5=>6]
}

publicであるはずの$dummyは出てこなくなり、currentで返した結果が表示されるようになります。
このように、Iteratorインターフェイスを使うことでループで返す値を好き勝手に変更することができるようになります。
ただ、変なことをやってもわかりにくくなるだけなので、基本的にあまり使わないほうがいいと思います。

IteratorAggregateインターフェイス

Iteratorインターフェイスは必ず5個のメソッドを実装する必要があって面倒です。
PHPには最初からイテレータが幾つも用意されており、それらで賄える範囲であれば簡単に実装できるIteratorAggregateがあります。

class Test implements IteratorAggregate
{
    public $dummy = '出てこない';
    private $data1 = [1, 2, 3];
    private $data2 = [4, 5, 6];

    /**
     * @Override
     * イテレータを返す
     * @return Iterator
     */
    public function getIterator(){
        $iter = new AppendIterator();
        $iter->append(new ArrayIterator($this->data1));
        $iter->append(new ArrayIterator($this->data2));
        return $iter;
    }
}

$test = new Test();
foreach ($test as $key => $val) {
    echo $key, '=>', $val; // [0=>1, 1=>2, 2=>3, 0=>4, 1=>5, 2=>6]
}

IteratorAggregate::getIteratorに適当なイテレータを投げれば、foreachループでそれが出てくるようになります。
今回は配列をイテレータにするArrayIterator、複数のイテレータを順にまとめるAppendIteratorを使って、$data1$data2が順に出てくるようにしました。

もちろん、書くのが楽になったからといって使いまくると何が出てくるかわからないブラックボックスになってしまいます。
RecursiveRegexIteratorあたりまで来ると正直何言ってるかわからないので、変なイテレータには手を出さない方がいいと思います。

ジェネレータ

ジェネレータはクロージャとセットにされがちですが、単に何度もreturn(yield)できる関数という認識でいいと思います。

function getPrimeNumber(){
    yield 2;
    yield 3;
    yield 5;
    yield 7;
    yield 11;
    yield 13;
    yield 17;
    return 19; // returnは出ない
}

$primes = getPrimeNumber();
foreach($primes as $prime){
    echo $prime; // 2, 3, 5, 7, 11, 13, 17
}

returnのかわりにyieldというキーワードを使います。
yieldキーワードが入った関数は、関数呼び出しの返り値が自動的にGeneratorインスタンスになります。
たとえ絶対にyieldを通らない実装だったとしてもそうなるので、少し注意が必要です。

返ってくるのはオブジェクトなので、その返り値をforeachでループすることができるわけですが、その際は関数の最初からではなくyieldで止まったところの次の文から処理が再開されます。
関数内の変数値などは維持されるので、何も考えずにメモ化ができたりします。
上で出した例では全く意味がありませんが、無限数列などを作ったりする際にはジェネレータがとても役立ちます。

// フィボナッチ数を求めるジェネレータ
function getFibonacci(){
    $fa = 0;
    yield $fa;
    $fb = 1;
    yield $fb;

    while (true) {
        $fib = $fa + $fb;
        $fa = $fb;
        $fb = $fib;
        yield $fib;
    }
}

$count = 0;
foreach (getFibonacci() as $fibonacci) {
    echo $fibonacci . "\n";
    // 適当に切らないと無限ループする
    if ($count++ > 30) {
        break;
    }
}

再帰もメモ化もgotoもなんもなしに、普通に定義通りのフィボナッチ数を求める関数が書けてしまいました。

ジェネレータは頻繁に出番があるかというと無いですが、覚えておくといざというとき便利な機能です。

まとめ

自発的に使うのはforeachとwhile/forだけでいいよ。

それ以外はあまり出てくるものでもないので、出てきたときに調べるくらいで大丈夫でしょう。

使う必要のないもの

foreachのリファレンス

foreachでリファレンスが取れますが、使用してはいけません。

$array = range(0, 5);
foreach ($array as &$val) {
    $val *= 2;
}
var_dump($array); // [2, 4, 6, 8, 10]

$val = 42;
var_dump($array); // [2, 4, 6, 8, 42] ←

そもそもリファレンスはあらゆる場面で一切使用禁止です。
リファレンスで高速化が云々とか言ってる人は全員間違い1なので、生暖かい目でスルーしましょう。

do - whileの早期return

do-while記法は、簡易的な早期returnに使用できます。
以下はマニュアルに載っている例です。

do {
    if ($i < 5) {
        echo "i は十分大きくはありません。";
        break;
    }
    $i *= $factor;
    if ($i < $minimum_limit) {
        break;
    }
    echo "iはOKです。";

    /* 実際の処理 */

} while (0);

breakはループを抜ける文なので、$i<5だったり$i < $minimum_limitの場合は、このwhileループを抜けます。
条件に当てはまらずループの最後まで来た場合、条件が常に偽であるため、そのままループを終了して先に進みます。
はい、早期returnできました。

もちろんこのような書き方はせず、メソッドなどに出してください。

current / reset / next / prev / end

配列ポインタを手動で操作することが可能です。

$array = range(0, 5);

while(true){
    echo key($array), current($array);
    if(next($array) === false){
        break;
    }
}

配列ポインタ操作関数はresetnextprevendなどひととおり揃っています。
が、あえてこれらの関数を使わなければならない場面はありません。

さらにマニュアルには書かれていないのですが、実はこいつらの引数はZ_PARAM_ARRAY_OR_OBJECT_HTであり、つまりオブジェクトを受け付けます。

class Test{
    public $a = 1;
    protected $b = 2;
    private $c = 3;
}
$array = new Test();

while(true){
    var_dump(key($array), current($array) );
    if(next($array) === false){
        break;
    }
}

01.png

マジかよ。

each

PHP7.2でDeprecatedになったため、使ってはいけません。

いにしえのPHPではメモリ節約のためにeachを使おうなどとされていた時代もありましたが、PHP7時代においては間違った記述です。

上のほうでforeachしたら配列のコピーが作成され云々とか言いましたが実は嘘で、現在ではforeachループするだけならコピーは作成されません。
ループ中で元の配列を変更しようとしたときに初めてコピーが作成されます。
これはコピーオンライトと呼ばれる技術で、最近のPHPの最適化技術のひとつです。
現在のPHPは非常に最適化が進んでいるので、下手なことを考えるより標準機能を使った方がよっぽど使用メモリも少なく速度も速いです。

ジェネレータの変な使い方

素のジェネレータはforeachしかできず、whileやforで書くことができないのですが、実はGeneratorクラスはIteratorインターフェイスをimplementsしているので、手動でループさせることもできます。

$fibonacci = getFibonacci();

while ($fibonacci->key() < 28) {
    $fibonacci->next();
    echo $fibonacci->current(); // 1, 1, 2, 3, 5, 8, …
}

こんな処理が必要になるのであれば、ジェネレータではなく他の機能を使った方がよいでしょう。

yield fromキーワードを使って、別のジェネレータの返り値を取り込むことができます。

function gen()
{
    yield 1;
    yield from [2, 3];
    yield from gen2();
}

function gen2()
{
    yield 4;
    yield 5;
}

$gen = gen();
foreach ($gen as $v) {
    echo $v; // 1, 2, 3, 4, 5
}

こんな処理を作り込むよりも、データ構造を見直した方がよいでしょう。

ソート

usortなんかは関数型っぽい書き方なのでループと言えないこともない可能性がなきにしもあらずな気がしないでもないんだけどループの範疇に含めるべきものだろうか?

$arr = [3, 1, 2];

usort($arr, function ($a, $b) {
    return $a <=> $b;
});

var_dump($arr); // 1, 2, 3

やはりループっぽくはないですね。

その他

思いついたのを並べたらこんなかんじでした。
見落としや間違いがあったら誰かがプルリクしてくれるはず。


  1. 0.1%くらい正しいことを言っている可能性もあるが、見分けは付かないので全て間違いと考えてかまわない。 

Bashで文字列の構造体を使いたい

$
0
0

自身の備忘を兼ねて記載を行っています。
「とりあえず動いた」程度のソースなどもございますので参考程度にブラシアップ頂けると幸いです。
また、誤りやもっとよいコーディングやきれいな書き方があるなどご指摘頂けるととてもうれしいです。

今回のお題

任意の件数のデータについて同一の処理を行いたく、構造体で実現しようと思い作成

では、ソースです。

sample01.sh
#! /bin/bash

# 構造体
LOOP_LISTS=(
    ABCDEFG
    hijklmn
    1234567
    あいうえお
)

i=1
# 構造体の中身をループで一つづつ表示
for LOOP_LIST in ${LOOP_LISTS[@]}; do
    # 同一の処理(今回は表示)
    echo "${i}${LOOP_LIST}"
    let i++
done

# 構造体の中身を指定で表示(1番目のデータは「0」になる)
echo "2番目指定:${LOOP_LISTS[1]}"
実行結果
$ sh sample01.sh
1:ABCDEFG
2:hijklmn
3:1234567
4:あいうえお
2番目指定:hijklmn
Viewing all 91 articles
Browse latest View live