7-1. データの集まり

データの集まりとして、あらゆるコンピュータ言語で利用可能なのが配列型です。 また、昨今のプログラミング言語で大抵利用可能なのが、線形リストや連想配列です。 さらに、 Java8 から、新しいデータの集まりの取り扱い方として Stream が導入されました。

Map

Map とはデータの集まりx1, x2, ..., xn 全てに与えられた関数 f(x)を 掛けた 集まり f(x1), f(x2), ..., f(xn)を求めるものです。 例えば、 Stream に値の2乗を求めるには次のようにします。


      stream.map((x)->x*x)
  

Reduce

Reduce とは足し算など結合法則の成立する2項演算により、データの集ま りを集計するものを言います。 データの集まり x1, x2, ..., xn に対して2項演算g(x,y) があった時、 g(g(...,g(g(x1,x2),x3),...),xn) を求めることを言います。 但し、結合法則とは、 g(g(x,y),z)=g(x,g(y,z)) が成り立つことを言い ます。

例えば、 Stream の値のすべての積を求めるには、変数の初期化の値を含め、 次のようにします。


      stream.reduce(1,(x,y)->x*y);
  

並列計算

Map も Reduce も並列計算可能です。 Map はすべてバラバラに別プロセスで実行できます。 プロセス数が無制限なら、1ステップですべての処理が行なえます。 一方、 Reduce は各ペアごとの集計が並列でできるため、プロセス数が無制限 なら、最速では1ステップで要素数を半分にすることができます。 つまり最速なら、要素数の対数ステップで集計を完了することができます。

Stream には parallel() メソッド、 sequncial() メソッドがあり、順次 処理と並列処理を切り替えることもできます。

7-2. 演習

演習7-2

git://edu.net.c.dendai.ac.jp/git/spro/7/1 より下記のプログラムを読み込み、実行し、プログラムの文法や動作を確 認しなさい。


package spro7;

import java.util.Arrays;
import java.util.List;
import java.util.function.DoubleSupplier;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;

public class Main {
	final private static int n=10000000;
	public static void main(String[] args) {
		double[] a = getRandom(n);
		DoubleStream asp = Arrays.stream(a);
		DoubleStream as = Arrays.stream(a);
		List<Double> al = Arrays.stream(a)
				.boxed()
				.collect(Collectors.toList());
		DoubleSupplier[] ds ={
				()->sumarray(a),
				()->sumstream(as),
				()->sumlist(al),
				()->sumpstream(asp)
		};
		long[] t = new long[ds.length+1];
		double[] result = new double[ds.length];
		int i;
		for(i = 0; i < ds.length; i++){
			t[i] = System.currentTimeMillis();
			result[i] = ds[i].getAsDouble();
		}
		t[i] = System.currentTimeMillis();
		for(i = 0; i < ds.length; i++) {
			System.out.println(result[i]);
			System.out.println(t[i+1] - t[i]);
		}
	}
	private static double sumlist(List<Double> al) {
		double sum = 0;
		for(double x:al) {
			sum+=x;
		}
		return sum;
	}
	private static double sumpstream(DoubleStream as) {
		return as.parallel().sum();
	}
	private static double sumstream(DoubleStream as) {
		return as.sequential().sum();
	}
	private static double sumarray(double[] a2) {
		double sum = 0;
		for(double x:a2) {
			sum+=x;
		}
		return sum;
	}
	private static double[] getRandom(int n2) {
		double[] result = new double[n2];
		for(int i=0; i<n2; i++ ) {
			result[i] = Math.random();
		}
		return result;
	}

}

演習7-3

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. すべてを表示する

演習7-4

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. Math.random によりひとつ乱数を生成する
  3. すべての要素にその乱数をかける
  4. すべてを表示する

演習7-5

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. double 型で一つの値を決める
  3. すべての要素にその値をかける
  4. すべてを表示する

演習7-6

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. double 型で一つの値を決める
  3. すべての要素にその乱数をかけて、小数点以下を求める
  4. 小数点以下の平均値を求める
  5. 求めた平均値を表示する

7-3. 付録

コレクションのデザインパターン

オブジェクトの集まりに関する重要なデザインパターンにイテレータ(列 挙子)があります。 これは、集まりから生成するオブジェクトで、イテレータには、次の値を 取り出すメソッドと、次の値があるかどうかを調べるメソッドを実装しま す。

java.util.Collection のサブクラス(List, Set)は、java.lang.Iterable インタフェースを implement しています。 これは iterator() メソッドを実装していることを意味します。 これで取得したオブジェクトは java.util.Iterator インタフェースを実 装しています。これは、 next() メソッドと、 hasNext() メソッドを実 装していることを意味します。

例7-1


List<String> list = getList();
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
    String string = iterator.next();
    System.out.println(string);
}

しかし、Java では Iterable なオブジェクトの集まりには拡張for文が適 用できます。

例7-2


List<String> list = getList();
for(Strng string : list){
    System.out.println(string);
}

java.util.Iterator には値を変更したり消去したり挿入したりすること はできません。 しかし、java.util.ListIterator はそれらが可能になります。


List<Integer> list = getList();
ListIterator<Integer> iterator = list.listIterator();
while(iterator.hasNext()){
    int x  = iterator.next();
    iterator.set(x+1);
}

7-4. 演習解答

演習7-3

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. すべてを表示する

配列

  
int[] a = new int[10000];
for(int i=0; i<a.length; i++){
  a[i]=i;
}
for(int x : a){
  System.out.println(x);
}

ArrayList

  
List<Integer> a = new ArrayList<>();
for(int i=0; i<10000; i++){
  a.add(i);
}
for(int x : a){
  System.out.println(x);
}

Stream

  
IntStream a = IntStream.iterate(0,x->x+1).limit(10000);
a.forEach(System.out::println);

演習7-4

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. Math.random によりひとつ乱数を生成する
  3. すべての要素にその乱数をかける
  4. すべてを表示する

配列

  
double[] a = new double[10000];
for(int i=0; i<a.length; i++){
  a[i]=i;
}
double r = Math.random();
for(int i=0; i<a.length; i++){
  a[i]*=r;
}
for(double x : a){
  System.out.println(x);
}

ArrayList

  
List<Double> a = new ArrayList<>();
for(int i=0; i<10000; i++){
  a.add((double)i);
}
double r = Math.random();
for(ListIterator<Double> i = a.listIterator(); i.hasNext();){
  double x = i.next();
  i.set(x*y);
}
for(Double x : a){
  System.out.println(x);
}

Stream

  
double r = Math.random();
DoubleStream.iterate(0,x->x+1.0).limit(10000)
.map(x->x*r)
.forEach(System.out::println);

演習7-5

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. double 型で一つの値を決める
  3. すべての要素にその値をかける
  4. すべてを表示する

配列

  
double[] a = new double[10000];
for(int i=0; i<a.length; i++){
  a[i]=i;
}
final double r = 3.14;
for(int i=0; i<a.length; i++){
  a[i]*=r;
}
for(double x : a){
  System.out.println(x);
}

ArrayList

  
List<Double> a = new ArrayList<>();
for(int i=0; i<10000; i++){
  a.add((double)i);
}
final double r = 3.14;
for(ListIterator<Double> i = a.listIterator(); i.hasNext();){
  double x = i.next();
  i.set(x*y);
}
for(Double x : a){
  System.out.println(x);
}

Stream

  
final double r = 3.14;
DoubleStream.iterate(0,x->x+1.0).limit(10000)
.map(x->x*y)
.forEach(System.out::println);

演習7-6

以下の処理を配列、ArrayList、Stream でそれぞれ作ること

  1. 0から9999までの値を持つ集まりを作る
  2. double 型で一つの値を決める
  3. すべての要素にその乱数をかけて、小数点以下を求める
  4. 小数点以下の平均値を求める
  5. 求めた平均値を表示する

配列

  
double[] a = new double[10000];
for(int i=0; i<a.length; i++){
  a[i]=i;
}
final double r = 3.14;
for(int i=0; i<a.length; i++){
  a[i]*=r;
  a[i]-=(int)a[i];
}
double sum=0;
for(int i=0; i<a.length; i++){
  sum+=a[i];
}
double average = sum/a.length;
System.out.println(average);

ArrayList

  
List<Double> a = new ArrayList<>();
for(int i=0; i<10000; i++){
  a.add((double)i);
}
final double r = 3.14;
for(ListIterator<Double> i = a.listIterator(); i.hasNext();){
  double x = i.next()*y;
  x -= (int) x;
  i.set(x);
}
double sum=0;
for(Double x : a){
  sum+=x;
}
double average = sum/a.size();
System.out.println(average);

Stream

  
final double r = 3.14;
double average = DoubleStream.iterate(0,x->x+1.0).limit(10000)
.map(x->{x*=y;x-=(int)x; return x;})
.average();
System.out.println(average);