ISSI2013で発表しました

国立情報学研究所で開催された国際ワークショップ:社会のイノベーションを誘発する情報システムで口頭発表をさせて頂きました。
SNS版のプライバシー侵害情報分類表をベースとしたプライバシー侵害検知アプリケーションを開発し、その初お披露目となり緊張しましたが、たくさんのフィードバックをいただき、今後の展開に役立てられそうです。

発表名:Setting of Access Control by Detecting Privacy Leaks in SNS

Facebookから友人の投稿写真を取得するPythonスクリプト

プライバシー侵害の研究の一環として、FacebookからFQLを使用して友人の投稿写真を取得するPYthonスクリプトを作りました。取得した写真の取り扱いには注意してください。

https://github.com/shmachid/facebook_mining/blob/master/getPic_from_fb.py

スクリプト実行手順は以下となります。

  1. 下記のサイトにアクセスし、Facebookアプリの登録をする
  2. http://miningthesocialweb.appspot.com/
    全ての権限を与える必要はないが、スクリプトが動かなくなる可能性もあるので注意してください。
    求められた権限はそのまま許可し、不要になったら、Facebookアプリを削除すれば良いです。

  3. 権限を与えたら、query実行ページに遷移する
  4. http://miningthesocialweb.appspot.com/query

  5. ページ遷移時に表示される ACCESS_TOKEN をコピーする
  6. ディレクトリ配下の out/facebook.access_token に3のTOKENを貼り付ける
  7. プログラムを実行する
  8. 自分のコネクション数にもよるが、画像1万枚で4時間ほどかかります。

  9. 処理終了後、ディレクトリ配下に picture/ が出来るので写真を確認する
  10. 一枚ずつ見るのが面倒な場合は、 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 Views in CouchDB

前回に続いて、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 to CouchDB with tweepy and jsonpickle

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賞を受賞しました

大学院の有志チームで参加していたRICOH & Java developer challenge2011でRICOH賞をいただきました。
アプリケーション機能は非常にシンプルでしたが、顔認識技術とビジネスモデルとの組み合わせ、アプリケーションとしての完成度が評価されたのではと思います。また、水着の三愛がリコーの傘下といこともあり、画像解析 × ファッションレポート のアプリケーションが受け入れられたのもあるようです。

グランプリの北陸先端技術大学院さんも顔画像技術を使用されており、エンタープライズアプリケーションにおいてもこれらの技術は注目されているのだと感じました。
また、他チームのアイデアや実装は素晴らしく、若い方々の発想力に驚きました。もう少しブラッシュアップすればRICOHからアプリケーションとして出ても良いのではと思われるアプリケーションもあり良い刺激をもらえました。

約8ヶ月ほど、チームで活動をしてきましたが、最後に良い結果が出て良かったです。チームの皆さんありがとうございました。

RICOH & Java Developer Challenge 2011 最終選考 提出しました

大学院の有志メンバと参加しているRICOH & Java Developer Challenge 2011の最終選考に提出できました。
9月末に1次選考(Emulator編)を無事通過し、最終選考へ向けて実機上でMFPアプリケーションを開発してきました。
5月にチームを発足してから半年以上と長期に渡って活動してきましたが、ようやくシンプルながらも面白いモノが完成しました。
このプログラミングコンテストからは技術的な部分のみでなく、チーム活動の進め方という点に関しても多くの得る事あった為、参加して良かったと思います。

最終選考は2012/1/12(木)です。あと、Ustreamでも放送があるそうです。

PSP:Personal Software Process – 結果分析

自身の開発プロセス改善を目的として7回に渡りシナリオ仕様毎に、
計画立案 → 設計 → 設計レビュー → コーディング → コードレビュー → コンパイル → テスト → 事後分析
を繰り返し行いましたので、最後に結果を分析しました。

    1. 見積規模の正確さの分析

・  プログラムごとの見積規模と実績規模を比較し、見積の正確性を検証する。

【プログラム毎の見積規模と実績規模】

Program No. 見積規模(LOC) 実績規模/KLOC
1 60
2 150 79
3 164 124
4 124 104
5 144 96
6 137 152
7 248 189

全体的に見積規模と実績規模はある程度の近似値で見積が出来ていた。
通常最も避ける必要がある点は、見積規模に対して実績規模が大きく上まるケース(受注金額に対して、予定工数が上まってしまう為)であるが、そのような例は見受けられなかった。
仕様が複雑化していった後半のプログラムにおいても、大きな差異は無かった理由としては設計フェーズで仕様を完全に把握出来たことが功を奏したと推測される。

    1. 見積時間の正確さの分析

・  プログラムごとの見積時間と実績時間を比較し、見積の正確性を検証する。

【プログラム毎の見積時間と実績時間】

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. フェーズ毎の実績時間

・  フェーズ毎の実績時間を相関し、フェーズ間の関連を確認する。

【プログラム毎の実績時間】

プログラム 計画立案 設計 設計レビュー コーディング コードレビュー コンパイル テスト 事後分析
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程度がテスト時間であると定義出来そうである。

    1. 設計、コーディングで作り込まれた欠陥の型

・  設計、コーディングで作り込まれた欠陥を型ごとに分類し、グラフにより型による欠陥数の傾向を図示する。
※ 型番号に関しては、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件であったが、自分の欠陥傾向を掴む為には統合開発環境を使用せずに計測したほうが良かったかもしれない。

    1. 設計レビュー、コードレビュー、コンパイル、テストで発見した欠陥数/KLOCの傾向

・各プロセスで発見した欠陥数/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は前回のプログラムを再利用・追加していく仕様である為、前プログラムで欠陥を押さえることが出来ていれば、必然的に当該プログラムでも欠陥が減少することが確認できた。これにより、プログラムやクラスを再利用する時は、当然ではあるが、欠陥が無い状態で利用可能とすべきことを再認識できた。

    1. 設計レビュー、コードレビュー、コンパイル、テストでの欠陥除去数/KLOC

・各プロセスで除去した欠陥数/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:機能 の欠陥が大半を占めたようにコードレビューでより欠陥を除去出来た可能性がある。よって、今後はレビューにより時間を割くように意識していく。

    1. レビューしたLOCに対して欠陥除去率を分析

・設計レビュー、コードレビュー時の時間当たりのレビューLOC数に対する欠陥除去率を一覧化し、傾向を分析する。

時間 LOC数 欠陥除去数 欠陥除去率
設計レビュー 45 437 0 0.0%
コードレビュー 55 0 0.0%

コードレビュー、設計レビューにおいて、1件も欠陥を発見・除去出来ていないのは問題である。まだ実施時間が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以降は設計フェーズで仕様を正確に捉えるようにシフトすることができた。また、設計レビューやコーディングレビューで事前に欠陥を発見・修正することで、テストフェーズにおける欠陥数、大きな欠陥による修正時間の増加を押さえることが出来ると考えた。

    1. パーソナルプロセスを改善するための最も優先すべき点とその理由

【最も優先すべき点】

統一した計測方法で作業ログを残し、改善点を見つける為の振り返りを行う。

【理由】

現在の個人データは不十分 且つ、データ量が少ない為、正確な見積値として使用する事が出来ない。これを改善する為に、日々の業務や個人開発おいて、統一した計測方法で作業ログを残し、後日、改善点や傾向を把握する為の振り返りを行い、パーソナルプロセス改善を行う。

    1. 現在の作業能力と将来あるべき作業能力および改善のゴール

【現在の作業能力】
規模見積に関して、今までは仕様に対する規模推測を大きく見積過ぎていた。演習を行うことで似たような仕様(過去に経験した仕様)であれば、ある程度正確な規模が見積もれるようになっている。過去の実績時間を使用することで、全体を見通した時間が見積もれるようになったが、まだ若干のブレがある。
また、計画や設計フェーズの重要性を自分のデータを通して、実感出来ている。例えば、仕様は確実に理解することで、コーディング時間や欠陥数、欠陥に対する対応時間の短縮等の効果が大きい事を理解できている。

【将来あるべき作業能力】
まず最初に持たなければならない能力としては、時間見積の正確性である。規模見積が行えたとしても、時間見積が全く異なっていれば、見積が出来ているとは言えないからである。その為に必要なアクションとしては、今後の作業ログを確実に残していき、自分の見積時の基データを収集することである。次に必要な能力としては、レビュー能力と考えた。演習では欠陥検出は出来なかったが、欠陥傾向としてコーディングレビューが有効と判断した為、本能力を向上させることにより欠陥をある程度減少させることが可能である。
最終的なゴールとしては、自分が作業を行う為の見積ではなく、一般的な技術者が作業を行う場合の適性値を見積ることである。この為には、自分の能力と一般的な技術者の能力差を把握する必要がある。

    1. PSP演習後の感想

PSP演習を実施するにつれて、設計の重要性が再認識出来たのは非常に良い結果であった。また、いつも見積を行う際にドンブリ勘定を行なっていたが、より精度の高い見積を行うことの重要性と手法を学べた事が良かった。今後は自分の基データを収集すると共に継続的にプロセス改善を行なっていき、機会があればチームに対してTSPを適用していきたい。これにより、トラブルプロジェクト(欠陥や期間、金額など)を減少させる1要素になると感じた。

ということで、PSPの演習自体の仕様としては業務にあまり活かせない可能性はありますが、
個々のプロセスを振り返る事で自身の開発プロセスを改善する第1歩となるという点では間違いありません。

以上です。

Collatz Problem with Erlang

よく複数のプログラミングパラダイムを理解することは非常に良いことだと言われますが、
今回は関数型言語である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).

● 実行方法・結果

  1. 実行環境
  2. OS: CentOS5.6
    Ver: Erlang R12B-5.12
    Emu: Erlang emulator Ver5.6.5

  3. 実行方法・結果
  4. $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>
    


PSP:Personal Software Process – Lesson7

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();

	}

}

PSP:Personal Software Process – Lesson6

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();

    }

}

PSP:Personal Software Process – Lesson5

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();

    }

}

PSP:Personal Software Process – Lesson4

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();

	}

}

PSP:Personal Software Process – Lesson3

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();

	}

}

PSP:Personal Software Process – Lesson2

前回に続いて、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 – Lesson1

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 × Google maps with WebSocket

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は次バージョンからサポートされるようです。

現在対応出来ていない仕様やバグは以下です。

  • tagエラー
  • 初期画面で自身の位置情報が固定
  • InのTweet量に対して、Outは視認性を良くする為にSleepを入れている関係上、叙々に配信が遅れてしまう
  • いづれも軽微な対応で済みそうですので、暇をみて対応したいと思います。

    簡易Javaネットワークプログラミング

    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 -> 東京都品川区東大井
    

    以上です。

    Perl, Python, PHPのベンチマークを計測しました

    Perl, Python, PHPのベンチマークを計測しました。

    =====
    1. 環境 及び シナリオ

    1. 実行環境
    2. 実行環境は以下である。
      Platform: Linux(CentOS 5.6)  @sakura VPS
      Memory: 512 MB
      CPU: 仮想2Core

    3. プログラミング言語
    4. 実行・考察を行うプログラミング言語はLAMP構成として通常扱われるスクリプト言語の以下を使用する。
      ・Perl
      ・Python2.4
      ・PHP5.2

    5. 実施シナリオ
    6. 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文の計測が行いづらい為。

    7. 実施前考察
    8. 本レポートで実行対象となるプログラミング言語はそれぞれ実績のある言語であり、Webサービス開発においてよく使用されている。私はPerl, PHPの経験はあるがPythonは今回が初めての使用となる。
      Perlは全体的に処理が速く、ファイル操作に長けているイメージがある為、全般的にPerlのパフォーマンスが良いと考えている。
      PHPは全体的に処理が遅いイメージがある。特にrandom()やファイル操作が遅いイメージがある。
      Pythonを使用するのは今回が初めてである為、あまりイメージが出来ないが、処理速度的にはPerlと同様の計測値となると予測する。

      よって、 ほぼ全シナリオでPerl < Python < PHP の順番で処理コストが高くなると考える。

    9. 実施方法
    10. 以下の要領で実施を行う。

      1. プログラミング言語毎に22個のプログラムを作成する。
      2. 実施シナリオ毎のアルゴリズムは同一とする。実行時間計測アルゴリズムに関しては後述する。
      3. 言語によっては命令が存在しないケースがある為、その場合は代替(同等)機能を使用する。No.16のPythonにおけるFor文等。
      4. Perlに関しては、ミリ秒単位を取得する組み込み関数がない為、Time::Hiresモジュールを使用する。
      5. シナリオ毎に10回実行し、実行結果として平均値を記録する。その際のコマンドは以下ように実行する。(以下はPythonでの例)
        # loop=1; while [ $loop -le 10 ]; do loop=`expr $loop + 1`; ./check1.py >> result/check1; done;
      6. eの実行結果から平均値を出力するスクリプトを実行し、平均値を得る。平均値出力スクリプトは以下である。
        ====
        #! /usr/bin/perl
        open(IN, $ARGV[0]);
        while (<IN>) {
        $total += $_;
        $count++;
        }
        close(IN);
        print $total / $count . “n”;
        ====
    11. 実行時間計測方法とサンプルコード
    12. 言語毎の実行時間計測は以下のように行う。

      以下では、各言語のシナリオ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. 実行

    1. 実行結果
    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ならではの優位性が示されているシナリオもある為、現状のまま考察を進める事とする。

    1. randint()
    2. Pythonのrandint()が非常に遅い結果となった。Perlと比較すると約27倍の差である。調査したところ、Perl, PHPのrand()は組み込み関数であるのに対し、Pythonのrandint()は標準モジュールでの提供であった。
      また、randint()よりもchoice()を使用した方が速いとの情報があった為、変更し実行計測したところ、
      27.65 → 12.36 と半分以下の実行結果となった。但し、Perl, PHPと比較するとまだ遅い結果であった。
      この事からC等で記述される組み込み関数はやはり速いということが分かった。

    3. switch文
    4. Perlのswitch文が異常な程、遅い結果となった。これはPerlには組み込みでswitch文がない為、標準モジュールのSwitchを使用した事が影響していると考えられる。最速であったPHPと比較するとその差は100倍である。
      3.1のrand()と同様に以下に組み込み機能が速いかを再認識させられた。

    5. File操作
    6. 実行前の考察よりPHPはファイル操作が不得意であると考えていたが、その通りの結果であった。特にfopen(), fclose(), fwrite()のパフォーマンスが悪かった。
      また、ファイル操作はPerlが非常に有利だと考えていたが、Pythonが健闘している。

    7. その他
    8. シナリオ毎の実行結果の色付けを確認すると、PHP列に橙色が多く付いている。PHPは全般的に処理が遅いと考えていたが、結果から見ると異なっていた。以前の経験ではPHPのrandom()は遅いと感じていたが、やはりPerlと比較すると倍以上の差が出ていた。(Pythonとの比較に関しては、3.1を参照の事)

    9. 最後に
    10. それぞれのプログラミング言語には高速化技術が存在する。例えば、Perl: mod_perl, Python: mod_wsgi, PHP: APC, eAcceralator等である。これらの技術を使用すれば、より良いパフォーマンスが出やすいが、その前段階として、言語毎の特徴を押さえた上でプログラミング言語の選択や処理ロジック, アルゴリズムを組んでいく必要がある。

    =====

    おわりです。

    QRコード with ZXing

    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();
    				}
    			}
    		});
    	}
    
    }