アーカイブ

‘プログラミング’ カテゴリーのアーカイブ

Twitterのbotを作った話

うんこちんちんbotという世界で最も役に立たない上に、コンセプトすら迷走しているbotを作ったのです。ずいぶん前から作っていたのだけど、最近ようやく「まぁこんなもんでしょう」というところまで来たので、ぐだぐだと書いてみようかと思います。

きっかけ

ない。うんことかちんちんとか言うbotがあったら楽しいんじゃない?俺が!みたいに思って、カッとなって作った。コンセプトは最初から迷走しているし、深く考えないで作ってみたらいーんじゃない?っていう軽いノリでした。だって、Twitterだし。

今のところの機能

  • 3時間毎に定型文を言う。
  • 1日1回定型文以外のことを言う。
  • リプライが来たら少ない語彙の中から返事する。

フォローしてる人から見える機能はこんなところで、DMでコマンドを受け付けてpostする文やリプライするキーワードと文章を関連付けたりしてます。

他のbotがどういう風になってるか、あまり興味がないので知らないのでわかりませんが、機能追加がしやすいように拡張性だけは高くなってて、他の機能を破壊せずに新しい機能を追加できる作りになっています。

言語だの何だの

言語はRuby、DBにSQLite3を使っています。Twitter用のライブラリとかは全部自前で作ってます。一度そういうライブラリを使ったことあるけど、メソッド調べたりするのめんどくさいからやめました。WebのAPIなんて、直接叩いた方が早いんじゃないの?って思うんだけど、どうなんだろう。

データを格納するのはファイルにするか、DBにするか作る前に迷ったんだけど、データの読み書きがしやすいDBを選択しました。ActiveRecordとかのORマッピングなんちゃらとかは使ってないですね。そんなにテーブルもカラムもないし、SELECT文の結果を格納するクラスだけあればいい程度です。

開発環境はMacで、GitとTracを使っています。いかんせん物忘れが激しいのでTracでチケット発行する運用は便利です。Gitよいね。

今後の方向性

かわいいアイコンも作っていただいたので、愛されゆるふわbotとしてやっていこうと思っています。「全然すごくないけど、なんかいい」みたいなゆるい感じを続けていきたい。むさ苦しいギークは放っておいて女の子にキャーキャー言われて、キャラクター商品にならねぇかなぁ。

マルコフ連鎖で文章を生成して、人工無能!というのも思いついたんだけど、キャラ付けができなくなるのでやめました。これは別の機会にやりましょう。

カテゴリー: プログラミング タグ:

仕事をサボるためのTwitterクライアントは「Sabotage」にした

これの続き。

仕事をサボるために、仕事中にTwitterクライアントを作っていたのです。作ったプロトタイプなようなもの(一生プロトタイプです)を何を間違えたのか、公開してしまったので、名前を決めました。20秒くらい熟考して、「Sabotage(サボタージュ)」という名前にしましたよ。ついでにいくつかの機能を実装して、バージョンを0.1にしました。0と1の間には無限の数字がありますね。

Sabotageに関するドキュメントおよびダウンロードは、こちらを更新していきます。ブログ記事では、何となくモヤっとしたことを書きつつ、粛々とリリースを続けていければいいかな。ニーズがあるとは思えないので、自分がやりたいことを実装して、ブログのネタになればいいと思います。

今回のリリースでは、ふぁぼれる機能を実装しました。これはTwitter APIを叩けばいいだけなので、何の困難もありません。GETやPOST関連の部分はすでに実装しているし、特別なパラメータもいらないので、余裕です。

他にも、エラーメッセージをいい加減な感じで考えたりしました。元々、エラー時のコールバック関数については作ってありましたが、何の確認もしてなかったので、実行されるかどうかすらわからなかったんですよ。今回は、正しくメッセージが表示されるであろうことは確認しています。

個人的な目玉としては、設定ファイルの読み込みですかね。こういうのを外部化しておけば、変更がある程度は楽になります。JavaScriptでは、ファイルの読み書きはできないという先入観があったんですが、Ajaxでファイル(テキスト)へリクエストを送れば、読むことだけはできることに気付きました。

んで、そもそも「なんでHTMLとJavaScriptで作ってるの?」って話です。VBだの、Javaだの、色々あるじゃん。理由は主に3つあります。「VBは使えないから」「JavaのGUIは作ったことから」「その2つに興味がないから」です。あと、大それたモノでもないので、HTMLでいいし、自宅のMacでは使わないので、Windows + IEで使えればいいやって感じですね。こんな酔狂なことをやってるヤツは少ないぞ!というのも動機の1つです。

Ajaxには興味があったんですけど、自分でサーバ用意して、サーバのアプリケーション作ってというのは学習に手間がかかりすぎるっていうのも1つです。もっと簡単にできる、それこそエディタだけで学習できる方法の1つがTwitterクライアントでした。ローカルファイルにリクエスト送れば非同期通信できるけど、それは面白くないわけです。自分で用意したデータが取れたところで、大して面白くないのは、過去の学習から学んだことです。(HelloWorldなんて1回やれば十分です)

面白くないことを堪え忍んで学習するような苦行はしたくありません。俺は修行僧じゃないしね。

カテゴリー: プログラミング タグ: , ,

CRLFはデータ、LFは改行としたCSVファイルの読み込み

CSVファイルで、データにCRLFが入っていて、レコードの区切りがLFというものを1行ずつ処理する必要に迫られたので、作りました。言語はJavaです。実際、そんなデータがあるのかって話ですが、あるから作ったのですよ。

BufferedWriterのreadLineでは、CRLFだろうが、LFだろうが、改行コードを行の区切りとして読み込みます。今回の要件は、CRLFはデータとして、LFのみはレコードの区切りとして扱わなければなりませんので、readLineでなく、readを使ってCRLFは改行として扱わないということになります。

public class ReadLineSample {

    private Reader r = null;

    private static final char CR = '\r'

    private static final char LF = '\n'

    public ReadLineSample(Reader r) {
        this.r = r;
    }

    public String readLine() throws IOException {

        StringBuilder sb = new StringBuilder();

        // 読み込みフラグ。改行コードCRを検出した場合にON。
        boolean hasCR = false;

        while(true) {
            int i = r.read();
            if(i < 0) {
                // ストリームの終端

                if(sb.length() == 0) {
                	// バッファに文字を読み込んでいない場合
                    return null;
                }

                   return sb.toString();
            }

            char ch = (char)i;

            if(ch == LF && !hasCR) {
                // 改行コードLFであり、1文字前がCRでない
                return sb.toString();
            }

            sb.append(ch);

            if(ch == CR) {
                // CR
                hasCR = true;
                continue;
            }

            // フラグをOFF
            hasCR = false;
        }
    }
}

ストリームの終端でnullを返すので、呼び出し側はBufferedReaderのreadLineを使う時と同じロジックになります。このままでも使えるし、メソッドだけをコピペしても使えます。シグネチャが同じなので、BufferedReaderのサブクラスにして、readLineをオーバーライドしても使えると思います。

実際に使いどころがあるかというと、微妙なところです。調べてみても見つからないので、晒してみようっていうだけです。ないってことは、誰もこんなもの必要としてないってことなんだろうなぁ。

カテゴリー: プログラミング タグ: ,

仕事をサボるためにTwitterクライアントを作った

仕事中にTwitterをしたいのだけど、堂々とTwitterの画面を開くわけにはいかず、かといってクライアントをインストールするわけにもいかないというジレンマがあります。Excelっぽく見えるクライアントもあるけど、そこまで大袈裟なものはいらない。色々とワガママですが、しょうがない。ワガママじゃないと、仕事はさぼれないんだよ!

というわけで、自分でTwitterクライアントを作ってみたわけです。ブラウザ(IE7のみ。IE8は知らない)だけで動く、死ぬほどシンプルなTwitterクライアントです。 HTMLとJavaScriptしか使ってないので、インストールも必要ない。ただ、HTMLファイルを開くだけで、目眩く息抜きタイムが始まるわけです。後ろに誰かが通りかかっても「こいつサボってやがる。けしからん」と思われない。

(追記 2009.7.18:TLの取得だけならSafariでもできるみたい)

モチベーションを維持するために公開とかしてみるけど、使えるもんじゃないです。サポートしている機能は少ないし、UIは死ぬほど拙いです。何が起こってる/起こったかを通知しません。結果が表示されるまで何もわかりません。エラーが起こったら、何か表示されるかもしれませんよ!(エラーの処理を確認してない)

はっきり言えることは「あなたはこれを使うべきではない」ということです。

これに関してはこちらでメンテナンスしていきます。

機能

  • タイムラインの取得
  • リプライ(mentions)の取得
  • post(status update)の送信
  • リプライ(@)の送信
  • RTの送信

使い方

ログイン

  • Settingボタン→IDとパスワードを入力→Loginボタン
  • 入力されたIDとパスワードが正しいかはこの時点で確認してません。
  • Settingボタンをもう1回押すと、Loginボタンとかが消えますが、ログアウトしているわけではないです。
  • この設定は保存されません。ウィンドウを閉じたり、更新ボタンを押したりしたら消えます。

タイムラインの取得

  • friendsボタンを押すとタイムラインを取得します。最初は50件取得します。2度目以降は取得してない分を取得します。
  • mentionsボタンを押すとリプライを取得します。

POST

  • テキストエリアにメッセージを入力して、updateボタンを押すと送信されます。メッセージが消えたら、たぶん送信されてるはずです。
  • 表示されているタイムラインのユーザ名をクリックすると、「@user」がテキストエリアに入力されます。メッセージを送信すれば、リプライとして送信されます。
  • 表示されているタイムラインのメッセージをクリックすると、「RT @user: メッセージ」がテキストエリアに入力されます。これは普通のPOSTとして送信されます。

情熱が続いてたらサポートするであろう機能

  • ふぁぼったりする
  • エラーメッセージの表示
  • 自動更新
  • 設定部分の見栄えをよくする
  • 最大取得数の設定
  • 画面のホワイトアウト

情熱が続いていても絶対しない機能

  • アイコンの表示
  • UIをカッコよくする

ダウンロード

twitter_js.zip

ポリシーは、楽しく、若干の後ろめたさを感じながら仕事をサボるためのクライアントです。それに反するような機能はサポートしません。

カテゴリー: プログラミング タグ: ,

GaucheでFizzBuzz

新しい言語を学ぶと、だいたいHello World!からやります。んで、分岐と繰り返しの文法を覚えたらFizzBuzzを書いてみることにしています。今回はLISPの一種であるGaucheでやってみました。Scalaをやる上で関数型言語のノウハウを勉強するためにGaucheに手を出したのです。

FizzBuzz問題は簡単です。1から100までをカウントアップして、3の倍数の場合はFizzを出力し、5の倍数の場合はBuzzを出力し、3の倍数かつ5の倍数の場合はFizzBuzzを出力し、それ以外の場合は数値をそのまま出力するだけです。分岐と繰り返しがわかっていれば作れるプログラムです。

ただ、関数型言語の場合、繰り返しは再帰を指すようです。つまり、CやJavaでfor文を使う部分を再帰で行うわけです。繰り返し構文(実際は関数)を使ったら、コンセプトから外れるので、再帰でFizzBuzzということになります。

(define (fizzbuzz n)
  (if (< 100 n)
      '()
      (cons (cond
       ((= (remainder n 15) 0) (string-copy "FizzBuzz"))
       ((= (remainder n 5) 0) (string-copy "Buzz"))
       ((= (remainder n 3) 0) (string-copy "Fizz"))
       (else n)) (fizzbuzz (+ n 1)))))

かなり印象が変わりますね。シンプルするために、1から3までをカウントアップする関数を作ってみます。

(define (countup n)
  (if (< 3 n)
      '()
      (cons n (countup (+ n 1)))))

これは以下のコードと同じです。

 (cons 1 (cons 2 (cons 3 '())))

関数の内側から評価されるので、以下のようになります。

  1. (cons 3 ‘())が評価され、(3)が返る。
  2. (cons 2 ‘(3))が評価され、(2 3)が返る。
  3. (cons 1 ‘(2 3))が評価され、最終的な結果は(1 2 3)

手続き型の言語だと、馴染みがないので理解しづらいところです。

カテゴリー: プログラミング タグ: ,

ScalaでServletを使ってみる

Scalaを仕事をサボって勉強しています。ScalaはJava VM上で動くオブジェクト指向+関数型言語で、Javaクラスを使ったり、ScalaクラスをJavaで使ったりできるんですね。とても楽しいです。

インタープリタで対話的に実行できるし、スクリプト言語のように実行できるし、コンパイルして実行することもできます。実行環境は、MacPortsで入れることができるので、インストールもお手軽にできます。

んで、Scalaをコンパイルすると、.classファイルができる。つまり、Javaをコンパイルした時と同じモノが出来るわけです。このおかげで、JavaとScalaはシームレスな連携をするので、ScalaでWebアプリを実装できるんじゃねーかと思い、やってみた。

環境は以下の通り。

  • Mac OS 10.5.6
  • Eclipse 3.4.2 + Scala Eclipse Plugin 2.7.3 final
  • JVM 1.6.0
  • Tomcat 6.0.18

EclipseはJava EEの開発なので、WTPが入ってるヤツです。Tomcatでなく、GlassFishあたりでも動きますが、スタンダードなのを使っています。Windowsのことは知りませんが、同じようにすれば動くんじゃないですかね?

Tomcatを入れる

Tomcatをダウンロードします。

http://tomcat.apache.com/index.html

解凍して適当なディレクトリに置きます。以下のような感じです。

~/tomcat/apach-tomcat-6.0.18

http://localhost:8080/で動作確認だけできればいいので、落としてコピーして終わりです。コマンド叩いてメンドーなことはしません。

Eclipseにプラグインを入れる

Scala Eclipse Pluginは、以下のアップデートサイトから手に入れることが出来ます。

http://www.scala-lang.org/scala-eclipse-plugin

Scala実行環境もこれで入りますが、PATHは通らないので、対話的に実行したりする場合は別途MacPortsとかしたほうがいいかもしれません。

Scalaのプラグインを入れたので、Scalaプロジェクトが作れるようになっているはずです。

作成

プロジェクトの構成ですが、Scalaプロジェクト+動的ウェブプロジェクトという構成にします。動的ウェブプロジェクトのソースフォルダにScalaのソースが作成できないため、Scalaプロジェクトを別にしています。パスを通せば問題ないので、この構成作っていきます。

新規作成からScala Projectを作ると、見慣れた感じのプロジェクトができるので、ソースフォルダにパッケージを作ります。なんでもいいんですが、「jp.my.webapp.servlet」という名前で話を進めます。

このパッケージにScalaソースファイルを作成します。javax.servlet.httpのクラスを使用するため、ビルドパスにサーバ・ランタイムを設定する必要があります。

package jp.my.webapp.servlet

import javax.servlet.http.{
  HttpServlet,
  HttpServletRequest => HSReq,
  HttpServletResponse => HSResp
}

class TestServlet extends HttpServlet {

  override def doGet(req :HSReq, resp :HSResp) {
    val message =
      <HTML><HEAD><TITLE>Hello, Scala</TITLE></HEAD>
      <BODY><P>Hello, Scala with Servlet</P></BODY></HTML>
    resp.getWriter().print(message)
  }
}

import文は{}で括ることによって、パッケージから複数のクラスをimportできます。「クラス名=>省略名」という書き方も出来ます。ScalaではXMLリテラルを書くことができ、整形文で文法にエラーがなければHTMLもリテラルにできます。XMLリテラルは、scala.xml.Elem型で、printメソッド内でtoStringにより文字列化されます。

これでScalaによるServletは終了です。次は、webプロジェクトです。

こちらはさらに簡単です。パスを通して、web.xmlを書いて終わりです。プロジェクトはwebappという名前で作りました。

パスは、Scalaプロジェクトとscala-library.jarに通します。Java EE Module Dependencies(日本語化してると「Java EE モジュール依存関係」だかです)にプロジェクトとjarを追加します。scala-library.jarはScalaプロジェクトを見ればパスが書いてありますが、あまりに深いところにあったので、MacPortsで入れた実行環境の方に通してしまいました。

web.xmlにservletとservlet-mappingを記述します。

<servlet>
  <servlet-name>HelloScala</servlet-name>
  <servlet-class>jp.my.webapp.servlet.TestServlet</servlet-class>
</servlet>
<servlet-mapping>
  <servlet-name>HelloScala</servlet-name>
  <url-pattern>/sayHello/</url-pattern>
</servlet-mapping>

あとは新規作成でサーバを作り、解凍したTomcatのディレクトリを指定すると、サーバビューにTomcatが登録されるので、webappプロジェクトをドラッグ&ドロップで追加します。そして、サーバを起動し、ブラウザでhttp://localhost:8080/webapp/sayHello/へアクセスしてみます。

アクセスした様子

アクセスした様子

ウェブフレームワークは使わずにやってみましたが、StrutsやSpring、DWRといったものを使ってもよさそうです。Scalaのウェブフレームワークもあるようですね。最初はDWRでAjaxを目論んでいたんですが、クラスパス周りでハマり、エラーの切り分けがよくわからないので、フレームワークなしとなりました。

ソースに関しては、IBMのdeveloperWorksとほぼ同じです。実装については、このエントリーよりも優しく書いてあるので、そちらを参考にした方がいいと思います。

参考:多忙な Java 開発者のための Scala ガイド: Scala とサーブレット

カテゴリー: プログラミング タグ: , ,

Javaで末尾の全角/半角スペースをトリムする

夜も遅いっていうか、もう朝なので簡単にメモ。

Javaで文末の全角半角スペースをトリムするという、実に簡単な実装なんだけど、いくつか実装方法があって、性能としてどれが最も優れているか気になったので、テストしてみました。実装方法は以下の3つ。

  1. 正規表現(String#replaceAll)
  2. StringBuilderで後方から走査して、半角/全角スペースの場合は削除する
  3. char配列で後方から走査して、半角/全角スペースでない場合は新しいchar配列に格納する。

上記の実装をstaticメソッドにして、それぞれ1000回ずつ呼び出して、プロファイルしてみました。環境はMac OS 10.5、JDK1.5、NetBeans 6.5です。

結果は、3>2>1の順で高速に動きました。1は正規表現を使っているので最も遅かったですね。

  1. 正規表現:7.85ms
  2. StringBuilder:2.45ms
  3. char配列:0.464ms

正直、StringBuilderとchar配列でここまで差が出るとは思わなかった。配列とStringでインスタンスを2回作るので2と3はそんなに差が出ないと思ったんですけどね。何事もやってみるもんです。

以下はソース。乱暴に書いてます。

public class Main {

private static final String VALUE = "あいう    ";

    public static void main(String[] args) {

        for(int i = 0; i < 1000; i++) {
            trim1(VALUE);
        }

        for(int i = 0; i < 1000; i++) {
            trim2(VALUE);
        }

        for(int i = 0; i < 1000; i++) {
            trim3(VALUE);
        }
    }

    public static String trim1(String value) {
        return value.replaceAll("( | )+\\z", "");
    }

    public static String trim2(String value) {

        StringBuilder sb = new StringBuilder(value);

        for(int i = sb.length()-1; 0 <= i; i--) {
            char c = sb.charAt(i);
            if(c == ' ' || c == ' ') sb.deleteCharAt(i);
            else break;
        }
        return sb.toString();
    }

    public static String trim3(String value) {

        char[] ary = value.toCharArray();
        char[] trimed = new char[ary.length];

        for(int i = ary.length-1; 0 <= i; i--) {

            if(ary[i] == ' ' || ary[i] == ' ') continue;

            else trimed[i] = ary[i];

        }

        return new String(trimed);
    }
}
カテゴリー: プログラミング タグ: ,