2009年11月16日月曜日

次世代スーパーコンピュータが必要な理由

このブログ記事をはてなブックマークに追加

先日の事業仕分けによる次世代スーパーコンピュータ(次世代スパコン)の予算見直しで事実上の「凍結」との結論が出たことで、多くの人々から次世代スパコンについて注目が集まることになったが、情報不足のためか、一部誤解があるようだ。そこで、自分の知っている範囲で次世代スパコンについて記したいと思う。もし自分の知識が至らず間違っている場合は指摘して頂けると有り難い。

次世代スーパーコンピュータ

次世代スパコンは富士通のCPU、"Vinus" SPARC64 VIIIfxか、その後継CPUで構成する公算が高いが、このCPUはスカラ型だ。もともと、NECと日立がベクトル型のCPUを開発する予定であったが、撤退によりベクトル型とスカラ型の混成システムから、スカラ型のみのシステムに変更された。因みに、この富士通のCPU "Vinus"は現時点で世界最高速のCPUであり、国産で高速なCPUを開発できるのかという疑問も払拭できている。

ところで、この撤退により次世代スーパーコンピュータ開発について危惧する声が出たのだが、実のところ次世代スパコン開発に関わるユーザや開発者の一部では、開発がしやすくなったとして喜んでいたりする。もともとの混成システムでは、実用的に使う場合、ベクタ部はベクタ部のみの利用、スカラ部ではスカラ部のみの利用でしかパフォーマンスが出せず、これでベクタ部とスカラ部を一台に入れる意味があるのかというもっともな疑問が出ていたようだ。また、次世代スーパーコンピュータ開発実施本部のプロジェクトリーダーである渡辺氏がNEC出身であることからのあらぬ疑いもされずに済むようになったと思う。

NECと日立が撤退した理由として、百数十億円の負担が重荷になったと答えている。しかし、実際はそんな負担額では済まなかったというのが関係者間での通説だ。つまり、それ以上の巨額の開発資金を企業は自腹で負担しなくてはならなかったのだ。では何故そのような負担をしてまでも次世代スパコンに参加したかったのか。それは「世界一高速なコンピュータを作りました」という事実が企業にとって非常に大きな宣伝になるから。世間一般ではそれで世界一の技術があると見なしてくれるのだ。それに、売り上げももちろん重要だが、技術者の士気も上がることのメリットが大きい。技術者の士気はなかなか目に見えにくいものだが、そういう技術者の士気があってこそ、今日の技術立国日本に成長したのだと思う。そして、国や理研側としては実際にかかる費用よりかなり割安に開発できるのだからある意味Win-Winの関係という訳だ。

因みにスパコン開発のために必要な総費用は1,154億円(現在ではNECと日立の撤退により若干増えて1,230億円)は7年間の総費用であり、それにこの金額はスパコンで利用するアプリケーション開発や研究、センターの建設などすべてを含めた費用であり、ハードウェア作製の資金はこの中の一部が担っている。これを考えると決して高い予算ではないように思う。

次世代スパコンについてもっと詳しく知りたい場合は、理化学研究所 次世代スーパーコンピュータ開発実地本部が参考になる。

研究者からみたスーパーコンピューティング

スパコンを利用する研究者からしてみれば世界トップレベルの速いコンピュータ(CPUだけではなくメモリやネットワークのレイテンシを含む)がちゃんと利用できればそれでいいのだが、それにはまず国がちゃんと音頭を取ってくれないとそんな素敵なコンピュータは使えないわけで、国がやるなら日本のためにということで、前述の次世代スーパーコンピュータということになる。

で、本当に重要なのは次世代スパコンを使った実際の研究であり、そのためにグランドチャレンジ・アプリケーションの研究開発などのプロジェクトがある。これは、ライフサイエンス分野ナノテクノロジー分野が対象で非常に大きな役割を果たすと思われる。これらに加えて産学の研究者・技術者へのインフラとしての意味合いも強い。共同利用を積極的に推進しているのだ。

実はこれには訳があって、前回の地球シミュレータ運用の際に、アプリケーション開発が難しい、特定分野に偏っている、申請が通りにくいなどの問題点が指摘されたので、よりよく研究に利用してもらうにはどうしたら良いかという視点からこのような方針になったようなのだ。そして、すでに複数の大学や研究機関との連携をとりつつある。

という訳で、今後代替措置もなくスパコンが使えなくなったりすると産学の基礎研究、応用研究に大きなダメージが出ることは間違いないと思う。

予算「凍結」について

ここでは政治が云々、政党が云々というつもりはない。どのようにすればスーパーコンピューティングの重要性を理解してもらえるか考えてみる。

今回の事業仕分けによる次世代スパコン「凍結」の際、以下のような意見が出た。

「世界一を目指す理由は何か。2位ではだめなのか」
「一時的にトップを取る意味はどれくらいあるか」
「一番だから良いわけではない」

実際にこう思っている一般の方々もいると思う。それに対して、前述の理研のページで一般の方にも分かる説明が出ている。

トップの座にいられるのは、わずか数年なんですね。

個々の研究者や科学を理解している人たちも早速ブログやTwitterで多くの意見を出している。このような議論は積極的に行うべきだと思うし、多くの人の目に触れれば触れるほど認知度は上がってくるはずだ。また、今回の事業仕分けについて文科省でも意見を募集しているので、ここで意見を述べるのも効果があるかもしれない。

さらに、一部の議員の方にも次世代スパコンについては理解してもらっているようで、民主党の藤末健三議員のブログTwitter木俣佳丈議員のブログでも言及されている。

以上をまとめると、技術力の維持と科学の発展のために次世代スパコンは必要であり、このような理由を積極的にアピールして一般の方々にそれを知ってもらうことが大事なのだと思う。

続きを読む...

2009年11月1日日曜日

Google Wave: 「てめーはおれを怒らせた」ボットを作ってみた

このブログ記事をはてなブックマークに追加

「てめーはおれを怒らせた」ボットといきなり言われても何のことだか分からないと思うけど、要はユーザの発言に含まれる特定の文字に反応して動作するGoogle Waveのボット billowlet@appspot.com を作成したのだ。


先日、Google Waveの開発者用アカウントをGoogleからもらえたので早速何か作ってみることにした。どうせならGoogle App Engineを利用したかったので簡単に作れそうなボット(bot)を作成してみたわけだ。因みに元ネタのジョジョの奇妙な冒険とは言い回しが似ているというだけで内容とは関係ないので悪しからず。

このボットはユーザの発言内容に含まれる特定の文字に反応して動作し、ネコみたいな喋り方にさせられたり、英語や中国語で喋らされたり、笑いっぱなしにさせられたり、どもらされたりと、ユーザの発言がいろいろ変化する。因みに、「ごめんなさい」もしくは「ゆるして」と言うことで、動作を取り消すことができる。画像は実際の使用例だ。

ボットのアカウントはbillowlet@appspot.comで、使い方はGoogle WaveのContactsパネル内にある+ボタンでこのアカウントを追加するだけだ。そして、このボットアカウントをWaveに参加させればよい。因みに、ボットに使われている仔猫のアイコンについてはフリー素材ROKOの画像を利用させてもらった。

以下に「てめーはおれを怒らせた」ボットのデモのリンクを張っておくが、こちらはWave Sandboxアカウント所持者のみが利用できる。

「てめーはおれを怒らせた」ボット デモ

実際に使ってみて、このGoogle Waveはとても面白いWebアプリケーションだと思った。今回のアプリケーション作成でも将来性を強く感じることができたので、これから多くの人が使うことでどのように発展していくのか楽しみだ。

以下にGoogle App Engine上のソースコードを示しておく。ところで、stringモジュールのtranslate関数がunicodeに対してちゃんと動作しないのは何とかならないものかな。

追記(2009/11/23):

開発者用アカウント(Sandbox)では正常に動作するボットの発言が、Previewアカウントでは表示されないので調べてみたら、どうやらGAEにおけるGoogle Wave APIのバグのようだ。現在、GAEでCreateBlip/CreateChildを使うと正常に動作しない。今のところ対策はないようなので、修正されるまで待つしかないようだ。因みにそれ以外のAPIは動作するようで、ボットの発言はないが自分の発言はちゃんとボットによって変更されて表示される。

追記(2009/11/24):

Google Wave APIのバグが直って、Previewアカウントでも正常に動作するようになったみたい。しかし、今度はしばらくするとボットが反応しなくなるバグがあるようだ。プレビュー版だし不具合が出るのは仕方がないな。未知のバグを見つけたらできるだけ報告するようにしよう。

追記(2009/11/27):

ボットが反応しなくなるバグが直ったようだ。これでボットの開発については問題なくなったかな。

billowlet.py

#!/usr/bin/env python # -*- coding: utf-8 -*- import re, string, urllib, urllib2, random from google.appengine.ext import db from waveapi import events from waveapi import model from waveapi import robot from waveapi import document from waveapi import simplejson HIRAGANA = u"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわをん" KATAKANA = u"アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨワヲン" class UserData(db.Model): user = db.StringProperty(multiline=False) status = db.StringProperty(multiline=False) def OnParticipantsChanged(properties, context): added = properties["participantsAdded"] for participant in added: if participant != "billowlet@appspot.com": Notify(context, participant) def OnRobotAdded(properties, context): root_wavelet = context.GetRootWavelet() root_wavelet.CreateBlip().GetDocument().SetText(u"おれはbillowlet。よろしくな。") def Translate(text, from_lang, to_lang): url = "http://ajax.googleapis.com/ajax/services/language/translate?v=1.0&q=%s&langpair=%s%%7C%s" % (urllib.quote(text.encode("utf-8")), from_lang, to_lang) data = simplejson.loads(urllib2.urlopen(url).read()) return data["responseData"]["translatedText"] def BeCat(text): text = re.sub(u"。", u"にゃ。", text) text = re.sub(u"?|\?", u"にゃぁ?", text) text = re.sub(u"!|\!", u"にゃっ!", text) text = re.sub(u"な", u"にゃ", text) if text[-1] not in (u"。", u"?", u"?", u"!", u"!"): text += u"にゃ" return text def DotMarks(text): from_kana = u"かきくけこさしすせそたちつてとはひふへほカキクケコサシスセソタチツテトハヒフヘホ" to_kana = u"がぎぐげござじずぜぞだぢづでどばびぶべぼガギグゲゴザジズゼゾダヂヅデドバビブベボ" for f, t in zip(from_kana, to_kana): text = text.replace(f, t) return text def Laugh(text, ch): for i in range(len(text), 0, -1): text = text[:i] + ch + text[i:] return text.replace(u"。", u"").replace(u"、", u"") + ch * random.randint(3, 10) def Kana(idx): return HIRAGANA[idx] + u"|" + KATAKANA[idx] def OnBlipCreated(properties, context): blip = context.GetBlipById(properties["blipId"]) doc = blip.GetDocument() user = blip.GetCreator() data = UserData.all().filter("user =", user).fetch(1) if data: status = data[0].status else: status = None text = doc.GetText().strip() if status and re.match(u"ごめんなさい|ゆるして", text): blip.CreateChild().GetDocument().SetText(u"ゆるしてやる。やれやれだぜ。") if data: data[0].delete() angry = range(len(HIRAGANA)) random.shuffle(angry) if status: if status == u"ネコになれ。": text = BeCat(text) doc.SetText(text) elif status == u"英国紳士になれ。": text = Translate(text, "ja", "en") doc.SetText(text) elif status == u"台湾に住め。": text = Translate(text, "ja", "zh-TW") doc.SetText(text) elif status == u"インチキ日本人になれ。": text = Translate(Translate(text, "ja", "en"), "en", "ja") doc.SetText(text) elif status == u"大事なことは2回言え。": doc.SetText(text + text + u"\n大事なことなので2回言いました。") elif status == u"笑え。": doc.SetText(Laugh(text, u"w")) elif status == u"濁れ。": doc.SetText(DotMarks(text)) elif status == u"逆さまになれ。": doc.SetText(text[::-1]) elif status == u"どもれ。": doc.SetText((text[0] + u"、") * 3 + text) elif status == u"うざくなれ。": doc.SetText(Laugh(text, u"っ")) else: userdata = UserData() if re.search(Kana(angry[0]), text): status = u"ネコになれ。" elif re.search(Kana(angry[1]), text): status = u"英国紳士になれ。" elif re.search(Kana(angry[2]), text): status = u"台湾に住め。" elif re.search(Kana(angry[3]), text): status = u"インチキ日本人になれ。" elif re.search(Kana(angry[4]), text): status = u"大事なことは2回言え。" elif re.search(Kana(angry[5]), text): status = u"笑え。" elif re.search(Kana(angry[6]), text): status = u"濁れ。" elif re.search(Kana(angry[7]), text): status = u"逆さまになれ。" elif re.search(Kana(angry[8]), text): status = u"どもれ。" elif re.search(Kana(angry[9]), text): status = u"うざくなれ。" if status: blip.CreateChild().GetDocument().SetText(u"てめーはおれを怒らせた。%s" % status) userdata.user = user userdata.status = status userdata.put() def Notify(context, participant): root_wavelet = context.GetRootWavelet() root_wavelet.CreateBlip().GetDocument().SetText(u"%s、おれを怒らせるなよ。" % participant.split("@")[0]) def main(): myRobot = robot.Robot("billowlet", image_url="http://billowlet.appspot.com/assets/icon.gif", version="1", profile_url="http://billowlet.appspot.com/") myRobot.RegisterHandler(events.WAVELET_PARTICIPANTS_CHANGED, OnParticipantsChanged) myRobot.RegisterHandler(events.BLIP_SUBMITTED, OnBlipCreated) myRobot.RegisterHandler(events.WAVELET_SELF_ADDED, OnRobotAdded) myRobot.Run() if __name__ == "__main__": main()

app.yaml

application: billowlet version: 1 runtime: python api_version: 1 handlers: - url: /_wave/.* script: billowlet.py - url: /assets static_dir: assets

続きを読む...

2009年10月26日月曜日

Androidアプリケーションを開発するための準備

このブログ記事をはてなブックマークに追加

来月、海外に出張することもあってdocomoのHT-03Aを購入してしまった。本当は海外で使うというのは口実でAndroidアプリケーションを開発したいだけだったりする。ここでは個人的なメモも兼ねて、Windows上でAndroidアプリケーションを開発するための準備について記しておく。

まず、Eclipse Classicをダウンロード(現時点の最新版は3.5.1)して、それを適当なディレクトリに展開する。C:\に展開した場合、C:\eclipse\というディレクトリができる。因みに日本語版でも問題ないと思うが、自分は英語版のEclipseを使っている。

次にAndroid SDKをダウンロード(現時点の最新プラットフォームは2.0 "eclair")して、やはり適当な場所に展開する。C:\android\に展開した場合、C:\android\android-sdk-windows\というディレクトリが作成される。C:\android\android-sdk-windows\toolsを環境変数PATHに追加しておく。

展開したディレクトリにあるSDK Setup.exeを実行すると、Android SDK and AVD Managerが起動するので、そのままプラットフォームをアップデートする。もし、SSLでエラーが出る場合は、Settingsの"Force https://... sources to be fetched using http://..."のチェックボックスをオンにすること。Installed Packagesでインストールされたプラットフォームを確認できる。必要なバージョンのプラットフォームがインストールされていないときは、Available Packagesで必要なバージョンをインストールできる。

Androidのアップデートが終わったら、Eclipseを起動させる。最初に作業ディレクトリ(workspace)を訊かれるが適当なディレクトリを指定する。起動したら[Help]-[Install New Software...]でAddボタンを押し、NameにAndroid Plugin、Locationにhttps://dl-ssl.google.com/android/eclipse/を指定する。Developer Toolsという項目が表示されるので、チェックボックスをチェックしてNextボタンを押し、最後にFinishボタンを押して、Eclipseを再起動する。

次いで、[Window]-[Preferences]でダイアログを開き、ツリーメニューのAndroidを選択する。SDK LocationにC:\android\android-sdk-windowsを指定して、Applyボタンを押す。

Eclipse上からAndroidのエミュレータを利用するために、Android仮想デバイス(Android Virtual Device, AVD)を作成する。[Windows]-[Android SDK and AVD manager]で作成するか、コマンドラインで以下のコマンドを実行する(ここではデバイス名をmy_avdとしているが、どのような名前でも良い)。自分はコマンドラインで作成した。因みにtargetオプションで使用するプラットフォームのバージョンを設定できる。

android create avd --target 3 --name my_avd

あとは、コードを書くだけだ。動作の確認の意味でも、最初はHello, Worldアプリケーションあたりを作成するのが良いと思う。

Androidの実機でデバッグするには、まず、開発しているプロジェクトのManifestファイルのApplicationタグにandroid:debuggable="true"を記述する(または、ダイアログのApplicationタグの項目でDebuggableをtrueにする)。

さらに、実機(自分の場合はHT-03A)のメニューで、[設定]-[アプリケーション]-[開発]-[USBデバッグ]をチェックする。開発中にスリープモードにしたくない場合は、[スリープモードにしない]をチェックしておく。そして、実機と開発するPCをUSBで接続する。そうするとWindowsが新しいハードウェアを見つけるのでWindows Updateはせずに特定の場所からインストールする。場所はC:\android\android-sdk-windows\usb_driverを指定する。最後にWindowsを再起動する。

実機をUSB接続してEclipseを起動し、先ほどandroid:debuggable="true"にしたプロジェクトを開き、[Run]-[Run Configurations]を選択して、ダイアログを開く。ツリーメニューのAndroid Applicationから現在開いているプロジェクトを選択して、右のペインのTargetタグをクリックし、Deployment Target Selection ModeをManualにする。これでアプリケーション実行時にAndroid Device Chooserダイアログが開くので、実機を選択して実行する。実機上でアプリケーションが起動したら成功だ。

因みに作成したアプリケーションを配布するには、Java SEのkeytoolでキーストアと鍵を作成して、EclipseのAndroid Toolsからアプリケーションに署名をする必要がある。Android Marketを利用したい場合は、デベロッパープロフィールを作成して、クレジットカードで$25を支払えば配布できるようになる。

追記(2009/10/28): Android 2.0 (eclair)が正式リリースされたので、それに合わせて内容を修正した。

続きを読む...

2009年10月21日水曜日

無線LANで現在位置を取得してGoogleマップとストリートビューで表示する

このブログ記事をはてなブックマークに追加

昨年の8月に書いた記事、「Google App Engine: Gearsで位置情報を取得してストリートビューで表示させる」で、無線LANから取得した現在位置をGoogleマップストリートビューで表示するウェブアプリを作ったが、今回、それをブログなどにもそのまま貼り付けられるようにしてみた。もともと、JavaScriptを使ったアプリなのでそれほど変更するところもなかったのだが、無用な部分を削除し、きちんと画面にエラーメッセージを表示するようにした。動作させるためにはGearsをインストールしている必要がある。

無線LANから現在位置情報を取り出すためにGearsのGeolocation APIを使った。位置情報を取り出したらあとはGoogle Maps APIでそれをGoogleマップとストリートビューで表示させるだけだ。因みに、今年の7月からGoogleマップの画面で左上にある四角いボタンを押すと現在位置を表示できるようになっている。

今回のアプリでは、現在位置を示すマーカーに自作の扇形アイコンを使った。ストリートビューの向きと連動している。ペグマン(人型アイコン)でもよかったのだろうけど、個人的には向いている方向が分かりづらいと思う。



以下にソースファイルを示す。ページを読み込んだ際にGearsにより位置情報を取得している。

<script src="http://maps.google.com/maps?file=api&v=2.x&key=Google Maps API Key" type="text/javascript"></script> <script type="text/javascript" src="http://code.google.com/apis/gears/gears_init.js"></script> <script type="text/javascript"> var map; var pano; var overlayInstance = null; var marker; var info_window = false; var lat, lng; var error_msg = ""; function updatePosition(position) { lat = position.latitude; lng = position.longitude; initialize(); } function initialize_geo() { if (window.google && google.gears) { var geo = google.gears.factory.create("beta.geolocation"); geo.getCurrentPosition(updatePosition, function(positionError) { document.getElementById("error").innerHTML = positionError.message; }); } else { error_msg = "位置情報の取得に<a href=\"http://gears.google.com/\">Gears</a>が必要です。"; document.getElementById("error").innerHTML = error_msg; // 秋葉原. lat = 35.698584; lng = 139.774216; initialize(); } } function initialize() { if (GBrowserIsCompatible()) { var latlng = new GLatLng(lat, lng); var zoom = 15; marker = new GMarker(latlng, {icon: getArrowIcon(0.0), clickable: false}); pano = new GStreetviewPanorama(document.getElementById("pano")); GEvent.addListener(pano, "error", handleNoFlash); GEvent.addListener(pano, "initialized", moveStreet); GEvent.addListener(pano, "yawchanged", yawStreet); map = new GMap2(document.getElementById("map_canvas")); GEvent.addListener(map, "click", moveWalker); map.addControl(new GSmallMapControl()); map.addControl(new GMapTypeControl()); map.setCenter(latlng, zoom); map.addOverlay(marker); map.enableScrollWheelZoom(); pano.setLocationAndPOV(latlng); toggleOverlay(); GEvent.addListener(map, "click", function(overlay, latlng) { pano.setLocationAndPOV(latlng); }); } var client = new GStreetviewClient(); client.getNearestPanorama(latlng, setNewLatLng) } function setNewLatLng(data){ if (data.code != 200) { return; } var new_latlng = data.location.latlng; pano.setLocationAndPOV(new_latlng); map.panTo(new_latlng); marker.setLatLng(new_latlng); } function getArrowIcon(bearing) { var icon = new GIcon(); icon.image = getArrowUrl(bearing); icon.iconSize = new GSize(92, 92); icon.iconAnchor = new GPoint(46, 46); return icon; } function getArrowUrl(bearing) { var id = 3 * Math.round(bearing / 3); return "view_marker_" + id + ".png"; } function moveStreet(location_) { map.setCenter(location_.latlng); marker.setLatLng(location_.latlng); document.getElementById("error").innerHTML = error_msg; } function yawStreet(yaw_) { marker.setImage(getArrowUrl(yaw_)); } function moveWalker(overlay_, latlng_) { var client = new GStreetviewClient(); client.getNearestPanorama(latlng_, setNewLatLng) } function toggleOverlay() { if (!overlayInstance) { overlayInstance = new GStreetviewOverlay(); map.addOverlay(overlayInstance); } else { map.removeOverlay(overlayInstance); overlayInstance = null; } } function toggleMarkerLocation() { if (!info_window) { pov = pano.getPOV(); var displayString = [ marker.getLatLng(), "POV yaw: " + pov.yaw, "POV pitch: " + pov.pitch, "POV zoom: " + pov.zoom ].join("<br />"); map.openInfoWindowHtml(marker.getLatLng(), displayString); info_window = true; } else { map.closeInfoWindow(); info_window = false; } } function handleNoFlash(errorCode) { if (errorCode == GStreetviewPanorama.ErrorValues.FLASH_UNAVAILABLE) { document.getElementById("error").innerHTML = "Flashがサポートされていません。"; } else if (errorCode == GStreetviewPanorama.ErrorValues.NO_NEARBY_PANO) { document.getElementById("error").innerHTML = "ストリートビューの範囲外です。"; } } if (window.attachEvent){ window.attachEvent("onload", initialize_geo); window.attachEvent("onunload", GUnload); } else { window.addEventListener("load", initialize_geo, false); window.addEventListener("unload", GUnload, false); } </script> <table><tr> <td><div id="pano" style="float:left; background-color: black; width: 250px; height: 250px"></div></td> <td><div id="map_canvas" style="width: 250px; height: 250px"></div></td> </tr></table> <div><input type="button" onclick="toggleOverlay()" value="ストリートビューレイヤー" /> <input type="button" onclick="toggleMarkerLocation()" value="現在位置" /> <span id="error"></span></div>

続きを読む...

2009年10月5日月曜日

Python: 画像で与えられた迷路に対し2点間の最短経路を求める

このブログ記事をはてなブックマークに追加

迷路の描かれた画像に対して、ピクセルの座標で指定したスタート地点とゴール地点の最短経路を求めるプログラムをPython+PILで書いてみた。使用する画像は、デジカメで撮ったものでも、ウェブから拾ってきたものでも、ペイントソフトで自作したものでも構わない。

まずは使用例を見て欲しい。この画像は携帯カメラで撮った自作の簡単な迷路だ(画像上)。それに対して指定した2点間の最短経路を赤線で示してみた(画像下)。ピクセル単位で計測しているので赤線が若干ガタガタしていて完全な最短経路ではないがほぼ最短と考えていいだろう。迷路画像(画像上)をmaze01.jpgとし、スタート地点の座標が(240, 160)、ゴール地点の座標が(210, 400)の場合、コマンドラインで以下のように実行する。

maze_solver.py maze01.jpg -s 240 160 -g 210 400

これで最短経路を求めることができ、画像ビューアが立ち上がって経路の描かれた画像が表示される(画像下)。画像ビューアではなく画像ファイル(ここではmaze01out.jpgとする)に出力したい場合は、以下のようにする。

maze_solver.py maze01.jpg -s 240 160 -g 210 400 -o maze01out.jpg

このプログラムは2つのパートで成り立っていて、一つは画像認識を使った画像データの領域分けであり、もう一つはその領域内の2点間の経路探索である。それぞれ、連結成分ラベル付け(connected component labeling, CCL)アルゴリズムA*探索アルゴリズムを利用して処理している。詳細についてはそれぞれのリンク先と最後に示したソースコードを参照して欲しい。

次の例は、Wikipediaの迷路の項目で例示されている迷路画像(画像上)を解いてみたものだ(画像中、下)。いずれも迷路の真ん中の小部屋(270, 130)をスタート地点とし、画像中段ではゴールを左上(0, 0)、下段では右下(490, 268)とした。スタートとゴールまでの経路は複数あるが、いずれも最短経路を示している。

画像上段をmaze02.pngとした場合、画像中段、下段は、それぞれ以下のように実行して作成した。

maze_solver.py maze02.png -s 270 130 -g 0 0

maze_solver.py maze02.png -s 270 130 -g 490 268

使い方の詳細は、-hオプションを付けて実行すれば良い。

-aオプションで、探索する画像の大きさを指定している。あまり大きな画像だと時間が掛かるのでデフォルトで60,000ピクセルにしている。ただし、複雑な迷路や写りの悪い写真だと画像がつぶれてしまい、正しい結果にならないかもしれないので、そのようなときは指定するピクセルを大きくする必要がある。元の画像ファイルが指定サイズ以下の場合は実サイズのままで処理される。

-tオプションでは、CCLアルゴリズムで使用する色の閾値を指定している。デジカメなどで撮影した画像は、例えば白い部分でも黄色っぽい白や赤っぽい白など完全に同じ色情報でない場合が多く、大体白に近ければ同じ色と見なすわけだが、その許容範囲がこの閾値となる。因みに閾値を0にすると完全に同じ色情報を持っていないと違う色だと見なされる。

Usage: maze_solver.py [options] imagefile Options: -h, --help show this help message and exit -o OUT_IMAGE, --output-image=OUT_IMAGE output-imagefile -s START, --start=START start position [default: (0, 0)] -g GOAL, --goal=GOAL goal position [default: (0, 0)] -a AREA, --area-size=AREA area size [default: 60000] -t THRESHOLD, --threshold=THRESHOLD threshold of the CCL [default: 30]

以下、ソースコード。

maze_solver.py

#!/usr/bin/env python # -*- coding: utf-8 -*- import sys, os, math, heapq, Image, ImageDraw # Connected Component Labeling - Label Equivalence method class CCL: def __init__(self, imagefile, threshold, area): im = Image.open(imagefile) im = im.convert("RGB") self.orig_size = im.size orig_area = self.orig_size[0] * self.orig_size[1] if orig_area <= area: self.size_rate = 1.0 else: self.size_rate = math.sqrt(float(area) / orig_area) im = im.resize((int(self.orig_size[0] * self.size_rate), int(self.orig_size[1] * self.size_rate))) self.size = im.size self.th = threshold self.W = im.size[0] self.D = im.getdata() self.N = len(self.D) self.L = [] self.R = [] for i in range(len(self.D)): self.L.append(i) self.R.append(i) def diff(self, d1, d2): return abs(d1[0] - d2[0]) + abs(d1[1] - d2[1]) + abs(d1[2] - d2[2]) def scanning(self): has_label = False for i in range(self.N): label = self.N if i - self.W >= 0 and self.diff(self.D[i], self.D[i-self.W]) <= self.th: label = min(label, self.L[i-self.W]) if i + self.W < self.N and self.diff(self.D[i], self.D[i+self.W]) <= self.th: label = min(label, self.L[i+self.W]) if i % self.W != 0 and self.diff(self.D[i], self.D[i-1]) <= self.th: label = min(label, self.L[i-1]) if (i + 1) % self.W != 0 and self.diff(self.D[i], self.D[i+1]) <= self.th: label = min(label, self.L[i+1]) if label < self.L[i]: self.R[self.L[i]] = label has_label = True return has_label def analysis(self): for i in range(self.N): label = self.L[i] if label == i: ref = label label = self.R[ref] while True: ref = label label = self.R[ref] if ref == label: break self.R[i] = label def labeling(self): for i in range(self.N): self.L[i] = self.R[self.R[self.L[i]]] def calculate(self): while True: if self.scanning(): self.analysis() self.labeling() else: return self.L, self.W, self.size_rate, self.orig_size, self.size # A* Search Algorithm class AStar: def __init__(self, data, start, goal): self.data = data self.nrow, self.ncol = len(data), len(data[0]) self.start = start self.goal = goal if data[self.start[0]][self.start[1]] != data[self.goal[0]][self.goal[1]]: print "No route!" sys.exit(0) self.passage = data[self.start[0]][self.start[1]] def heuristic(self, pos): return math.sqrt((pos[0] - self.goal[0])**2 + (pos[1] - self.goal[1])**2) def distance(self, path): if len(path) == 0: return 0 sum = 0 p1 = self.start for p2 in path: sum += math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2) p1 = p2 return int(sum) def neighborhood(self, pos): row, col = pos neighbors = [] if row > 0 and self.data[row-1][col] == self.passage: neighbors.append((row - 1, col)) if row < self.nrow - 1 and self.data[row+1][col] == self.passage: neighbors.append((row + 1, col)) if col > 0 and self.data[row][col-1] == self.passage: neighbors.append((row, col - 1)) if col < self.ncol - 1 and self.data[row][col+1] == self.passage: neighbors.append((row, col + 1)) if row > 0 and col > 0 and self.data[row-1][col-1] == self.passage: neighbors.append((row - 1, col - 1)) if row < self.nrow - 1 and col > 0 and self.data[row+1][col-1] == self.passage: neighbors.append((row + 1, col - 1)) if row > 0 and col < self.ncol - 1 and self.data[row-1][col+1] == self.passage: neighbors.append((row - 1, col + 1)) if row < self.nrow - 1 and col < self.ncol - 1 and self.data[row+1][col+1] == self.passage: neighbors.append((row + 1, col + 1)) return neighbors def search(self): path = [] queue = [] checked = [self.start] heapq.heappush(queue, (self.distance(checked) + self.heuristic(self.start), checked)) while len(queue) > 0: score, path = heapq.heappop(queue) last = path[-1] if last == self.goal: return path neighbors = self.neighborhood(last) for nb in neighbors: if nb in checked: continue checked.append(nb) newpath = path + [nb] heapq.heappush(queue, (self.distance(newpath) + self.heuristic(nb), newpath)) return [] def draw_path(in_image, out_image, path, SR): im = Image.open(in_image) im = im.convert("RGB") draw = ImageDraw.Draw(im) draw.line([(int(p[1] / SR), int(p[0] / SR)) for p in path], fill=255) if out_image: im.save(out_image) else: im.show() def main(args): from optparse import OptionParser usage = "usage: %prog [options] imagefile" parser = OptionParser(usage=usage) parser.add_option("-o", "--output-image", type="string", help="output-imagefile", dest="out_image", default="") parser.add_option("-s", "--start", type="int", nargs=2, help="start position [default: %default]", dest="start", default=(0, 0)) parser.add_option("-g", "--goal", type="int", nargs=2, help="goal position [default: %default]", dest="goal", default=(0, 0)) parser.add_option("-a", "--area-size", type="int", help="area size [default: %default]", dest="area", default=60000) parser.add_option("-t", "--threshold", type="int", help="threshold of the CCL [default: %default]", dest="threshold", default=30) options, args = parser.parse_args() if len(args) == 0: parser.print_help() parser.exit() in_image = args[0] print "Connected component labeling phase..." ccl = CCL(in_image, options.threshold, options.area) D, W, SR, ORIG_SZ, SZ = ccl.calculate() print " Width: %d (%d), Height: %d (%d)" % (ORIG_SZ[0], SZ[0], ORIG_SZ[1], SZ[1]) data = [D[i:i+W] for i in range(0, len(D), W)] print "A* searching phase..." print " Start: (%d, %d), Goal: (%d, %d)" % (options.start + options.goal) start = (int(options.start[1] * SR), int(options.start[0] * SR)) goal = (int(options.goal[1] * SR), int(options.goal[0] * SR)) astar = AStar(data, start, goal) path = astar.search() print "Drawing path on the imagefile..." draw_path(in_image, options.out_image, path, SR) if __name__ == "__main__": main(sys.argv)

続きを読む...

2009年9月15日火曜日

C++: STL algorithmのlower_boundとupper_boundの使い方

このブログ記事をはてなブックマークに追加

C++ STLのalgorithmに入っているlower_boundとupper_boundが間違えやすいのでメモ。

vector<int>で定義されたコンテナvの要素のうち、A以上、B以下の要素を求める場合。

vector<int>::iterator a = lower_bound(v.begin(), v.end(), A); vector<int>::iterator b = upper_bound(v.begin(), v.end(), B); if (a != v.end() && b != v.begin() && *a <= *(b - 1)) cout << "[" << *a << ", " << *(b - 1) << "]" << endl; else cout << "no element" << endl;

*aが最小の要素、*(b-1)が最大の要素になる。

A以上、B未満の場合。

vector<int>::iterator a = lower_bound(v.begin(), v.end(), A); vector<int>::iterator b = lower_bound(v.begin(), v.end(), B); if (a != v.end() && b != v.begin() && *a <= *(b - 1)) cout << "[" << *a << ", " << *(b - 1) << "]" << endl; else cout << "no element" << endl;

因みに、lower_boundとupper_boundに渡すコンテナはソートされている必要がある。

#include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { vector<int> v; for (int i = 100; i > 0; i--) v.push_back(i); int A = 30; // 下限. int B = 50; // 上限. vector<int>::iterator a, b; cout << "要素数" << v.size() << "で、"; cout << "最小要素" << *min_element(v.begin(), v.end()) << "、"; cout << "最大要素" << *max_element(v.begin(), v.end()); cout << "のコンテナから、" << endl; // lower_bound, upper_boundに渡すコンテナはソートされている必要がある. sort(v.begin(), v.end()); cout << A << "以上、" << B << "以下の要素を求める: "; a = lower_bound(v.begin(), v.end(), A); b = upper_bound(v.begin(), v.end(), B); if (a != v.end() && b != v.begin() && *a <= *(b - 1)) cout << "最小要素 " << *a << ", 最大要素 " << *(b - 1) << endl; else cout << "要素なし" << endl; cout << A << "以上、" << B << "未満の要素を求める: "; a = lower_bound(v.begin(), v.end(), A); b = lower_bound(v.begin(), v.end(), B); if (a != v.end() && b != v.begin() && *a <= *(b - 1)) cout << "最小要素 " << *a << ", 最大要素 " << *(b - 1) << endl; else cout << "要素なし" << endl; return 0; }

出力結果:

要素数100で、最小要素1、最大要素100のコンテナから、 30以上、50以下の要素を求める: 最小要素 30, 最大要素 50 30以上、50未満の要素を求める: 最小要素 30, 最大要素 49

続きを読む...

2009年9月8日火曜日

JavaScriptで素数を計算して動的に表示する

このブログ記事をはてなブックマークに追加

JavaScriptを使って素数を計算して、それを動的に表示してみた。今更という感じもするけど、10行そこそこでこのような動的な表示ができるというのは、とても便利だと思う。とは言え、普段はあまりJavaScriptを使ったHTMLは書いてないな。

までの素数


テキストボックスに数字を入力すると、その数までの素数を計算して表示する。最大入力桁を5桁にしたので、99,999までの素数を求めることができる。括弧内の数値は求めた素数の数を示す。

複数のブラウザ(Google Chrome, Firefox, IE8, Safari4)で確認してみたけど、最近はどのブラウザもJavaScriptが速い。今後、ますますJavaScript、特にAjaxなどは要の技術になっていくのだろうな。

以下にソースを示す。

<script type="text/javascript"><!-- function calcPrimes(data) { var primes = []; if (data.value >= 2) primes.push(2); for (i = 3; i <= data.value; i += 2) { for (j = 0; i > primes[j] * primes[j] && i % primes[j] != 0; j++); if (i < primes[j] * primes[j]) primes.push(i); } document.getElementById("primes").innerHTML = primes.length ? " (" + primes.length + "): " + primes.join(", ") : ""; } // --></script> <form><input type="text" size="5" maxlength="5" onkeyup="calcPrimes(this)">までの素数<span id="primes"></span></form>

続きを読む...

2009年9月1日火曜日

結局Pythonを使ってコマンドラインで動作するTwitterクライアントを作ってしまった

このブログ記事をはてなブックマークに追加

Twitterのアカウントを取ってから既に1年半になるが、活発に使っているとは言い難い。その原因の一つとしてTwitterのクライアントがある。どうにも自分が利用するのにピッタリだと思うクライアントが見つからなかったのだ。そこで結局、自分の好みに合わせてコマンドライン上で動作するシンプルなTwitterクライアントをPythonで作ってしまった。しかも、ワンライナー(1行プログラム)。

最初の頃はいくつかのクライアントを使ってみたのだが、PCでの作業はシェルで行うことが多いので別のウィンドウを開きたくなかったり、Windows、Unix、MacなどのOSが変わっても同じクライアントを使いたかったり、GUIじゃなくてCUIで操作したかったり、それほど使い込むつもりがないので極力シンプルでコンパクトになっていて欲しかったり、そもそもクライアントをインストールしたくなかったりと、かなり条件を厳しく求めていたら使えるクライアントがなくなってしまい、結局、公式サイトもしくは自分で作成した掲示板からたまにつぶやくだけになってしまった(ただし、Twitterfeedは利用している)。

しかし、最近になってタイムラインをよく眺めるようになり、これが結構面白いと気がついた。自分と違う考え方に触れるのは楽しい。そして、自分ももう少しつぶやいてみようかと思ったのだが、公式サイトや掲示板からのポストはいちいちブラウザを開かなくてはならず手軽だとは言い難かった。

そこで、先日、PythonのワンライナーでTwitterを使えるようにしたこともあり、もう少し改良してそれをクライアントにすることにした。Pythonスクリプトなら、Pythonが入っていればどのOSでも動作するし、使い方はどこでも一緒で、コマンドラインでそのまま使え、タイムラインをgrepなどで簡単に選択表示できる。それに、たった1行のソースコードなので簡単に中身を確認でき、パスワード漏洩やキーロガーなどを心配しなくてもいい。

tw.py

でタイムラインを取得することができ、

tw.py つぶやき tw.py つぶやき http://handasse.blogspot.com/ ブログのURLです。 tw.py "つぶやき&つぶやき" tw.py "つぶやき つぶやき"

などでTwitterにつぶやくことができる。つぶやきに&などの特殊文字や連続でスペースが入る場合などはダブルクォーテーション(")で括ること。つぶやきに入っているURLは自動的にbit.lyによる短縮URLとなる。

また、Windows PowerShellで使用する場合は、プロファイル($profileで表示されるファイルで、Microsoft.PowerShell_profile.ps1などを指し、PowerShell起動時に読み込まれる)に以下の関数を定義しておくと便利だ。

function tw { python 実行パス\tw.py "$args" }

Unixなどで日本語コードが異なっていても、ソースを変更せずにnkfなどを使ったエイリアスで簡単に対処できる。ただ、特殊文字などを使ったときに問題が出るかもしれない。以下の例は、Unix側の表示がUTF-8でシェルがtcshの場合。

alias tw 'tw.py `echo -n \!* | nkf -s` | nkf -w'

これならば tw とするだけで実行できる。また、PowerShellでは tw | select-string フレンド名、Unixでは tw | grep フレンド名で、指定したフレンドの発言をタイムラインから簡単に抜き出すこともできる。

今回のPythonによるTwitterクライアントのソースを以下に示す。simplejsonを使っている。bit.lyのアカウント名とAPI Keyはbit.lyアカウントのページですぐに取得できる。また、Base64でエンコードされたユーザ名とパスワードの作成は、Pythonのbase64モジュールを使ってもいいし、以前にGoogle App Engineで作ったバイナリ/アスキー変換を利用してもらっても構わない。まあ、エンコードされていてもセキュリティが強固になるわけではないのだが、ぱっと見でばれてしまう平文よりはいいんじゃないかな。ワンライナーにしたのもぱっと見で解りづらくするためだし。

ところで、TwitterのAPIでデータを取得したり送信したりすると、たまに失敗することがあるので、エラーが出たときは再度実行すること。

tw.py

#!/usr/bin/env python import sys,re,urllib,urllib2,xml.sax.saxutils,base64,simplejson;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com',base64.b64decode('エンコードされたユーザ名'),base64.b64decode('エンコードされたパスワード'));urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));tw=None;len(sys.argv)>1and[globals().__setitem__('tw',' '.join(sys.argv[1:]))];F=lambda x:str(simplejson.loads(urllib2.urlopen('http://api.bit.ly/shorten?version=2.0.1&longUrl=%s&login=bit.lyのアカウント名&apiKey=bit.lyのAPI Key'%urllib.quote(x)).read())['results'][x]['shortUrl']);G=lambda x:re.sub(re.escape(x),F(x),tw);tw and[globals().__setitem__('tw',G(link))for link in sorted(re.findall(r'(http(?:s?)\:\/\/[^\/\ ]+\/.*?)(?:[\ <>\"\{\}\|\\\^\[\]\`]|$)',tw),reverse=True)];tw and[urllib2.urlopen('http://twitter.com/statuses/update.xml',urllib.urlencode({'status':tw.decode('cp932').encode('utf-8')}))]or[sys.stdout.write(''.join(['%s: %s\n'%(d['user']['screen_name'],re.sub(r'\r\n|\n|\r',' ',xml.sax.saxutils.unescape(d['text'])))for d in simplejson.loads(urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json').read())]).encode('cp932','replace'))]

追記(2009/9/9):

今までTwitterのお気に入り機能を使っていなかったけど、ふぁぼったーを見て使いたくなったので、今回のコマンドライン型Twitterクライアントでも使えるように改良した。

まず、tw.pyについてはつぶやきの最後に[つぶやきのID]を表示するようにした。また、fav.pyでつぶやきをお気に入りに加えることができる。使い方は以下の通り。

fav.py [つぶやきのID]

tw.pyで表示されたIDを入力する。コピー&ペーストだと簡単。IDの括弧([])は入れても入れなくても構わない。また、IDを付けずに

fav.py

とすれば、自分のお気に入りを表示することができる。

ただ、お気に入りを使わないのであればtw.pyによるIDの表示は目障りになるかもしれないので、そのときは以前のtw.pyを使った方がいいと思う。

以下、修正したソース。

tw.py

#!/usr/bin/env python import sys,re,urllib,urllib2,xml.sax.saxutils,base64,simplejson;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com',base64.b64decode('エンコードされたユーザ名'),base64.b64decode('エンコードされたパスワード'));urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));tw=None;len(sys.argv)>1and[globals().__setitem__('tw',' '.join(sys.argv[1:]))];F=lambda x:str(simplejson.loads(urllib2.urlopen('http://api.bit.ly/shorten?version=2.0.1&longUrl=%s&login=bit.lyのアカウント名&apiKey=bit.lyのAPI Key'%urllib.quote(x)).read())['results'][x]['shortUrl']);G=lambda x:re.sub(re.escape(x),F(x),tw);tw and[globals().__setitem__('tw',G(link))for link in sorted(re.findall(r'(http(?:s?)\:\/\/[^\/\ ]+\/.*?)(?:[\ <>\"\{\}\|\\\^\[\]\`]|$)',tw),reverse=True)];tw and[urllib2.urlopen('http://twitter.com/statuses/update.xml',urllib.urlencode({'status':tw.decode('cp932').encode('utf-8')}))]or[sys.stdout.write(''.join(['%s: %s [%d]\n'%(d['user']['screen_name'],re.sub(r'\r\n|\n|\r',' ',xml.sax.saxutils.unescape(d['text'])),d['id'])for d in simplejson.loads(urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json').read())]).encode('cp932','replace'))]

fav.py

#!/usr/bin/env python import sys,re,urllib,urllib2,xml.sax.saxutils,base64,simplejson;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com',base64.b64decode('エンコードされたユーザ名'),base64.b64decode('エンコードされたパスワード'));urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));len(sys.argv)>1and[urllib2.urlopen('http://twitter.com/favorites/create/%d.xml'%int(sys.argv[1].strip('[]')),{})]or[sys.stdout.write(''.join(['%s: %s [%d]\n'%(d['user']['screen_name'],re.sub(r'\r\n|\n|\r',' ',xml.sax.saxutils.unescape(d['text'])),d['id'])for d in simplejson.loads(urllib2.urlopen('http://twitter.com/favorites.json').read())]).encode('cp932','replace'))]

続きを読む...

2009年8月28日金曜日

PythonのワンライナーでTwitterを使う

このブログ記事をはてなブックマークに追加

Twitterでつぶやいたり、タイムラインを取得したりするPythonのワンライナー(1行プログラム)を作ってみた。取り敢えずWindowsで動作は確認した。Pythonさえ入っていればどこでも動くと思う。シェルやcronに組み込んだり、ウェブアプリや自作プログラムで利用したり、Python以外に必要なものがないから手軽に使えるんじゃないかな。ただ、ユーザ名とパスワードは生テキストなのでその辺は気をつけるべきかも。

まず、Twitterでつぶやくワンライナー。

python -c "import urllib,urllib2;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com','username','password');urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));urllib2.urlopen('http://twitter.com/statuses/update.xml',urllib.urlencode({'status':'つぶやき'.decode('cp932').encode('utf-8')}))"

次に、タイムライン取得。simplejsonを使っている。

python -c "import sys,urllib,urllib2,xml.sax.saxutils,simplejson;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com','username','password');urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));sys.stdout.write(''.join(['%s: %s\n'%(d['user']['screen_name'],xml.sax.saxutils.unescape(d['text']))for d in simplejson.loads(urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json').read())]).encode('cp932','replace'))"

因みに文字コードはCP932、簡単に言えばShift JISなので(厳密には違うけど)、環境に合わせて修正したり、nkfをかませるなどして欲しい。それにしても、TwitterのAPIは使いやすくていいね。

追記(2009/8/29):

Twitterのつぶやきとタイムライン取得の両方を行うワンライナーも書いてみた。このコードはPython 2.5以上で動作する。

python -c "import sys,urllib,urllib2,xml.sax.saxutils,simplejson;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com','username','password');urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));sys.stdout.write(''.join(['%s: %s\n'%(d['user']['screen_name'],xml.sax.saxutils.unescape(d['text']))for d in simplejson.loads(urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json').read())]).encode('cp932','replace'))if len(sys.argv)<2 else urllib2.urlopen('http://twitter.com/statuses/update.xml',urllib.urlencode({'status':sys.argv[1].decode('cp932').encode('utf-8')}))" つぶやき

「つぶやき」を書けばそれがTwitterに送信され、書かなければタイムライン取得となる。また、「つぶやき」にスペースなどが入る場合はダブルクォーテーション(")で括ること。

次のコードであればPython 2.4以下でもいけるかな。

python -c "import sys,urllib,urllib2,xml.sax.saxutils,simplejson;pm=urllib2.HTTPPasswordMgrWithDefaultRealm();pm.add_password(None,'twitter.com','username','password');urllib2.install_opener(urllib2.build_opener(urllib2.HTTPBasicAuthHandler(pm)));len(sys.argv)<2and[sys.stdout.write(''.join(['%s: %s\n'%(d['user']['screen_name'],xml.sax.saxutils.unescape(d['text']))for d in simplejson.loads(urllib2.urlopen('http://twitter.com/statuses/friends_timeline.json').read())]).encode('cp932','replace'))]or[urllib2.urlopen('http://twitter.com/statuses/update.xml',urllib.urlencode({'status':sys.argv[1].decode('cp932').encode('utf-8')}))]" つぶやき

続きを読む...

2009年8月27日木曜日

Windows PowerShellを便利に使うための10のミニテクニック

このブログ記事をはてなブックマークに追加

Microsoftが提供していてWindows 7では標準搭載になるWindows PowerShellがもっと広まって欲しいこともあって、CodeZineWindowsのコマンドプロンプトを便利に使うための10のミニテクニックのPowerShell版を書いてみた。

まず、Windows PowerShell 2.0 CTP3をダウンロードして、インストールする。さらに便利に使うためにPowerShell Community ExtensionsからPSCX 1.2をダウンロード・インストールする。

さて、これで準備が整った。因みに以下のテクニックは、Windows XP、Vista、Windows 7のどのOSでも使えると思う(ただし、確認したのはXPのみ)。

コマンドプロンプトからエクスプローラに移動する

以下のように起動するだけ。

ii .

エクスプローラからコマンドプロンプトに移動する

これは標準では難しいと思う。自分はエクスプローラの使い勝手には非常に不満を持っているので、ずいぶん前からWindowsではFileVisorを使っている。FileVisorであれば以下のコマンドをホットキーに登録しておくことで、現在のディレクトリをカレントディレクトリとして一発でPowerShellを開くことができる。

C:\WINDOWS\system32\windowspowershell\v1.0\powershell.exe -NoExit -Command Set-Location $P

カレントディレクトリを記憶し、あとで戻ってくる

これはそのまま、pushd .popdが使える。

2つのディレクトリを行ったり来たりする

これもほとんど一緒。

doskey /exename=powershell.exe d1=cd $pwd

一時的にネットワークドライブを割り当てる必要はない

PowerShellではcdでそのままネットワークをまたげるので必要ないと思う。

cd \\computer1\project1\program1

処理結果をクリップボードにコピーする

ocb (Out-Clipboard)を使う。

dir | ocb tree | ocb

因みに、gcb (Get-Clipboard)を使うことで、PowerShell上に貼り付けもできる。

gcb

ずれたインデントを修正する

これもあまり変わらない。

more.com /t4 hello.c

ページごとに止めずに、一気に出力する。

more.com /t4 hello.c | echo

簡単な計算をする

setコマンドは使う必要はなく、そのまま計算させるだけ。

10-20+30 20

(10+20)*30/50 18

0xFF 255

因みに、小数も扱える。また、10進→16進変換は以下のようにすればできる。

"{0:X}" -f 255 FF

ファイルのタイムスタンプを変更する

UNIXのtouchコマンドと全く同じで以下のようにする。

touch file.ext

因みに、以下のようにもできる。

dir file.ext | % { $_.lastwritetime = get-date }

コマンドプロンプトの色やサイズを調整する

背景色を変える。

$host.ui.rawui.backgroundcolor = "blue"

画面の大きさを幅120桁、高さ50行に変える。

$host.ui.rawui.windowsize = new-object system.management.automation.host.size(120, 50)

画面のバッファを幅120桁、高さ3000行に変える。

$host.ui.rawui.buffersize = new-object system.management.automation.host.size(120, 3000)

おわりに

これを機にPowerShellが流行るといいなぁ。Windows 7では標準で付いてくるしね。

続きを読む...