「エンジニアでも恋がしたい！」にラムダ式で釣られてみた

##要約
@Paizaにて
[「エンジニアでも恋がしたい！」](https://paiza.jp/poh/enkoi)
という~~最近ありがちな萌え~~企画をやってましたので、釣られてみました。
どうせなら、ラムダ式の実践をしてみようかと思います。

問題形式は、標準入力からのデータを取得し、計算結果を標準出力に出すという形式です。

##一問目
標準入力から、
* 一行目 データが何行あるか
* 二行目以降 データ。一行目の値だけある
が来るので、データの合計を求めるというものです。
基本的なInputStreamの使用方法が学習できました。

```java:入力例1
4 
5 
10 
2 
3
```
```java:出力例1
20
```
```java:Mission1.java
try(Scanner scanner = new Scanner(System.in)){
    System.out.println(
        IntStream
        .range(0, scanner.nextInt()) // 行数を求める
        .map(i -> scanner.nextInt()) // 各行の値を求める
        .sum());                     // 合計する
}
```

*range(0, scanner.nextInt())
一行目の値を取得し、0から入力一行目未満の数でループを回す。(正確には、入力一行目分の要素の入ったIntStreamを返している)

*map(i -> scanner.nextInt())
データを取得し、取得した値のInputStreamを返している。
iはrangeで作成した値が入るのですが、使い道はありませんので、無視。

*sum()
合計する

##二問目
標準入力から、
* 一行目 データが何行あるか
* 二行目以降 データ。一行で商品の「必要な数 在庫数 単価」を表す。
が来るので、在庫数が必要な数より少ない場合、補填する数に単価をかけた数の合計を求めるというものです。
自分でクラスを作成したときのラムダ式の使用方法が学習できました。

```java:入力例1
4
10 5 100
4 0 300
2 2 200
2 3 100
```
```java:出力例1
1700
```

一行を表すクラスは以下のようです。（ファイルを分けて書いてありますが、実際の提出は一ファイルに納めないといけません。そのため、手抜きでprivate変数に普通にアクセスしてます）

```java:Product.java
class Product{
    	/** 必要な数*/
    	private int T;

    	/** 在庫数*/
    	private int S;

    	/** 単価*/
    	private int P;

	public Product(int T, int S, int P){
		this.T = T;
		this.S = S;
		this.P = P;
	}
}
```

```java:Mission2.java
final Scanner scanner = new Scanner(System.in);
System.out.println(
    	IntStream
    	.range(0, scanner.nextInt()) // 何行あるか指定している
    	// 一行を読み取って、Productクラスのインスタンスにする
    	.<Product>mapToObj(i ->new Product(scanner.nextInt(), scanner.nextInt(), scanner.nextInt()))
        .filter(p -> (p.S < p.T)) // 在庫の方が多い場合、計算しない
        // 商品ごとの購入費を算出する
        //必要な数から在庫数を引き、購入数をだす。その値に価格をかける
        .mapToInt(p ->((p.T - p.S) * p.P))
        .sum()   // 商品ごとの購入費を合計する
   );
scanner.close();
```

* <Product>mapToObj(i ->new Product(scanner.nextInt(), scanner.nextInt(), scanner.nextInt()))
scanner.nextInt()でデータを読み取って、一行が読み込めたら、Productにインスタンス化してます。
Javaのラムダ式でデータの型を変換するようなケースは「mapToなんとか」を使用するようです。クラスの場合はmapToObjですね。

* filter(p -> (p.S < p.T)) 
在庫数が必要な数未満であるときのみ計算を行う

*mapToInt(p ->((p.T - p.S) * p.P))
商品ごとの購入価格を算出します。必要な数から在庫数を引き購入数を出し、単価を掛けます。Productからintegerに変換するので、mapToIntを使用します。

*sum()
商品ごとの購入価格を合計します。

##三問目
標準入力から、
* 一行目 データの連続数 データが何行あるか
* 二行目以降 得点

二行目以降の得点をデータの連続数分の行を各行足して、その最大値を出力する。
たとえば、入力例の場合、
1-3行目:4  +  5 +  1 = 10
2-4行目:5  +  1 + 10 = 16
3-5行目:1  + 10 +  3 = 14
4-6行目:10 +  3 +  4 = 17
5-7行目:3  +  4 +  1 = 10 
で、最大値の17が出力される。

```java:入力例1
3 7 
4
5
1
10
3
4
1
```

```java:出力例1
17
```

```java:Mission3.java
//コマの総数 n
int n;

//区間の長さ t
int t;

// 得点リスト
int [] score;

try (Scanner scanner = new Scanner(System.in)){

	//コマの総数 n
   	 n =  scanner.nextInt();

   	//区間の長さ t
   	t =  scanner.nextInt();
   	scanner.nextLine();

   	// 得点を取得する
   	score = new int[t];
   	IntStream.range(0, score.length).forEach(index -> score[index] = scanner.nextInt());
}

int max =  IntStream.range(0, t-n+1)
       .map(i ->
       	Arrays.stream(score,i, i + n).sum()
       ).max().getAsInt();

System.out.println(max);
```

`IntStream.range(0, score.length).forEach(index -> score[index] = scanner.nextInt())`にて、全行を読み取って、scoreに値を代入する。
`Arrays.stream(score,i, i + n).sum()`にて、各行から数行分の点数を合計する。
`.max().getAsInt();`にて、各合計の中の最大値をintで取得してます。


##三問目・改
これでは毎回計算するので、遅い。そのため、しゃくとり算で実施したのが下記。各行を読み込みつつ、新しい行の得点を足し、無効になった得点を引く、という処理をしてます。
最初のn行目までのデータは単純に合計し、n行目以降は足して引いた値を返しています。
各行の得点は後で使用するため(引く値の取得)、配列に入れてます。

```java:Mission3.java
try(Scanner scanner = new Scanner(System.in)){

	//コマの総数 n
	int n =  scanner.nextInt();

	//区間の長さ t
	int t =  scanner.nextInt();
	scanner.nextLine();

	int [] score = new int[t];


	// total[0] : 一つ目の組み合わせの合計
	// total[1] : 二つ目以降の組み合わせの合計
	//           計算ごとに変わり、最後に最大値を入れる
	long[] total = new long[2];
	
	// 最初のセットの合計を取得する
	total[0] =
		IntStream
		.range(0, n)
		.map(i -> (score[i] = scanner.nextInt()))
		.sum();

	// 二つ目以降のセットの合計を出し、最大値を取得する
	total[1] = total[0];
	total[1] =
        	IntStream
        	.range(n, t)
        	.mapToLong(i ->
        	{
        		score[i] = scanner.nextInt();

        		// 前の得点を減算して、新しい得点を加算した値と返す
        		// 前の結果を残しておく手段がわからず、配列で対応
        		return total[1] += (- score[i-n] + score[i]);
        	})           // それぞれのコマの合計を取得する
        	.max()       // 最大値を取得する
        	.orElse(0L); // コマの総数と区間の長さが同じ場合、結果がないため、0を返す

    // 最初と二つ目以降のセットの合計値で大きい方を出力する
    System.out.println(Arrays.stream(total).max().getAsLong());
}
```
だいぶ、無理矢理感半端ない代物になりました。ラムダ式内にラムダ外で定義したintを使用できないので、配列を使用して、合計値をキープしてます。その合計値を、次の行での計算に使用してます。ここをどう扱えば賢いのか、教えていただけると嬉しいです、、、

##まとめ
以前ラムダ式の投稿をしましたが、ほぼ写経でした。今回はもう少し実践的な勉強ができたかと思います。
ラムダ式を使用すると、確かに行数が激減することが納得できました。一問目をラムダ式梨で書くと、一行目の値を保持する変数と、合計を求める変数を作成して、for文を書いて、となりますので。

私はラムダ初心者ですので、こうやった方がいいよ、的なアドバイスがあれば、大変嬉しいです。
以上です。