国立情報学研究所で開催された国際ワークショップ:社会のイノベーションを誘発する情報システムで口頭発表をさせて頂きました。
SNS版のプライバシー侵害情報分類表をベースとしたプライバシー侵害検知アプリケーションを開発し、その初お披露目となり緊張しましたが、たくさんのフィードバックをいただき、今後の展開に役立てられそうです。
発表名:Setting of Access Control by Detecting Privacy Leaks in SNS
国立情報学研究所で開催された国際ワークショップ:社会のイノベーションを誘発する情報システムで口頭発表をさせて頂きました。
SNS版のプライバシー侵害情報分類表をベースとしたプライバシー侵害検知アプリケーションを開発し、その初お披露目となり緊張しましたが、たくさんのフィードバックをいただき、今後の展開に役立てられそうです。
発表名:Setting of Access Control by Detecting Privacy Leaks in SNS
プライバシー侵害の研究の一環として、FacebookからFQLを使用して友人の投稿写真を取得するPYthonスクリプトを作りました。取得した写真の取り扱いには注意してください。
https://github.com/shmachid/facebook_mining/blob/master/getPic_from_fb.py
スクリプト実行手順は以下となります。
http://miningthesocialweb.appspot.com/
全ての権限を与える必要はないが、スクリプトが動かなくなる可能性もあるので注意してください。
求められた権限はそのまま許可し、不要になったら、Facebookアプリを削除すれば良いです。
http://miningthesocialweb.appspot.com/query
自分のコネクション数にもよるが、画像1万枚で4時間ほどかかります。
一枚ずつ見るのが面倒な場合は、 out/get_picture.html を開いても良です。
ディレクトリ構成は以下のようにします。out/以下にトークン情報を格納します。
[shmachid@hoge:~/fb] $ ls getPic_from_fb.py out [shmachid@hoge:~/fb] $ cd out [shmachid@hoge:~/fb/out] $ ls facebook.access_token
# -*- coding: utf-8 -*- import os import sys import json import urllib import urllib2 from urllib import urlencode import webbrowser class FQL(object): ## This class was made by ptwobrussell. ## please check original code. ## https://github.com/ptwobrussell/Mining-the-Social-Web.git ENDPOINT = 'https://api.facebook.com/method/' def __init__(self, access_token=None): self.access_token = access_token def _fetch(cls, url, params=None): conn = urllib2.urlopen(url, data=urlencode(params)) try: return json.loads(conn.read()) finally: conn.close() def query(self, q): if q.strip().startswith('{'): return self.multiquery(q) else: params = dict(query=q, access_token=self.access_token, format='json') url = self.ENDPOINT + 'fql.query' return self._fetch(url, params=params) def multiquery(self, q): params = dict(queries=q, access_token=self.access_token, format='json') url = self.ENDPOINT + 'fql.multiquery' return self._fetch(url, params=params) if __name__ == '__main__': try: ## First, you need to assign facebook application and get access-token. ## please get the access token from following web-site and copy&paste to file. ## http://miningthesocialweb.appspot.com/ ACCESS_TOKEN = open('out/facebook.access_token').read() if ACCESS_TOKEN == '': print >> sys.stderr, "Check your token" exit() if not os.path.isdir('picture'): os.mkdir('picture') q = "select target_id from connection where source_id = me() and target_type = 'user'" except IOError, e: try: print >> sys.stderr, "IO Error" exit() except IndexError, e: print >> sys.stderr, "Index Error" exit() fql = FQL(access_token=ACCESS_TOKEN) ## 1. get persons id print "get person id start" persons = [str(t['target_id']) for t in fql.query(q)] print json.dumps(persons, indent=4) ## 2. get album id print "get album id start" cnt = 1 work = [] for i in persons: u = 'select aid from album where owner = %s' % (i) work += fql.query(u) print cnt cnt += 1 albums = [str(t['aid']) for t in work] print json.dumps(albums, indent=4) ## 3. get photo id print "get photo id start" cnt = 1 work2 = [] for al in albums: e = 'select object_id from photo where aid = "%s"' % (al) work2 += fql.query(e) print cnt cnt += 1 photos = [str(t['object_id']) for t in work2] while 'error_code' in photos: photos.remove('error_code') while 'request_args' in photos: photos.remove('request_args') print json.dumps(photos, indent=4) print "Abount photo count: %s" % (len(photos)) ## 4. get photo url print "get photo url start" cnt = 1 photo_srcs = [] N = 30 for k in range(len(photos) / N + 1): r = 'select src, width, height from photo_src where photo_id in (%s)' \ % (','.join(photos[k * N:(k + 1) * N])) photo_srcs += fql.query(r) print cnt cnt += 1 ## 5. select picture : width=320 only print "get photo via urllib start" cnt = 1 photo_src = [] for photo in photo_srcs: if photo['width'] == 320: photo_src.append('<img src="' + photo['src'] + '">') filename = photo['src'].split('/') ## get picture try: urllib.urlretrieve(photo['src'], os.path.join(os.getcwd(), 'picture', filename[-1])) except: print >> sys.stderr, "Can't get this picture '%s'" % (filename[-1]) print cnt cnt += 1 ## 6. create HTML print "create HTML start" OUT = os.path.basename('get_picture.html') html = "<html><body>%s</body></html>" % (' '.join(photo_src[0:])) f = open(os.path.join(os.getcwd(), 'out', OUT), 'w') f.write(html) f.close() ## if you want to show these pictures in a browser, please execute following command at your terminal. ## `open out/get_picture.html` #webbrowser.open('file://' + f.name) print "Done!"
前回に続いて、MapReduceによるView作成、View実行を確認してみました。
Javascript以外の言語でMapReduceを行う場合は、couchDBのiniファイルに以下のようなイメージで追記します。
正常反映されれば、FutonのView Codeセグメントでlanguageとして使用可能となります。
[query_servers] python = /usr/bin/couchpy
下記はサンプルとして、tweetのユーザ情報でlocation = ‘Tokyo’であるtweetを出力しています。
また、本サンプルではReduce処理を使用していません。
https://github.com/shmachid/twitter_mining/blob/master/define_view_in_couch.py
# -*- coding: utf-8 -*- import couchdb from couchdb.design import ViewDefinition SERVER_URL = 'YOUR COUCHDB URL' #ex: http://localhost:5984 DB_USER = 'YOUR USER' DB_PASSWD = 'YOUR PASSWORD' DB = 'YOUR DB NAME' server = couchdb.Server(SERVER_URL) server.resource.credentials = (DB_USER, DB_PASSWD) try: db = server.create(DB) except couchdb.http.PreconditionFailed, e: db = server[DB] def mapper(doc): if doc['author']['location'] == "Tokyo": yield (doc['id'], doc['text']) ## if you need to use reduce function, please remove bellow the comment-tag. #def reducer(keys, values, rereduce): # return max(values) view = ViewDefinition('index', 'map_location', mapper, language='python') #view = ViewDefinition('index', 'map_location', mapper, reducer, language='python') view.sync(db) records = [(row.key, row.value) for row in db.view('index/map_location')] for record in records: print record[1]
Viewを作成・更新後、初回実行時にViewの更新処理が走る為、View実行完了まで時間が掛かりました。(対象件数は25万件であった)
データ更新が絶えず入っている状況でViewへアクセスがあった場合は、どのよう挙動となるのかが気になります。
ログは以下のように出力されます。何か良い方法あるのかな、探したけど分からなかった。
[Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244494 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244520 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244548 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244579 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244606 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244634 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244664 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244693 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244721 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244747 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244773 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244797 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244821 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244848 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244877 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244903 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244934 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244961 for tweets _design/index [Sat, 05 May 2012 17:01:30 GMT] [info] [<0.6123.0>] checkpointing view update at seq 244991 for tweets _design/index
Twitter Streaming API: filter()からCouchDBへ流しこむサンプルスクリプトを作りました。
返ってきたStatus情報等を全て格納しておりテーブルサイズが大きくなりやすいので、適度にフィルタを掛けると良いです。
連休前と連休中で画像共有サービスの利用頻度が変わるのかを確認する為にFilter条件をtwippleで行なったところ、ゴールデンウィーク中は1日で19万件程度tweetされていました。
連休前として2週間程前に収集した際は10万件程でしたので、行楽中は画像共有サービスの利用が増えるようです。
https://github.com/shmachid/twitter_mining/blob/master/stream2couch.py
# -*- coding: utf-8 -*- import sys import time from datetime import datetime import tweepy import json import jsonpickle import couchdb if (len(sys.argv) < 2): print "Usage: please check your parameter" sys.exit() QUERY = sys.argv[1:] SERVER_URL = 'YOUR COUCHDB URL' #ex: http://localhost:5984 DB_USER = 'YOUR USER' DB_PASSWD = 'YOUR PASSWORD' DB = 'YOUR DB NAME' CONSUMER_KEY = 'YOUR CONSUMERKEY' CONSUMER_SECRET = 'YOUR CONSUMER_SECRET' ACCESS_TOKEN = 'YOUR ACCESS_TOKEN' ACCESS_TOKEN_SECRET = 'YOUR ACCESS_TOKEN_SECRET' auth = tweepy.OAuthHandler(CONSUMER_KEY, CONSUMER_SECRET) auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET) server = couchdb.Server(SERVER_URL) server.resource.credentials = (DB_USER, DB_PASSWD) try: db = server.create(DB) except couchdb.http.PreconditionFailed, e: db = server[DB] class CustomStreamListener(tweepy.StreamListener): def on_status(self, status): results = {} try: if status.id_str in db: return True pickled = jsonpickle.encode(status) results = json.loads(pickled) del results['_api'] db[results['id_str']] = results except Exception, e: print >> sys.stderr, "Encountered Exception:", e pass def on_error(self, status_code): print >> sys.stderr, "Encountered error with status code:", status_code return True def on_timeout(self): print >> sys.stderr, "Timeout..." return True streaming_api = tweepy.streaming.Stream(auth, CustomStreamListener(), timeout=60) print >> sys.stderr, 'Filtering parameters: "%s"' % (' '.join(sys.argv[1:]),) try: # sample(): streaming_api.sample() streaming_api.filter(follow=None, track=QUERY) except Exception, e: print >> sys.stderr, "Error: '%s' '%S'" % (str(datetime.now()), str(e)) finally: print >> sys.stderr, "disconnecting..." streaming_api.disconnect()
大学院の有志チームで参加していたRICOH & Java developer challenge2011でRICOH賞をいただきました。
アプリケーション機能は非常にシンプルでしたが、顔認識技術とビジネスモデルとの組み合わせ、アプリケーションとしての完成度が評価されたのではと思います。また、水着の三愛がリコーの傘下といこともあり、画像解析 × ファッションレポート のアプリケーションが受け入れられたのもあるようです。
グランプリの北陸先端技術大学院さんも顔画像技術を使用されており、エンタープライズアプリケーションにおいてもこれらの技術は注目されているのだと感じました。
また、他チームのアイデアや実装は素晴らしく、若い方々の発想力に驚きました。もう少しブラッシュアップすればRICOHからアプリケーションとして出ても良いのではと思われるアプリケーションもあり良い刺激をもらえました。
約8ヶ月ほど、チームで活動をしてきましたが、最後に良い結果が出て良かったです。チームの皆さんありがとうございました。
大学院の有志メンバと参加しているRICOH & Java Developer Challenge 2011の最終選考に提出できました。
9月末に1次選考(Emulator編)を無事通過し、最終選考へ向けて実機上でMFPアプリケーションを開発してきました。
5月にチームを発足してから半年以上と長期に渡って活動してきましたが、ようやくシンプルながらも面白いモノが完成しました。
このプログラミングコンテストからは技術的な部分のみでなく、チーム活動の進め方という点に関しても多くの得る事あった為、参加して良かったと思います。
最終選考は2012/1/12(木)です。あと、Ustreamでも放送があるそうです。
自身の開発プロセス改善を目的として7回に渡りシナリオ仕様毎に、
計画立案 → 設計 → 設計レビュー → コーディング → コードレビュー → コンパイル → テスト → 事後分析
を繰り返し行いましたので、最後に結果を分析しました。
・ プログラムごとの見積規模と実績規模を比較し、見積の正確性を検証する。
【プログラム毎の見積規模と実績規模】
Program No. | 見積規模(LOC) | 実績規模/KLOC |
1 | – | 60 |
2 | 150 | 79 |
3 | 164 | 124 |
4 | 124 | 104 |
5 | 144 | 96 |
6 | 137 | 152 |
7 | 248 | 189 |
全体的に見積規模と実績規模はある程度の近似値で見積が出来ていた。
通常最も避ける必要がある点は、見積規模に対して実績規模が大きく上まるケース(受注金額に対して、予定工数が上まってしまう為)であるが、そのような例は見受けられなかった。
仕様が複雑化していった後半のプログラムにおいても、大きな差異は無かった理由としては設計フェーズで仕様を完全に把握出来たことが功を奏したと推測される。
・ プログラムごとの見積時間と実績時間を比較し、見積の正確性を検証する。
【プログラム毎の見積時間と実績時間】
Program No. | 見積時間(min) | 実績時間(min) |
1 | – | 300 |
2 | 120 | 470 |
3 | 250 | 297 |
4 | 315 | 369 |
5 | 385 | 565 |
6 | 224 | 451 |
7 | 168 | 381 |
時間に関しては、規模と対照的な結果であった。実績時間が見積時間を大きく上まっているケースが多く、終盤においてもこの現象が見られる。まだ見積時に使用する自分のデータが安定していない事と仕様の複雑さや難易度が見積時間に反映出来ていないことが影響していると推測出来る。対応方法としては、見積時間に対してある程度の時間を上乗せすると実績時間と見積時間が合致する。
・ フェーズ毎の実績時間を相関し、フェーズ間の関連を確認する。
【プログラム毎の実績時間】
プログラム | 計画立案 | 設計 | 設計レビュー | コーディング | コードレビュー | コンパイル | テスト | 事後分析 |
1 | 10 | 45 | – | 120 | – | 0 | 65 | 60 |
2 | 20 | 30 | – | 183 | – | 5 | 192 | 40 |
3 | 20 | 60 | – | 94 | – | 3 | 90 | 30 |
4 | 60 | 105 | – | 80 | – | 1 | 63 | 60 |
5 | 60 | 65 | 20 | 190 | 15 | 5 | 140 | 70 |
6 | 40 | 65 | 15 | 137 | 20 | 1 | 113 | 60 |
7 | 70 | 50 | 10 | 100 | 20 | 1 | 70 | 60 |
合計 | 280 | 420 | 45 | 904 | 55 | 16 | 733 | 380 |
コーディング(水色)とテスト(緑色)の関係をみると、コーディング時間が長くなると連動してテスト時間も大きくなっている事が確認出来る。また、ほぼ一定の距離感で連動する事が確認出来る。これにより、直感的な単純な見積を行う場合に、開発時間の3/4程度がテスト時間であると定義出来そうである。
・ 設計、コーディングで作り込まれた欠陥を型ごとに分類し、グラフにより型による欠陥数の傾向を図示する。
※ 型番号に関しては、PSPの手順書を参照のこと。
作り込み欠陥 | |||||||||||
型 | 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100 | 合計 |
設計 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
コード | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 23 | 0 | 0 | 24 |
欠陥の殆どが80:機能に集中している。
この結果から考察すると、単純なミスというよりもまだプログラムの機能として仕様が満たされていないケースがあると思われる。プログラム3以降は設計フェーズで仕様を確実に把握してから、コーディングに移っていた事を考慮すると、テストやコンパイル前に行うコーディングレビューが不十分である可能性が高い。よって、コーディングレビューの精度を高める事で欠陥数は押さえることが出来そうである。
また、今回はEclipseを使用した為、コンパイル時の欠陥は1件であったが、自分の欠陥傾向を掴む為には統合開発環境を使用せずに計測したほうが良かったかもしれない。
・各プロセスで発見した欠陥数/KLOCを一覧化し、傾向を分析する。
発見欠陥数 | 発見欠陥数/KLOC | |
設計レビュー | 0 | 0 |
コードレビュー | 0 | 0 |
コンパイル | 1 | 1.2 |
テスト | 23 | 28.6 |
合計 | 24 | 29.9 |
Program No. | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
欠陥数 | 5 | 4 | 3 | 3 | 4 | 3 | 2 |
欠陥数/KLOC | 83.3 | 50.6 | 24.2 | 28.4 | 41.7 | 19.7 | 10.6 |
若干ムラはあるものの演習を行う事で、欠陥数は減少傾向である。特にプログラム3以降で欠陥が大きく下がっているのは、このプログラム以降は計画・設計フェーズを重視し、仕様を完全に把握してからコーディングを行うようにしたからと考えられる。また、プログラム5, 6,7は前回のプログラムを再利用・追加していく仕様である為、前プログラムで欠陥を押さえることが出来ていれば、必然的に当該プログラムでも欠陥が減少することが確認できた。これにより、プログラムやクラスを再利用する時は、当然ではあるが、欠陥が無い状態で利用可能とすべきことを再認識できた。
・各プロセスで除去した欠陥数/KLOCを一覧化し、プロセス別の除去欠陥能力を示す。
除去欠陥数 | 除去欠陥数/KLOC | 除去欠陥率 | |
設計レビュー | 0 | 0 | 0.0% |
コードレビュー | 0 | 0 | 0.0% |
コンパイル | 0 | 0 | 0.0% |
テスト | 24 | 29.9 | 100.0% |
合計 | 24 | 29.9 | 100.0% |
欠陥は全てテストフェーズにて確認し、除去を行った。テストフェーズは最終段階であり、後フェーズで発見するほど対応コストが高くなる為、より前フェーズである設計レビューやコードレビューで欠陥を発見する必要がある。前段で確認した欠陥タイプの分析では、80:機能 の欠陥が大半を占めたようにコードレビューでより欠陥を除去出来た可能性がある。よって、今後はレビューにより時間を割くように意識していく。
・設計レビュー、コードレビュー時の時間当たりのレビューLOC数に対する欠陥除去率を一覧化し、傾向を分析する。
時間 | LOC数 | 欠陥除去数 | 欠陥除去率 | |
設計レビュー | 45 | 437 | 0 | 0.0% |
コードレビュー | 55 | 0 | 0.0% |
コードレビュー、設計レビューにおいて、1件も欠陥を発見・除去出来ていないのは問題である。まだ実施時間が1時間以下である為、今後のレビュー活動に注視する。設計に関しては、仕様を完全に把握してから進めるようにしていた為、発見出来なかったのも納得出来ているが、コードレビューでは数件は発見・除去できたはずである。
・ 欠陥の修正時間を確認する。
【テストフェーズにおける欠陥修正時間と平均修正時間】
プログラム | 欠陥数 | 修正時間 | 平均修正時間 |
1 | 5 | 50 | 10 |
2 | 4 | 197 | 49.25 |
3 | 3 | 73 | 24.3 |
4 | 3 | 45 | 15 |
5 | 4 | 116 | 29 |
6 | 3 | 80 | 26.7 |
7 | 2 | 50 | 25 |
合計 | 24 | 611 | 25.5 |
突出して欠陥修正時間が伸びているプログラム2の際は、設計が非常に甘かったと言える。この演習で非常に時間が掛かってしまった為、プログラム3以降は設計フェーズで仕様を正確に捉えるようにシフトすることができた。また、設計レビューやコーディングレビューで事前に欠陥を発見・修正することで、テストフェーズにおける欠陥数、大きな欠陥による修正時間の増加を押さえることが出来ると考えた。
【最も優先すべき点】
統一した計測方法で作業ログを残し、改善点を見つける為の振り返りを行う。
【理由】
現在の個人データは不十分 且つ、データ量が少ない為、正確な見積値として使用する事が出来ない。これを改善する為に、日々の業務や個人開発おいて、統一した計測方法で作業ログを残し、後日、改善点や傾向を把握する為の振り返りを行い、パーソナルプロセス改善を行う。
【現在の作業能力】
規模見積に関して、今までは仕様に対する規模推測を大きく見積過ぎていた。演習を行うことで似たような仕様(過去に経験した仕様)であれば、ある程度正確な規模が見積もれるようになっている。過去の実績時間を使用することで、全体を見通した時間が見積もれるようになったが、まだ若干のブレがある。
また、計画や設計フェーズの重要性を自分のデータを通して、実感出来ている。例えば、仕様は確実に理解することで、コーディング時間や欠陥数、欠陥に対する対応時間の短縮等の効果が大きい事を理解できている。
【将来あるべき作業能力】
まず最初に持たなければならない能力としては、時間見積の正確性である。規模見積が行えたとしても、時間見積が全く異なっていれば、見積が出来ているとは言えないからである。その為に必要なアクションとしては、今後の作業ログを確実に残していき、自分の見積時の基データを収集することである。次に必要な能力としては、レビュー能力と考えた。演習では欠陥検出は出来なかったが、欠陥傾向としてコーディングレビューが有効と判断した為、本能力を向上させることにより欠陥をある程度減少させることが可能である。
最終的なゴールとしては、自分が作業を行う為の見積ではなく、一般的な技術者が作業を行う場合の適性値を見積ることである。この為には、自分の能力と一般的な技術者の能力差を把握する必要がある。
PSP演習を実施するにつれて、設計の重要性が再認識出来たのは非常に良い結果であった。また、いつも見積を行う際にドンブリ勘定を行なっていたが、より精度の高い見積を行うことの重要性と手法を学べた事が良かった。今後は自分の基データを収集すると共に継続的にプロセス改善を行なっていき、機会があればチームに対してTSPを適用していきたい。これにより、トラブルプロジェクト(欠陥や期間、金額など)を減少させる1要素になると感じた。
ということで、PSPの演習自体の仕様としては業務にあまり活かせない可能性はありますが、
個々のプロセスを振り返る事で自身の開発プロセスを改善する第1歩となるという点では間違いありません。
以上です。
よく複数のプログラミングパラダイムを理解することは非常に良いことだと言われますが、
今回は関数型言語であるErlangでCollatz問題を実装してみました。
Erlangをさわるのは今回が初めてになります。実装に際しては、評判の良かった戦闘機本を読みました。
普段使用している言語であれば、あっさり実装出来そうな(完全解決出来ていない問題なので、プログラム上の意味)仕様でさえも、かなりの時間が掛かってしまいました。
新しいパラダイムを理解するのは非常に大変だと実感できました。また、並行処理を実装する・理解するまではもっと訓練が必要そうです。
● 仕様
Nが1以上256未満の整数のとき、Nに対して関数fを有限回繰り返し適用すると1になることを示す。
【Collatz問題とは】
問題の概要は、「任意の0でない自然数 n をとり、
n が偶数の場合、n を 2 で割る
n が奇数の場合、n に 3 をかけて 1 を足す
という操作を繰り返すと、有限回で 1 に到達する」という主張である(1 に到達すると、1→4→2→1 を繰り返す)。
-module(hoge). -export([f/1]). f(X) when is_integer(X), (X >= 1), (X < 256) -> collatz(X); f(_X) -> io:format("This function can use Integer and under 256.n"). collatz(1) -> 1; collatz(X) when (X rem 2 == 0) -> collatzEn(X); collatz(X) when (X rem 2 == 1) -> collatzOn(X). collatzEn(X) -> io:format("~p~n", [X]), collatz(X div 2). collatzOn(X) -> io:format("~p~n", [X]), collatz(X * 3 + 1).
● 実行方法・結果
OS: CentOS5.6
Ver: Erlang R12B-5.12
Emu: Erlang emulator Ver5.6.5
$erlcによってコンパイルすると、hoge.beamが生成されます。
その後、erlコマンドでEmulatorを起動し、実装したCollatz関数を呼び出すと、最終的に1となります。
[shmachid@hoge work]$ erlc hoge.erl [shmachid@hoge work]$ erl Erlang (BEAM) emulator version 5.6.5 [64-bit] [smp:2] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.6.5 (abort with ^G) 1> hoge:f(13). 13 40 20 10 5 16 8 4 2 1 2>
Lesson3, Lesson5, Lesson6で開発したプログラムをもとに、Lesson7用の仕様を追加していきます。
これにより、自身の計測値さえあれば、開発に対する多少のブレを含めた上での見積時間と見積総行数を見積もることが出来ます。
※ なお、Lesson5, 6に関しては、クラスとして利用出来るよう変更してください。
Lesson7: 「線形回帰パラメータと予測区間を計算する」
● プログラム仕様スクリプト
ASGKIT PROG7を参照のこと
● 実行結果
線形回帰パラメータと予測区間を計算する
rxy: 0.954497 r2: 0.911064 tail area: 0.000018 β0: -22.552533 β1: 1.727932 yk: 644.429384 range: 230.001974 UPI: 874.431357 LPI: 414.42741
● ソースコード
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.text.DecimalFormat; import java.util.Iterator; import java.util.LinkedList; /** * PSP Lesson7: * 線形回帰パラメータと予測区間を計算する * */ public class Program7 { private int size; private double[] data1; private double[] data2; private double sumData1; private double sumData2; private double avgData1; private double avgData2; private double powData1; private double powData2; private double sumMulti; private double beta0; private double beta1; private double r; private double r2; private double y; private double tailArea; private double p; private double x; private double deviation; private double ran; private double range; private double upi; private double lpi; public static void main(String[] args) { Program7 pg7 = new Program7(); try { File f = new File(args[0]); FileInputStream fi = new FileInputStream(f); BufferedReader br = new BufferedReader(new InputStreamReader(fi, "UTF-8")); // 分析対象となるデータ行を指定する int data1 = Integer.parseInt(args[1]); int data2 = Integer.parseInt(args[2]); // 分析対象のLOC long num = Long.parseLong(args[3]); LinkedList<Double> linkData1 = new LinkedList<Double>(); LinkedList<Double> linkData2 = new LinkedList<Double>(); String line; while ((line = br.readLine()) != null) { String[] strrec = line.split("t"); linkData1.add(Double.parseDouble(strrec[data1 - 1])); linkData2.add(Double.parseDouble(strrec[data2 - 1])); } pg7.size = linkData1.size(); pg7.data1 = new double[pg7.size]; pg7.data2 = new double[pg7.size]; // 合計値を計算する pg7.sumData1 = pg7.calcSum(linkData1); pg7.sumData2 = pg7.calcSum(linkData2); // 合計値から平均値を計算する pg7.avgData1 = pg7.calcAvg(pg7.sumData1); pg7.avgData2 = pg7.calcAvg(pg7.sumData2); // X, Yの二乗の合計値、X*Yの合計値を計算する pg7.powData1 = pg7.calcSumPow(linkData1); pg7.powData2 = pg7.calcSumPow(linkData2); pg7.sumMulti = pg7.calcSumMulti(linkData1, linkData2); // β1 を計算する pg7.beta1 = pg7.calcBeta1(); // β0 を計算する pg7.beta0 = pg7.calcBeta0(); // R, R2 を計算する pg7.r = pg7.calcR(); pg7.r2 = pg7.calcR2(); // tail areaを計算する pg7.tailArea = pg7.calcTail(); // yを計算する pg7.y = pg7.calcY(num); // xを計算する pg7.x = pg7.calcX(); // 標準偏差を計算する pg7.deviation = pg7.calcDeviation(); // Rangeを前計算する pg7.ran = pg7.calcRan(num); // Rangeを計算する pg7.range = pg7.calcRange(); // UPIを計算する pg7.upi = pg7.calcUPI(); // LPIを計算する pg7.lpi = pg7.calcLPI(); System.out.println("rxy: " + pg7.changeScale(pg7.r, 6)); System.out.println("r2: " + pg7.changeScale(pg7.r2, 6)); System.out.println("tail area: " + new DecimalFormat("0.#######").format(pg7.changeScale( pg7.tailArea, 8))); System.out.println("β0: " + pg7.changeScale(pg7.beta0, 6)); System.out.println("β1: " + pg7.changeScale(pg7.beta1, 6)); System.out.println("yk: " + pg7.changeScale(pg7.y, 6)); System.out.println("range: " + pg7.changeScale(pg7.range, 6)); System.out.println("UPI: " + pg7.changeScale(pg7.upi, 6)); System.out.println("LPI: " + pg7.changeScale(pg7.lpi, 6)); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 合計値を計算する * */ private Double calcSum(LinkedList<Double> linkArray) { double sum = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { sum = sum + Double.parseDouble(i.next().toString()); } return sum; } /** * 平均値を計算する * */ private Double calcAvg(Double sum) { return sum / size; } /** * X, Yの2乗合計値を計算する * */ private Double calcSumPow(LinkedList<Double> linkArray) { double sum = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { sum = sum + Math.pow(Double.parseDouble(i.next().toString()), 2); } return sum; } /** * X*Yの合計値を計算する * */ private Double calcSumMulti(LinkedList<Double> linkData1, LinkedList<Double> linkData2) { double sum = 0; int i = 0; while (linkData1.size() != 0 || linkData2.size() != 0) { data1[i] = linkData1.pop(); data2[i] = linkData2.pop(); sum = sum + (data1[i] * data2[i]); i++; } return sum; } /** * β1 を計算する * */ private Double calcBeta1() { double num1 = (sumMulti - (size * avgData1 * avgData2)); double num2 = (powData1 - (size * avgData1 * avgData1)); return num1 / num2; } /** * β0 を計算する * */ private Double calcBeta0() { return avgData2 - (beta1 * avgData1); } /** * R を計算する * */ private Double calcR() { double num1 = (size * sumMulti) - (sumData1 * sumData2); double num21 = (size * powData1) - (sumData1 * sumData1); double num22 = (size * powData2) - (sumData2 * sumData2); return num1 / (Math.sqrt(num21 * num22)); } /** * R2 を計算する * */ private Double calcR2() { return r * r; } /** * Tail Area を計算する * */ private Double calcTail() { double num1 = r * Math.sqrt(size - 2); double num2 = Math.sqrt(1 - r * r); Tdist tDist = new Tdist(); tDist.start(num1 / num2, size - 2); p = tDist.getP(); return (double) (1 - 2 * p); } /** * Y を計算する * */ private Double calcY(long num) { return beta0 + beta1 * num; } /** * PからXを計算する * */ private Double calcX() { RevTdist RevTdist = new RevTdist(); RevTdist.start(0.0, size - 2, 0.35); double x = RevTdist.getX(); return x; } /** * 標準偏差を計算する * */ private Double calcDeviation() { double sum = 0; for (int i = 0; i < data1.length; i++) { sum += Math.pow((data2[i] - beta0 - beta1 * data1[i]), 2); } double dist = sum / (size - 2); double dev = Math.sqrt(dist); return dev; } /** * Rangeを前計算する * */ private Double calcRan(long num) { double num1 = Math.pow(num - avgData1, 2); double num2 = 0; for (int i = 0; i < data1.length; i++) { num2 += Math.pow(data1[i] - avgData1, 2); } double sq = 1 + (double) 1 / size + (double) (num1 / num2); return Math.sqrt(sq); } /** * Rangeを計算する * */ private Double calcRange() { return x * deviation * ran; } /** * UPIを計算する * */ private Double calcUPI() { return y + range; } /** * LPIを計算する * */ private Double calcLPI() { return y - range; } /** * 四捨五入を行う * */ private double changeScale(double val, int i) { BigDecimal bd = new BigDecimal(val); return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
Lesson5で開発したプログラムをもとに、Lesson6用の要件を追加します。
Lesson6: 「t分布関数を0.0からxの範囲で積分した結果がpとなるようなxを求める」
● プログラム仕様スクリプト
ASGKIT PROG6を参照のこと
● 実行結果
正しい積分結果値よりxをを絞り込みながら調整しつつ、シンプソン法の最適幅、積分結果、t分布関数値を得る
OK! [W=2] Result P: 0.0 NG! [W=2] 0.062143 NG! [W=4] 0.000017 OK! [W=8] Result P: 0.178335 NG! [W=2] 0.116564 NG! [W=4] 0.000061 OK! [W=8] Result P: 0.31305 NG! [W=2] 0.145412 NG! [W=4] 0.00164 OK! [W=8] Result P: 0.396 NG! [W=2] 0.139119 NG! [W=4] 0.008551 NG! [W=8] 0.000075 OK! [W=16] Result P: 0.441942 NG! [W=2] 0.10295 NG! [W=4] 0.021035 NG! [W=8] 0.000398 OK! [W=16] Result P: 0.466616 NG! [W=2] 0.048413 NG! [W=4] 0.035425 NG! [W=8] 0.001493 OK! [W=16] Result P: 0.480029 NG! [W=2] 0.014094 NG! [W=4] 0.047057 NG! [W=8] 0.003926 NG! [W=16] 0.00001 OK! [W=32] Result P: 0.487552 NG! [W=2] 0.077696 NG! [W=4] 0.052655 NG! [W=8] 0.007991 NG! [W=16] 0.00004 OK! [W=32] Result P: 0.491935 NG! [W=2] 0.13875 NG! [W=4] 0.050897 NG! [W=8] 0.013594 NG! [W=16] 0.000132 OK! [W=32] Result P: 0.494589 NG! [W=2] 0.195789 NG! [W=4] 0.041945 NG! [W=8] 0.020339 NG! [W=16] 0.000344 OK! [W=32] Result P: 0.496255 NG! [W=2] 0.13875 NG! [W=4] 0.050897 NG! [W=8] 0.013594 NG! [W=16] 0.000132 OK! [W=32] Result P: 0.494589 NG! [W=2] 0.167808 NG! [W=4] 0.04727 NG! [W=8] 0.016859 NG! [W=16] 0.000218 OK! [W=32] Result P: 0.495514 NG! [W=2] 0.153413 NG! [W=4] 0.049303 NG! [W=8] 0.015197 NG! [W=16] 0.000171 OK! [W=32] Result P: 0.495078 NG! [W=2] 0.146114 NG! [W=4] 0.050156 NG! [W=8] 0.014388 NG! [W=16] 0.000151 OK! [W=32] Result P: 0.49484 NG! [W=2] 0.149772 NG! [W=4] 0.049743 NG! [W=8] 0.01479 NG! [W=16] 0.000161 OK! [W=32] Result P: 0.494961 NG! [W=2] 0.151592 NG! [W=4] 0.049525 NG! [W=8] 0.014993 NG! [W=16] 0.000165 OK! [W=32] Result P: 0.49502 NG! [W=2] 0.150681 NG! [W=4] 0.049634 NG! [W=8] 0.014892 NG! [W=16] 0.000162 OK! [W=32] Result P: 0.49499 NG! [W=2] 0.151136 NG! [W=4] 0.049578 NG! [W=8] 0.014943 NG! [W=16] 0.000164 OK! [W=32] Result P: 0.495005 NG! [W=2] 0.150912 NG! [W=4] 0.049609 NG! [W=8] 0.014916 NG! [W=16] 0.000164 OK! [W=32] Result P: 0.494998 NG! [W=2] 0.151025 NG! [W=4] 0.049595 NG! [W=8] 0.014929 NG! [W=16] 0.000163 OK! [W=32] Result P: 0.495001 NG! [W=2] 0.150967 NG! [W=4] 0.049602 NG! [W=8] 0.014923 NG! [W=16] 0.000163 OK! [W=32] Result P: 0.495 Result X: 4.60400390625
● ソースコード
import java.math.BigDecimal; import java.text.DecimalFormat; /** * PSP Lesson6: * t分布関数を0.0からxの範囲で積分した結果がpとなるようなxを求める */ public class Program6 { private double x; private int dof; private double p; private double d; private int W; private int initW; private double E; private int checkF1; private int checkF2; private long adjustCount; public Program6() { initW = 1; W = initW; E = 0.00001; d = 0.5; checkF1 = 0; checkF2 = 0; adjustCount = 0; } public static void main(String[] args) { Program6 pg6 = new Program6(); Double doubleArray[] = new Double[2]; // 実行時引数からx(trial value), dof, pを取得する pg6.x = Double.parseDouble(args[0]); pg6.dof = Integer.parseInt(args[1]); pg6.p = Double.parseDouble(args[2]); boolean loopF = true; while (loopF) { for (int i = 0; i < 2; i++) { doubleArray[i] = (double) 0; // wを計算する double w = pg6.x / pg6.W; for (int j = 0; j <= pg6.W; j++) { // p2を計算する double p2 = 1 + (Math.pow((double) w * j, 2) / pg6.dof); p2 = pg6.changeScale(p2, 6); // p3を計算する double p3 = Math.pow(p2, -(double) (pg6.dof + 1) / 2); p3 = pg6.changeScale(p3, 6); // p4を計算する double p4 = pg6.calcP4(p3, pg6.dof); p4 = pg6.changeScale(p4, 6); // p5を計算する double p5 = p3 * p4; p5 = pg6.changeScale(p5, 6); // p6を計算する double p6 = pg6.calcP6(w, p5, j); doubleArray[i] += p6; } doubleArray[i] = pg6.changeScale(doubleArray[i], 6); if (i == 0) { pg6.W = pg6.W * 2; } else { if ((doubleArray[i - 1] - doubleArray[i]) < pg6.E && (doubleArray[i] - doubleArray[i - 1]) < pg6.E) { System.out.println("OK! [W=" + pg6.W + "]"); System.out.println("Result P: " + doubleArray[i]); if (doubleArray[i] != pg6.p) { pg6.adjustD(doubleArray[i]); pg6.x += pg6.d; pg6.W = pg6.initW; i = -1; } else { System.out.println("Result X: " + pg6.x); loopF = false; } } else { if (doubleArray[i - 1] > doubleArray[i]) { System.out.println("NG! [W=" + pg6.W + "] " + new DecimalFormat("0.######") .format(doubleArray[i - 1] - doubleArray[i])); } else { System.out.println("NG! [W=" + pg6.W + "] " + new DecimalFormat("0.######") .format(doubleArray[i] - doubleArray[i - 1])); } // 結果保持 doubleArray[i - 1] = doubleArray[i]; // シンプソン法適用時の幅拡張 pg6.W = pg6.W * 2; i = 0; } } } } } /** * dを調整する * */ private void adjustD(Double result) { adjustCount += 1; double w = (double) (result - p); if (d < 0) { d = -d; } if (adjustCount == 1) { if (w > 0) { checkF1 = -1; checkF2 = -1; } else { checkF1 = 1; checkF2 = 1; } } // 正しい積分結果値(P)を通り越えるまでは、0.5ポイント単位で増減させる // 通過後は、より詳細に増減させて積分結果値(P)に近づくように調整する if (checkF1 == checkF2) { if (w > 0) { d = -0.5; checkF2 = -1; } else { d = 0.5; checkF2 = 1; } } else { if (w > 0) { d /= 2; d = -d; } else { d /= 2; } } } /** * p4を計算する * */ private Double calcP4(Double p3, int dof) { double base = fact(((double) (this.dof + 1) / 2) - 1); double frac = fact((double) this.dof / 2 - 1.0); return base / (Math.pow(this.dof * Math.PI, 0.5) * frac); } /** * p6を計算する * */ private Double calcP6(Double w, Double p5, int i) { int multi = 0; if (i == 0 || i == this.W) { multi = 1; // 偶数の場合はmulti = 2 } else if (i % 2 == 0) { multi = 2; // 奇数の場合はmulti = 4 } else { multi = 4; } return (double) (w / 3) * multi * p5; } /** * 階乗計算 * */ private double fact(double i) { if (i == 1) { return 1; } else { if (i == 0) { return i; } else if (i < 1) { return i * Math.sqrt(Math.PI); } else { // 再帰呼び出しする return i * fact(i - 1); } } } /** * 四捨五入を行う * */ private double changeScale(double val, int i) { BigDecimal bd = new BigDecimal(val); return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
Lesson5: 「シンプソン法を使用して、t分布関数を数値的に分析する」
● プログラム仕様スクリプト
ASGKIT PROG5を参照のこと
● 実行結果
シンプソン法の幅をより詳細に設定していき、差分が0.00001以下であれば適切な幅とみなし終了する。
NG! [W=2] 0.094385 NG! [W=4] 0.02666 NG! [W=8] 0.000162 OK! [W=16] Result: 0.494999
● ソースコード
import java.math.BigDecimal; import java.text.DecimalFormat; /** * PSP Lesson5: * シンプソン法を使用して、t分布関数を数値的に分析する */ public class Program5 { private double x; private int dof; private int W; private double E; public static void main(String[] args) { Program5 pg5 = new Program5(); Double doubleArray[] = new Double[2]; // 実行時引数からx, dof, Wを取得する pg5.x = Double.parseDouble(args[0]); pg5.dof = Integer.parseInt(args[1]); pg5.W = Integer.parseInt(args[2]); pg5.E = 0.00001; for (int i = 0; i < 2; i++) { doubleArray[i] = (double) 0; // wを計算する double w = pg5.x / pg5.W; for (int j = 0; j <= pg5.W; j++) { // p2を計算する double p2 = 1 + (Math.pow((double) w * j, 2) / pg5.dof); p2 = pg5.changeScale(p2, 6); // p3を計算する double p3 = Math.pow(p2, -(double) (pg5.dof + 1) / 2); p3 = pg5.changeScale(p3, 6); // p4を計算する double p4 = pg5.calcP4(p3, pg5.dof); p4 = pg5.changeScale(p4, 6); // p5を計算する double p5 = p3 * p4; p5 = pg5.changeScale(p5, 6); // p6を計算する double p6 = pg5.calcP6(w, p5, j); doubleArray[i] += p6; } doubleArray[i] = pg5.changeScale(doubleArray[i], 6); if (i == 0) { pg5.W = pg5.W * 2; } else { if ((doubleArray[i-1] - doubleArray[i]) < pg5.E && (doubleArray[i] - doubleArray[i-1]) < pg5.E) { System.out.println("OK! [W=" + pg5.W + "]"); System.out.println("Result: " + doubleArray[i]); } else { if (doubleArray[i-1] > doubleArray[i]) { System.out .println("NG! [W=" + pg5.W + "] " + new DecimalFormat("0.######") .format(doubleArray[i-1] - doubleArray[i])); } else { System.out .println("NG! [W=" + pg5.W + "] " + new DecimalFormat("0.######") .format(doubleArray[i] - doubleArray[i-1])); } // 結果保持 doubleArray[i - 1] = doubleArray[i]; // シンプソン法適用時の幅拡張 pg5.W = pg5.W * 2; i = 0; } } } } /** * p4を計算する * */ private Double calcP4(Double p3, int dof) { double base = fact(((double) (this.dof + 1) / 2) - 1); double frac = fact((double) this.dof / 2 - 1.0); return base / (Math.pow(this.dof * Math.PI, 0.5) * frac); } /** * p6を計算する * */ private Double calcP6(Double w, Double p5, int i) { int multi = 0; if (i == 0 || i == this.W) { multi = 1; // 偶数の場合はmulti = 2 } else if (i % 2 == 0) { multi = 2; // 奇数の場合はmulti = 4 } else { multi = 4; } return (double) (w / 3) * multi * p5; } /** * 階乗計算 * */ private double fact(double i) { if (i == 1) { return 1; } else { if (i == 0) { return i; } else if (i < 1) { return i * Math.sqrt(Math.PI); } else { // 再帰呼び出しする return i * fact(i - 1); } } } /** * 四捨五入を行う * */ private double changeScale(double val, int i) { BigDecimal bd = new BigDecimal(val); return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
Lesson4: 「ファイルから数値データのリストを読み込み、相対規模表を作成する」
● プログラム仕様スクリプト
ASGKIT PROG4を参照のこと
● 実行結果
シンプソン法の幅をより詳細に設定していき、差分が0.00001以下であれば適切な幅とみなし終了する。
Very Small: 6.3375 Small: 8.4393 Medium: 11.2381 Large: 14.965 Very Large 19.928
● ソースコード
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.util.Iterator; import java.util.LinkedList; /** * PSP Lesson4: * ファイルから数値データのリストを読み込み、相対規模表を作成する */ public class Program4 { private int size; private LinkedList<Double> linkArray; private LinkedList<Double> logArray; private double vs; private double s; private double m; private double l; private double vl; public static void main(String[] args) { Program4 pg4 = new Program4(); try { File f = new File(args[0]); FileInputStream fi = new FileInputStream(f); BufferedReader br = new BufferedReader(new InputStreamReader(fi, "UTF-8")); LinkedList<Double> linkData1 = new LinkedList<Double>(); LinkedList<Double> linkData2 = new LinkedList<Double>(); String line; while ((line = br.readLine()) != null) { String[] strrec = line.split("t"); linkData1.add(Double.parseDouble(strrec[1])); linkData2.add(Double.parseDouble(strrec[2])); } pg4.size = linkData1.size(); // アイテム当たりの規模を計算する pg4.linkArray = new LinkedList<Double>(); pg4.calcVol(linkData1, linkData2); // アイテム毎のln(x)を計算する pg4.logArray = new LinkedList<Double>(); Double lnSum = pg4.calcLn(); // ln(x)の平均値を計算する Double lnAvg = pg4.calcLnAvg(lnSum); // 平均からの対数値の分散を計算する double lnPowSum = pg4.calcSumPow(lnAvg); // 標準偏差を計算する double lnDeviation = pg4.calcDeviation(lnPowSum); // 規模範囲を計算する pg4.calcScale(lnAvg, lnDeviation); System.out.println("Very Small: " + pg4.vs); System.out.println("Small: " + pg4.s); System.out.println("Medium: " + pg4.m); System.out.println("Large: " + pg4.l); System.out.println("Very Large " + pg4.vl); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * アイテム当たりの規模を計算する * */ private void calcVol(LinkedList<Double> linkData1, LinkedList<Double> linkData2) { double i = 0; double j = 0; while (linkData1.size() != 0 || linkData2.size() != 0) { i = linkData1.pop(); j = linkData2.pop(); this.linkArray.add(this.changeScale(i / j, 4)); } } /** * アイテム毎のln(x)を計算する * */ private Double calcLn() { double j = 0; double logSum = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { j = this.changeScale( Math.log(Double.parseDouble(i.next().toString())), 6); this.logArray.add(j); // 合計値も計算する logSum += j; } return this.changeScale(logSum, 5); } /** * ln(x)の平均値を計算する * */ private Double calcLnAvg(Double sum) { return this.changeScale(sum / size, 6); } /** * 平均からの対数値の分散を計算する * */ private Double calcSumPow(double calclnAvg) { double sum = 0; for (Iterator i = logArray.iterator(); i.hasNext();) { sum = sum + Math.pow(Double.parseDouble(i.next().toString()) - calclnAvg, 2); } return this.changeScale(sum, 6); } /** * lnの標準偏差を計算する * */ private Double calcDeviation(Double sum) { return this.changeScale(Math.sqrt(sum / (size - 1)), 6); } /** * 規模範囲を計算する * */ private void calcScale(double lnAvg, double deviation) { vs = this.changeScale(Math.exp(lnAvg - (2 * deviation)), 4); s = this.changeScale(Math.exp(lnAvg - deviation), 4); m = this.changeScale(Math.exp(lnAvg), 4); l = this.changeScale(Math.exp(lnAvg + deviation), 4); vl = this.changeScale(Math.exp(lnAvg + (2 * deviation)), 4); } /** * 四捨五入を行う * */ private double changeScale(double val, int i) { BigDecimal bd = new BigDecimal(val); return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
Lesson1, Lesson3に続いて、進めていきます。
各回のスクリプトはSoftware Engineering Instituteよりダウンロード可能です。
Lesson3: 「ファイルから数値データのリストを読み込み、相関係数と線形回帰パラメータを計算し、時間等の見積を行う」
● プログラム仕様スクリプト
ASGKIT PROG3を参照のこと
● 実行結果
β0: -22.54 β1: 1.7279 r: 0.9545 r2: 0.9111 y: 644.4294
● ソースコード
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.util.Iterator; import java.util.LinkedList; /** * PSP Lesson3: * ファイルから数値データのリストを読み込み、相関係数と線形回帰パラメータを計算し、時間等の見積を行う */ public class Program3 { private int size; private double sumData1; private double sumData2; private double avgData1; private double avgData2; private double powData1; private double powData2; private double sumMulti; private double beta0; private double beta1; private double r; private double r2; private double y; public static void main(String[] args) { Program3 pg3 = new Program3(); try { File f = new File(args[0]); FileInputStream fi = new FileInputStream(f); BufferedReader br = new BufferedReader(new InputStreamReader(fi, "UTF-8")); // 分析対象となるデータ行を指定する int data1 = Integer.parseInt(args[1]); int data2 = Integer.parseInt(args[2]); // 分析対象のLOC long num = Long.parseLong(args[3]); LinkedList<Double> linkData1 = new LinkedList<Double>(); LinkedList<Double> linkData2 = new LinkedList<Double>(); String line; while ((line = br.readLine()) != null) { String[] strrec = line.split("t"); linkData1.add(Double.parseDouble(strrec[data1 - 1])); linkData2.add(Double.parseDouble(strrec[data2 - 1])); } pg3.size = linkData1.size(); // 合計値を計算する pg3.sumData1 = pg3.calcSum(linkData1); pg3.sumData2 = pg3.calcSum(linkData2); // 合計値から平均値を計算する pg3.avgData1 = pg3.calcAvg(pg3.sumData1); pg3.avgData2 = pg3.calcAvg(pg3.sumData2); // X, Yの二乗の合計値、X*Yの合計値を計算する pg3.powData1 = pg3.calcSumPow(linkData1); pg3.powData2 = pg3.calcSumPow(linkData2); pg3.sumMulti = pg3.calcSumMulti(linkData1, linkData2); // β1 を計算する pg3.beta1 = pg3.calcBeta1(); // β0 を計算する pg3.beta0 = pg3.calcBeta0(); // R, R2 を計算する pg3.r = pg3.calcR(); pg3.r2 = pg3.calcR2(); // 最終的な結果を得る pg3.y = pg3.calcY(num); System.out.println("β0: " + pg3.beta0); System.out.println("β1: " + pg3.beta1); System.out.println("r: " + pg3.r); System.out.println("r2: " + pg3.r2); System.out.println("y: " + pg3.y); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 合計値を計算する * */ private Double calcSum(LinkedList<Double> linkArray) { double sum = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { sum = sum + Double.parseDouble(i.next().toString()); } return sum; } /** * 平均値を計算する * */ private Double calcAvg(Double sum) { return this.changeScale(sum / size, 2); } /** * X, Yの2乗合計値を計算する * */ private Double calcSumPow(LinkedList<Double> linkArray) { double sum = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { sum = sum + Math.pow(Double.parseDouble(i.next().toString()), 2); } return sum; } /** * X*Yの合計値を計算する * */ private Double calcSumMulti(LinkedList<Double> linkData1, LinkedList<Double> linkData2) { double sum = 0; double i = 0; double j = 0; while (linkData1.size() != 0 || linkData2.size() != 0) { i = linkData1.pop(); j = linkData2.pop(); sum = sum + (i * j); } return sum; } /** * β1 を計算する * */ private Double calcBeta1() { double num1 = (sumMulti - (size * avgData1 * avgData2)); double num2 = (powData1 - (size * avgData1 * avgData1)); return this.changeScale(num1 / num2, 4); } /** * β0 を計算する * */ private Double calcBeta0() { return this.changeScale(avgData2 - (beta1 * avgData1), 2); } /** * R を計算する * */ private Double calcR() { double num1 = (size * sumMulti) - (sumData1 * sumData2); double num21 = (size * powData1) - (sumData1 * sumData1); double num22 = (size * powData2) - (sumData2 * sumData2); return this.changeScale(num1 / (Math.sqrt(num21 * num22)), 4); } /** * R2 を計算する * */ private Double calcR2() { return this.changeScale(r * r, 4); } /** * Y を計算する * */ private Double calcY(long num) { return this.changeScale(beta0 + beta1 * num, 4); } /** * 四捨五入を行う * */ private double changeScale(double val, int i) { BigDecimal bd = new BigDecimal(val); return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
前回に続いて、Lesson2を実施しました。
分析結果は特に載せていませんが、簡単なプログラムでもバグがあったり、結構時間を食ったりしました。
なお、PSPではコーディングに凝り過ぎる必要は無いと明記していますので、大まかな感じでOKです。
但し、コーディングフェーズを終了する際は要件が確実に含まれているか、コードエラーがないかなどのチェックを設けてる必要があります。
Lesson2: 「ソースファイルの行数、クラ数数、メソッド数をカウントする」
● プログラム仕様スクリプト
ASGKIT PROG2を参照のこと
● 実行結果
Part Name: Program3 Number of Items: 11 Part Size: 112 Total Size: 122
● ソースコード
import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.io.StreamTokenizer; import java.util.ArrayList; /** * PSP Lesson2: * ソフトウェア開発プロセス特論 演習1 Date 2011/10/13 ソースファイルの行数、クラス数、メソッド数をカウントする */ public class Program2 extends StreamTokenizer { private Boolean wordBool = false; private String beforeWord = null; private String className = null; private int lineSize = 0; private int totalSize = 0; private int methodCnt = 0; ArrayList<String> classArray = new ArrayList<String>(); ArrayList<String> methodArray = new ArrayList<String>(); public Program2(Reader r) { super(r); this.eolIsSignificant(true); // 改行コードを検出する this.slashStarComments(true); // /* */ 型のコメント処理を有効にする this.slashSlashComments(true); // //型コメントの処理を有効にする this.wordChars('_', '_'); // 英数字の他に識別子に使える文字の定義 this.parseNumbers(); // 数値リテラルを解析する classArray.add("class"); classArray.add("interface"); methodArray.add("void"); } public static void main(String[] args) { Reader reader = null; try { reader = new BufferedReader(new FileReader(new File(args[0]))); Program2 pg2 = new Program2(reader); pg2.checkCnt(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } void checkCnt() throws IOException { while (this.nextToken() != TT_EOF) { switch (this.ttype) { case TT_EOL: // 改行コードの検出 if (this.wordBool == true) { this.lineSize++; this.wordBool = false; } this.beforeWord = "r"; break; case TT_WORD: // 語の検出(キーワードを含む) this.wordBool = true; if (this.beforeWord != null) { // method件数をカウントする (コンストラクタは対応出来ていない) if (this.methodArray.contains(this.sval) || this.beforeWord.equals("return")) { this.methodCnt++; } // class名を取得する, 複数class対応 if (this.classArray.contains(this.beforeWord)) { this.className = this.sval; if (this.totalSize > 0) { this.print(); } this.initialize(); } } this.beforeWord = this.sval; break; default: // その他のトークン {}()[]+-=*/<>,;: this.wordBool = true; this.beforeWord = "symbol"; break; } } // 最終行に改行が無いケースの対応 if (this.beforeWord.equals("symbol")) { this.lineSize++; } this.totalSize += this.lineSize; this.print(); System.out.println("Total Size: " + this.totalSize); } private void print() { System.out.println("Part Name: " + this.className); System.out.println("Number of Items: " + this.methodCnt); System.out.println("Part Size: " + this.lineSize); } private void initialize() { this.totalSize += this.lineSize; this.lineSize = 0; this.methodCnt = 0; } }
PSP: Personal Software Process とは、ソフトウェア開発効率の向上の為に、個人毎のソフトウェア開発プロセスを見直しする枠組みです。
PSPはグローバルに多くの大学やソフトウェア開発会社で適用しており、マイクロソフトやボーイングでも使用しているそうです。
個々がPSPによって、自分の開発時の癖やパフォーマンスを把握・改善を行い、開発効率向上を行います。
最終的には全チームメンバがPSPを実施し、TSP:Team Software Process へと発展させます。
今回はPSPガイドブック ソフトウェアエンジニア自己改善 (IT Architects’ Archive)に沿いながら、8本ほどのプログラムを開発し、開発プロセスの見直しをしました。
各プログラムは小さいものばかりで、コーディング自体は2時間程あればコーディング出来ると思います。
今回使用する言語はJAVAを選択しましたが、PHPやPascal、Cでも何でも大丈夫です。
但し、狙いはコーディングではなく、実装前の全体計画や設計、テスト、バグの傾向等を含めた事後分析と多岐に俯瞰する必要があります。
なお、各回のスクリプトはSoftware Engineering Instituteよりダウンロード可能です。
Lesson1: 「ファイルから数値データのリストを読み込み、データの平均値と標準偏差を計算する」
● プログラム仕様スクリプト
ASGKIT PROG1を参照のこと
● 実行結果
Average: 60.32 Standard Deviation: 62.26
● ソースコード
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; import java.util.Iterator; import java.util.LinkedList; /** * PSP Lesson1: * ファイルから数値データのリストを読み込み、データの平均値と標準偏差を計算する */ public class Program1 { private LinkedList<Double> linkArray; private double resultAvg; private double resultStgDeviation; public static void main(String[] args) { Program1 pg1 = new Program1(); try { File f = new File(args[0]); FileInputStream fi = new FileInputStream(f); BufferedReader br = new BufferedReader(new InputStreamReader(fi, "UTF-8")); pg1.linkArray = new LinkedList<Double>(); String line; while ((line = br.readLine()) != null) { pg1.linkArray.add(Double.parseDouble(line)); } pg1.calcAvg(); pg1.calcStgDeviation(); System.out.println("Average: " + pg1.resultAvg); System.out.println("Standard Deviation: " + pg1.resultStgDeviation); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } /** * 平均値を計算する * */ private void calcAvg() { double avg = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { avg = avg + Double.parseDouble(i.next().toString()); } resultAvg = this.changeScale(avg / linkArray.size(), 2); } /** * 標準偏差を計算する * */ private void calcStgDeviation() { // 1. データ毎に平均値との差分を2乗し合計値を取る // 2. 合計値をデータ個数-1で除算し、平方根を計算する double wkStg = 0; for (Iterator i = linkArray.iterator(); i.hasNext();) { wkStg = wkStg + Math.pow(Double.parseDouble(i.next().toString()) - resultAvg, 2); } resultStgDeviation = this.changeScale( Math.sqrt(wkStg / (linkArray.size() - 1)), 2); } /** * 四捨五入を行う * */ private double changeScale(double val, int i) { BigDecimal bd = new BigDecimal(val); return bd.setScale(i, BigDecimal.ROUND_HALF_UP).doubleValue(); } }
twitter API と Google Maps API のmashupサービス twigeo を作りました。
これだけだとありきたりの為、最近話題に上がるWebSocketを使用し、
“ほぼ”リアルタイムにServer => Clientへtweetをpushするようにしています。
誰得なサービスか分からないのですが、、皆さん様々な場所でtweetされているのですね。
WebSocketを実現する為にPHP WebSocketを使用させてもらいました。
PHP WebSocketはチャットのような複数クライアント間でメッセージのやり取りを行いやすいように
設計がされていますが、本サービスではクライアント間のやり取りはなく、
一方的にサーバ側からpushするのみとなりますので、少し手を入れる必要がありました。
また、twitter StreamingAPIを使用する際にOAuthが必要ですので、tmhOAuthを使用しています。
なお、現状WebSocketをサポートしているブラウザは、Chrome, Safariとなります。
Firefoxは次バージョンからサポートされるようです。
現在対応出来ていない仕様やバグは以下です。
いづれも軽微な対応で済みそうですので、暇をみて対応したいと思います。
Javaでsocket機能を使用した非常に簡易なネットワークプログラムを作りました。
ありきたりですが、Clientから郵便番号をリクエストすると、Serverから対応する住所情報をレスポンスします。
郵便データは、日本郵便 郵便データダウンロードから事前取得し、Server起動時に読み込むような非常に簡易なプログラムです。
1. Server Code
import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintStream; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.HashMap; public class OriginalServer { public HashMap<String, String> map; private String file; public OriginalServer(String file) { this.file = file; } public void read() { try { File f = new File(file); byte[] b = new byte[(int) f.length()]; FileInputStream fi = null; fi = new FileInputStream(f); fi.read(b); String str = new String(b); String[] strrec = str.split("n"); System.out.println("To be prepared....."); String[] workArray; map = new HashMap<String, String>(); for (int i = 0; i < strrec.length; i++) { workArray = strrec[i].split(","); map.put(workArray[2].replace(""", ""), workArray[6].replace(""", "") + workArray[7].replace(""", "") + workArray[8].replace(""", "")); } System.out.println("Wait for request....."); } catch (FileNotFoundException e) { System.out.println("File Not Found Error"); System.exit(-1); } catch (IOException e) { System.out.println("File Read Error"); System.exit(-1); } } public static void main(String args[]) { try { ServerSocket serverSocket = new ServerSocket( Integer.parseInt(args[0])); OriginalServer os = new OriginalServer(args[1]); // read address-File os.read(); while (true) { Socket socket = serverSocket.accept(); BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream())); String post; while ((post = reader.readLine()) != null) { // readline() + Empty() = Start HTTP-BODY if (post.isEmpty()) { post = reader.readLine().replace("-", ""); break; } } String addr; if (os.map.containsKey(post)) { addr = "〒" + post.substring(0, 3) + "-" + post.substring(3, post.length()) + " -> " + os.map.get(post) + "n"; } else { addr = "There is no postal code.n"; } PrintStream writer = new PrintStream(socket.getOutputStream()); writer.print("HTTP/1.1 200 OKn"); writer.print("Connection: closen"); writer.print("Content-Length: " + addr.length() + "n"); writer.print("Content-Type:text/plainnn"); writer.print(addr); writer.flush(); writer.close(); reader.close(); socket.close(); } } catch (SocketException e) { System.out.println("Socket Error"); System.exit(-1); } catch (IOException e) { System.out.println("IO Error"); System.exit(-1); } } }
2. Client Code
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; public class OriginalClient { public static void main(String args[]) { try { Socket socket = new Socket(args[0], Integer.parseInt(args[1])); BufferedReader reader = new BufferedReader(new InputStreamReader( socket.getInputStream())); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream())); writer.write("GET / HTTP/1.1rn"); writer.write("Host: " + args[0] + ":" + args[1] + "rn"); writer.write("Connection: closern"); writer.write("rn"); writer.write(args[2] + "rn"); writer.flush(); String line; while ((line = reader.readLine()) != null) { if (line.isEmpty()) { System.out.println(reader.readLine()); } } writer.close(); reader.close(); socket.close(); } catch (UnknownHostException e) { System.out.println("Host not found"); System.exit(-1); } catch (SocketException e) { } catch (IOException e) { System.out.println("IO error"); System.exit(-1); } } }
3. 実行結果: Server
C:hogenetworkPog > java OriginalServer 12345 KEN_ALL.CSV To be Prepared.... Wait for request....
4. 実行結果: Client
C:hogenetworkPog > java OriginalClient localhost 12345 1400011 〒140-0011 -> 東京都品川区東大井
以上です。
=====
1. 環境 及び シナリオ
実行環境は以下である。
Platform: Linux(CentOS 5.6) @sakura VPS
Memory: 512 MB
CPU: 仮想2Core
実行・考察を行うプログラミング言語はLAMP構成として通常扱われるスクリプト言語の以下を使用する。
・Perl
・Python2.4
・PHP5.2
No. | 演算 | 概要 |
1 | i = i + 1 | 加算 |
2 | i = i – 1 | 減算 |
3 | i = i * i | 乗算 |
4 | i = i / 3 | 除算 |
5 | i = i % 3 | 剰余 |
6 | s = “Hello,World!” | 文字列代入 |
7 | i = len(string) | 文字列数 |
8 | s = random() | ランダム(指定なし) |
9 | s = random(1, 100) | ランダム(指定あり) |
10 | IF (s = “Hello,World!) | 文字列判断 |
11 | IF (i = 5) | 数値判断 |
12 | IF (i = TRUE) | Bool値判断 |
13 | IF文ネスト | 条件分岐ネスト |
14 | Switch文 | ループ(Switch) |
15 | While文 | ループ(While) |
16 | For文 | ループ(For) |
17 | Foreach文 | ループ(Foreach) |
18 | print “Hello, World!” | 文字列出力 |
19 | 関数呼び出し | 関数 |
20 | File Open, Close | オープン, クローズ |
21 | File read | 1行毎の読み込み |
22 | File Write | 1行毎の書き込み |
実行時のループ回数は、10000000回ループさせる。但し、実行に時間の掛かり過ぎるシナリオ等に関しては回数を減らす事とする。例外シナリオは以下である。
No.17: 100000回 PHPにおいて例外が発生する為。
No.18: 10000回 標準出力に文字列出力する為、処理
対象のprint文の計測が行いづらい為。
本レポートで実行対象となるプログラミング言語はそれぞれ実績のある言語であり、Webサービス開発においてよく使用されている。私はPerl, PHPの経験はあるがPythonは今回が初めての使用となる。
Perlは全体的に処理が速く、ファイル操作に長けているイメージがある為、全般的にPerlのパフォーマンスが良いと考えている。
PHPは全体的に処理が遅いイメージがある。特にrandom()やファイル操作が遅いイメージがある。
Pythonを使用するのは今回が初めてである為、あまりイメージが出来ないが、処理速度的にはPerlと同様の計測値となると予測する。
よって、 ほぼ全シナリオでPerl < Python < PHP の順番で処理コストが高くなると考える。
以下の要領で実施を行う。
言語毎の実行時間計測は以下のように行う。
以下では、各言語のシナリオ1のプログラムである。
Perl
====
#!/usr/bin/perl
use Time::HiRes;
my $starttime = Time::HiRes::time;
for($i=0; $i<10000000; $i++) {
}
my $looptime = Time::HiRes::time – $starttime;
my $starttime = Time::HiRes::time;
my $j=0;
for(my $i=0; $i<10000000; $i++) {
$j = $j + 1;
}
print Time::HiRes::time – $starttime – $looptime . “n”;
====
Python
====
#!/usr/bin/python
import time
starttime = time.clock()
for i in range(10000000):
pass
looptime = time.clock() – starttime
starttime = time.clock()
j = 0
for i in range(10000000):
j = j + 1
print time.clock() – starttime – looptime
====
PHP
====
<?php
$starttime = microtime(true);
for($i = 0; $i<10000000; $i++) {
}
$looptime = microtime(true) – $starttime;
$starttime = microtime(true);
$j=0;
for($i=0; $i<10000000; $i++) {
$j = $j + 1;
}
echo microtime(true) – $starttime – $looptime . “n”;
?>
====
2. 実行
以下が実行結果である。シナリオ毎に最も良いタイムに橙色にしている。また、特徴的な値となった結果は赤色とし、次項以降で考察を行う。なお、単位は全て”秒”である。
演算 | Perl | Python | PHP |
i = i + 1 | 0.8 | 1.04 | 0.44 |
i = i – 1 | 0.81 | 1.13 | 0.45 |
i = i * i | 0.82 | 1.3 | 0.46 |
i = i / 3 | 0.74 | 1.56 | 0.73 |
i = i % 3 | 0.84 | 1.47 | 0.5 |
s = “Hello,World!” | 0.87 | 0.65 | 0.93 |
i = len(string) | 1.12 | 1.64 | 1.8 |
s = random() | 0.22 | 2.05 | 1.73 |
s = random(1, 100) | 0.95 | 27.65 | 2.86 |
IF (s = “Hello,World!) | 0.89 | 0.51 | 0.78 |
IF (i = 5) | 0.94 | 0.49 | 0.44 |
IF (i = TRUE) | 0.97 | 1.08 | 0.37 |
IF文ネスト | 0.60 | 2.05 | 1.67 |
Switch文 | 251.03 | 2.81 | 2.01 |
While文 | 1.13 | 1.64 | 0.77 |
For文 | 0.67 | 1.18 | 0.91 |
Foreach文 | 0.02 | 0.01 | 0.06 |
print “Hello, World!” | 1.2 | 0.47 | 0.96 |
関数呼び出し | 1.74 | 1.52 | 1.56 |
File Open, Close | 69.01 | 61.46 | 80.04 |
File read | 2 | 1.46 | 3.54 |
File Write | 2.02 | 4.94 | 23.18 |
3. 考察
実行前の考察では、全てのシナリオでPerlが優位だと考えていたが、結果は異なっていた。
この考察としてはPerlの時間計測用の関数において、ミリ秒単位で計測が出来なかった為、
Time::Hiresモジュールを使用した事が影響している可能性がある。
但し、Perlならではの優位性が示されているシナリオもある為、現状のまま考察を進める事とする。
Pythonのrandint()が非常に遅い結果となった。Perlと比較すると約27倍の差である。調査したところ、Perl, PHPのrand()は組み込み関数であるのに対し、Pythonのrandint()は標準モジュールでの提供であった。
また、randint()よりもchoice()を使用した方が速いとの情報があった為、変更し実行計測したところ、
27.65 → 12.36 と半分以下の実行結果となった。但し、Perl, PHPと比較するとまだ遅い結果であった。
この事からC等で記述される組み込み関数はやはり速いということが分かった。
Perlのswitch文が異常な程、遅い結果となった。これはPerlには組み込みでswitch文がない為、標準モジュールのSwitchを使用した事が影響していると考えられる。最速であったPHPと比較するとその差は100倍である。
3.1のrand()と同様に以下に組み込み機能が速いかを再認識させられた。
実行前の考察よりPHPはファイル操作が不得意であると考えていたが、その通りの結果であった。特にfopen(), fclose(), fwrite()のパフォーマンスが悪かった。
また、ファイル操作はPerlが非常に有利だと考えていたが、Pythonが健闘している。
シナリオ毎の実行結果の色付けを確認すると、PHP列に橙色が多く付いている。PHPは全般的に処理が遅いと考えていたが、結果から見ると異なっていた。以前の経験ではPHPのrandom()は遅いと感じていたが、やはりPerlと比較すると倍以上の差が出ていた。(Pythonとの比較に関しては、3.1を参照の事)
それぞれのプログラミング言語には高速化技術が存在する。例えば、Perl: mod_perl, Python: mod_wsgi, PHP: APC, eAcceralator等である。これらの技術を使用すれば、より良いパフォーマンスが出やすいが、その前段階として、言語毎の特徴を押さえた上でプログラミング言語の選択や処理ロジック, アルゴリズムを組んでいく必要がある。
=====
おわりです。
ZXing(”Zebra Crossing”)を使用したQRコード生成ツールを作成しました。
ZXingはGoogleがオープンソースで公開しているQRコード生成ライブラリです。
Androidでも使用されていますが、今回はローカル起動までとなります。
以下では、Antを使用してビルドしていますので、Antのセットアップをしておいてください。
1. ZXing セットアップ
まず、Google CodeからZXingをダウンロードします。
任意のディレクトリへ展開します。私は “C:Eclipse_programzxing-1.6” としました。
次に展開済みのフォルダ内にある READ MEファイルを開き、ビルド用のコマンドを確認します。
Please refer to the project page for more information: http://code.google.com/p/zxing/ in particular: http://code.google.com/p/zxing/wiki/GettingStarted To get started, you can try building and running the command-line client; you will need to have Apache's Ant tool installed to run this: ant -f core/build.xml ant -f javase/build.xml java -cp javase/javase.jar:core/core.jar com.google.zxing.client.j2se.CommandLineRunner [URL]
コマンドプロンプトを開き、展開をしたディレクトリへ移動し、12,13行目のAntコマンドを実行します。
C:Eclipse_programzxing-1.6>ant -f core/build.xml Buildfile: C:Eclipse_programzxing-1.6corebuild.xml clean: [delete] Deleting directory C:Eclipse_programzxing-1.6corebuild [delete] Deleting: C:Eclipse_programzxing-1.6corecore.jar build: init: compile: [mkdir] Created dir: C:Eclipse_programzxing-1.6corebuild [javac] C:Eclipse_programzxing-1.6corebuild.xml:36: warning: 'includeant runtime' was not set, defaulting to build.sysclasspath=last; set to false for re peatable builds [javac] Compiling 171 source files to C:Eclipse_programzxing-1.6corebuild [jar] Building jar: C:Eclipse_programzxing-1.6corecore.jar BUILD SUCCESSFUL Total time: 10 seconds
C:Eclipse_programzxing-1.6>ant -f javase/build.xml Buildfile: C:Eclipse_programzxing-1.6javasebuild.xml init: build: [javac] C:Eclipse_programzxing-1.6javasebuild.xml:40: warning: 'includea ntruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds BUILD SUCCESSFUL Total time: 0 seconds
最後にEclipseで使用する為に “C:Eclipse_programzxing-1.6corecore.jar” をライブラリ追加すればOKです。
2. 最終的なソースコード
何らかの文字列を入力すると、都度QRコードを生成するようになっています。
また、保存ボタン押下で生成済みのQRコードを保存します。
ちょっと怪しい部分もありますが、、ひとまず動くと思います。
import java.awt.BorderLayout; import java.awt.Color; import java.awt.Container; import java.awt.Graphics2D; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.util.Hashtable; import javax.imageio.ImageIO; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JTextField; import javax.swing.SwingUtilities; import com.google.zxing.EncodeHintType; import com.google.zxing.WriterException; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import com.google.zxing.qrcode.encoder.ByteMatrix; import com.google.zxing.qrcode.encoder.Encoder; import com.google.zxing.qrcode.encoder.QRCode; public class Url2QR extends JFrame implements ActionListener { private static Url2QR frame; private final JTextField textField; private final JButton dispButton; private final JButton saveButton; private final JLabel label; private final JPanel panelTop; private final JPanel panelBottom; private final ImageIcon icon; private final BufferedImage bufImg; private static int SIZE = 4; public Url2QR() throws WriterException { super("QRコード生成"); panelTop = new JPanel(); panelBottom = new JPanel(); textField = new JTextField(20); dispButton = new JButton("表示"); saveButton = new JButton("保存"); bufImg = barcodeWrite(); icon = new ImageIcon(bufImg); label = new JLabel(icon); Container container = getContentPane(); container.setLayout(new BorderLayout()); container.add(textField, BorderLayout.CENTER); container.add(panelTop, BorderLayout.EAST); container.add(panelBottom, BorderLayout.SOUTH); panelTop.setLayout(new BorderLayout()); panelTop.add(dispButton, BorderLayout.WEST); panelTop.add(saveButton, BorderLayout.EAST); panelBottom.setLayout(new BorderLayout()); panelBottom.add(label, BorderLayout.WEST); dispButton.setActionCommand("display"); dispButton.addActionListener(this); saveButton.setActionCommand("save"); saveButton.addActionListener(this); saveButton.setEnabled(false); KeyListener keyListener = new KeyListener() { public void keyPressed(KeyEvent keyEvent) { } public void keyReleased(KeyEvent keyEvent) { repaintIcon(); } public void keyTyped(KeyEvent keyEvent) { } }; textField.addKeyListener(keyListener); } public void actionPerformed(ActionEvent e) { if (e.getActionCommand().equals("display")) { repaintIcon(); } else if (e.getActionCommand().equals("save")) { saveQR(); } } public void repaintIcon() { try { if (textField.getText().isEmpty()) { saveButton.setEnabled(false); } else { saveButton.setEnabled(true); } label.setIcon(new ImageIcon(barcodeWrite())); } catch (WriterException e) { e.printStackTrace(); } label.setToolTipText(textField.getText()); frame.pack(); } public void saveQR() { JFileChooser filechooser = new JFileChooser(); int selected = filechooser.showSaveDialog(this); if (selected == JFileChooser.APPROVE_OPTION) { File file = filechooser.getSelectedFile(); try { ImageIO.write(barcodeWrite(), "png", file); } catch (IOException e) { System.out.println(e); } catch (WriterException e) { e.printStackTrace(); } } else if (selected == JFileChooser.CANCEL_OPTION) { } else if (selected == JFileChooser.ERROR_OPTION) { label.setText("Error"); } } public BufferedImage barcodeWrite() throws WriterException { Hashtable hints = new Hashtable(); hints.put(EncodeHintType.CHARACTER_SET, "SHIFT_JIS"); QRCode qrCode = new QRCode(); Encoder.encode(textField.getText(), ErrorCorrectionLevel.L, hints, qrCode); ByteMatrix byteMatrix = qrCode.getMatrix(); BufferedImage image = new BufferedImage(byteMatrix.getWidth() * SIZE, byteMatrix.getHeight() * SIZE, BufferedImage.TYPE_4BYTE_ABGR); if (!textField.getText().isEmpty()) { Graphics2D g2D = image.createGraphics(); for (int y = 0; y < byteMatrix.getHeight(); y++) { for (int x = 0; x < byteMatrix.getWidth(); x++) { if (byteMatrix.get(x, y) == 1) { g2D.setColor(Color.black); } else { g2D.setColor(Color.white); } g2D.fillRect(x * SIZE, y * SIZE, SIZE, SIZE); } } } return image; } public static void startGUI() throws WriterException { frame = new Url2QR(); frame.pack(); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(EXIT_ON_CLOSE); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { try { startGUI(); } catch (WriterException e) { e.printStackTrace(); } } }); } }