ミュウツーの逆襲 EVOLUTIONを観て
ミュウツーの逆襲 EVOLUTIONを観てきたので解釈と感想を小分けで書く。
ミュウツーのアイデンティティ
ミュウツーは人間によって作られたのでミュウツーはもちろん人間ではないし、親から生まれたポケモンでもない。そこから自分は何者なのか、何を目的に作られたのか自分を作った研究者に問う。研究者は自分達の研究のために作り出したのでその問いに答えることができない。だが、どうやら自分はポケモンのコピーでそのポケモンよりも強く作られたのかと知る。
アイデンティティを探す
サカキにポケモンとは人間に使われる道具だと言われ、それが役目だと思い道具として使われるがそれは外的に与えられた役割という存在理由で自分が自分としてなぜここにいるのかという内的な存在理由とは別のものと知り、力によって支配から逃れる。ここで他社との関係は力による支配するものと支配されるものと認識する。
コピーポケモンを作り始めたのは自分と同じ属性を持つ仲間が欲しかったため。さらに、自分を作った人間への復讐と称して弱い人間に使われているポケモンたちを解放する動きを見せる。
ミュウの登場からラストまで
ミュウ「技なしならば本物はコピーには負けない(オリジナル>=コピー)」
結果は引き分け
これは命として存在している時点でオリジナルもコピーも等しく存在者であるという隠喩。さらに、サトシの石化(死)に対しすべてのポケモンが涙を流す。これは他者のために涙を流せる(悲しむ、思いやる)ことが人でありポケモンであるというメッセージだと受け取った。
ロケット団「昔の自分を見ているようで~」
悪になりきれず、どっち付かずの自分達をオリジナルかコピーの二者択一の闘争を見て重ねている。
ラスト
サトシ「なんでオレ達、こんなところにいるんだ?」
カスミ「いるんだからいるんでしょうねー。」
最後に近いからかこのセリフが印象に残った。
感想
この映画から
・生き物はその価値を外側から見たときにはすべて等しい命である。
・自分の存在したい理由は他者(社会)から与えられるものではなく、自分で見つけるもの。
・他者を思いやることができるということが人間
のようなメッセージを受け取った。
アーマードミュウツーはゴテゴテで少しダサかった...
続 : Javaで計算機作成(式記法変換)
この記事を書いた動機
前に書いたJavaの簡単な計算機(式記法変換編) - Mineneの物置で大変見苦しいコードを載せてしまったのでそのリベンジとして、見ていて気持ちの良い読みやすいコードを書きたくて、さらにそれを皆さんにお見せできればなと思いこの記事を書く。
なので、前の記事を見ていない人は見ることをお勧めします。(まあ見てもダメダメコードしか載ってませんが)
内容
計算機全体に必要な段取りと処理は前記事などで書いているのでここでは省略し、ここでは計算機の一部である中置記法の式(優先度は括弧による)を読み込み、のちに計算しやすいように後置記法に直すクラスの作成する。
中置記法から後置記法に直すことを二分木を作成し、その木をたどることで達成する。
前の記事ではやっつけ感のあるコードを書いてしまったので、今回は前回書いたコードをもう少し読みやすい丁寧なコードに書きかえる。
それと同時に、なぜそう書き直すのかを解説する。
前回からの更新点
・コメントの削除
・メソッドの分割
・アクセス修飾子によるクラスの役割の可視化
コード
import java.io.*; import java.text.*; import java.util.StringTokenizer; import java.lang.NumberFormatException; class Node{ private String node; private String formula = ""; private Node right = null; private Node left = null; Node(String str){ this.node = str; } void createTree(){ int height = 0; int opPlace = 0; char start; char finish; String tmp; removeSpace(); if(node.length()>1){ if(!correct()){ System.out.println("式が正しくありません"); System.exit(0); } } start = formula.charAt(0); finish = formula.charAt(formula.length()-1); if(start=='(' && finish==')'){ removeOuterBracket(); } opPlace = getOpPlace(); if(opPlace==0){ left = null; right = null; return; } left = new Node(formula.substring(0,opPlace)); left.createTree(); right = new Node(formula.substring(opPlace+1)); right.createTree(); node = formula.substring(opPlace,opPlace+1); } String traceTree(){ String l = ""; String r = ""; if(left != null) l += left.traceTree()+" "; if(right != null) r += right.traceTree()+" "; return l + r + node; } private boolean correct(){ char tmp; int numCount = 0; int charCount = 0; for(int i=0;i<node.length();i++){ tmp = node.charAt(i); if(isNum(tmp)){ numCount++; }else if(!(tmp=='(' || tmp==')')){ charCount++; } } if(numCount==charCount+1) return true; return false; } private void removeSpace(){ String tmp; StringTokenizer token; token = new StringTokenizer(node," "); while(token.hasMoreTokens()){ tmp = token.nextToken(); formula += tmp; } } private void removeOuterBracket(){ int count = 0; int height = 0; char tmp; for(int i=0;i<formula.length();i++){ tmp = formula.charAt(i); if(tmp=='(') height++; else if(tmp==')') height--; if(height==0) count++; } if(count==1) formula = formula.substring(1,formula.length()-1); } private int getOpPlace(){ char tmp; int place = 0; int height = 0; for(int i=0;i<formula.length();i++){ tmp = formula.charAt(i); if(tmp=='(') height++; else if(tmp==')') height--; else if((tmp=='+' || tmp=='-' || tmp=='*' || tmp=='/') && height==0){ place = i; break; } } return place; } private boolean isNum(char num){ String tmp = String.valueOf(num); try{ Integer.parseInt(tmp); return true; }catch(NumberFormatException e){ return false; } } }
解説
コメントについて
更新点でも書いた通り今回のプログラムにはなんとコメントが一切書かれていない、なぜならコメントを書かずともメソッド名や変数名だけで読めばわかる(多分)コードになっているからだ。例えば"removeSpace"や"getOpPlace"を見ると、そこのメソッドでは何を行っているのかが一目でわかるようになっている。ただ、コメントが無さすぎると複数人で開発しているときに大変なので書くべきところには多少は書いておいた方がいい(無意味なのはダメ)。
メソッドの分割について
前のコードにはメソッドが"createTree"と"traceTree"の二つのみに分かれていたが、これではその中身を上から順にすべて読んでいかないと結局どのような処理で二分木を操作しているのかわからない。今回はメソッドを複数に分けた、これで上からコードを眺めるだけでcreateTreeは
- >"空白を取り除き"
- >"式が正しいか判別し"
- >"一番外側の括弧を取り除き"
- >"演算子の場所を得た後に"
- >"ノードを作っている"ことがわかる。
コメントを書いても流れが一目でわかると言いたい人もいるだろうが、これはプログラムなのであって文章ではない。一目でわかる名前がついていればコメントなぞ邪魔なだけなのである。
アクセス修飾子について
今回メソッドを多数に分けて書いたわけだが、それぞれがどのような働きをし、どこから呼ばれうるのかそれぞれ一つずつ見ていては大変だ。そこでアクセス修飾子を使う。アクセス修飾子をつけているとそのクラス、メソッドの役割が明確にすることができ、他のところからうっかり使えないメソッドを呼べないようにすることができる。今回使われているのは同じクラスからしか呼ぶことができない"private"だけだが他にもpackage内から呼ばれうる"protected"などがある。mainメソッドに書く"public"もその一つだ。今回の例でいうとcreateTreeとtraceTreeは他の場所から呼ばれうるが他のメソッドはその二つのメソッドのどちらかからしか呼ばれることはないというのがわかる。ちなみに、アクセス修飾子を書かないと"protected"がコンパイルでつく。
おわりに
これで完璧とまでは言わないが前回より読む気になるコードになっていると思う。これを機にこれから各コードたちも美しく書いていけるように心がけたい。
メソッド分割やアクセス修飾子のところで可読性について偉そうに語っているがimportにワイルドカード( * )を使っている。ゆるして。
記事が長くなってしまうので詳しくは書かなかったが、前回から式の成立条件も更新しているのでぜひコードを読んでみてほしい。
最後に、参考図書としてコードを書き換えるにあたって使用した本を上げておく。
では、次の記事でまた会いましょう。
参考図書
・Brian W.Kernighan, Rob Pike (2003)『プログラミング作法』福崎俊博 訳,アスキー
・松浦佐江子 (2016)『ソフトウェア設計論ー役に立つUMLモデリングへ向けて』コロナ社
追記
あまりにもひどい処理(特に式が正しいかどうかの判定の部分)だったのでこちらに改善したコードを載せておきます。式が正しいかどうかの処理を書き換えているときに「(よくわかんないけど)これ構文規則から二分木作ると確実だな?」と思ったのですがせっかくひねり出したものを消すのももったいなくここで供養させていただきます。
package calculator; import java.io.*; import java.text.*; import java.util.StringTokenizer; import java.lang.NumberFormatException; class Node{ private String node; private String formula = ""; private Node right = null; private Node left = null; Node(String str){ this.node = str; } //前処理 void mae(){ String tmp = removeSpace(); //スペース除去 for(int i=0;i<tmp.length();i++){ char tnp; tnp = tmp.charAt(i); if(tnp=='-' && (i==0 || tmp.charAt(i-1)=='(' || tmp.charAt(i-1)=='+' || tmp.charAt(i-1)=='-' || tmp.charAt(i-1)=='*' || tmp.charAt(i-1)=='/')){ //符号付き計算のマイナスの値があった時の処理 formula += "0"; } else if(tnp=='-' && i!=0){ //符号付き計算のマイナスの値があった時の処理 formula += "+0"; } formula += tnp; } node = formula; //スペースを除去した式に更新 return; } //中置記法から二分木を作成 void createTree(){ try{ int opPlace = 0; //演算子の位置 char start; //式のはじめ char finish; //式の終わり int bflag = 1; //()が外れているかどうかのフラグ System.out.println(formula); if(node.length()>0){ if(!correct()){ //式が正しいかどうかの判定 System.out.println("式が正しくありません"); node = ""; return; //System.exit(0); } } start = node.charAt(0); finish = node.charAt(node.length()-1); while(bflag==1){ //()を外す if(start=='(' && finish==')'){ bflag = removeOuterBracket(); } else bflag = 0; } opPlace = getOpPlace(); //演算子の位置を取得 if(opPlace==0){ //演算子がなければ数値 left = null; right = null; return; } left = new Node(node.substring(0,opPlace)); //演算子の前半を再帰的に二分木作成 left.createTree(); right = new Node(node.substring(opPlace+1)); //演算子の後半を再帰的に二分木作成 right.createTree(); node = node.substring(opPlace,opPlace+1); //このノードに演算子を挿入 }catch(StringIndexOutOfBoundsException e){ System.out.println("式を入力してください"); //e.printStackTrace(); } } //走査 String traceTree(){ String l = ""; String r = ""; if(left != null) l += left.traceTree()+" "; if(right != null) r += right.traceTree()+" "; return l + r + node; } //式が正しいかどうか private boolean correct(){ char tmp1; int numCount = 0; int charCount = 0; int height = 0; char tmp; for(int i=0;i<node.length();i++){ tmp = node.charAt(i); if(!isNum(tmp) && tmp!='+' && tmp!='-' && tmp!='*' && tmp!='/' && tmp!='(' && tmp!=')'){ //数値でなければこれらの記号のはず return false; } } for(int i=0;i<node.length();i++){ //()の深さが正しいかどうか tmp = node.charAt(i); if(tmp=='(') height++; else if(tmp==')') height--; } if(height!=0) return false; int j; for(int i=0;i<node.length();i++){ //数値と演算子の数を取得 tmp1 = node.charAt(i); if(isNum(tmp1)){ numCount++; for(j=i+1;j<node.length();j++){ //i番目数値ならば記号まで読み飛ばし tmp = node.charAt(j); if(!isNum(tmp)){ tmp1 = tmp; break; } } i = j; } if(tmp1=='+' || tmp1=='-' || tmp1=='*' || tmp1=='/') charCount++; //記号のうち演算子をカウント } if(numCount==charCount+1) return true; //数値は演算子+1と同じ個数のはず return false; } //空白を除去する private String removeSpace(){ String retmp=""; String tmp; StringTokenizer token; token = new StringTokenizer(node," "); while(token.hasMoreTokens()){ //空白読み飛ばし tmp = token.nextToken(); retmp += tmp; } return retmp; } //一番外側の()をはずす private int removeOuterBracket(){ int count = 0; int height = 0; char tmp; for(int i=0;i<node.length();i++){ tmp = node.charAt(i); if(tmp=='(') height++; else if(tmp==')') height--; if(height==0) count++; //何回()が閉じられるか } if(count==1){ //一番外側同士で閉じられている node = node.substring(1,node.length()-1); return 1; } return 0; } //演算子の場所を取得 private int getOpPlace(){ char tmp; int place = 0; int height = 0; for(int i=node.length()-1;i>-1;i--){ tmp = node.charAt(i); if(tmp==')') height++; else if(tmp=='(') height--; else if((tmp=='+' || tmp=='-' || tmp=='*' || tmp=='/') && height==0){ //深さが0の演算子の場所を後ろから取得 place = i; break; } } return place; } //数値かどうか private boolean isNum(char num){ String tmp = String.valueOf(num); try{ Integer.parseInt(tmp); return true; }catch(NumberFormatException e){ return false; } } }
AtCoder: Tenka1 Programmer Beginner Contest 2019 -A,B,C
はじめに
ブログ主の競技プログラミング歴→8ヶ月くらい
書こうと思った動機:
せっかくブログを開設したんだし何か手軽にアウトプットできるものないかなと探したところ、競プロの自分が解けた問題をどのように解いたのかをとりあえず書き出そうと思い、今書いている。(まだ未熟なので計算量などの考察についてはおいおいしていくつもり)
A問題 問題概要
数直線上の点1,2,3にそれぞれ座標A,B,Cが入力として与えられ、点1から点2に行くときに点3を通れば' Yes '、通らなければ' No 'を出力しろ
制約
・0≤A,B,C≤100
・A,B,Cは相異なる整数
考えたこと
数直線上での各点の関係が小さい方から
1->3->2 or 2->3->1
の順になっていれば点3によることができるので
1<3<2 or 2<3<1 なら' Yes '、他なら' No 'を出力
コード
#include<stdio.h> int main(void){ int a,b,c; scanf("%d %d %d",&a,&b,&c); if((a<c && c<b) || (b<c && c<a)){ printf("Yes\n"); }else{ printf("No\n"); } return 0; }
B問題 問題概要
英小文字からなる長さNの文字列Sと整数Kが与えられるので、文字列Sの中でK番目の文字と異なる文字を' * 'に置き換えて出力しろ
制約
・1≤K≤N≤10
・Sは英小文字からなる長さNの文字列
・N,Kは整数
考えたこと
K番目の文字をどこかに格納して、文字列の1文字1文字と比較してその都度' * 'に置き換え、その後文字列を出力すればいけそう
コード
#include<stdio.h> int main(void){ int n,k,i; char target; //k番目の文字列を格納する変数 scanf("%d",&n); char s[n]; scanf("%s",s); scanf("%d",&k); target = s[k-1]; //配列は0から数えるので添え字はk-1 for(i=0;i<n;i++){ if(s[i]!=target){ s[i] = '*'; } } printf("%s",s); return 0; }
C問題 問題概要
N個の白か黒の石が一列に並んでいる。
石の状態は長さNの文字列Sであらわされ、白の時は' . '、黒の時は' # '。
黒い石のすぐ右に白い石が来ないように色を0回以上変更する。
変更した回数の最小値を出力しろ。
制約
・1≤N≤2×105
・Sは英小文字からなる長さNの文字列
・N,Kは整数
考えたこと
まず、黒い石のすぐ右側に白い石を置けないので作る白黒の列は
白白白白白(すべて白)
黒黒黒黒黒(すべて黒)
白白白黒黒(白黒はっきり分かれる)
の3種類だなと考えた。
次に、どうやってこの白黒の列を実現するかを考える。
左から順に石を見ていって、今見ている石の左側の黒い石と右側の白い石の数をそれぞれ数え、合計し、最小のが答えになる。
ただ、左から見ていく際に一回一回その場で左右の白黒をカウントしていては計算量が多く(多分)、時間がかかる。
そこで、先にそれぞれについてカウントして配列として持っておく累積和というものを使う。(説明するの難しいのでコードみてちょ)
コード
#include<stdio.h> //a,bの小さい方を返す関数 int min(int a,int b){ if(a>b) return b; return a; } int main(void){ int n,i,tmp,ans=100000; scanf("%d",&n); char s[n]; /* a[n]->見ている石(左からi番目)からそれを含め左に何個黒があるかa[i] b[n]->見ている石からそれを含め右に何個白があるかb[i] */ int a[n],b[n]; //初期化 for(i=0;i<n;i++){ a[i]=0; b[i]=0; } scanf("%s",s); if(s[0]=='#') a[0]=1; //一番左が黒なら1 if(s[n-1]=='.') b[n-1]=1; //一番右が白なら1 for(i=1;i<n;i++){ if(s[i]=='#') a[i]=a[i-1]+1; //i番目が黒なら+1 else a[i]=a[i-1]; //i番目が黒でないならそのままの値を代入 if(s[n-1-i]=='.') b[n-1-i]=b[n-i]+1; //i番目が白なら+1 else b[n-1-i]=b[n-i]; //i番目が白でないならそのままの値を代入 } for(i=0;i<n;i++) ans=min(ans,a[i]+b[i]-1); //-1しているのは自分の分を引いている printf("%d\n",ans); return 0; }
累積和を行っている部分は
if(s[i]=='#'){ a[i]=a[i-1]+1; b[n-1-i]=b[n-i]+1; }else{ a[i]=a[i-1]; b[n-1-i]=b[n-i]; }
とも書けるかもしれない
黒は左から、白は右から数えたので配列a,bの累積和もそれぞれ
aは1->n-1
bはn-2->0
へ伸びている
読みにくいかもしれない(自分はそのまんまなのでこちらが読みやすいと思っている)
Javaの簡単な計算機(式記法変換編)
この記事では二分木を使って中置記法から後置記法へ変換する手順を書きます
ここが一番厄介で考慮することは
計算の優先順位である()をどう扱うかの一点に尽きます
とりあえずコードを
int height = 0; //かっこの深さの変数 int opPlace = 0; //演算子の位置を格納する変数 //式の最初と最後を格納する変数 char start; char finish; //一時的な変数 char tnp; String temp; //空白を読み飛ばすためのトークン StringTokenizer token; token = new StringTokenizer(str," "); //空白を読み飛ばして式を詰める while(token.hasMoreTokens()){ temp = token.nextToken(); formula += temp; } //式の最初と最後を確認 start = formula.charAt(0); finish = formula.charAt(formula.length()-1); //最初と最後でかっこが閉じていれば排除する int count = 0; for(int i=0;i<formula.length();i++){ tnp = formula.charAt(i); if(tnp=='('){ height++; }else if(tnp==')'){ height--; } if(height==0){ count++; } } if(start=='(' && finish==')' && count==1){ formula = formula.substring(1,formula.length()-1); } //かっこの外側にある演算子の場所を獲得 for(int i=0;i<formula.length();i++){ tnp = formula.charAt(i); if(tnp=='('){ height++; }else if(tnp==')'){ height--; }else if((tnp=='+' || tnp=='-' || tnp=='*' || tnp=='/') && height==0){ opPlace = i; break; } } //演算子がなければ数値と考えノードを閉じる if(opPlace==0){ left = null; right = null; return; } //演算子の左側を再帰的に呼び出す temp = formula.substring(0,opPlace); left = new Node(temp); left.createTree(); //演算子の右側を再帰的に呼び出す temp = formula.substring(opPlace+1); right = new Node(temp); right.createTree(); //演算子をノードに入れる str = formula.substring(opPlace,opPlace+1); }
はい、正直書くのめんどくさいし説明はコメントを見てくださいと言いたいですが...
流れを説明しますと
1.一番外側に()があればそれを外す // (~式~) の感じ
2.かっこの外側にある演算子の右左で式を分けてその演算子をノードに入れる
3.2で分けた左右の式でそれぞれ再帰を回す
4.演算子がなくなれば(数値のみ)それをノードに入れて終了
流れ1のところでかっこの深さを数えるところで式の整合性を検査しています
このかっこを外すところがとても厄介なのですが説明するのがとても難しい(書くことが多すぎて)ので各自書いてみるときにでも考えてください. 一部はコード中コメントを読めばわかります
上のコードで二分木を作成したのであとは後順走査をするだけです
String traceTree(){ String s=""; String t=""; if(left != null) s += left.traceTree()+" "; if(right != null) t += right.traceTree()+" "; return s + t + str; }
さてこれでstackマシン編で分けた仕事の2,3,4,5までの(ここでは仕事2)を書いてきたわけですが, あとはMainに仕事をMainに並べるだけです. なのでMainは割愛とさせていただきます(仕事1の入力はどこでも使うから頑張って).
理念も何もない見苦しいコードを載せてもうしわけないです. 少しでも参考になれば幸いです. ここまで読んでくれた方お疲れさまでしたこれにて「Javaの簡単な計算機」は終わりとなります.
ね、簡単でしょ?
Javaの簡単な計算機(命令変換編)
この記事は後置記法の計算式からstackマシンが理解できる命令列に直す手順と簡単にコードの紹介をします(前回記事の仕事3)
この部分も簡単で
計算式をStringで受け取ってからStringTokenizerやsplitやらで式を分割して(APIで調べるなり)
if(isNum(ch)){ //文字chが数値かどうか order += push; order += ch; }else if(ch.equals("+")){ //"*"の置き換え order += "add "; }else if(ch.equals("-")){ //"-"の置き換え order += "sub "; }else if(ch.equals("*")){ //"*"の置き換え order += "mul "; }else if(ch.equals("/")){ //"/"の置き換え order += "div "; } return order; //命令列を返す
と長いStringで命令列を作ります
isNum()は自身で定義
static boolean isNum(String s){ try{ Integer.parseInt(s); return true; }catch(NumberFormatException e){ return false; } }
本来、例外処理をこのように使うのはよくないので何かほかの書き方を思いついた方はそちらを採用するのがいいと思います
さらに, 最終的に命令列orderはString型で出てきますがこれもあまりよくないです. 命令列は本来一列ずつ"push 1","push 2","add"のように読ませるものなのでリストや配列を使って構造を制限するとなお良し
ね、簡単でしょ?_(:3 」∠)_
Javaの簡単な計算機(Stackマシン編)
中置記法で書かれた計算式の計算をめざして書きます(原則計算の優先度を()で入力してもらう)
コードを書く前に計算機の仕事の流れを考えます.
1.中置記法で入力してもらう
2.中置記法を後置記法に直す //stackマシンは後置記法の命令を受け取るとする
3.後置記法をstackマシンが理解できる命令列に直す
4.計算
5.表示
仕事を分けた後はこれらをもとにclassを作ります
/***ここまでが多分一般的なコードを書く準備でここからが本題***/
正直よくわからないので適当に書きます
Stringの命令列が入ると計算を行うStackマシンを書きます
(stack自体はjavaで用意されているものを使用するのでとても簡単)
//命令列を受け取り数値をstackにpush,他なら各種操作 //stackの定義 Deque<Double> stack = new ArrayDeque<Double>(); if(order.equals("push")){ //数値をpush }else{ if(order.equals("add")){ //加算 }else if(order.equals("sub")){ //減算 }else if(order.equals("mul")){ //乗算 }else if(order.equals("div")){ //除算 }else if(order.equals("wrt")){ //結果の表示 }else if(order.equals("halt")){ //終了命令 } }
ここで各計算の実行時にstackから数値を2つpopして行いますが、後に入れたものからpopされるのでどちらが先かに注意してください
とりあえずこれで計算と表示部分(仕事4,5)はこれで完成
ね、簡単でしょ?
※例外処理(0で割るなど)とかは頑張って
word
中置記法:「1+2」のような一般的な書き方
後置記法:「1 2 +」のような数値→演算子のような書き方
stack: データ構造の一つでデータを後に入れたものを先に取り出すような形(円筒を考えて上からものを入れていき上から取り出すようなイメージ)