watermint.org

Takayuki Okazaki’s blog

id:Yoshioriさんの作ってるやつの別実装。id:taichitaichiさんのとも別の視点で作ってみました。ポイントは次のような感じです。

  • 一定時間しか保持しない、というよりは一定時間経過したエントリを隠す。
  • タイムアウトしたエントリの自動削除機構までは含まない(Mapの使われ方によって削除のタイミングはニーズが読めない)。ただし、明示的な削除方法は提供する。
  • 見えなくなる時間がわりと正確。ScheduledExecutorServiceを使って、正確なタイミングで削除されることを信頼しない。

この実装では、毎回Mapの操作に対してその時点でのスナップショットを作成します。snapshotの作成はわりと高価な作業ですが、それよりも時間に対して正確であることを目標としています。経験的に、Mapに数万エントリも入っていることはふつうの用途としてはまれなので、そこは目をつぶります。スレッドセーフとなるように気をつけましたが、テストはしていません。あ、全体的にテストはしていません。

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * 指定時間以上経過したエントリを隠すマップ.
 *
 * @author takayuki
 */
public class TimeoutMap<K, V> implements Map<K, V>, Serializable {
    private final long timeout;
    private ConcurrentHashMap<K, ValueAndTime<V>> map
            = new ConcurrentHashMap<K, ValueAndTime<V>>();

    final class ValueAndTime<V> implements Serializable {
        private V value;
        private long time;

        public ValueAndTime(V value, long time) {
            this.value = value;
            this.time = time;
        }

        public long getTime() {
            return time;
        }

        public V getValue() {
            return value;
        }
    }

    final class EntrySetImpl<K, V> implements Map.Entry<K, V>, Serializable {
        private K key;
        private AtomicReference<V> value;

        public EntrySetImpl(K key, V value) {
            this.key = key;
            this.value = new AtomicReference<V>(value);
        }

        public K getKey() {
            return key;
        }

        public V getValue() {
            return value.get();
        }

        public V setValue(V value) {
            return this.value.getAndSet(value);
        }
    }

    /**
     * コンストラクタ.
     * @param timeout タイムアウト(ミリ秒).
     */
    public TimeoutMap(long timeout) {
        this.timeout = timeout;
    }

    private ConcurrentHashMap<K, ValueAndTime<V>> snapshot() {
        long t = System.currentTimeMillis() - timeout;
        ConcurrentHashMap<K, ValueAndTime<V>> snapshot
                = new ConcurrentHashMap<K, ValueAndTime<V>>(map);

        for (K key : snapshot.keySet()) {
            if (snapshot.get(key).getTime() > t) {
                snapshot.remove(key);
            }
        }

        return snapshot;
    }

    private Collection<V> snapshotOfValues() {
        Collection<V> values = new ArrayList<V>();
        Map<K, ValueAndTime<V>> snapshot = snapshot();

        for (K key : snapshot.keySet()) {
            values.add(snapshot.get(key).getValue());
        }
        return values;
    }

    /**
     * タイムアウトしたキーのエントリを削除.
     */
    public void gc() {
        map = snapshot();
    }

    public int size() {
        return snapshot().size();
    }

    public boolean isEmpty() {
        return snapshot().isEmpty();
    }

    public boolean containsKey(Object key) {
        return snapshot().containsKey(key);
    }

    public boolean containsValue(Object value) {
        return snapshotOfValues().contains(value);
    }

    public V get(Object key) {
        ValueAndTime<V> v = snapshot().get(key);
        return v == null ? null : v.getValue();
    }

    public V put(K key, V value) {
        ValueAndTime<V> prev = map.put(key,
                new ValueAndTime(value, System.currentTimeMillis()));
        return prev == null ? null : prev.getValue();
    }

    public V remove(Object key) {
        return map.remove(key).getValue();
    }

    public void putAll(Map<? extends K, ? extends V> map) {
        for (K key : map.keySet()) {
            put(key, map.get(key));
        }
    }

    public void clear() {
        map.clear();
    }

    public Set<K> keySet() {
        return snapshot().keySet();
    }

    public Collection<V> values() {
        return snapshotOfValues();
    }

    public Set<Entry<K, V>> entrySet() {
        Set<Entry<K, V>> entryset = new HashSet<Entry<K, V>>();
        Map<K, ValueAndTime<V>> snapshot = snapshot();

        for (K key : snapshot.keySet()) {
            entryset.add(new EntrySetImpl(key, snapshot.get(key).getValue()));
        }

        return entryset;
    }
}

追記: スレッドセーフにしたいなあと思って書いているといろいろ、ぼろが見つかりますね。gc()が実行されている間、put(Object key, V value)と、remove(Object key)の実行をブロックしないとエントリの欠落や削除漏れが出そうです。

この間買ってかなり気に入っているContour Design Shuttle Pro2。唯一気に入らないのが、このShuttle制御用の常駐アプリのアイコン。個人的に、Macにせよ、Windowsにせよ常駐系のアイコン表示はなるべくシンプルにしておきたいので、表示させるのは必要最小限にします。Windowsだとタスクトレイに多くて3つ表示させるぐらいですかね。MS IMEのメニューがタスクトレイに入らないのが気に入りません。Macはいまのところ8つです。Macの場合はことえりにしろATOKにしろ同じカテゴリにはいるし、アイコンもシンプルな単色で気持ちいいです。Contour Design Shuttle Pro2のアイコンも本当は出したくなくて設定を確認しましたがどうやらなさそう。それに常駐アプリがないとアプリケーションごとの設定切り替えができなくて不便です。
Original ContourShuttle menu icon
アイコンのデザインで気に入らないところは、形よりも色です。色はほかのMac OS Xアイコンと調和がとれるようにシンプルにしてほしい。そこで、/Applications/Contour Shuttle.app/Contents/Resources/ContourShuttleHelper.app/Contents/Resources/ContourShuttleMenu.app/Contents/ResourcesにあるShuttleMenu.tiffを編集してほかのアイコンにテイストを合わせることにしました。Mac OS X標準のアイコンは15%黒のアンチエイリアス付きで描画されているのでそれをまねして書きます。
Modify CountourShuttleMenu icon
サイズはほかのアイコンに合わせて1ピクセル小さくしました。ベースラインも1ピクセル下げてあります。
Modified ContourShuttle menu icon
これで完成。テイストが合ったのでだいぶ気にならなくなりました。一件落着。
一応、作ったアイコンをおいておきます。

9月から就職活動をしてみた経験と、アルゴリズムデザインに載っている完全マッチングアルゴリズムからの考察です。今回の転職では数社のエージェント(民間職業紹介業者)に協力いただいています。そのエージェントの働き方と、安定マッチングアルゴリズムの挙動とは興味深い類似性があることに気づきました。安定マッチングアルゴリズムは上述のアルゴリズムデザインという本で最初に紹介されているアルゴリズムです。アルゴリズムに興味がある方は、重くて高価な本ですが、ぜひ読んでみてください。高いので図書館で借りて読むといいと思います(なければリクエストすると買っていただけることがあります)。
_DSC4538
安定マッチングとは大まかには、企業と就職希望者のようにお互いに自由な希望順位をもった者同士を安定的にマッチングさせることです。ここで、安定的といっているのは、ある希望者Aさんが、企業XにAさんの希望順位通り内定をもらったとします。その後、希望者Bさんの話を聞いていて気が変わって企業Yに興味を持つようになり、内定を辞退するようなことになると、収拾が付かなくなってしまいます。そこで、問題を捕らえるに当たって、希望順位は不動とすることで、マッチングの結果が安定します。
_DSC4588
アルゴリズムデザインでは、安定マッチングの例を男女の結婚マッチングとして紹介しています。これは、就職希望の例だと企業Xが複数の希望者を採用しても良いので、アルゴリズムとして設計する難易度が上がってしまうため、男女の結婚のように1対1のペアとしてマッチングさせるアルゴリズムを最初に紹介しています。このアルゴリズムはGaleとShapleyによるものなので、GSアルゴリズムと紹介されています。
昨今の経済状況では、ある企業の採用枠は高々1人のことが多いので、転職エージェントはGSアルゴリズムが使えそうです。
_DSC4724
GSアルゴリズムを使うとして、転職エージェントが行うことは次のようになります。

  1. 最初に、同数の企業と希望者がいて、まだ誰も内定もオファーももらっていないとします。
  2. 企業xが優先順位としてもっとも高く、まだオファーを出していない希望者aを選びます。希望者aが別の企業yから内定をもらっていない場合、希望者aは内定を受諾します。
  3. 希望者aが別の企業yから内定をもらっている場合、希望者aが企業xをより好むなら企業yの内定を辞退し、企業xの内定を受諾します。希望者aが企業yをより好むなら、企業xはオファーを出しません。
  4. すべての就職希望者にオファーを出していない、採用枠が埋まっていない企業が存在する間このプロセスを繰り返します。

これでしばらく操作を続ければ、企業と採用希望者の安定マッチングが得られます。
_DSC4733
このGSアルゴリズムを使ったマッチングは、O(n2)で終了する優れたアルゴリズムです(その証明など詳しくはアルゴリズムデザインで確認してください)。安定マッチングが得られてよしよしと思うところですが、このアルゴリズムでは就職希望者の優先順位よりも企業側の優先順位がより優先されます。企業の優先順位と、就職希望者の優先順位がうまく合致する場合にはフェアな安定マッチングとなりますが、そうではない場合企業側の優先順位に基づいて安定マッチングが完了し、就職希望者は不満を持つかもしれません。
_DSC4741
不況のさなか、就職希望者側にアンフェアに感じるマッチングであったとしても、満足すべきだろうと言い聞かされるかもしれません。しかしながら、就職希望者側の希望条件すらヒアリングしない転職エージェントが今回多かったような印象を持ったのは、やはり転職エージェントにとってのスポンサーが企業側だからのように見えて仕方がありません。
企業側とはネゴシエーションをしないし、就職希望者側を説得にかかるエージェントはまさにGSアルゴリズムでの安定マッチングを経験的に実践しているのでしょう。そういうエージェントに限って「あなたの将来を考えて」などと発言するのです。

昼ご飯と晩ご飯というエントリで先週書きましたが、一応、まだ野菜中心生活です。飲み会があったり、急いでいる日はレトルトだったりしますが。
Dinner of Dec 15th
15日の晩ご飯。コンソメ、ブロッコリー、白菜、にんじん、大豆、ブナピー、タマネギ、ほうれん草。
Dinner of Dec 18th
18日の晩ご飯。コンソメ、ブロッコリー、長ネギ、白菜、ジャガイモ、卵。
ナイシトール
あとついでにナイシトールを大人買い。
ex walker
ex walker (エクサウォーカー)というワコールが出しているエクササイズ向けのやつも買いました。
Lunch of Dec 19th
19日の昼ご飯。あまり時間がなかったのでカップ麺。水を入れて電子レンジでできるというので、便利だなあと思ってたら見事に吹き出して電子レンジのなかもかなりドロドロ状態に・・・。ああ・・。ちゃんと説明通りの加熱時間にしたつもりでしたが。今度からは安全のためお湯を沸かすことにします。掃除で余計時間かかりますからね。
Dinner of Dec 20th
20日の晩ご飯。コンソメと白菜、ほうれん草。白菜が安かったのでたくさん使いました。
Breakfast of Dec 21st
21日の朝ご飯。おいしいリンゴをいただいたので最近は朝に食べています。
Lunch of Dec 21st
21日の昼ご飯。ペヤング焼きそば。ペヤングを食べるのは実は人生初。少なくとも記憶の範囲では地元には売っていなくて(日清の工場も近くにあったせいかもしれませんが)、レトルトの焼きそばといえばUFOでした。ペヤングという名前は知ってましたが、初めてみたのが上京した8年前。そのときから、そのうち食べようと思っていましたが、ようやくそのときが来ました。最近のUFOは湯切りのギミックが進化しているので、こういう昔ながらのタイプはちょっと新鮮でした。
Dinner of Dec 21st
21日の晩ご飯。コンソメ、ブロッコリー、大豆、タマネギ、ブナピー。似たような感じの食事が続きますが、あんまり気にしません。
Lunch of Dec 22nd
今日の昼ご飯。コンソメが切れたのでカレールウで。カレールウ(ジャワカレー・辛口)、にんじん、じゃがいも、白菜、キャベツ、タマネギ、ほうれん草、ブナピー、アスパラガス。
不思議なことに、量的にはかなり食べているんですが、外食すると以前はぺろりと食べられた量が、いまでは多く感じるようになりました。ダイエット目標まであと脂肪5kg分(36,000kcal)減らさなければならないので、まだまだです。でも、ここ数日で移動平均でみても2kgは減りました。筋肉が減ってるだけかもしれませんが。

Premiere ProではMotion-JPEGを読み込みできないようなので、仕方なくあらかじめPremiereで読み込めるようにQuickTime形式に変換することにしました。でもたくさんファイルがあると一つずつ変換するのは面倒くさいので、AppleScriptを作ることにしました。用途は違えど、だいたい同じような需要はあるもので、検索したらすぐ出てきました。今回は「ムービーをQuickTime形式へ変換するAppleScript (影羽連盟)」を参考にさせていただきました。ファイル名の変換のところで、ウチでは動かないところがあったのでその部分を修正しています。

on run
	activate
	set itemList to choose file with prompt "ムービーを選択してください" default location (path to home folder) with multiple selections allowed without invisibles
	my saveAsQuickTimeMovie(itemList)
end run

on open inputList
	my saveAsQuickTimeMovie(inputList)
end open

on saveAsQuickTimeMovie(movieList)
	activate
	tell application "QuickTime Player"
		launch
		activate
		stop every document
		close every document saving no
		repeat with aMovie in movieList
			open aMovie
			set new_file to (aMovie & ".mov") as string
			save self contained document 1 in new_file -- 独立再生形式で保存
			close document 1 saving no
		end repeat
		quit
	end tell
end saveAsQuickTimeMovie

オリジナルのソースでは独立形式にするか、参照形式にするかを選択しますが、ウチではとりあえず独立形式にしたかったのでダイアログを無くしました。あと、ファイル名を拡張子だけ変更するためにQuickTimeのドキュメント名からファイル名を取得していたのですが、Ricoh GX100のムービーだとドキュメント名が空になってしまい、うまく動作しませんでした。このため、新規ファイル名は強引に元ファイル名に “.mov”を追加するだけにしました。元スクリプトでは “.” ドットを探して拡張子を変えていましたが、このあたりでどうもディレクトリ名に “.”が入ってるとダメなケースがあるようでしたので、その問題も回避できるようになりました。

Premiere Pro CS4にはD90のDムービー(AVI形式)が直接インポートできないようです。これにはかなり困っています。わざわざQuickTime Proとかを使ってH.264等に変換しないとインポートできないんです。After Effects CS4なら大丈夫なのに・・・。もちろん、Final Cut Express 4でも大丈夫。
Premiere Pro CS4 doesn't support Nikon D90's D-movie compression format
体験版ではAdobe以外の提供しているコーデックは使用できない、という制限があるので、体験版を使っていたときはてっきりこの制限に引っかかってインポートできないのだと思っていましたが、製品版を入れてもダメ。一度体験版時のバイナリをアンインストールして、入れ直してもダメ・・。調べてみると、デジカメのムービーにありがちなMotion-JPEGで圧縮されたファイルはサポートしていないみたい。うすうす気づいていましたが、Premiereは高価なHDカム向けのようですね。仕方ないので、D90で撮ったムービーはFinal Cut Expressで編集します。

12月19日に発売されたAdobeのCreative Suite 4 Production Premium。ビックカメラで取り置きしてもらって、昨日買ってきました。
Adobe Creative Suite 4 Production Premium for Mac OS (Upgrade A)
Creative Suite 3 Web Premiumからのアップグレードということで、アップグレードAというパッケージ。あんまりCS3からアップグレードする気はなかったんですが、動画の撮れるNikon D90を買ってからそろそろ動画もいじり始めようと思ってAfter EffectsPremiere ProのためにProduction Premiumを選びました。結構最後まで、AppleのFinal Cut Studio 2にするか迷いました。値段で言うと、Production Premiumへのアップグレードが9万8千円、Final Cut Studio 2が14万8千円。結果的には、
Adobe Creative Suite 4 Production Premium for Mac OS (Upgrade A) and Final Cut Express 4
Final Cut Expressという廉価版も併せて買ってしまいました。廉価版でもHD編集できるし、特にLiveTypeというタイトルを作るための機能が魅力的でした。別に何でもいいと思うなら、Macを買うと付いてくるiMovie HDやiDVD、Windowsのムービーメーカーで十分なんですが・・。
Adobe Creative Suite 3 plus Creative Suite 4
これは体験版で入れていたときのディレクトリ構成ですが、まさにAdobe一色となったアプリケーションフォルダ。
Adobe Creative Suite 4 Production Premium (+Web Premium CS3)
Illustrator CS3やPhotoshop CS3などをアンインストールして、すこし整理しました。Adobeに問い合わせたところ、Web PremiumからProduction Premiumへアップグレードした場合でも、Acrobat ProやDreamweaver CS3、Fireworks CS3等は「移行期間」ということで利用可能だとのこと。さて、早速昨日は一晩中さわっていたんですが、

ビデオ編集用にこういうのも買ってきました。Contour DesignのShuttle Pro2です。フレーム単位の調節をするならこういうコントローラがほしくなります。ドライバやプリセットが少し古いので、Premiere Pro CS4は直接認識しませんでしたが、少し設定をすれば使えるようになりました。ムービー編集以外でも、Apertureのプリセットなんかもあってなかなか便利です。左手側にはこのコントローラー、右手側にはペンタブのIntuos3 PTZ-930という構成で、どんどんエンジニアっぽくないデスクトップ環境になってきました。

引っ越しをしてから掃除機を買いました。iRobotのRoomba 530です。
iRobot Roomba 530
使っている掃除機は、ずいぶん前のエントリに書きました。

今もこのエントリで紹介した日立のPV-SH1を使っています。
Hitachi PV-SH1
今回はその掃除機を補助するためのものです。3年ほどPV-SH1を使っていて、特に不満はないのですが、ある程度部屋がきれいなうちは毎日あるいは2〜3日に一度は掃除機をかけてきれいにするのですが、一人暮らしであれこれ言われることもないため、汚くなりだすと全然掃除をしなくなりました。特に出張や旅行などで、部屋中に大量のモノが散らかると、その後1〜2週間はそのモノが片付けられないままになったり・・・。出張が重なると1月以上も散らかったまま、掃除機もかけないままの状態が続きました。
_DSC3984
一つの原因は部屋の大きさに対して、モノが多すぎること。これについては、今回引っ越しの際に思い切ってかなり処分(およそ3割削減)したので解決できました。
もう一つの原因は定期的に掃除する習慣が維持できないこと。特に仕事が忙しくなると、家事の優先度はどんどん低くなります。そこで、今回はこの問題を解決すべく、全自動な掃除機を導入することにしました。Roomba 530を買うことは引っ越しのかなり早い段階で決めていたので、家具もRoombaが使いやすいように選んだり、レイアウトしました。二つの原因に対して対策をとった成果もあって、少なくともこの4〜5週間はきれいな状態をキープできています。

家具選び&レイアウトで気をつけたことは次のようなポイントです。

  • 家具はRoombaが通れるような脚の高さがあること(9cm程度)。すでにある家具で高さが足りない場合は足す。
  • 床に座るようにレイアウトしない。イスを使う。
  • 床にものをおかない。
  • コード類が床に這わないようにする。

Roombaを使う醍醐味はベッドの下や棚の下まで自動的に掃除してくれるところだと思うので、ベッド下などの空間は空けてあります。あと、いろいろRoombaのレビューをみていると、コードやラグに絡まって動けなくなることがあるらしいので、そういったモノは床におかないことにしました。絡まりが多いと、エッジクリーニングブラシが折れるケースがあるらしいので、Roombaを使うなら、ベッド下云々よりも、床にあまり引っかかりそうなものをおかないことが大事なようです。

Roombaを使っていて、最近一番助かったのは、前の部屋を引き払って出て行くときの掃除でした。引っ越し屋さんに家具を運び出してもらった後の、本棚の裏だったところ、ベッドの下だったところはホコリだらけ。特に配線がごちゃごちゃしていたところは棚の移動が面倒でここ2年ほどほとんど掃除できていませんでした・・・。すごいホコリです。
R0028564
掃除の様子をムービーで納めてあるのでお暇な方はご覧ください。動画に音は入れてありません。

ちなみに前半は24倍速再生、後半は12倍速です。念のため (^^;
R0028569
部屋の隅の一部はブラシが届かなかったようですが、全体的にゴミはほとんど無くなりました。ここまでできあがれば、後仕上げだけですからやる気も出てきます。ちなみに、こんなにゴミがとれました(写真) [汚いのでリンクだけにしておきます]。

多摩の恵

フリッカーからのブログ投稿テスト

運動してもなかなか体重が減らないので、食事を減らすという方向性も考えましたが(実際少し試しましたが)うまくいかなさそうです。そこで、量はいくら食べてもいいという条件で、肉系をやめて野菜系にすることにしました。でも最初から肉がなくなると寂しいので少しだけ鶏肉を使って良いことにします。
昼ご飯
今日の昼ご飯。トマトソース、大豆、長ネギ、ブロッコリー、ほうれん草、タマネギ、ジャガイモ、鶏肉。
晩ご飯
今日の晩ご飯。スープがコンソメになった以外材料は同じです。昼ご飯と比べてブロッコリーを多めにしました。たぶん、ふつうに二人前ぐらいの量なので、おなかいっぱいです。
晩ご飯とヒューガルデン
でも結局ビールを呑んでしまったので、摂取カロリーはそんなに減ってませんね・・・。