{javascript} clearIntervalの使い方

clearIntervalしても止まらないんだけど!?な時

勘のいい人用に一言で終わらせると、「setInterval自体は一度しか実行されない」



では、以下勘の悪い人用

まず間違った書き方

//実行される度に1ずつ増やして出力
var i = 0;
function hoge(){
  console.log(i++);
}

//1000ms毎にhoge()を実行
var tid = setInterval(function(){
  hoge();
}, 1000);

//3秒を超えたら停止?
if (i > 3){
  clearInterval(tid);
  console.log("停止");
}

まず理解しないといけないのは、setIntervalは一度しか行われていないということ。
setIntervalで一定時間毎にhoge()を行うとは記述してあるが、setInterval自体を何度も行っているわけではない。
あくまで流れとしては

-処理開始-
①setIntervalを実行(hogeが1000msごとに実行されることが予約される)
②i>3を評価
-処理終了-

だから、①と②がまず一瞬で処理される。
当然この場合、if文に差し掛かる段階ではsetIntervalは実行されてるけどhoge()が3回も実行されるほど時間がたってない。
よってif文ではfalseが返され、clearIntervalは実行されない
その後もsetIntervalは実行されないが、命令自体は生きているので1000ms毎にhoge()は実行され続ける。

なのでこう書くことになる

//実行される度に1ずつ増やして出力
var i = 0;
function hoge(){
  console.log(i++);
}

//1000ms毎にhoge()とif文を実行
var tid = setInterval(function(){
  hoge();
  if(i > 3){
    clearInterval(tid);
  }
}, 1000);

一応流れを書くと、

-処理開始-
①setIntervalを実行
-処理終了-

となるわけだから、setIntervalが一回実行されて処理終了。
ただ命令自体は生きているので、1000ms毎にhoge()とif文が実行され続ける。
この書き方ならhoge()が実行され続ける限り、同時にif文も実行されるので、i>3がtrueになった時clearIntervalが実行、相手(setInterval)は死ぬ。


ちなみにifを先に持ってきてしまうと、この場合コンソールには4まで表示されるので気をつけよう。
これも勘のいい人用に一言で終わらせると、「clearIntervalはbreakじゃない」

var tid = setInterval(function(){
  if(i > 3){
    clearInterval(tid);
  }
  hoge();
}, 1000);

この時の処理

setInterval開始

if実行 //0 > 3 -> false
hoge()実行 //0表示

if実行 //1 > 3 -> false
hoge()実行 //1表示

〜中略〜

if実行 //4 > 3 -> true (次回からsetIntervalの中身は実行されない)
hoge()実行 //4表示 (3回目にclearIntervalされなかった段階で、4回目のhoge()が約束される)

いくらclearIntervalしたとしても、すでに始まってる処理をその瞬間抜けるわけじゃないことに注意。
forやwhileなどでは、breakが記述されてる部分からループ外に飛ぶわけだけど、clearIntervalはbreakとは全然別物だということ。

歯医者さん行きたくない

{VBA} 一つ前に選択していたセルに戻る

何に使うのか知らないけど友達に聞かれたので作ってみた

Dim cellHistories(99) As Range '配列の要素数 = セルの移動履歴残したい数
Dim skipRecord As Boolean

Private Sub Worksheet_SelectionChange(ByVal target As Range)
    Call recordCurrentCell(target)
End Sub

Private Sub recordCurrentCell(target As Range) '(a)配列を後ろにズラして (b)0番目に今のセルを記録
    If Not skipRecord Then 'selectPreviousCell()による呼び出し時(=巻き戻し時)は記録しない
        For i = UBound(cellHistories) - 1 To 0 Step -1 '(a)
            If Not cellHistories(i) Is Nothing Then
                Set cellHistories(i + 1) = cellHistories(i)
            End If
        Next i
        Set cellHistories(0) = target '(b)
    End If
End Sub

Sub selectPreviousCell() '履歴をもとに巻き戻す
    On Error GoTo skip '履歴が無い時は無視
        skipRecord = True '巻き戻し時の移動を記録されるのを防ぐフラッグ
        cellHistories(1).Activate
        For i = 0 To UBound(cellHistories) - 1
            If Not cellHistories(i + 1) Is Nothing Then
                Set cellHistories(i) = cellHistories(i + 1)
            Else
                GoTo skip '履歴が無い時はループを抜ける
            End If
        Next i
skip:
    skipRecord = False '
End Sub

ちなみに消費期限が4日切れたサンドイッチ食べても今のところ大丈夫だけど不安な気持ちになるから食べないほうがいい

'○○' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。

まえおき

Javaをインストールする段階でやることだけど、IDEにおんぶに抱っこ状態で気付かなかったのでメモ
※今回は.javaファイルをコンパイルできなかった場合を例に書いていきますが、タイトルのようなエラーメッセージの場合、原因は本記事を読めば大体わかるかと思います。本記事内の「javac」を、ご自身の実行したかった命令と置き換えて読んで下さい。

コンパイルできない

cd C:\~(中略)~\ aaa
javac Test.java
'javac' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。

原因

PCがjavacという命令を知らない状態。

PCで元々定義されてる命令はPCが知っている為実行できるが、それ以外の命令は何をすれば良いのか教えてやらねばならぬ。

そこで実際に命令として実行するexeファイル(今回の場合は javac.exe)の場所を教えてやる必要がある。

その場所は以下で設定できる

解決方法

1. 環境変数ウィンドウを開く

Win+R(ファイル名を指定して実行)

「sysdm.cpl」を入力して「OK」
※コントロールパネル内の「システムの詳細設定」と同じ

「システムのプロパティ」ウィンドウが開くので、詳細設定タブ>環境変数

※ここにjavac.exeがあるフォルダを指定すると、コマンドプロンプトからjavacの命令が使えるようになる。
※ていうか指定したフォルダ内の全てのexeファイルが命令として使えるようになる。

2. ○○のユーザー環境変数にフォルダのパスを追加

・既に変数欄に「Path」が存在する場合

Pathの欄をダブルクリック

右部「新規」をクリックすると左部一覧の空白行に入力可能な状態となるので、 「C:\Program Files\Java\jdk1.8.0_131\bin」等、JDKがインストールされた場所下binフォルダのパスを入力する。
※バージョンやインストール場所によって適切に変更する。
※binフォルダ内にjavac.exe等があることを一度見てみると良いと思う。

・変数欄に「Path」が存在しない場合

ウィンドウ中部「新規」をクリック

変数名には「Path」

変数値には「C:\Program Files\Java\jdk1.8.0_131\bin」等、JDKがインストールされた場所下binフォルダのパスを入力する。
※バージョンやインストール場所によって適切に変更する。
※binフォルダ内にjavac.exe等があることを一度見てみると良いと思う。

3. 解決or別のエラー

設定したPathが間違っていないか、binフォルダの中身をリネーム・削除する趣味を持っていない限り同じ、少なくとも同じエラーは出ないと思う。

コンパイルして同じフォルダ内に*.classファイルができていれば、コーヒーを一口飲んでこのページを閉じる。

{Ruby} getsで受け取った文字列の長さ

getsで受け取った文字列の長さ(.size .length)を取得しようとしたけど実際より多く数えられちゃうよさん向け

str = gets # <="aaa"を入力
puts str.size # 4

この時strには"aaa\n"が入ってる

puts str # "aaa"
p str # "aaa\n"

なんとなくputs使ってたけどp使おって思いました。

改行コードを数えたくない場合はchompで改行コードを除外する。

puts str.chomp.size # 3

javaで標準入力を受け取るときは改行コードが入って来ないみたいなんで意識したことなかったけど、なんとなくputs使うんじゃなくてp使おって思いました。楽しかったです。またやりたいです。

{Java} paizaでの標準入力の受け取り方

paizaで与えられる文字列をどうやって使えばいいかわからん人用。

この記事の目次
・基本的な受け取り方
・複数行入力される時
・スペース区切りで入力される時

基本的な受け取り方

Scanner sc = new Scanner(System.in);
String line = sc.nextLine();
System.out.println("XXXXXX");

とりあえず言語でJavaを選んだらこんなコードが出てくる。
この sc.nextLine() が標準入力を受け取る合図みたいなもの。
(詳しくは省略するけど、Scannerをscで初期化してるので、sc.nextLine()になってるぐらいに思っといて)
仮に標準入力で与えられるのが "hoge" だった場合、

String x = sc.nextLine(); // xに"hoge"が代入される
System.out.println(x); // "hoge"

となる。終わり。

複数行入力されるとき。

問題によって入力が複数回行われる時がある。

入力される値
入力は以下のフォーマットで与えられます。
c_1
c_2
入力は 2 行となり末尾に改行が入ります。

一例としてはこんな感じで書かれていることが多い。

これは単純に1回目のsc.nextLine()では1行目
2回目のsc.nextLine()では2行目が受け取られる

仮に2行入力され、1行目が"one"、2行目が"two"だった場合、

String x = sc.nextLine(); // xに"one"が代入される
String y = sc.nextLine(); // yに"two"が代入される
System.out.println(x); // "one"
System.out.println(y); // "two"

となる。要は入力される回数だけreadLine()!を繰り返せばいい。

つまり3回入力されるなら

String[] strs = new String[3]; // 標準入力を受け取る用の空の配列を定義
for (int i = 0; i < 3; i++) { // 3回ループさせ、
    strs[i] = sc.nextLine(); // i番目に1つづつ受け取っていく
}

て感じでループ内で取得して配列に格納することもできる。
ただ受け取る入力がそれぞれ違った種類の情報のときは

String time = sc.nextLine(); // 15
String place = sc.nextLine(); // "cafe"
String occasion = sc.nextLine(); // "hungry"

と、一つずつ変数に入れるべきだと思う。

スペース区切りで入力されるとき。

一行で値を複数入力してくる問題もある。

入力される値
入力は以下のフォーマットで与えられます。
c_1 h_1
1 行目には文字列Aと文字列Bが半角スペース区切りで与えられます。

こんな感じで。 仮に"Tanaka" "Suzuki"の二つの文字列が与えられる場合、

String x = sc.nextLine(); // xに"Tanaka Suzuki"が代入される
System.out.println(x); // "Tanaka Suzuki"

となる。単純に1つの文字列として入力されるが、大抵"Tanaka"と"Suzuki"は別々に使いたいと思う。そんな時は

String[] strArray = sc.nextLine().split(" "); // " "がある所で区切って格納する

System.out.println(strArray[0]); // "Tanaka"
System.out.println(strArray[1]); // "Suzuki"

sc.nextLine();の後に .split(" ") を足せば解決できる。
カッコ内に " " のように半角スペースを指定すれば、半角スペースをその区切りとして認識して、区切りごとに配列の要素として順に格納される。

ただ大抵扱うのは数値なので、初めからint型にキャストしてやりたい。

int[] intArray = Arrays.stream(sc.nextLine().split(" ")) //" "で区切ってString配列へ
    .mapToInt(Integer::parseInt) //数値にして
    .toArray(); //配列へ

String型で受け取っておいてintに変換してもいいけど、確実に数値が入ってくるなら、受け取る→配列型にする→型変換するを一気にやっちゃえる。
※わかりやすいように行を分けてるけど、一行で書けるからね。

{Java} 配列とリストどっち使うの?

swiftの配列に慣れるとjavaの配列がわからなくなるのでまとめ
この記事の目次 ・生成
・取り出し
・格納
・追加、検索、削除
・ラッパー型とプリミティブ型
・一挙に複数の値をリストに格納するには

配列とリストの違い

配列

・サイズを変更できない
 →追加、削除ができない
・処理が早い
・書き方が簡単(後述)

リスト

・サイズが可変である
 →追加、削除ができる
・処理が遅い
・書き方がややこしい(後述)

あたりをとりあえず押さえとかないといけないらしい。

生成

配列
int[] list = new int[5]; //要素数5個
リスト
List<Integer> list = new ArrayList<Integer>();

配列は要素数を決める必要があるが、リストは可変であるため必要ない。
※配列は宣言と初期化を別々に行うことで宣言の段階では要素数を決める必要は無いが、どうせ使う前に初期化するので実質初めに決めないといけない。

int[] list; //宣言
list = new int[5]; //初期化

あとリストはプリミティブ型を使うことができない(後述)

取り出し

配列
String value = list[0];
リスト
String value = list.get(0);

リストのほうがちょっとめんどくさい。

格納

配列
list[0] = "簡単";
リスト
list.set(0, "ややこしい");

リストのほうがちょっとめんどくさい。

末尾への追加・検索・削除

配列

不可

リスト
list.add("可"); //末尾に"可"を追加する
int index = list.indexOf("検索したい文字や数値"); // indexにキー番号が代入
list.remove(0); //0番目を削除

このへんは全て配列ではできない。

まとめ

配列のほうは追加や削除ができない分、何個入るかわかんないデータとかを取り扱う時はリストのほうが良さそう。
ただ取り出したり格納するだけなら、早くて簡単な配列を使えば良さそう

ただ生成の項目でも少し触れたけど、リストはプリミティブ型を使うことができない。

intとIntegerはどっちも数値を格納できるし、同じように使うことができるけど、Integerは一旦intに変換されて計算されてるみたい。
このIntegerはプリミティブ型に対してラッパー型と呼ばれてるらしい。

要はラッパー型のままループなんかに使ったりすると、毎回変換が行われる分効率の悪いコードになるそう。
長いループでリストに格納されてるデータを使うときなんかは、一旦ループの外でプリミティブ型に変換してから使ってやるべきっぽい。
例えば

suuchi.add(3);
suuchi.add(7);
suuchi.add(4);
suuchi.add(5);
suuchi.add(8);

for (int i = 0; i < suuchi.size(); i++) {
    int temp = suuchi.get(i); //長いループの外でプリミティブ型に変換
    for (int j = 0; j < 1000; j++) {
        System.out.println(temp * j);
    }
    
}

素数*1000回ずつ繰り返されるfor文の中で毎回変換させてたらすごく効率が悪いので、要素数分しか繰り返されない所で自分で変換させてやるほうが良いっぽい。

ていうかこういう時に一々addするのも面倒だと思ったので、

(おまけ)一挙に複数の値をリストに格納

いろいろ方法はあるみたいだが、とりあえず一番シンプルなのはこれ。

List<Integer> list = Arrays.asList(3, 2, 4, 5);

ただこの方法だと可変でなくなるという致命的で致命的な致命的欠点があるので、

Collections.addAll()
List<Integer> list = new ArrayList<Integer>(); //宣言のみ
Collections.addAll(suuchi, 3, 2, 4, 6);

list.add(8) //可変なので追加できる

とすれば、可変のまま一挙に追加できる。

おわり。

追記

覚えやすいように、 配列 → カップ麺(簡単で早い)
リスト → 腕組みしてるラーメン屋のラーメン(めんどくさいけどうまい)
って覚え方を個人的にしてたけど、後者は後から胡椒を.addするとむしろ怒られそうだから理に適ってないことが判明した