12. デザインパターン(3/x)
Javaの総称型解説
参照: https://www.sejuku.net/blog/22699
アルゴリズムの効率化について考える。
例)
冪乗(べき乗計算)
https://en.m.wikipedia.org/wiki/Addition-chain_exponentiation
冪乗計算を効率的に行うアルゴリズム 繰り返し2乗法 をJava で メソッド pow1 として実装する。
さらに、pow1 を 特定の型以外にも適用できるように、 総称型メソッド を用いて汎用性を持たせる。
プロジェクト名 java7 を作成する。
class L12 を作成し、以下について実験する。
総称型入門
授業で解説を交えながら、以下を作成する。
package java7;
public class L12 {
public static void main(String[] args) {
int x = 1;
int y = 2;
swap(x,y);
System.out.println("x= " + x);
System.out.println("y= " + y);
Pair p = new Pair(1,2);
//Pair2<String> p = new Pair2<>("abc","xyz");
//Pair2<Double> p = new Pair2<>(1.2, 2.2);
System.out.println("px= " + p.first);
System.out.println("py= " + p.second);
p.swap();
System.out.println("px= " + p.first);
System.out.println("py= " + p.second);
p.swap();
System.out.println("px= " + p.first);
System.out.println("py= " + p.second);
}
// うまく行かない例
static void swap(int a, int b) {
System.out.println("a= " + a);
System.out.println("b= " + b);
int tmp;
tmp = a;
a = b;
b = tmp;
System.out.println("a= " + a);
System.out.println("b= " + b);
}
static class Pair {
int first;
int second;
Pair(int f, int s) {
first = f;
second = s;
}
void swap() {
int tmp;
tmp = first;
first = second;
second = tmp;
}
}
static class Pair2<F> {
F first;
F second;
Pair2(F f, F s) {
first = f;
second = s;
}
void swap() {
F tmp;
tmp = first;
first = second;
second = tmp;
}
}
}
ちなみに、2進数の排他的論理和 Xor を利用してビット演算を行えば、2つの変数の整数の値の入れ替えは一時変数なしで行える。
例)
b ^= a;
a ^= b;
b ^= a;
画像の入れ替えバージョン(二進数の0と1を、黒と白と考える)
白黒画像のAとBを用意する、
画像A
画像B
画像Aに画像Bを合成する。
合成方法は、Xor
同じ色の部分は黒 つまり黒と黒は 黒 、白と白は 黒。 髪の毛はどちらも黒なので 黒。背景はどちらも白なので 白。
異なる色部分は白 つまり黒白 白黒 と異なる色は 白になる。
画像Aはこのようになる。
= A xor B
画像Bに、合成後の画像AをXorで合成する。すると画像Bは、合成前の画像Aになる。
なぜなら、合成後の画像Aは、元の画像Aと元の画像Bのどちらに対しても、
Xorで合成する場合 色の変化が必要なら 白
Xorで合成する場合 色の変化が不要なら 黒
の状態になっているので。
合成後の画像B
= B xor A
合成後の画像Aに合成後の画像BをXorで合成する。すると画像Aは、合成前の画像Bになる。
合成後の画像A
= A xor B←合成後の画像B
画像Aと画像Bの内容を入れ替えることが出来た。
プロジェクト java7 に以下を追加する。
class L12a を作成し、以下について実験する。
授業で解説を交えながら、以下を作成する。
package java7;
import java.math.BigInteger;
public class L12a {
public static void main(String[] args) {
// 冪乗(べき乗の計算)
int x = 1;
//long x = 1L;
for(int i=0; i<65; i++){
System.out.println(i +":"+ x);
x *= 2;
}
BigInteger bigx = BigInteger.ONE;
for(int i=0; i<65; i++){
System.out.println(i +":"+ bigx);
bigx = bigx.multiply(BigInteger.valueOf(2L));
}
bigx = BigInteger.valueOf(2L);
for(int i=1; i<65; i*=2){
System.out.println(i +":"+ bigx);
bigx = bigx.multiply(bigx);
}
pow1(2,64);
pow1(2,63);
pow1(2,62);
pow1(3,64);
pow1(3,63);
}
// 繰返し2乗法
// https://en.m.wikipedia.org/wiki/Addition-chain_exponentiation
static BigInteger pow1(int x, int n) {
if(n<1) return BigInteger.ONE;
BigInteger tmp = pow1(x, n/2);
tmp = tmp.multiply(tmp);
if(n % 2 == 1) tmp = tmp.multiply(BigInteger.valueOf(x));
System.out.println(n +":"+ tmp);
return tmp;
}
}
プロジェクト java7 に以下を追加する。
class L12b を作成し、以下について実験する。
授業で解説を交えながら、以下を作成する。
package java7;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.BinaryOperator;
public class L12b {
public static void main(String[] args) {
// 冪乗(べき乗の計算)
pow2(BigInteger.valueOf(2), 64, BigInteger.ONE, (a,b)-> a.multiply(b));
pow2(BigInteger.valueOf(2), 63, BigInteger.ONE, (a,b)-> a.multiply(b));
pow2(2L, 63, 1L, (a,b)-> a*b);
pow2("ab", 10, "", (a,b)-> a+b);
// 型推論を利用したダイヤモンド演算子 <> の利用例
ArrayList<ArrayList<Integer>> ai = new ArrayList<>();
ai.add( new ArrayList<Integer>(Arrays.asList(0)));
ai.add( new ArrayList<Integer>(Arrays.asList(1)));
// 冪集合
pow2(
ai,
4,
new ArrayList<ArrayList<Integer>>(),
(xs,ys) -> {
if(xs.isEmpty()) return ys;
ArrayList<ArrayList<Integer>> tmp = new ArrayList<>();
for(ArrayList<Integer> x:xs) {
for(ArrayList<Integer> y:ys) {
ArrayList<Integer> tmp2 = new ArrayList<>();
for(Integer e:x) {
tmp2.add(e);
}
for(Integer e:y) {
tmp2.add(e);
}
tmp.add(tmp2);
}
}
return tmp;
}
);
// 他にも pow2 の利用例を探る
}
// 繰返し2乗法
// https://en.m.wikipedia.org/wiki/Addition-chain_exponentiation
static <T> T pow2(T x, int n, T one, BinaryOperator<T> op) {
if(n<1) return one;
T tmp = pow2(x, n/2, one, op);
tmp = op.apply(tmp, tmp);
if(n % 2 == 1) tmp = op.apply(tmp, x);
System.out.println(n +":"+ tmp);
return tmp;
}
}
繰り返し2乗法で置換を処理する。
置換:
数字の列 0 1 2 の並べ替えは、次の6通りある。
0 1 2 、0 2 1、1 2 0、1 0 2、2 0 1、2 1 0
これを数字を入れ替えて(置換して)並べ変える操作方法が6通りあると考える(置換が6個)。
0 1 2 は並べかえなし(または巡回なし)。
2 1 0 は、1は置換なし。0と2を入れ替える(または0 2 で巡回する)。
1 2 0 は、0→1 、1→2、2→0 になるように入れ替える(または0 1 2の巡回)。
など。
上では置換の方法を元の数字(0 1 2) を どの数字で置き換えるかで表している。
0→2 1→0 2→1 なら 2 0 1
置換は巡回で表すことができる。
0→1→2→0 を (0 1 2)
この表記方法では 2→0 の部分は省略する(必ず先頭の数字に置換するので)。
置換を複数の巡回で表記することができる
1 2 0 4 3
は、1 2 0 の3個の置換と 4 3 の2個の置換に分けることができる(置換する数値で共通のものがないので)。
すると 1 2 0 は (0 1 2) の巡回で、4 3 は (3 4)の巡回である。
1 2 0 4 3 は (0 1 2)(3 4)
巡回の数字は順に並ぶ必要はない。
4 3 0 1 2 は (0 4 2)(1 3) や(2 0 4)(3 1) と同じ。(0 2 4)(1 3)とは異なる。
例)
ArrayList で置換テーブルを与え、置換テーブルを繰り返し適用して数値を並べ替えるプログラム
import java.util.ArrayList;
import java.util.Collections;
import java.util.function.BinaryOperator;
import java.util.stream.Collectors;
public class L12c {
public static void main(String[] args) {
// 置換の繰り返し
// 置換なし(元のまま)
ArrayList<Integer> e = new ArrayList<>();
Collections.addAll(e, 0, 1, 2, 3, 4);
// 置換テーブル(1回巡回)
ArrayList<Integer> r = new ArrayList<>();
Collections.addAll(r, 1, 2, 3, 4, 0);
BinaryOperator<ArrayList<Integer>> op = (x, y) -> x.stream()
.map(i -> y.get(i))
.collect(Collectors.toCollection(ArrayList<Integer>::new));
pow2(r,16,e,op);
// 置換なし(元のまま)
ArrayList<Integer> e2 = new ArrayList<>();
Collections.addAll(e2, 0, 1, 2, 3, 4, 5, 6, 7);
// 置換テーブル(1回巡回)
ArrayList<Integer> r2 = new ArrayList<>();
//0, 1, 2, 3, 4, 5, 6, 7
//1, 2, 3, 4, 5, 6, 7, 0 //8回で巡回
//0, 1, 2, 3, 4, 5, 6, 7
//1, 2, 3, 4, 5, 6, 0, 7 //7回で巡回
//0, 1, 2, 3, 4, 5, 6, 7
//1, 2, 3, 4, 5, 0, 6, 7 //6回で巡回
// 8回で巡回×7回で巡回×6回で巡回 を合成
//0, 1, 2, 3, 4, 5, 6, 7
//1, 2, 3, 4, 5, 6, 7, 0
//2, 3, 4, 5, 6, 7, 1, 0
//3, 4, 5, 6, 7, 2, 1, 0
Collections.addAll(r2, 3, 4, 5, 6, 7, 2, 1, 0);
pow2(r2,8,e2,op);
pow2(r2,8*7,e2,op);
pow2(r2,8*7*6,e2,op);
//置換テーブルを分析
//3, 4, 5, 6, 7, 2, 1, 0
//(0 3 6 1 4 7)(2 5) 6回の巡回と2回の巡回の組み合わせ。→6回で元に戻る。
pow2(r2,3*8,e2,op);
}
// 繰返し2乗法
// https://en.m.wikipedia.org/wiki/Addition-chain_exponentiation
static <T> T pow2(T x, int n, T one, BinaryOperator<T> op) {
if (n < 1)
return one;
T tmp = pow2(x, n / 2, one, op);
tmp = op.apply(tmp, tmp);
if (n % 2 == 1)
tmp = op.apply(tmp, x);
System.out.println(n + ":" + tmp);
return tmp;
}
}
対称群の元の最大位数について、こちらの卒業論文に表が記載されている。
www.lab2.toho-u.ac.jp/sci/is/shirayanagi/2013/koizumi.pdf
例えば、P.8の 1≦n≦50でのn次対称群の元の最大位数 の表を見ると、
N=8 つまり8個の数字(8次対称群の元)を特定の方法で並べ替える操作を繰り返した場合、
15回以内に元に戻る(元の最大位数が15)
ということがわかる。
さらに、P.13の、1≦n≦50 でのn次対称群の元の位数の組み合わせ の表を見ると、
N=8の時の最大位数を与える元の位数の組み合わせをみると、[3,5]
であり、
元に戻るために最大回数の15回を必要とするのは、3回の巡回と5回の巡回を組み合わせた置換、
(0 1 2)(3 4 5 6 7) や (0 2 4)(1 3 5 6 7)
などである
ということがわかる。
次年度検討項目:
余力があれば、Decoratorパターン Template Method パターン Strategy パターン 辺りを扱う。
興味がある受講生は上記のデザインパターンについて調べてJavaで実装してみるとよい。