

閲覧ありがとうございます。  
現在java独学中のプログラミング初心者です。

キーボードから数字一文字を入力し、その数字が配列の中の数字のどれかと一致していれば「アタリ！」、一致していなければ「ハズレ」と表示されるコードを書きたいのですがどう書けば良いのかわからず質問させて頂きました。

[code]

    public class Aaaa {
    
        public static void main(String[] args) {
            // TODO 自動生成されたメソッド・スタブ
            int[] numbers = {3,4,9};
    
            System.out.println("１行の数字を入力してください");
            int input=new java.util.Scanner(System.in).nextInt();
    
    
            for(int a:numbers) {
                if(a==input) {
                    System.out.println("アタリ！");
                }   
                if(a!=input) {
                    System.out.println("ハズレ");
                }
            }
    
        }
    }
    
[/code]

こちらが私が書いたコードなのですが、これだと「9」と入力した場合、

ハズレ  
ハズレ  
アタリ！

と表示されてしまいます。  
どのように改善すればアタリ！とだけ表示されるのでしょうか？

初歩的な質問だとは思いますが、どなたか教えていただけると助かります。  
宜しくお願い致します。


CC BY-SA 4.0
2018-07-03T12:50:55.060
45272
2018-07-03T16:42:40.027
2018-07-03T13:14:47.917
19110
29166
question
java array
配列のfor文の中にif文を入れる文について


### アルゴリズムについての整理

int型配列numbersにint型値inputが含まれていればアタリを表示し含まれていなければハズレを出力する。この要件を満たすような手続きの記述方法はいくつかある。  
(紙面節約のためjavaらしくない{}の配置をしているがうまく読み替えてください)

###  プログラム1

プログラム1は、上記アルゴリズムを直接書き下す方法でありおそらく速度も速い。  
下記の方法ではbreakを使っており、アタリを見つけ次第forを抜けるため効率的である。  
このようにアルゴリズムを直接書き下すようなやり方を命令的プログラミングと表現することがある。

[code]

    class Aaaa{
        public static void main (String[] args) {
            int[] numbers = {3,4,9};
            int input = new java.util.Scanner(System.in).nextInt();
            for(int i=0;i<numbers.length;i++){
                if(numbers[i]==input){System.out.println("アタリ！");break;}
                if(i==numbers.length-1){System.out.println("ハズレ！");}
    }}}
    
[/code]

上記のアルゴリズムでは拡張for文 つまりfor(int a:numbers)という記述を使っていない。  
これはなぜか？配列の最後の要素において処理を分岐する必要があるからである。  
(アタリが無かったということを記述する必要がある)  
ある特定の要素のみ処理を変えるという記述が必要ならば拡張for文はうまくいかない。  
参考までに下記URLを記載しておく。(イテレータという概念を用いて最後の要素だけ処理を変えるというテクであるが、素直にfor文を書いたほうがいいと思われる)  
<https://hacknote.jp/archives/20420/>

### プログラム2

プログラム1は動作は速いがいかにも不安が付きまとう。インデックスの指定が間違っているのではないか、バグが混入しているのではないか？  
我々はもっと見通しのよい、バグのないことを確証できるようなプログラムを書きたいと思う。  
本当はpythonやSQLでいうところのin演算子があればよいのだが、javaにはない。

[code]

    Arrays.asList(numbers).contains(input)
    
[/code]

本当は上記のように判定できたらいいのだが、javaはint型の配列(正確にはprimitive型の配列)をListにうまくキャストしてくれない。(上記の配列を要素として持つ長さ1のListになるため、この記述では期待する結果を得ることができない)  
参考になるURLをもう一つ記述する。  
<https://stackoverflow.com/questions/1128723/how-can-i-test-if-an-array-
contains-a-certain-value>  
この質問と回答は非常にレベルが高いが要約すると、

[code]

    class Aaaa{
        public static void main (String[] args){
            int[] numbers = {3,4,9};
            int input = new java.util.Scanner(System.in).nextInt();
            if(java.util.stream.IntStream.of(numbers).anyMatch(a -> a == input)) 
                {System.out.println("アタリ");}
            else
                {System.out.println("ハズレ");}
    }
    
[/code]

と記述するのがいいと示唆している。特に重要なのがif文の中である。  
まずはint型配列numbersをint型のストリームにキャストしている。ストリームは配列などを頭から順番に1つずつ要素を取り出していくようなものだとイメージしておけばよい。  
a -> a==inputはいわゆるラムダ式と呼ばれているもので、これは入力はa
出力はa==inputというbool型数値となるような関数を短く書いているに過ぎない。aの具体的な値は3とか4とか9が順番に入っていくとイメージすればいい。  
上記ラムダ式(関数)は入力3,4,9というストリームをFalse,False,Trueというストリームに置きかえる。  
anyMatch関数はFalse,False,Trueという流れ(ストリーム)をTrueに置き換える。  
anyMatchは例えばTrue,False,Falseという流れを受け取った場合、最初がTrueだと分かった時点で計算をbreakしてTrueを返す、そういう関数であるので効率はそれほど悪くない。  
プログラム2のような書き方を宣言的なプログラムと呼ぶことがある。アルゴリズムの詳細(インデックスがどうとか、分岐がどうとか)をプログラマが指定することなくただnumbersの中にinputと==になるものがあれば"アタリ"、そうでないならば"ハズレ"と出力せよと書いている。

### どちらがよいか

基本的にはプログラム2のような書き方を推奨する。ストリームAPIは汎用性が高いプログラムを短く記述でき細かい分岐の指定を回避することができるためである。ただプログラム1のような記述を知っておくことも重要である。


CC BY-SA 4.0
2018-07-03T13:07:46.100
45273
2018-07-03T16:42:40.027
2018-07-03T16:42:40.027
25980
25980
45272
answer


### 解決策

`numbers` に含まれるすべての要素と等しくないと分かってから初めて「ハズレ」と出力するようにしてください。

### 実験

どのようにプログラムが動いているか、具体的な値を使って実験してみましょう。

たとえば入力された数字が `9` だったとします。これは `{3, 4, 9}` に含まれているので、期待している出力は「アタリ！」です。

質問文中にあるプログラムの for 文を見てみます。

[code]

    for(int a:numbers) {
        if(a==input) {
            System.out.println("アタリ！");
        }   
        if(a!=input) {
            System.out.println("ハズレ");
        }
    }
    
[/code]

この for では、配列 `numbers` 、つまり `{3, 4, 9}` を頭から順番に試すことで、`9` と等しいかどうか検査しようとしています。

さて、`{3, 4, 9}` の先頭は `3` なので、まず `a` に `3` が代入され、1 つ目の if 文のところで `a == input`
であるかどうか確かめられます。`3` と `9` は等しくないので、この if 文の中身は実行されません。

次に 2 つ目の if 文で `a != input` であるか確かめられます。これは真なので if
文の中身が実行され、「ハズレ」と出力されます。……あれ、期待していた動作と異なる動き方になってしまいました :-(

また、この後 for 文の最初に戻り、`a` に `4` が代入されて「ハズレ」が出力されます。最後に `a` に `9` が代入され、今回は `a ==
input` なので「アタリ！」が出力されます。

したがって全体的には実際の出力が

[code]

    ハズレ
    ハズレ
    アタリ！
    
[/code]

と 3 行に渡って出力されることになってしまいます。

### 原因

こうなってしまう原因は、配列 `numbers` の中身 **すべて** に対して「等しいかどうか」を調べるより **前**
に「ハズレ」と出力してしまうことにあります。現状のプログラムだと、各要素 1
つだけの比較のみを元にして何回も「アタリ！」「ハズレ」を出力してしまっているのです。

### 解決策 (再掲)

したがって、`numbers` に含まれるすべての要素と等しくないと分かってから初めて「ハズレ」と出力するようすれば良いのです。

たとえば for 文を回す前に「アタリだったかどうか」を示す boolean 型の変数を用意しておいて、`false` で初期化しておきます。そして for
文の中ではこの変数を弄ることしかせず、for 文が終わってから「アタリ！」「ハズレ」を true/false に基づいて 1
回だけ出力するようにすると、求める動作になります。

サンプルコードを以下に隠しておきます（マウスカーソルを上にかざすと表示されます）ので、必要に応じて参考にして下さい。

> boolean flag = false;  
>  for (int a : numbers) {  
>      if (a == input) {  
>          flag = true;  
>          break;  
>      }  
>  }  
>  if (flag) {  
>      System.out.println("アタリ!");  
>  } else {  
>      System.out.println("ハズレ");  
>  }


CC BY-SA 4.0
2018-07-03T13:32:00.920
45274
2018-07-03T13:43:16.883
2018-07-03T13:43:16.883
19110
19110
45272
answer