@blog.justoneplanet.info

日々勉強

iOSでUIViewのタップを検出する

■実装

以下のようにUITapGestureRecognizerを使う。

UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapped:)];
[view addGestureRecognizer:recognizer];// view.tag == VIEW_HOGE_TAG

viewがタップされると以下のメソッドが実行される。

- (void)onTapped:(UITapGestureRecognizer *)recognizer {
    switch (recognizer.view.tag) {
        case VIEW_HOGE_TAG:
            // hogehoge
            break;
            
        case VIEW_FUGA_TAG:
            // fugafuga
            break;
            
        default:
            break;
    }
}

CakePHP 2でRedisにSessionを保持する

Cake2系用のが無かったのでCake1系用のこれをforkして作ってみた。

■導入

iRedis

Vendorに配置する

cakephp-redis-session

app/Model/DataSource/Session/に配置する

core.php

core.phpに以下のように記述する。

Configure::write('Session.handler', array('engine' => 'RedisSession'));
Configure::write('RedisSession.hostname', 'some.host.name');
Configure::write('RedisSession.port', 1337);//port
Configure::write('RedisSession.password', 'your password');// password
Configure::write('RedisSession.database', 0);// database number

■コード

オリジナルのコードはパスワードやDB番号が設定できなかったので改良してある。

App::import('Vendor', 'iRedis/iredis');
App::uses('DatabaseSession', 'Model/Datasource/Session');
/**
 * Redis Session Store Class
 */
class RedisSession extends DatabaseSession implements CakeSessionHandlerInterface {
    private static $store;
    private static $timeout;
    
    public function __construct() {
        parent::__construct();
        $timeout = Configure::read('Session.timeout');
        if (empty($timeout)) {
            $timeout = 60 * 24 * 90;
        }
        self::$timeout = $timeout;
    }

    /**
     * open
     * connect to Redis
     * authorize
     * select database
     */
    public function open() {
        $host = Configure::read('RedisSession.hostname');
        $port = Configure::read('RedisSession.port');
        $password = Configure::read('RedisSession.password');
        $database = Configure::read('RedisSession.database');

        if ($host !== null && $port !== null) {
            $redis = new iRedisForRedisSession(array('hostname' => $host, 'port' => $port));
        }
        else {
            $redis = new iRedisForRedisSession();
        }
        if (!empty($password)) {
            $redis->auth($password);
        }
        if (!empty($database)) {
            $redis->select($database);
        }

        self::$store = $redis;
    }

    /**
     * close
     * disconnect from Redis
     * @return type 
     */
    public function close() {
        self::$store->disconnect();
        return true;
    }

    /**
     * read
     * @param type $id
     * @return type 
     * - Return whatever is stored in key
     */
    public function read($id) {
        return self::$store->get($id);
    }

    /**
     * write
     * @param type $id
     * @param type $data
     * @return type 
     * - SETEX data with timeout calculated in open()
     */
    public function write($id, $data) {
        self::$store->setex($id, self::$timeout, $data);
        return true;
    }

    /**
     * destroy
     * @param type $id
     * @return type 
     * - DEL the key from store
     */
    public function destroy($id) {
        self::$store->del($id);
        return true;
    }

    /**
     * gc
     * @param type $expires
     * @return type 
     * not needed as SETEX automatically removes itself after timeout
     */
    public function gc($expires = null) {
        return true;
    }
}
if (!class_exists('iRedisForRedisSession')) {
    class iRedisForRedisSession extends iRedis {
        function __destruct() {
            // don't disconnect yet
        }
        function disconnect() {
            parent::__destruct();
        }
    }
}

iRedisのコードを読んでみると面白いのだがマジックメソッド__callを使って各コマンドをcallしている。

AndroidでIsolatedContextを使ってみる

■コード

以下のようにしてIsolatedContextのサブクラスを定義する。

public class HogeActivityTest extends ActivityInstrumentationTestCase2<Kaomoji> {
    private Kaomoji mActivity;
    private static final MockContentResolver RESOLVER = new MockContentResolver();
    private static class TestIsolatedContext extends IsolatedContext {
        private Context mContext;
        public TestIsolatedContext(ContentResolver resolver, Context targetContext) {
            super(resolver, targetContext);
            mContext = targetContext;
        }
        @Override
        public SharedPreferences getSharedPreferences(String name, int mode) {
            return mContext.getSharedPreferences("prefix_" + name, mode);
        }
    }
}

必要なメソッドはOverrideして実装する。以下のようにしてインスタンス化して、production環境に影響しない事を確かめる。

TestIsolatedContext context = new TestIsolatedContext(RESOLVER, mActivity);
SharedPreferences preferences = context.getSharedPreferences(Constants.KEY_USER_DATA, Context.MODE_PRIVATE);
String str = preferences.getString(Constants.KEY_USER_STRING, null);
assertEquals(str, null);// production側で文字列が保存されているのに関係しない

参考

ちょうどIsolatedContextを使ってるテストがなかったので何となく間に合わせ的なコードになってしまった。

AndroidでContentProviderをテストする

IsolatedContextの記事を書こうとしらProviderTestCase2の事を書いてなかった事に気づいたので書いておく。

■コード

public class ProviderTest extends ProviderTestCase2<Provider> {
    private Provider mProvider;
    private Uri mAuthority;

    public ProviderTest() {
        super(Provider.class, "");
    }
    
    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mProvider = getProvider();
        mAuthority = Provider.URI;
    }
    
    @Override
    protected void tearDown() throws Exception {
        super.tearDown();
        mProvider = null;
        mAuthority = null;
    }
    
    public void testOnCreate() {
        assertFalse(mProvider.onCreate());
    }
    
    public void testGetType() {
        assertEquals(mProvider.getType(Provider.URI), null);
    }
    
    public void test1Insert() {
        String key   = "key1";
        String value = "value1";
        ContentValues values = new ContentValues();
        values.put("key", key);
        values.put("value", value);
        Uri result = mProvider.insert(mAuthority, values);
        assertEquals(mAuthority, result);
        
        Cursor cursor = mProvider.query(
                mAuthority,
                new String[]{"_id", "key", "value"},
                "`key` = ?",
                new String[]{key},
                null
        );
        assertTrue(cursor.moveToFirst());
        assertTrue(0 < cursor.getCount());
        assertEquals(cursor.getString(cursor.getColumnIndex("key")), key);
        assertEquals(cursor.getString(cursor.getColumnIndex("value")), value);
        cursor.close();
    }
    
    public void test2Update() {
        String key      = "key1";
        String value    = "valuevalue";
        ContentValues values = new ContentValues();
        values.put("key", key);
        values.put("value", value);
        int row = mProvider.update(
                mAuthority,
                values,
                "`key` = ?",
                new String[]{key}
        );
        assertTrue(0 < row);
        
        Cursor cursor = mProvider.query(
                mAuthority,
                new String[]{"_id", "key", "value"},
                "`key` = ?",
                new String[]{key},
                null
        );
        assertTrue(cursor.moveToFirst());
        assertTrue(0 < cursor.getCount());
        assertEquals(cursor.getString(cursor.getColumnIndex("key")), key);
        assertEquals(cursor.getString(cursor.getColumnIndex("value")), value);
        cursor.close();
    }
    
    public void test3Delete() {
        String key = "key1";
        int row = mProvider.delete(mAuthority, "`key` = ?", new String[]{key});
        assertTrue(row > 0);
    }
}

ProviderTestCase2のコンストラクタがIsolatedContextオブジェクトを生成し、production環境とは別の環境で実行される。メソッドの実行順序を指定しているのが良くない。

hipacheをインストールする

簡単なので別に記事にするほどでもない。

■インストール

npm install hipache -g

■設定

Redisのportや接続に関する設定は以下のファイルを編集する。同階層のファイルを見れば大体分かる。

vim node_modules/hipache/config/config.json

以下のようにしてvhostの設定をする。

redis-cli rpush frontend:example.com hoge.identifer #フロントエンドを作って名前を付ける
redis-cli rpush frontend:example.com http://123.123.123.123:80 #フロントエンドにバックエンドを関連付ける
redis-cli rpush frontend:example.com http://234.234.234.234:80 #フロントエンドにバックエンドを関連付ける

■起動

sudo hipache

参考

殆どgithubに書いてあるものの和訳になってしまった。

AndroidでViewがResizeされたのを検知する

殆どstackoverflowのコードと同様だがlistenerに発火する条件だけ変更した。

public class DetectableResizedLinearLayout extends LinearLayout {
    private OnResizedListener mListener;

    public DetectableResizedLinearLayout(Context context) {
        super(context);
    }
    public DetectableResizedLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public DetectableResizedLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }
    
    public void setOnSoftKeyShownListener(OnResizedListener listener) {
        mListener = listener;
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mListener != null) {
            final int newSpec = MeasureSpec.getSize(heightMeasureSpec); 
            final int oldSpec = getMeasuredHeight();
            if (oldSpec > newSpec + oldSpec / 4){
                mListener.onContracted();
            }
            else if (oldSpec + newSpec / 4 < newSpec) {
                mListener.onExpanded();
            }
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
    
    public interface OnResizedListener {
        public void onContracted();
        public void onExpanded();
    }
}

上述のようにしないと非同期で画像を読み込むViewなどがあった場合など想定していないタイミングで発火してしまう。

参考

PHPで文字列の類似度を求める

レーベンシュタイン距離を使う。

function mb_str_split($str, $split_len = 1) {
    mb_internal_encoding('UTF-8');
    mb_regex_encoding('UTF-8');
    if ($split_len <= 0) {
        $split_len = 1;
    }

    $strlen = mb_strlen($str, 'UTF-8');
    $ret    = array();

    for ($i = 0; $i < $strlen; $i += $split_len) {
        $ret[] = mb_substr($str, $i, $split_len);
    }
    return $ret;
}

function getLevenshteinFactor($string1, $string2, $insert = 1, $delete = 1, $replace = 1) {
    $string1 = mb_str_split($string1);
    $length1 = count($string1);
    $string2 = mb_str_split($string2);
    $length2 = count($string2);

    if ($length1 < $length2) {
        $c = $string1;
        $string1 = $string2;
        $string2 = $c;
        $o = $length1;
        $length1 = $length2;
        $length2 = $o;
    }

    $d = array();
    $d[0] = array();
    for ($i = 0; $i < $length2 + 1; $i++) {
        $d[0][$i] = $i;
    }

    for ($i = 1; $i < $length1 + 1; $i ++) {
        $d[$i] = array();
        $d[$i][0] = $i;
        for ($j = 1; $j < $length2 + 1; $j ++) {
            $cost = ($string1[$i - 1] === $string2[$j - 1]) ? 0 : 1;
            $d[$i][$j] = min(
                $d[$i - 1][$j] + $insert,
                $d[$i][$j - 1] + $delete,
                $d[$i - 1][$j - 1] + ($replace * $cost)
            );print($d[$i][$j]);
        }
    }
    $distance = $d[$length1][$length2];
    $rate = $distance / $length1;
    return $rate;
}

他のサイトから持ってきたけど期待した値と少し違ったので調整した。

mecabに新しい単語を学習させる

mac上で実行するために文字コードをUTF-8にしたりして居いる以外は殆ど公式サイトと変わらない。

■インストール

brew install mecab
brew install mecab-ipadic

これによってインストールされるmecab-ipadicには、csvが含まれず最小構成となっているため、別途ダウンロードする。

wget https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.tar.gz
wget https://mecab.googlecode.com/files/mecab-ipadic-2.7.0-20070801.model.bz2
tar xvzf mecab-ipadic-2.7.0-20070801.tar.gz
bzip2 -d mecab-ipadic-2.7.0-20070801.model.bz2
mv mecab-ipadic-2.7.0-20070801.model mecab-ipadic-2.7.0-20070801/
mv mecab-ipadic-2.7.0-20070801 ~/
cd ~/mecab-ipadic-2.7.0-20070801/ #適当な場所

設定ファイル

vim /usr/local/Cellar/mecab/0.996/etc/mecabrc

以下のようになっている。

dicdir =  /usr/local/Cellar/mecab/0.996/lib/mecab/dic/ipadic

UTF-8化

後述のmecab-cost-trainに文字コードオプションがなさそうで上手くいかないため変換する。

cp -r mecab-ipadic-2.7.0-20070801 mecab-ipadic-2.7.0-20070801.utf8
cd mecab-ipadic-2.7.0-20070801.utf8
nkf -w -Lu --overwrite mecab-ipadic-2.7.0-20070801.model
find `pwd` -type f -exec grep -Il "" {} \; | xargs -I{} nkf -w -Lu --overwrite {}
#find `pwd` -maxdepth 1 -type f -name '*.csv' | xargs -I{} nkf -w -Lu --overwrite {} # CSVだけじゃだめ

grep

UTF-8にファイルを変換していれば問題ないのだが、EUC-JPのままの場合は以下のようにする必要がある。

ggrep `echo "名詞,固有名詞,人名" | nkf -e` -r Noun.* | nkf -w

ターミナルをEUC-JPにしてもいいのだが、これだけのために設定変更するのは面倒。

■新規単語の追加

vim add.csv
小島よしお,1289,1289,7438,名詞,固有名詞,人名,一般,*,*,小島よしお,コジマヨシオ
なう,0,0,0,助詞,終助詞,*,*,*,*,なう,ナウ,ナウ
まーす,0,0,0,助動詞,*,*,*,特殊・マス,基本形,まーす,マース,マース

コストなどの推定

/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-index -m mecab-ipadic-2.7.0-20070801.model -f utf8 -t utf8 -d ~/mecab-ipadic-2.7.0-20070801/ -u add2.csv -a add.csv 

新規単語の学習

cオプションでUTF-8を指定するのを忘れるとEUC-JPで出力されて、mecabコマンドの出力が文字化けする。

/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-index -f utf8 -t utf8 -c utf8 -d ./ -o ./

学習データの作成

以下の例を用いる。タブ区切りであることに注意する必要がある。

vim train
京都	名詞,固有名詞,地域,一般,*,*,京都,キョウト,キョート
なう	助詞,終助詞,*,*,*,*,なう,ナウ,ナウ
EOS
ラーメン	名詞,一般,*,*,*,*,ラーメン,ラーメン,ラーメン
なう	助詞,終助詞,*,*,*,*,なう,ナウ,ナウ
EOS
行っ	動詞,自立,*,*,五段・カ行促音便,連用タ接続,行く,イッ,イッ
て	助詞,接続助詞,*,*,*,*,て,テ,テ
き	動詞,非自立,*,*,カ変・クル,連用形,くる,キ,キ
まーす	助動詞,*,*,*,特殊・マス,基本形,まーす,マース,マース
EOS

以下のコマンドで学習させる。new_modelがutf8とeuc-jpが混在で出力される

/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-cost-train -M mecab-ipadic-2.7.0-20070801.model -d ./ train new_model 
mkdir new_dictionary
/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-gen -d ./ -o ./new_dictionary -m new_model

以下のコマンドで辞書をコンパイルする。

/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-index -f utf8 -t utf8 -c utf8 -d ./ -o ./new_dictionary

以下のコマンドで辞書を指定してmecabを起動する。

mecab -d ./new_dictionary

binary以外、dictionaryはgit管理してもいいかもしれない。

■ユーザー辞書

/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-index -d ./ -u add.dic -f utf8 -t utf8 add.csv
mecab -u add.dic

参考

■UniDic

wget http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Funidic%2F58338%2Funidic-mecab-2.1.2_model.zip
wget http://sourceforge.jp/frs/redir.php?m=jaist&f=%2Funidic%2F58338%2Funidic-mecab-2.1.2_src.zip
unzip unidic-mecab-2.1.2_src.zip
unzip unidic-mecab-2.1.2_model.zip
cd unidic-mecab-2.1.2_src
./configure
make
vim dicrc

以下のようにして出力形式を揃える

;output-format-type = unidic

前述のcsvの形式でも学習はできる。しかしlex.csvを見ると以下の形式となっている。

表層形,左文脈ID,右文脈ID,コスト,品詞大分類,品詞中分類,品詞小分類,品詞細分類,活用型,活用形,語彙素読み,語彙素(語彙素表記+語彙素細分類),書字形出現形,発音形出現形,書字形基本形,発音形基本形,語種,語頭変化型,語頭変化形,語末変化型,語末変化形
/usr/local/Cellar/mecab/0.996/libexec/mecab/mecab-dict-index -d ./ -o ./

参考