2013年6月26日水曜日

自分用Google ReaderをNode.js+MongoDBで作成。オープンソースだよ。

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

はじめに


 なんかGoogle Readerが6末に終わるらしいんであー代替ねー。めんどいなー。自分で作るかー。という感じです。完全に俺得です。せっかくなんでNode.jsとか使うかーと。でDB周りはMongoDBでええかーとか適当に。勉強がてら。粛々と。のんびりと。

紹介


 Google Readerの代替という事でどういう名前にしようかと思ったんですけどまぁ"やぎ"だし
Goat Readerでいいやという事にしました。とりあえず触れる環境があるんで、メンドイ方は以下を見て下さい。モバイルにはまだ対応してません。Twitterでログインして使ったりも出来ます。ログインしていない状態でアクセスするとAnonymouseモードとなります。


できること


 繰り返しになりますが適当に作った上自分用なので完全に俺得機能しか乗せる気がありません。更に別に完成してないんでまぁアレです。今のところできることは大体以下です。

  • Twitter認証でのユーザ登録
  • RSSフィードの登録
  • RSSフィードの削除
  • Google Readerのsubscriptions.xmlのインポート
  • 登録したRSSフィードのクロールして未読アイテムを追加
  • 表示モード2つ

今後追加するかなーという機能としては以下です。
  • ふぁぼ、あとで読む機能
  • 未読、既読、すべて表示切り替え
  • 新しい順、古い順の表示
  • TwitterのTLのURL付きツイートをフィード化
  • TwitterのTLからhotを抽出
  • フィードへのカテゴリ設定
  • アイテムへのタグ追加
  • アイテムへのコメント追加
  • モバイル対応
  • Typescript化する
  • gruntを導入する

ソース


 ソースはGithubで公開しています。pullreq歓迎です。色々まだ足りてません。
sys1yagi/Goat-Reader

使う


 単純に使うだけなら簡単です。Twitterでログインして、Settingsにフィード登録してしばーらく待つとアイテムが増えているはずです。Google Readerのsubscriptions.xmlのインポートも概ね出来ます。

導入の手順


 README.mdを参照して下さい->https://github.com/sys1yagi/Goat-Reader

内部的な奴


 チョイスは結構適当なんですが、まぁとりあえずnpmやbowerで持ってこれるヤツを中心に利用しています。とりあえず列挙してみます。

フロントエンド


モジュール 説明
bootstrap.css Twitter社が提供してる良い感じのCSSですね。便利。
jquery 言わずと知れたjQuery。言うまでもないっすね。モバイル向けにはZeptoを使うと思います。
flight Twitter社製のコンポーネントベースのフレームワークです。各DOMの処理はこのFlightのコンポーネントを使って動作させています。
requirejs Flightを使う為に必要なので入れてます。CommonsJSの何かの実装だった気がします。AMDかな?この辺はFirefox OSなどHTML5製アプリを作る上で必須なパラダイムだよねーと。
es5-shim これもFlightを使う為のライブラリです。ECMAScript5の機能の一部を実装しているんだとか。
hogan Twitter社が提供しているライブラリで、scriptタグの中にHTMLテンプレートを記述して、jsからDOM生成に使えるというヤツです。JSPとかPHPに似てるかも。
datejs 日付の文字列フォーマットを良い感じにするライブラリです。DateFormatくらいは標準で欲しいんだけどなぁ・・・

バックエンド


モジュール 説明
Node.js nodeかわいいよnode
MongoDB mongoかわいいよmongo
mongoose Node.jsからMongoDBに良い感じにアクセスする為のモジュールです。JSON形式でテーブルスキーマを定義してモデル作ったり出来ます
express Node.jsのアプリケーションフレームワーク。割と色々やってくれる。助かる。
jade HTMLテンプレエンジン。文法が独特でちょっとつらい。HTML2Jadeとかで変換して使ったりした
fibers 非同期処理系をブロッキングする為のモジュール。Node.jsは非同期コールバックネスト地獄に陥りやすいのよね。
cron Node.js内でクロール処理をさせるのに使った。まんまcron
jquery サーバサイドでも活躍!でもテスト動作させる為にしか使ってないかも?
mocha テスティングフレームワーク。あんまりテスト書いてないけど。ブロッキングの要るテストが書きやすいのでjasmineやめてこちらにした。
passport TwitterのOAuthに使った。これは便利。Facebookとかもあるらしい
passport-twitter これな。
xml2json XMLをjsonに変換してくれる。助かる。
forever Node.jsのプロセス管理ツール。daemon化したりあれこれ

注意点


・スケールとか全く考えてないよ。ユーザ増えると爆死する気がするので自分で鯖立てて個人利用を推奨します。

終わりに


 基本的に"Node.jsでアプリ作りたい"というのがメインの動機です。で、Node.js使った感想は"剥き出しだなぁ"と。Pythonのmod_pythonだとかと同じでリクエストハンドラと云々がアレ程度にしか用意されてないんで上に色々作る必要があるなぁと。Express使ってもJadeと連携とかSession StoreがどうとかFile uploadが便利とかはあるけど、リクエストハンドラ周りとか自分で色々アレしたんでまだまだかなーと。でも多分既にその辺カバーするフレームワーク出てるだろうなーと。いずれその辺に対応していくかもしれないなーと。

 npmやbowerで色々持ってくる仕組みはとてもおもろいと感じた。これってMavenとかGradleにかなり近いはずなんであーMaven使っておけばなぁとか思った。これからはGradleの時代なのでGradleでJava系プロジェクトは扱うようにしていきたいなぁーとか。今回はnpmとbowerを個別で色々触っていたけど次回何かやるならYeomanを使おうかなと思う。

で、

やっぱプログラミングって楽しいなぁ

と思った。忙しくて大変だったけど。

2013年6月7日金曜日

シャフラーズ問題の回答 @sys1yagi編

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

 CodeIQで結城 浩さんが出題していた断片情報からデータを復元しよう!に挑戦したので解答をポストします。結果は満点でした。よかつた。今回はすごく簡単でした。30分程度で書き散らかしたのでソースは汚いです。
 

問題


 問題は以下の様なデータから、 aa = 10とかいった風にペアのデータを突き止めろ、というモノでした。てっきりデータ数が膨大だったりするのかと思ってたら1000件程度。サクーッといけました。
 10 22 24 = aa bb cc
 53 33 10 = dd ee aa
 24 33 53 = bb ee dd
 

解答ソース


 上記フォーマットのデータファイルを食わすと、ペアを解析してprintlnする感じのソースです。最初データを見たとき、「連立方程式っぽいな」と思ったんでそういう感じで解くようにしました。
 10 22 24 = aa bb cc
 53 33 10 = dd ee aa
 24 33 53 = bb ee dd
というデータがあった時、aaに着目すると、1行目ではaaの候補は10, 22, 24になります。
んで、2行目と比較すると53, 33, 10のうち10だけが1行目に存在してます。つー事でaa=10だ!という事。そんだけです。
 10 22 24 = aa
 53 33 10 = aa
 ↓重複する数字だけ取り出す
 10 = aa
 あとはデータを読み込んでkey,valueのリストを保持するShufflerクラスのリストを作って、名前の一覧を抽出し、名前に着目しながら1件ずつ特定していく感じ。特定された名前と数字はShufflerクラスにフィードバックして、Shuffler内でそれらの値をリストから削除する、という構造です。今見返すと実装大分汚いですが、大体そんな感じです。

package jp.dip.sys1.shufflers;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Main {
    static class Shuffler {
        private List<String> mIdList = new ArrayList<String>();
        private List<String> mNameList = new ArrayList<String>();

        public Shuffler(String[] ids, String[] names) {
            for (String id : ids) {
                this.mIdList.add(id);
            }
            for (String name : names) {
                this.mNameList.add(name);
            }
        }

        public List<String> getIdList() {
            return mIdList;
        }

        public List<String> getNameList() {
            return mNameList;
        }

        public void found(String id, String name) {
            mIdList.remove(id);
            mNameList.remove(name);
        }
        public User getUser(){
            if(mIdList.size() == 1 && mNameList.size() == 1){
                User user = new User(mIdList.get(0), mNameList.get(0));
                found(mIdList.get(0), mNameList.get(0));
                return user;
            }
            return null;
        } 
        @Override
        public String toString() {
            if(mIdList.isEmpty() && mNameList.isEmpty()){
                return null;
            }
            StringBuilder sb = new StringBuilder();
            for (String id : mIdList) {
                sb.append(id + " ");
            }
            sb.append("= ");
            for (String name : mNameList) {
                sb.append(name + " ");
            }
            return sb.toString();
        }
    }

    static class ShufflerFactory {
        public static Shuffler createShuffler(String line) {
            if (line == null) {
                return null;
            }
            String[] pair = line.split(" = ");
            String[] ids = pair[0].split(" ");
            String[] names = pair[1].split(" ");
            Shuffler shuffler = new Shuffler(ids, names);

            return shuffler;
        }
    }

    static class User {
        private String mId;
        private String mName;

        public User(String id, String name) {
            mId = id;
            mName = name;
        }

        public String getId() {
            return mId;
        }

        public String getName() {
            return mName;
        }

        @Override
        public String toString() {
            return mId + " = " + mName;
        }
        @Override
        public int hashCode() {
            return toString().hashCode();
        }
        @Override
        public boolean equals(Object obj) {
            return this.hashCode() == obj.hashCode();
        }
    }

    static class Matcher {
        private String mTargetName = null;
        private List<String> mCandidateIds = new ArrayList<String>();
        private User mUser = null;

        public Matcher(String targetName) {
            mTargetName = targetName;
        }
        public boolean identify(Shuffler shuffler) {
            for (String name : shuffler.getNameList()) {
                if (mTargetName.equals(name)) {
                    if (mCandidateIds.isEmpty()) {
                        for (String id : shuffler.getIdList()) {
                            mCandidateIds.add(id);
                        }
                    } else {
                        List<String> newCandidateIds = new ArrayList<String>();
                        for (String id : shuffler.getIdList()) {
                            if(mCandidateIds.contains(id)){
                                newCandidateIds.add(id);
                            }
                        }
                        mCandidateIds = newCandidateIds;
                    }
                }
            }
            if(mCandidateIds.size() == 1){
                mUser = new User(mCandidateIds.get(0), mTargetName);
                return true;
            }
            else{
                return false;
            }
        }

        public User getUser() {
            return mUser;
        }
    }

    List<Shuffler> loadShufflers(String path) {
        List<Shuffler> list = new ArrayList<Main.Shuffler>();
        FileInputStream fis = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            fis = new FileInputStream(path);
            isr = new InputStreamReader(fis);
            br = new BufferedReader(isr);
            String line = null;
            while ((line = br.readLine()) != null) {
                Shuffler shuffler = ShufflerFactory.createShuffler(line);
                if (shuffler != null) {
                    list.add(shuffler);
                } else {
                    System.out.println("error.");
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                br.close();
                isr.close();
                fis.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return list;
    }

    List<String> treatName(List<Shuffler> list) {
        List<String> names = new ArrayList<String>();
        for (Shuffler shuffler : list) {
            for (String name : shuffler.getNameList()) {
                if (!names.contains(name)) {
                    names.add(name);
                }
            }
        }
        System.out.println(names.size());
        return names;
    }

    public void matching(String path) {
        List<Shuffler> list = loadShufflers(path);
        List<User> users = new ArrayList<Main.User>();
        List<String> names = treatName(list);
        for (String name : names) {
            Matcher matcher = new Matcher(name);
            for (Shuffler shuffler : list) {
                if (matcher.identify(shuffler)) {
                    break;
                }
            }
            User user = matcher.getUser();
            if (user != null) {
                if(!users.contains(user)){
                    users.add(user);
                }
                for (Shuffler shuffler : list) {
                    shuffler.found(user.getId(), user.getName());
                }
            }
        }
        for(Shuffler shuffler : list){
            User user = shuffler.getUser();
            if(user != null){
                if(!users.contains(user)){
                    users.add(user);
                }
                for(Shuffler shuffler2 : list){
                    shuffler2.found(user.getId(), user.getName());
                }
            }
        }
        Collections.sort(users, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return Integer.parseInt(o1.getId()) - Integer.parseInt(o2.getId());
            }
        });
        for (User user : users) {
            System.out.println(user);
        }
        List<String> remains = new ArrayList<String>();
        for(Shuffler shuffler : list){
           String remain = shuffler.toString();
           if(remain != null){
               if(!remains.contains(remain)){
                   remains.add(remain);
               }
           }
        }
        for(String remain : remains){
            System.out.println(remain);
        }
    }
    public static void main(String[] args) {
        Main main = new Main();
        //main.matching("shufflers/sample.txt");
        main.matching("shufflers/shufflers.txt");
    }
}