susunshunのお粗末な記録

お粗末に丁寧に生きる

AWSでブログサーバ構築(静的コンテンツをS3に配置)

前回は最も単純な構成でブログサーバを構築しました。susunshun.hatenablog.com

今回はこれにS3を足してコンテンツ(画像とか)を配置しましょう。

構成

EC2(Webサーバ、DBサーバ)

S3(コンテンツ)

大まかな手順

  1. バケットの作成
  2. 画像のアップロード
  3. S3のプラグイン導入

バケットの作成

マネジメントコンソールからS3を選択。
f:id:susunshun:20150325225112p:plain

”バケットとは”サポートから引用すると

バケットとは、 Amazon S3 に格納されるオブジェクトのコンテナです。すべてのオブジェクトはバケット内に格納されます。たとえば、photos/puppy.jpg という名前のオブジェクトが johnsmith バケットに格納される場合、URL http://johnsmith.s3.amazonaws.com/photos/puppy.jpg を使ってアドレスを解決できます。

だそうです。

基本的にS3の管理はバケット単位で行います。
またDNSを使用しない場合、バケット名が配置したコンテンツのURLに含まれます。

まずはこのバケットを作成しましょう。
f:id:susunshun:20150325225124p:plain


バケット名:任意
リージョン任意:任意(特にこだわりが無いなら東京にしておきましょう)
f:id:susunshun:20150325225135p:plain

何かエラーが出てますね。

実はバケット名は全てのリージョンで一意というのが条件です。他のバケットと名前が重複してはいけません。

ありがちな名前をつけてしまったのでバケット名を付け直してトライ。
f:id:susunshun:20150325225141p:plain

今度は無事バケットが作成されました!

画像のアップロード

タチコマ君の画像をアップロードしてみましょう。

アップロード対象のバケットをクリック
f:id:susunshun:20150325225152p:plain

左上の[upload]をクリック
f:id:susunshun:20150325225216p:plain

灰色の領域に画像をdrag&dropか、[add files]からファイルを追加。
アップロードの準備ができたことを確認して[start upload]をポチっ
f:id:susunshun:20150325225317p:plain

アップロードされました!
f:id:susunshun:20150325225325p:plain

実はこのままだとタチコマ君の画像はまだWebの世界に公開されていません。
※ブラウザから画像のURLを叩いてもアクセス拒否されます。

そこで画像を[右クリック] > [Make Public]
これで画像が公開されるようになります。
f:id:susunshun:20150325225332p:plain

S3のプラグイン導入 ※ここからはWordPress管理画面の操作になります

S3を設置しましたが、このままではWordPressから画像を追加してもS3にはアップロードされません。WordPressを導入しているサーバ(EC2)にアップロードされます。

プラグインを導入することでWordPressから画像を追加した場合、S3にアップロードされるようにしましょう。

この超便利なプラグインを使用します。※Amazonのセミナーで使用されてて知りましたwordpress.org

こいつを導入すると最初の設定を行えば、WordPressから画像を追加する際に、特にS3を意識することなく自動でS3に画像がアップロードされます。

それではプラグインを導入していきましょう。

WordPress管理画面サイドバーから[プラグイン] > [新規追加]を選択
f:id:susunshun:20150325233249p:plain

Amazon S3 for WordPress”で検索しインストール。
f:id:susunshun:20150325233252p:plain

インストール中の画面が表示されるので少々待ち。

完了したら[プラグインを有効化]をクリック
f:id:susunshun:20150325233256p:plain

するとサイドバーにAmazon S3が追加されます!
f:id:susunshun:20150325233300p:plain

次にプラグインの設定を行います。
今回導入したWordPressプラグインからAWSのサービスにアクセスするために、AccessKeyをとSecretKeyを設定しないといけません。

【AccessKeyの確認】※AWSの操作
AWSのダッシュボードから[Users] > [Manage AccessKey]
f:id:susunshun:20150325234036p:plain

AccessKeyはここに表示されますが、SecretKeyは表示されていません。
[Create AccessKey]をぽちり。
f:id:susunshun:20150325234206p:plain

どうやら無事AccessKeyが作成されたようです。
AccessKey、SecretKeyの確認はファイルをダウンロードして行います。
ということで[Download Credentials]をクリック。
f:id:susunshun:20150325234414p:plain

するとCVSがダウンロードされるので、そこからSecretKeyを確認しましょう。

AWSから確認したAccessKeyとSecretKeyを入力
f:id:susunshun:20150325234526p:plain

これでOK!かとおもいきや”Access Denied”というエラーが表示され先に進まない。
それはポリシーの設定をしていないからです。(恥ずかしながらこれを解決するのに結構時間がかかりました。。。。)

【IAMポリシーの設定】※AWSの操作
AWS複数サービス(今回はEC2とS3)を連携するときはポリシーを設定し、サービス間のアクセス制御を行います。

このサービスをAWSでは”IAM”と呼びます。

docs.aws.amazon.com

このページを読むとサービス間の制御を行うためだけでなく、一つのアカウントで複数のユーザが使用することも考慮されてのことらしいですね、なるほど。

つまりAccessKeyとSecretKeyがあれば誰でも自由にAWSにアクセスできるわけではありません。

今回は”S3がEC2にアクセスすることを許可しますよ”というポリシーを設定します。

キャプチャ撮り忘れました。。。

ダッシュボードの[Policies]をクリック

AmazonS3FullAccess ※とりあえず先に進めるためにFullAccessにしました

Policy > Attach

ユーザにひもづける

これでOK

てことでWordPressに戻りトライ。
うまくいくと詳細設定ができます。

Use This Backet:作成したバケット
File Uploads:チェック入れる
FilePermissions:チェック入れる
f:id:susunshun:20150326001531p:plain

以上で設定は完了です。

試しにWordPressから画像をアップロード、記事を作成して画像のパスがS3に向いてることを確認しましょう!

AWSでブログサーバ構築(WordPress)

近年AWSがいろんなシステムで使われてるね。
個人的に興味がありポチポチいじって遊んでます。(業務で使ってみたい)

「名前は知ってるけど実際どういう風に使われてるの。。。?」という人が意外と多いのではと思い、今回は最も単純な構成でAWSを使いWordPressのサーバを構築するよ。

必要なもの

AWSのアカウント(無料で作れます)
・PC(OS問いません)、今回はMacで説明します
・ネット環境

構成

本来はDBサーバとWebサーバは分けるべきですが、今回はまずはやってみようということで一台のサーバにDBとWebサーバを入れます。

大まかな手順

  1. 前準備
  2. インスタンス作成
  3. インスタンスSSHで接続しよう
  4. 必要SW導入(Apache, php, MySql, WordPress

ではやりましょう!

前準備

KeyPairの作成

まずはマネジメントコンソールでEC2を選択。

[KeyPair] > [Create Key Pair]

Key pair名を入力

【設定値】
KayPair:任意の値

[create]を押すと自動で何かダウンロードされると思います。
これは後でインスタンスにアクセスするための秘密鍵ですので無くさないようにしましょう。(再発行はできません。)

セキュリティグループの設定

セキュリティグループとはざっくり言うとファイアウォールみたいなものです。指定したポートやIPによりアクセス制限を掛けます。

今回は最低限の設定としてSSHとhttpのポートを開けます。

[Security Group] > [Create Security Group]

Security Groupを設定します。

【設定値】
Security Group Name:任意の値
Description:任意の値
VPC:デフォルト値で大丈夫です
Security Group Rule:SSHとhttp

前準備は以上です。

インスタンス作成

EC2インスタンス=サーバです。インスタンスを作成します。

[Instance] > [Launch Instance]

まずはAMIを選択します。UbuntuやCent OS、RedHat等よりどりみどりですが今回はAmazonLinuxを使用。

次にPCのスペックを選びますが、無料枠はmicroのみです。

Step3はデフォルト設定のまま

ストレージの設定
【設定値】
Size:8GB
Volume Type:Magnetic

Instance名を設定

【設定値】
Value:任意の値

前準備で作成したセキュリティグループをここで紐付けます

そのまま次へ

警告が出ていますがこれはセキュリティグループでIPアドレスによる制限をかけていないためです。実際に構築を行うときは環境に応じてIPアドレスで制限を行うよう、セキュリティグループを設定しましょう。

[Launch]を選択すると次のポップアップが出ることがあります。
SSDの使用を促すものなので、ここではこれに従いSSDを選択しましょう。

次にKey Pairを選択します。前準備で作成したものを使用しましょう。

【設定値】
Select a key pair:前準備で作成したKey Pair名

インスタンスの設定は以上です![View Instances]から設定を確認しましょう。

インスタンスSSHで接続しよう

SSH接続を行うために、パブリックDNSを確認しましょう。

Macなのでターミナルから接続を行います。

まずはKeyPairの権限を変更しておきましょう。
所有者は読み書き可能.所有者以外はファイルに対する権限は付与しません。

chmod 600 【Key Pair】


例)
chmod 600 /Users/apple/Desktop/mykey_tokyo.pem

以下のコマンドからSSHインスタンスに接続します。

ssh -i 【Key Pair】 ec2-user@【パブリックDNS


例)
ssh -i /Users/apple/Desktop/mykey_tokyo.pem ec2-user@ec2-XX-XX-XXX-XXX.ap-northeast-1.compute.amazonaws.com

これで接続ができました!

必要SW導入(Apache, php, MySql, WordPress

パッケージのアップデート

sudo yum -y update

apache,php, mysql(とmbstring)のインストール

sudo yum -y install httpd mysql-server php php-mysql php-mbstring

オプションでつけている"-y"は、yesかnoかを求められる際に全てyesとしてコマンドを実行するものです。都度yesと入力しても良いですが面倒なので"-y"はつけといた方がよいでしょう

apachemysqlサーバを起動しましょう

sudo /etc/init.d/httpd start
sudo /etc/init.d/pysqld start

【補足】サービスの自動起動

デフォルトの設定では、OSの再起動を行うたびapachemysqlは上記のコマンドで起動しなければいけません。それは手間なのでOSが立ち上がった際に自動でサービスが立ち上がるようにしておきましょう。

sudo /sbin/chkconfig mysqld on
$ sudo /sbin/chkconfig httpd on

最後にWordPressの導入

・ファイルのダウンロード
wget http://ja.wordpress.org/latest-ja.tar.gz ~/

・ファイルの解凍
tar zxvf ~/latest-ja.tar.gz

・解凍したWordPressPressのコンテンツを公開ディレクトリに配置
sudo cp -r ~/wordpress/* /var/www/html/

・コンテンツの所有者をapacheに変更
sudo chown apache:apache -R /var/www/html

WordPressで使用するDBの設定を行いましょう。

・ログイン
mysql -u root

・DBの生成
mysql> create database wordpress;

・ユーザ作成
mysql> grant all privileges on wordpress. * to "【ユーザ名(任意の値)】"@"localhost" identified by "【パスワード(任意の値)】";


例)
grant all privileges on wordpress. * to "wpuser"@"localhost" identified by "wppassword";

・変更を反映
mysql> flush privileges;
mysql> quit;

これでいちおうWordPressを用いたブログサーバの構築は完了です!

確認として以下URLにブラウザからアクセスしてみましょう

http://【Public DNS】/wp-admin/setup-config.php

正しく設定されていれば以下の画面が表示されます

今回は以上となりますがいかがだったでしょうか。(最後の方は書くのが面倒になってきて雑になってますごめんなさい)

たったこれだけの作業で極小規模ながら一つのシステムが作れてしまうあたり、SIerで働く身としては仕事でやることががっつり減ってしまうので冷や汗ものです。。。

余談ですが、アメリカと日本ではSIerの意味合いが違います。アメリカではユーザ(SIerから見たお客さん)は「できることは自分たちでやっちゃえ」スタンスなので、AWSをはじめとするクラウドサービスの台頭により、お客さん自らでインフラ構築をしてしまうことも少なくないそうです。

気が向いたらWebサーバとDBサーバ別々に立てる構成やロードバランサ(ELB)を使った負荷分散構成についても書きたいと思います。

もう月曜か。。。鬱だ。

格安SIMのススメ - iPhone6[au]→Xperia Z3 compact(SO-02G)[DMMmobile]への移行)

こんにちばんわ

久方ぶりのブログ更新です。

年始のドタバタですっかりブログを書くという習慣が無くなっておりました。
ということプログラミング関連でブログのネタになるものがないので今回スマホの移行について書きます。

最近テレビでも格安SIMのCMを見ることが増えてきましたね。
前々から気になってはいたのでとりあえず契約してみた。

いま

iphone6(au)。毎月7,000円くらいです。

DMM Mobileを選んだ理由

競合多い格安SIMの中でDMM Mobileを選んだ訳は

  • 比較的他社に比べて料金が安い
  • 使い切れなかったデータ量は翌月繰越しできる
  • 元がIIJmioなので通信量もそれなりに信頼できそう。。。!?
  • 3日あたりの通信容量が366MBまでと制限がゆるい
  • 万が一制限容量をオーバーしても”バースト”機能でデータの読み込み時にちょっとだけ速度が早くなる

といった点です。

料金プラン

通話付きプランは以下の通り

  • 1GB:1,460円
  • 3GB:1,980円
  • 5GB:2,380円

DMM mobile プラン・料金
※別途初期費用(3000円)とMNP転出手数料(2000円)

データ通信量はiPhone6(au)で毎月2.5GB〜3.0GBくらいなので多めに見積もって通話付きの5GBプランを選択。
なんとこれで2,380円!!安い。。。。

申し込んでから使用開始まで

DMM.comのアカウント登録(持ってる場合不要)

②(MNPの場合)MNP予約番号を取得

③ Webから申し込み(※1)

④ 身分証の画像を送付(※2)

⑤ 申し込み確定、SIM発送

⑥ SIM到着!

⑦ APN設定(※3)

⑧ 利用開始!

※1:クレジットカード必須。MNPを利用する人はこの時にMNP予約番号が必要
※2;斬新な確認方法ですが、免許証等の身分証を写真で撮って指定のメアドに送りますw
※3:通信会社のアクセスポイントに接続する為の通信設定

登録後、4日程してSIMが届きました。

MNPの人は要注意

ここで注意すべきなのはMNPを利用する場合、
⑤からDMM Mobileに契約が移ります。
つまり申込時に使用しているスマホWiFi専用機になるわけです。
DMM MobileのSIMが届くまでは移行元のSIMもDMM MobileのSIMも利用できないので注意。(この点で私はMNPをやめました。。。)

ちなみに初月の月額料金は日割りですが、③からの期間で日割りになります。

Xperia Z3 Compact(SO-02G)

DMM Mobileを運用する上でXperia Z3 Compact(SO-02G)を選択しました。
docomo Xperia(TM) Z3 Compact SO-02G | 製品 | NTTドコモ

  • 防水・防塵
  • 電池持ち良い(2600mAh)
  • 近年スマホの大型化が進む中であえてのコンパクトなサイズ(iPhone5くらい)
  • おさいふケータイ使いたい(この機種に限ったことではないですが)

な所に惹かれました。
いままで風呂入ってるときはiPhoneジップロックに入れていじっていたので
防水だけでも感激です

実際使ってみると期待通りの良機です!

あとは他のXperiaでも言えることですが、
プリインストールされているウォークマンアプリが高機能です。

DSEE HXという圧縮音源で損失された音域を補完してくれる機能があり、これを使うか使わないかでかなり印象が変わります。
詳しくは公式HPをどうぞ
DSEE HX | Soundテクノロジー | ソニーテクノロジー | ソニー

イコライザが優秀だし音源の音質を擬似的に上げる機能があったりと高機能でかなり気に入っています。

私は秋葉原イオシスで新古品の白ロムを38,000円で購入しました。
まだまだ在庫あるようです
SONY docomo Xperia Z3 Compact SO-02G Orange | 白ロム・タブレットや中古パソコン・アウトレット商品はイオシス

他の同世代・同スペックの白ロムと比べて圧倒的に安い。。。!!

ドコモの投げ売り施策のおかげですね。

これが例の投げ売り
s-max.jp

気になる通信速度は?

格安SIMなので大した期待はしていなかったのですが、意外な結果が出ました。
(測定場所は大田区の我が家、0時頃に確認しました)

まずiPhone6(au)
f:id:susunshun:20150314003900p:plain
場所が悪いのかもしれないけ
通信制限掛かっていない状態でこれはどちょっと遅すぎないか?

次にxperia(DMM Mobile)
f:id:susunshun:20150314005931p:plain
おお、意外と速度出てる。

通信速度も充分出ていますし、
DOCOMO回線を利用していることもあり理不尽な所で圏外になることも無いです。
二週間ほど使用してますが全く問題なく使えています。むしろauよりも快適です。

まとめ

  • 月額安い
  • 通信早い
  • SO-02Gすごい
  • メールや各種アプリの通知機能はiOSの方が良かった

SO-02Gにも大変満足していますが、DMM Mobileが思っていたよりも遥かに使えそうなので
iPhone6は売っぱらってauは解約しようかな

格安SIMが気になっている方は是非検討の材料にしてください!

ではでは

0503: Cup - ArrayListとLinkedList

ちょっとずつ問題が難しくなってきましたね。

問題

長いのでリンクをご参照ください。
Cup | Aizu Online Judge

イメージこんな

スポーツスタッキング - YouTube

方針

今回は綺麗な解法が思いつかず結構悩んだ。。。
一旦寝て次の日電車でぼーっとしてるときににいくつがルールがあることに気づく

【ルール①】移動したコップは次のターンには元いた方向に動かさない
☆      ☆
◎*  → ◎*
ABC   ABC

☆をA→Bに移動しました、それだけです。
☆は次に動くとしたらCです、Aにはいきません、元に戻っちゃうので

【ルール②】トレイAからトレイBに連続でコップは動かせない
①で☆をA→Bに動かしました。

AB間に限定して考えた場合、
・動かしたコップ(☆)は次はそのまま ※ルール①
・動かしたコップ(☆)の隣にあるコップ(◎)は直前まで動かしたコップ(◎)の下にあった。つまり問題の(規則2)により動かしたコップ方向には移動できない

(規則 2)大きいコップの上に小さいコップを重ねてはいけない.

B→Aパターンも考えてみましょう
 ☆    ☆ 
◎*  → ◎*
ABC   ABC

☆:そのまま(ルール①)
◎:動かせない(ルール②)
*:動かせつとしたらC方向

【ルール③】移動はAB間↔BC間の繰り返し
①②を踏まえるトAB間で移動を行った場合、BC間で移動を行うしかありません。
BC間で移動を行った場合、AB間で移動を行うしかありません。

これに気づいてしまえばあとは簡単。

アルゴリズム

①AB間で大きいコップを小さいコップ方向に移動
②BC間で大きいコップを小さいコップ方向に移動
③”①②”の繰り返し

回答

package joaapp;

import java.io.*;
import java.util.Stack;

class Main{
    static int n,m,ans=0;
    
    static Stack<Integer> stackA_ini;
    static Stack<Integer> stackB_ini;
    static Stack<Integer> stackC_ini;
    
    static Stack<Integer> stackA;
    static Stack<Integer> stackB;
    static Stack<Integer> stackC;
    
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    
    public static void main(String[] a)throws IOException{
        while(true){
            inputData();
            ans = Math.min(calcFromLeft(),calcFromRight());
            ans = (ans <= m) ? ans : -1;               
            System.out.println(ans);
        }
        
    }
    
    static int calcFromLeft(){
        int step = 0;
        
        //左初動
        while(true){
            // 終了判定
            if(isFinished()) break;
 
            // 左2つのコップで入れ替え
            if(peekStack(stackA)>peekStack(stackB))
                stackB.push(stackA.pop());
            else
                stackA.push(stackB.pop());
            
            step++;
            
            // 終了判定
            if(isFinished()) break;
                
            // 右2つのコップで入れ替え
            if(peekStack(stackB)>peekStack(stackC))
                stackC.push(stackB.pop());
            else
                stackB.push(stackC.pop());
            
            step++;
        }
        return step;
    }
    
    static int calcFromRight(){
        
        int step = 0;
        stackA = stackA_ini;
        stackB = stackB_ini;
        stackC = stackC_ini;
                 
        //右初動
        while(true){     
            // 終了判定
            if(isFinished()) break;
            
            // 右2つのコップで入れ替え
            if(peekStack(stackB)>peekStack(stackC))
                stackC.push(stackB.pop());
            else
                stackB.push(stackC.pop());

            step++;
            
            // 終了判定
            if(isFinished()) break;          
            
            // 左2つのコップで入れ替え
            if(peekStack(stackA)>peekStack(stackB))
                stackB.push(stackA.pop());
            else
                stackA.push(stackB.pop());
            
            step++;
        }
        return step;
    }
      
    static boolean isFinished(){
        if(stackB.empty() && (stackA.empty() || stackC.empty()))
            return true;
        else
            return false;
    }
    
    static int peekStack(Stack<Integer> stack){
        return stack.empty() ? 0:stack.peek();      
    }
    
    static void inputData() throws IOException{
        
        String[] nm = in.readLine().split(" ");
        String[] cupList;
        int cupNum = 0;

        stackA = new Stack<Integer>();
        stackB = new Stack<Integer>();
        stackC = new Stack<Integer>();
        stackA_ini = new Stack<Integer>();
        stackB_ini = new Stack<Integer>();
        stackC_ini = new Stack<Integer>();
        
        n = Integer.parseInt(nm[0]);
        m = Integer.parseInt(nm[1]);
        
        if(n+m == 0){
            System.exit(0);
        }
        
        cupList = in.readLine().split(" ");
        for(int j=0; Integer.parseInt(cupList[0])>j; j++){
            stackA_ini.push(Integer.parseInt(cupList[j+1]));
            stackA.push(Integer.parseInt(cupList[j+1]));
        }

        cupList = in.readLine().split(" ");
        for(int j=0; Integer.parseInt(cupList[0])>j; j++){
            stackB_ini.push(Integer.parseInt(cupList[j+1]));
            stackB.push(Integer.parseInt(cupList[j+1]));
        }
        
        cupList = in.readLine().split(" ");
        for(int j=0; Integer.parseInt(cupList[0])>j; j++){
            stackC_ini.push(Integer.parseInt(cupList[j+1]));
            stackC.push(Integer.parseInt(cupList[j+1]));
        }
    }
}

できました。これを提出すると。。。※susunshunが僕です
f:id:susunshun:20150120234941p:plain

11人中11位。。。結構いい線いってると思ったのに。

ArrayListとLinkedList

こんな記事を見かけました。
JavaのArrayListとLinkedListの違い

LinkedListは情報処理試験でお馴染みのスタックやキューを実装しているクラスです。
今回スタックが実装をイメージしやすかったのstackクラスを使用しました。

上記記事によると

先端の要素・終端の要素に対して操作できるメソッドを持っているので

とありますが今回はコップは最大でも15個。先端終端に簡単にアクセスできるというメリットを享受しにくい気がします。

だったら一般的に処理が軽いArrayListで実装してみたらどうだ。

import java.io.*;
import java.util.ArrayList;

class Main{
    static int n,m,ans=0;
    
    static ArrayList<Integer> stackA_ini ;    
    static ArrayList<Integer> stackB_ini ;    
    static ArrayList<Integer> stackC_ini ;    
    
    static ArrayList<Integer> stackA;
    static ArrayList<Integer> stackB;
    static ArrayList<Integer> stackC;    
    
    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    
    public static void main(String[] a)throws IOException{
        while(true){
            inputData();
            ans = Math.min(calcFromLeft(),calcFromRight());
            ans = (ans <= m) ? ans : -1;               
            System.out.println(ans);
        }
    }
    
    static int calcFromLeft(){
        int step = 0;
        
        //左初動
        while(true){
            // 終了判定
            if(isFinished()) break;
 
            // 左2つのコップで入れ替え
            if(peekStack(stackA)>peekStack(stackB)){
                stackB.add(stackA.get(stackA.size()-1));
                stackA.remove(stackA.size()-1);
            }else{
                stackA.add(stackB.get(stackB.size()-1));
                stackB.remove(stackB.size()-1);
            }
            
            step++;
            
            // 終了判定
            if(isFinished()) break;
                
            // 右2つのコップで入れ替え
            if(peekStack(stackB)>peekStack(stackC)){
                stackC.add(stackB.get(stackB.size()-1));
                stackB.remove(stackB.size()-1);
            }else{
                stackB.add(stackC.get(stackC.size()-1));
                stackC.remove(stackC.size()-1);
            }
            step++;
        }
        return step;
    }
    
    static int calcFromRight(){
        
        int step = 0;
        stackA = stackA_ini;
        stackB = stackB_ini;
        stackC = stackC_ini;
                 
        //右初動
        while(true){     
            // 終了判定
            if(isFinished()) break;
            
            // 右2つのコップで入れ替え
            if(peekStack(stackB)>peekStack(stackC)){
                stackC.add(stackB.get(stackB.size()-1));
                stackB.remove(stackB.size()-1);
            }else{
                stackB.add(stackC.get(stackC.size()-1));
                stackC.remove(stackC.size()-1);
            }
            step++;
            
            // 終了判定
            if(isFinished()) break;          
            
            // 左2つのコップで入れ替え
            if(peekStack(stackA)>peekStack(stackB)){
                stackB.add(stackA.get(stackA.size()-1));
                stackA.remove(stackA.size()-1);
            }else{
                stackA.add(stackB.get(stackB.size()-1));
                stackB.remove(stackB.size()-1);
            }
            
            step++;
        }
        return step;
    }
      
    static boolean isFinished(){
        if(stackB.isEmpty() && (stackA.isEmpty() || stackC.isEmpty()))
            return true;
        else
            return false;
    }
    
    static int peekStack(ArrayList<Integer> stack){
        return stack.isEmpty() ? 0:stack.get(stack.size()-1);      
    }
    
    static void inputData() throws IOException{
        
        String[] nm = in.readLine().split(" ");
        String[] cupList;
        int cupNum = 0;
        
        stackA = new ArrayList<Integer>();
        stackB = new ArrayList<Integer>();
        stackC = new ArrayList<Integer>();
        stackA_ini = new ArrayList<Integer>();
        stackB_ini = new ArrayList<Integer>();
        stackC_ini = new ArrayList<Integer>();        
        
        n = Integer.parseInt(nm[0]);
        m = Integer.parseInt(nm[1]);
        
        if(n+m == 0){
            System.exit(0);
        }
        
        cupList = in.readLine().split(" ");
        for(int j=0; Integer.parseInt(cupList[0])>j; j++){
            stackA_ini.add(Integer.parseInt(cupList[j+1]));
            stackA.add(Integer.parseInt(cupList[j+1]));
        }

        cupList = in.readLine().split(" ");
        for(int j=0; Integer.parseInt(cupList[0])>j; j++){
            stackB_ini.add(Integer.parseInt(cupList[j+1]));
            stackB.add(Integer.parseInt(cupList[j+1]));
        }
        
        cupList = in.readLine().split(" ");
        for(int j=0; Integer.parseInt(cupList[0])>j; j++){
            stackC_ini.add(Integer.parseInt(cupList[j+1]));
            stackC.add(Integer.parseInt(cupList[j+1]));
        }
    }
}

スタックを使ったときと全く処理は同じです。違いはArrayListかstackかだけ。
では実行時間を見てみましょう。
f:id:susunshun:20150120235814p:plain

!?!?

性能が大幅に改善されました。
アルゴリズムだけでなく使用するクラスの選択でここまで変わるものなのかぁ、一つ学びになりました。

0502: Dice - 値渡しとアドレス渡し

年末年始の連休が終わり悲しきかな仕事が始まってしまったのでAOJは週一問を目処に進める所存です。ということで今週のAOJ。

問題

ここで使用するサイコロは,上側に 1,南側に 2 があるときは,東側に 3 があるものとする.サイコロの向かいあう面の和は必ず 7 なので,見えない面はそれぞれ北側 5,西側 4,下側 6 になっている.

North,East,South,West の各操作は指示された方向へサイコロを 90 度回転させる.  Right,Left の 2 つの操作は上下の面はそのままで水平方向に 90 度回転させる.(回転させる向きに要注意.)

 初期配置で上の面に出ている目 1 を初期値とし, 1 回の操作が終わるたびに,上の面に出ている目の数を加算していき,指示にしたがってすべての操作を終えたときの合計値を出力するプログラムを作成しなさい.

入力ファイルの 1 行目には総指示回数 n が書かれていて,続く n 行の各行には「North,East,South,West,Right,Left」のいずれか 1 つの指示が書かれているものとする.ただし, n ≦ 10000 とする.

回答

import java.io.*;

class Main{
    public static void main(String[] a)throws IOException{
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        String str = null;        
        int orderNum;
        int sum = 1;
        
        // 各指示の移動パターン(数字はサンプルの初期位置の目に対応)
        int[] defaultDice = {1,2,3,4,5,6};
        int[] north = {5,1,3,4,6,2}; 
        int[] east  = {3,2,6,1,5,4}; 
        int[] west  = {4,2,1,6,5,3};
        int[] south = {2,6,3,4,1,5}; 
        int[] Left  = {1,3,5,2,4,6};
        int[] Right = {1,4,2,5,3,6};

        int[] beforeDice = new int[6];
        int[] afterDice = new int[6];
        
        while((str = in.readLine()) != null){
            if(str.equals("0")){
                // 0:処理終了
                System.exit(0);
            }else{
                // 命令回数の読み込み
                orderNum = Integer.parseInt(str);
                // 初期化
                beforeDice = defaultDice;
                sum = beforeDice[0];      
                
                for(int i=0; i<orderNum; i++){
                    str = in.readLine();
                    
                    if(str.equals("North")){
                        for(int j=0; j<6; j++){
                            afterDice[north[j] - 1] = beforeDice[j];
                        }
                    }
                    else if(str.equals("East")){
                        for(int j=0; j<6; j++){
                            afterDice[east[j] - 1] = beforeDice[j];
                        }
                    }
                    else if(str.equals("West")){
                        for(int j=0; j<6; j++){
                            afterDice[west[j] - 1] = beforeDice[j];
                        }
                    }
                    else if(str.equals("South")){
                        for(int j=0; j<6; j++){
                            afterDice[south[j] -1] = beforeDice[j];
                        }
                    }
                    else if(str.equals("Left")){
                        for(int j=0; j<6; j++){
                            afterDice[Left[j] -1] = beforeDice[j];
                        }
                    }
                    else if(str.equals("Right")){
                        for(int j=0; j<6; j++){
                                    afterDice[Right[j] -1] = beforeDice[j];
                        }
                    }
                    else{
                        System.out.println("Input Error");
                    }
                    beforeDice = afterDice;
                    sum += afterDice[0];
                }
                System.out.println(sum);
          
            }
        }
    }
}

方針

各操作でさいころの目がどこに移動するかを配列で定義しておきました。

例)
north = {5,1,3,4,6,2};
・1は5の位置に移動
・2は1の位置に移動
・3は3の位置に移動(固定)
・4は4の位置に移動(固定)
・5は6の位置に移動
・6は2の位置に移動

間違い探し

実は上記のプログラムでは正しい答えを出力しません。
タイトルでお察しの方もいるかと思いますが、

…
beforeDice = defaultDice;
…
beforeDice = afterDice;
…

はいここです。

該当の箇所の処理は、処理の区切りごとにサイコロの目の配置を初期状態{1,2,3,4,5,6}に戻す、という想定で処理を書いています。

うまく動作しないのはここが”アドレス渡し”になっているからです。

アドレス渡し

配列で値を取り出すときは、その値がどこに入っているかを示す”アドレス”を参照して値を取り出します。このアドレスを他の変数に代入することを”アドレス渡し”といいます。

beforeDice = defaultDice;

は”beforeDiceのアドレスにdefaultDiceのアドレスを代入する”ということを表しており2つの配列で同じアドレスを使用しているため、どちらかで配列の値を変更するともう一方の配列にも反映されるわけです。

今回defaultDiceの初期状態{1,2,3,4,5,6}は値が変化しないで欲しいのですが、beforeDiceの値をわちゃわちゃいじくってしまっているのでそれに釣られてdefaultDiceの値も変わってしまい、期待通りに動作しませんでした。。。(実は気づくのに時間かかった)

値渡し

配列でもアドレス渡しではなく、値だけを代入する”値渡し”を行う方法はあります。以下のように記述しましょう。

beforeDice = defaultDice.clone();

これでbeforeDiceには値だけが渡されます。beforeDiceとdefaultDiceで別々のアドレスを参照しているので、一方で値を変更してももう一方には反映されません。

余談:String型のswitch文について

                    switch(str){
                        case "North":
                            for(int j=0; j<6; j++){
                               afterDice[north[j] - 1] = beforeDice[j];
                            }
                            break;
                        case "West":
                            for(int j=0; j<6; j++){
                               afterDice[west[j] - 1] = beforeDice[j];
                            }
                            break;

                        ...

                        default:
                            System.out.println("Input Error");
                            break;
                    }

最初、NorthだとかWestだとかのif文の箇所は上記のようにswitch文で記載していて、開発環境で動作することを確認してから提出を行ったんだけど判定結果はコンパイルエラー。

String型のswitch文はJava SE 7から使えるようになったのですが、AOJの実行環境を確認してみるとJRE 1.6でした。。。以後気をつけなければ!

0501: Data Conversion - 配列とHashMap

あけましておめでとうございます。

今年一個目のAOJどす。

問題

与えられた変換表にもとづき,データを変換するプログラムを作成しなさい.

データに使われている文字は英字か数字で,英字は大文字と小文字を区別する.変換表に現れる文字の順序に規則性はない.

変換表は空白をはさんで前と後ろの 2 つの文字がある(文字列ではない).変換方法は,変換表のある行の前の文字がデータに現れたら,そのたびにその文字を後ろの文字に変換し出力する.変換は 1 度だけで,変換した文字がまた変換対象の文字になっても変換しない.変換表に現れない文字は変換せず,そのまま出力する.

入力ファイルには,変換表(最初の n + 1 行)に続き変換するデータ(n + 2 行目以降)が書いてある. 1 行目に変換表の行数 n,続く n 行の各行は,空白をはさんで 2 つの文字,さらに続けて, n + 2 行目に変換するデータの行数 m,続く m 行の各行は 1 文字である. m < 108 とする.出力は,出力例のように途中に空白や改行は入れず 1 行とせよ.

回答1(配列で作る)

変換表の”変換前”と”変換後”でそれぞれ配列を用意し、線形探索で変換する文字列に該当するか探索します。

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class Main{
    public static void main(String[] a)throws IOException{
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        String str = null;
        int stackNum;
        int transNum;
        String[] transBefore;
        String[] transAfter;
        
        while((str = in.readLine()) != null){
            if(str.equals("0")){
                System.exit(0);
            }else{
                transNum = Integer.parseInt(str);
                
                transBefore = new String[transNum];
                transAfter = new String[transNum];
                
                for(int i=0; i<transNum; i++){
                    str = in.readLine();
                    transBefore[i] = str.split(" ")[0];
                    transAfter[i] = str.split(" ")[1];                   
                }
                
                stackNum = Integer.parseInt(in.readLine());
                
                for(int i=0; i<stackNum; i++){
                    str = in.readLine();
                    for(int j=0; j<transNum; j++){
                        if(transBefore[j].equals(str)){
                            str = transAfter[j];
                            break;
                        }
                    }
                    System.out.print(str);
                }
                System.out.println();
            }
        }
    }
}

回答2(HashMapで作る)※抜粋

HashMapのkeyを”変換前”、valueを”変換後”として変換表の管理をします。

    	while((str = in.readLine()) != null){
            if(str.equals("0")){
                System.exit(0);
            }else{
                transNum = Integer.parseInt(str);
                transMap.clear();
                
                for(int i=0; i<transNum; i++){
                    str = in.readLine();
                    transMap.put(str.split(" ")[0],str.split(" ")[1]);
                }
                
                stackNum = Integer.parseInt(in.readLine());
                
                for(int i=0; i<stackNum; i++){
                    str = in.readLine();
                    for(int j=0; j<transNum; j++){
                        if(transMap.containsKey(str)){
                            str = transMap.get(str);
                            break;
                        }
                    }
                    System.out.print(str);
                }
                System.out.println();
            }
        }

実行時間の比較

復習がてら配列とHashMapの比較を。

配列
f:id:susunshun:20150102010859p:plain
HashMap
f:id:susunshun:20150102010912p:plain

ほとんど変わらず、といった所ですがHashMapの方がやや時間がかかっている。

今回は変換表の最大サイズが40組と小さいので線形探索の方がやや実行時間が短い結果となってるけども、変換表のサイズが数万と大きくなってくる場合にはハッシュの方が早くなってくるのでは。
(その場合には線形探索ではなく二分探索や他の探索方法した方がよいですが。。。)

「配列 HashMap 速度」でググルといろいろな説が出てくるけども、参照速度に関しては(要素数が十分に多い場合)ハッシュの方が早いです。

ハッシュの方が遅いって記事も見たよ、なんで!?

ハッシュは初期容量が設定されていて、要素の挿入によってこの容量を超える場合には確保している領域の拡張が行われるので、ハッシュの方が遅いと言われる所以はデータの挿入が頻繁に行われる(領域の拡張など参照以外の処理が多い)プログラムでの検証をしているのでしょう、多分(不安)

ただ予め要素数が推定でき、初期容量をうまく操れればデータの挿入が多いプログラムでもハッシュ兄貴も良い結果を残してくれるのでは

今回の問題だと最初に変換表を作成し、あとは参照のみといった内容なので(変換表のサイズが十分大きい場合には)ハッシュの方が有効策です。

結論としてはケースバイケースです(強気)

iOSアプリ開発について

実は裏でしこしことiOSアプリのお勉強もしております。

「C++なら大学の授業でやったからObjective-Cもいけるやろ」

くらいの強気だったけども、情報弱者の私には難しいです(゚Д゚)グワー

ただこちらは”アプリとして一つのサービスをとりあえず最後まで作ってみる”のが目的なので、処理内容を深くは追わずに実装することを優先で進める方針です

こちらも要所要所で更新したいと思いますー。

参考:

ハッシュは二分木(ツリー)より速い(ハッシュとツリーの速度比較) - プログラマはサイコロを振らない

0500:Card Game

開発環境について

最初はコーディング力鍛えるためにSublime text 3でやってたけどNetbeansに戻しました。

何でNetBeansかと言うとマスコットキャラが可愛いからです。

これがマスコットキャラのねこび〜ん。かわいい。。。

f:id:susunshun:20141231230612g:plain

eclipseAptana等も使ったけど正直違いがわかる程のスキルレベルには至ってません。コードかけて実行/デバッグできりゃ何でもよかです。

今日は日本情報オリンピックの過去問に着手。
他にも3問解いたけど面倒臭いので割愛。

問題

A と B の 2 人のプレーヤーが, 0 から 9 までの数字が書かれたカードを使ってゲームを行う.最初に, 2 人は与えられた n 枚ずつのカードを,裏向きにして横一列に並べる.その後, 2 人は各自の左から 1 枚ずつカードを表向きにしていき,書かれた数字が大きい方のカードの持ち主が,その 2 枚のカードを取る.このとき,その 2 枚のカードに書かれた数字の合計が,カードを取ったプレーヤーの得点となるものとする.ただし,開いた 2 枚のカードに同じ数字が書かれているときには,引き分けとし,各プレーヤーが自分のカードを 1 枚ずつ取るものとする.

回答

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

class Main{
    public static void main(String[] a)throws IOException{
    	BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

        String str = null;
        int gameNum;
        int scoreA = 0;
        int scoreB = 0;
        int numA;
        int numB;                
        
    	while((str = in.readLine()) != null){
            if(str.equals("0")){
                System.exit(0);
            }else{
                gameNum = Integer.parseInt(str);

                for(int i=0;i<gameNum;i++){
                    str = in.readLine();
                    numA = Integer.parseInt(str.split(" ")[0]);
                    numB = Integer.parseInt(str.split(" ")[1]);
                    
                    if(numA > numB){
                        scoreA += (numA + numB);
                    }else if(numA < numB){
                        scoreB += (numA + numB);                    
                    }else{
                        scoreA += numA;
                        scoreB += numB;
                    }
                }                    
                
                System.out.println(scoreA+" "+scoreB);
                scoreA = 0;
                scoreB = 0;
            }
        }
    }
}

特に詰まる所は無かったけど処理速度が遅いので、

  • gameNumの引き回し方
  • strの分割方法

あたりが改善ポイントでしょうか。