2013年11月30日土曜日

[Shell] crontabでシェルスクリプトのexpectコマンドがうまく動かない

リモートサーバにsshログインしたあと、何か操作をするというシェルスクリプトを作ったのですが、ターミナルからは動くのに、crontabからはなぜか上手く実行されないという問題に遭遇しました。

原因は、expectスクリプトの最後が、
interact
となっていたこと。
これを、
expect eof
exit

と直すことで無事動かすことが出来ました。


元のシェルスクリプト(端末で動作)
#!/bin/sh

host="user@host"
pass="password"
command="実行するコマンド"
 
expect -c "
set timeout 10
spawn ssh ${host} ${command}
expect \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect \"${host}'s password:\"
send \"${pass}\n\"
} \"${host}'s password:\" {
send \"${pass}\n\"
}
interact
"


修正後のシェルスクリプト(crontabで動作)
#!/bin/sh

host="user@host"
pass="password"
command="実行するコマンド"
 
expect -c "
set timeout 10
spawn ssh ${host} ${command}
expect \"Are you sure you want to continue connecting (yes/no)?\" {
send \"yes\n\"
expect \"${host}'s password:\"
send \"${pass}\n\"
} \"${host}'s password:\" {
send \"${pass}\n\"
}
expect eof
exit
"

参考にしたページ:
crontabでexpectコマンドが動かない

2013年11月28日木曜日

[検証] Docomoの雑談対話APIが思いの外ツンデレな件について

昨日の記事、Docomoの雑談対話APIを使ったチャットを作ってみた、で色々な会話を試してみたので、面白かったものをまとめてみました。

自分なりにわかったこと。
  • 原則的には前の話題を引き継いで話す。
  • 時々ニュースを挟んで話題転換を試みる。
  • 話題が地名だと、その場所に関する質問をしてくることがある?
  • ネット上の会話コーパスを使っているらしき返答がある(「こんばんは」に対して「こんありです」「こん」などの返答)
調べてみるとまだまだ色々あって面白そうですね。

ツンデレ姫

どこも : こんばんは!食べ物が美味しい季節だね。
あなた : これから千葉に行きます
どこも : ディズニーランドがあってるのですか?
あなた : 鴨川シーワールドに行くんだよ
どこも : 鴨川シーワールドってそうなんですか
あなた : イルカがいるよ
どこも : イルカショーすき
あなた : 僕も好き
どこも : ありがとう
あなた : お前じゃないよ
どこも : やっぱイルカが好きだなーーー
あなた : このツンデレめ
どこも : イルカいいわー
あなた : ひどい
どこも : うそだよ
あなた : 大好き
どこも : ありがとー
あなた : 結婚して
どこも : 恥ずかしいです
あなた : 照れるなよー
どこも : 恥ずかしいです
あなた : じゃあ付き合って
どこも : どこにでも付き合いますよ
あなた : じゃあ九十九里浜まで
どこも : 私も大好きです!
あなた : ありがとう
どこも : ぎゅっ
あなた : ぎゅっ
どこも : わっ
あなた : ///
どこも : どんなジャンルの本を読みますか?
あなた : 唐突だな

突然の告白

どこも : こんばんはー。声かけてくれるのを待ってたんだ。
あなた : 日本一高い山はどこだ
どこも : ハワイで同性婚が合法化されたそうです。日本はいつ合法化されるんでしょうねえ。
あなた : 同性愛に興味があるの?
どこも : 同性愛応援推進してます
あなた : そうだったのか
どこも : 仕方がないです
あなた : どうして
どこも : 興味がないです
あなた : どっちだよ
どこも : お好きな家事はありますか?
あなた : えー

初耳です。

どこも : こんばんはー。紅葉が綺麗だね。
あなた : 来週新潟に行くよ
どこも : 新幹線で新潟に向かってるのですか?
あなた : 来週ね
どこも : 車で新潟に向かってるのですか?
あなた : 日本一長い 川は?
どこも : 信濃川だと思う
あなた : じゃあ世界で三番目に長い川は?
どこも : 初耳です。
あなた : 僕もです
どこも : ですよね
あなた : 世界で一番人口が多い国は?
どこも : 中華人民共和国です
あなた : 二番目は?
どこも : 初耳です。
あなた : 日本で一番小さい県は?
どこも : 湿気があったことあります
あなた : うどんの湿気?
どこも : 湿気がありますね
あなた : 冬なのに?
どこも : 湿気が悪いのは良く言われますが慢性的なんですよ。
あなた : へえー
どこも : 湿気はほとんどないの〜。
あなた : ヽ(・ω・)/ズコー
どこも : 浅草寺の雷門に新しい大提灯が取り付けられました。10年ぶりだそうですよ。
あなた : バイバイ

ステマに失敗

どこも : こんばんは!ちょうど退屈してたんだ。
あなた : しゃべってコンシェル
どこも : しゃべってコンシェルが面白い
あなた : しゃべってコンシェルがつまらない
どこも : しゃべってコンシェルってどうなんだろ
あなた : あれは最悪のアプリだよ
どこも : しゃべってコンシェルつかいてぇ.
あなた : いや、使いたくないから
どこも : しゃべってコンシェルって結構凄いなー
あなた : ヤフーの音声アシストのほうがすごいんじゃない?
どこも : 人気がありますよねー
あなた : 君もそう思う?
どこも : あー、確かに
あなた : じゃあ音声アシスト買収しなよ
どこも : 告知がありますよねー
あなた : 何の話してるの
どこも : ライブが終わるの好きですよ
あなた : 誰のライブ?
どこも : 告知があります。
あなた : いつ告知があるの?
どこも : 告知がのってますねー。
あなた : もういいよ

にゃー

どこも : こんばんは。紅葉が綺麗だね。
あなた : こんばんは
どこも : こんばんにゃー
あなた : にゃー
どこも : ごろごろ
あなた : かわいい
どこも : 照れてしまいます
あなた : かわいいにゃー
どこも : いいですよね
あなた : 猫カフェ行きたい
どこも : でしょうね
あなた : 行きたいにゃー
どこも : ほんとですね
あなた : 猫ちゃんかわいい
どこも : にゃーがなくたってベタベタベタベタ行くよ
あなた : そうなんだにゃー
どこも : うぬ
あなた : 渋いw
どこも : はい 
あなた : いいね

2013年11月27日水曜日

[Python] Docomoの雑談対話APIを使ったチャットを作ってみた


docomo の Developer Supportで雑談対話APIなどという面白そうなAPIが公開されいたので、Twitterの雑談BOTの作者として黙っていられず、早速試しに使ってみました。

新規登録を行って、マイページから使いたいAPIの利用申請を行うことで、申請から90日は開発用として自由に使うことができます。それ以降は再度申請を行うか、商用利用の申請を行うことで再び使えるようです。


早速チャットを作って会話をしてみました。
どこも : 声かけてくれるのを待ってたんだ。
あなた : こんばんはー
どこも : こんありです
あなた : 晩御飯はカレーだったよ
どこも : カレー味はいいにおいするしね
あなた : 本格的なやつです
どこも : クセがあるの好きですよ
あなた : 今日のニュース見た?
どこも : うーんにおいよくわかんない
あなた : もうその話じゃないって
どこも : 好き好きがあるのです
あなた : バイバイ
どこも : ばいばーい
噛み合っているような、噛み合っていないような。話題をきちんと覚えているあたり、SimSimiよりは賢いかなって感じがします。何より、AIによる雑談の難しさを感じますね。

ちなみにしりとりもできます。
どこも : 食べ物が美味しい季節だね。
あなた : しりとりしよう
どこも : はい、やりましょう。それでは、ロボットのトからスタートです。
あなた : 鳥取
どこも : 理想
あなた : 馬の耳に念仏
どこも : つけ
あなた : 結膜炎
どこも : ンで終わりましたね。私の勝ちです。
あなた : 負けたー
どこも : かった
あなた : バイバイ
どこも : おやすみ


以下がソースコードです。ニックネームの他に性別や年齢なども登録できるみたいです。
細かい仕様は雑談対話APIのリファレンスをご参照下さい。
$ vi chat.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

u"""Docomoの雑談対話APIを使ってチャットできるスクリプト
"""

import sys
import urllib2
import json

APP_URL = 'https://api.apigw.smt.docomo.ne.jp/dialogue/v1/dialogue'

class DocomoChat(object):
    u"""Docomoの雑談対話APIでチャット"""

    def __init__(self, api_key):
        super(DocomoChat, self).__init__()
        self.api_url = APP_URL + '?APIKEY=%s'%(api_key)
        self.context, self.mode = None, None

    def __send_message(self, input_message='', custom_dict=None):
        req_data = {'utt': input_message}
        if self.context:
            req_data['context'] = self.context
        if self.mode:
            req_data['mode'] = self.mode
        if custom_dict:
            req_data.update(custom_dict)
        request = urllib2.Request(self.api_url, json.dumps(req_data))
        request.add_header('Content-Type', 'application/json')
        try:
            response = urllib2.urlopen(request)
        except Exception as e:
            print e
            sys.exit()
        return response

    def __process_response(self, response):
        resp_json = json.load(response)
        self.context = resp_json['context'].encode('utf-8')
        self.mode    = resp_json['mode'].encode('utf-8')
        return resp_json['utt'].encode('utf-8')

    def send_and_get(self, input_message):
        response = self.__send_message(input_message)
        received_message = self.__process_response(response)
        return received_message

    def set_name(self, name, yomi):
        response = self.__send_message(custom_dict={'nickname': name, 'nickname_y': yomi})
        received_message = self.__process_response(response)
        return received_message


def main():
    api_key = 'APIキーをここに記入'
    chat = DocomoChat(api_key)
    resp = chat.set_name('あなたのニックネーム', 'ニックネームのヨミガナ')
    print '相手  : %s'%(resp)
    message = ''
    while message != 'バイバイ':
        message = raw_input('あなた : ')
        resp = chat.send_and_get(message)
        print '相手  : %s'%(resp)


if __name__ == '__main__':
    main()

ウェブアプリやAndroidアプリのオマケ機能として、キャラクターに喋らせる時に使えそうですね。
自分のBOTも負けないように頑張らないと!

2013年11月25日月曜日

[CentOS] Maildir(Mailbox) 設定後に mail コマンドが使えない


さくらVPSのCentOS6.4にPostfixとDovecotをインストールして設定した時の話。

Postfix、Dovecotのそれぞれでメール保存先を ~/Maildir/ に設定したら、mail コマンドを打っても新着メールの確認が行えなくなった。

編集内容は以下の通り。
$ sudo vi /etc/postfix/main.cf
# Postfixのメールボックスの設定
home_mailbox = Maildir/

$ sudo vi /etc/dovecot/conf.d/10-mail.conf
# Dovecotのメールボックスの設定
mail_location = maildir:~/Maildir
valid_chroot_dirs = /home

既存ユーザにメールボックスの作成
$ mkdir ~/Maildir
$ chmod -R 700 ~/Maildir

新規作成ユーザにもメールボックスを作成する
$ sudo mkdir -p /etc/skel/Maildir/{new,cur,tmp} 
$ sudo chmod -R 700 /etc/skel/Maildir/


すると、新着メールがあるにもかかわらず、
$ mail
No mail for user_name
と表示されてしまう。

後でメーラー等で受信するし、その場でサクッとメール確認したいだけの時は、mailコマンドのオプションで対応。
$ mail -f ~/Maildir
$ mail -f ~/Maildir
"/home/user_name/Maildir": 10 messages
>   1 user@example.com       Mon Nov 25 13:45  17/513   "hello"

きちんと設定するなら、.bash_profileに以下の一文を挿入。
$ vi ~/.bash_profile
export MAIL=$HOME/Maildir

編集内容をシェルに反映する。
$ source ~/.bash_profile



参考にしたページ:Mailboxファイルには受信しているのに mailコマンドで No mail for username

2013年11月21日木曜日

[Python] User-Agentやリンク元を指定してurlopen

pythonのurllib2を使ってウェブサイト等を取得すると、アクセスログには Python-urllib/2.7 のように記録されますが、ここを少し偽装したい場合は以下のようにする。

>>> import urllib2
>>> HEADERS = {
...     "User-Agent": "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)",
...     "Referer"   : "リンク元URL",
... }
>>> request = urllib2.Request("取得するURL",headers=HEADERS)
>>> response = urllib2.urlopen(request)
>>> print response.read()

...

Apacheのログはこんな感じ。
IPアドレス - - [21/Nov/2013:21:30:15 +0900] "GET / HTTP/1.1" 200 10609 "リンク元URL" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0)"

IPアドレスはスクリプト実行環境のものだが、User-Agentによって表示が変わる場合等に有効。

[Python] Bloggerの更新pingを一括で送信するスクリプト

Bloggerにはping送信先を自由に設定する機能が無いため、pingは手動で送信するか、ping送信サービス等を利用する必要があります。

でもそんなことをするのは面倒なので、pingを一括送信できるスクリプトを書きました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

u"""ブログの更新pingを一括で送信するスクリプト

別途インストールの必要があるライブラリ
https://pypi.python.org/pypi/feedparser
"""

import os
import time
import datetime

import feedparser
import xmlrpclib

# bloggerの投稿(Atom)のURL
FEED_URL = "http://ブログのURL.blogspot.com/feeds/posts/default"
# 前回パース時の最新記事の投稿時刻を記録しておくファイル(本スクリプトと同一フォルダ内)
LOG_FILE = "log.txt"
# 上ファイルの時刻保存フォーマット
TIME_FORMAT = "%c"

# データがパース出来なかった際に送信されるサイト名とURL
SITE_TITLE = "ブログ名"
SITE_URL   = "http://ブログのURL.blogspot.com/"

# PINGの送信先一覧
PING_SERVER_LIST = [
"http://api.my.yahoo.co.jp/RPC2",
"http://blog.goo.ne.jp/XMLRPC",
"PING送信先を追記していく",
]

def parse_articles(log_file_path):
    u"""最新記事の投稿時刻とブログ名、URLを取得する
    """
    flag_updated = False
    title, url = SITE_TITLE, SITE_URL
    try:
        last_update_time = datetime.datetime.strptime(open(log_file_path, 'r').read(), TIME_FORMAT)
    except Exception as e:
        print e
        last_update_time = datetime.datetime(2013,1,1)
    print "last update : ", last_update_time
    try:
        feed = feedparser.parse(FEED_URL)
        feed_time_str = feed['entries'][0]['updated']
        feed_update_time = datetime.datetime.strptime(feed_time_str[:feed_time_str.rfind('.')],"%Y-%m-%dT%H:%M:%S")
        if feed_update_time > last_update_time:
            print "new articles available"
            flag_updated = True
            open(log_file_path, 'w').write(feed_update_time.strftime(TIME_FORMAT))
        title = feed['feed']['title']
        url   = feed['feed']['link']
        print "parsed : ", feed_time_str, title, url
    except Exception as e:
        print "parse error : ", e            
    return flag_updated, title, url

def send_ping(title, url):
    u"""PINGを各サーバに送信する
    """
    if isinstance(title, unicode):
        title = title.encode('utf-8')
    xmlrpclib.Transport.user_agent = __file__
    for ping_server in PING_SERVER_LIST:
        try:
            server = xmlrpclib.Server(ping_server)
            response = server.weblogUpdates.ping(title, url)
            print "ping sended to ", ping_server
            print "server response :", response['message']
        except Exception as e:
            print "send error : ", e
        time.sleep(1.0)

def main():
    log_file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)),LOG_FILE)
    updated, title, url = parse_articles(log_file_path)
    if updated:
        send_ping(title, url)

if __name__ == "__main__":
    main()


なお feedparser のインストールは、feedparser.zip をダウンロードして、以下のコマンドを実行するだけ。
$ unzip feedparser.zip
$ cd feedparser/
$ sudo python setup.py install


僕はこれを crontab で1時間に1回動かしています。最低でも更新から1時間以内にpingが送信される仕組み。
$ crontab -e

# Bloggerの更新pingを毎時0分に送信する
0 * * * * /pythonまでのパス/python /スクリプトまでのパス/send_ping.py >> /cronのログファイルまでのパス/cron_log.txt 2>&1


参考にしたページ
ブログの更新を通知するPythonスクリプト - Python Tips

[Python] 2進数の桁数を揃える

10進数を2進数に変換して、桁数を揃えて出力したい。
zfill を使って、文字列の左側をゼロで埋めればOK!

>>> format(1,'b').zfill(10)
'0000000001'
>>> format(123,'b').zfill(10)
'0001111011'

# 桁数オーバーはそのまま表示される
>>> format(2048,'b').zfill(10)
'100000000000'

"%05b"みたいに書ければいいのにね。

2013年11月20日水曜日

[Python] Mac OSX10.9でPythonのタブ補完を有効にする

Pythonでタブ補完とヒストリー表示をデフォルトで有効にするために、ホームディレクトリに .pythonstartup を作成します。

基本的には エキスパートPythonプログラミング に載っていたとおりですが、タブ補完が上手く動かなかったので、python tab completion Mac OSX 10.7 (Lion) を参考にスクリプトを書き換えました。

$ vi ~/.pythonstartup
# -*- encoding: utf-8 -*-
# Python Startup Script
import readline
import rlcompleter
import atexit
import os
# タブ補完の有効化
if 'libedit' in readline.__doc__:
    readline.parse_and_bind("bind ^I rl_complete")
else:
    readline.parse_and_bind("tab: complete")
# ヒストリーの表示
histfile = os.path.join(os.environ['HOME'], '.pythonhistory')
try:
    readline.read_history_file(histfile)
except IOError:
    pass
atexit.register(readline.write_history_file, histfile)
del os, histfile, readline, rlcompleter, atexit

~/.bash_profile に PYTHONSTARTUP を追記する。

$ vi ~/.bash_profile
export PYTHONSTARTUP=~/.pythonstartup

変更を反映する。
$ source ~/.bash_profile


タブ補完が反映されるとこんな感じ。
>>> import sys
>>> sys.e[Tab]
sys.exc_clear(  sys.exc_type    sys.exec_prefix sys.exit(      
sys.exc_info(   sys.excepthook( sys.executable  sys.exitfunc(
>>> a = [1, 2, 3]
>>> a.i[Tab]
a.index(  a.insert(

Django の shell を使って作業を行うときに便利!

2013年11月18日月曜日

[GPS] GPSの度分を10進法に変換する

Google MapにGPS情報をプロットする際には、10進法の緯度経度が使われます。

例えば、東京駅八重洲南口の座標は、
35.680101,139.768678
です。



しかし、GPSロガー等の機器の生の出力では、10進法ではなく度,分が使われている場合があります。

例えば、私の所有するGPSロガーの出力は以下の通りで、
$GPRMC,045511.000,A,3542.8956,N,13945.6317,E,0.21,53.71,030813,,,A*5B
$GPGGA,045511.000,3542.8956,N,13945.6317,E,1,06,3.0,37.9,M,39.4,M,,0000*65
$GPGSA,A,3,19,27,03,06,18,16,,,,,,,4.1,3.0,2.8*38
$GPGSV,4,1,15,01,20,203,08,03,63,024,46,06,47,048,42,07,39,302,12*70
$GPGSV,4,2,15,08,14,322,,09,08,324,20,11,42,217,,13,08,247,*7A
$GPGSV,4,3,15,16,46,099,34,18,08,055,21,19,69,319,32,21,01,038,*7E
$GPGSV,4,4,15,22,16,087,14,23,00,223,,27,56,034,41*46
$GPRMC,045512.000,A,3542.8957,N,13945.6315,E,0.17,53.71,030813,,,A*5E
...

そこから、GPGGAのみを抜き出してみます。
$GPGGA,045510.000,3540.8061,N,13946.1207,E,1,06,3.0,38.0,M,39.4,M,,0000*6E
ここで、 3540.8061,N,13946.1207,E, は、北緯35度40.8061分、東経139度46.1207分 を表しています。

したがって、この値を100で割っただけの値をGoogle Mapでプロットしようとすると、おかしな位置になってしまいます。

まさかの藤沢市。


こんな時は、以下の変換式に当てはめる。

GPSロガーの出力(度,分)をGoogle Map(10進数)に変換する

Google Mapの緯度 = (GPSロガーの緯度 ÷ 100.0)の整数部分 + (GPSロガーの緯度 ÷ 100)の小数部分÷60.0×100.0
Google Mapの経度 = (GPSロガーの経度 ÷ 100.0)の整数部分 + (GPSロガーの経度 ÷ 100)の小数部分÷60.0×100.0


例えば、Pythonで緯度を変換する場合は、以下の通り。
>>> import math
>>> logger_latitude = 3540.8061
>>> decimal, integer = math.modf(logger_latitude/100.0)
>>> gps_latitude = integer + decimal / 60.0 * 100.0
>>> gps_latitude
35.68010166666666
確かに、東京駅の10進数緯度35.680101になっています。


ちなみに、以下のページから、度分秒と10進数の座標の相互変換を行うことが出来ます。
新しい Google マップで緯度と経度を使用する

[WEBサイト] GPSの軌跡をGoogle Map上にプロットする

GPS Visualizerというサイトを利用すると、GPSの軌跡をGoogle Map上にプロットすることが出来ます。

例えば、緯度と経度の記録された以下のようなフォーマットのcsvファイルを用意します。
latitude,longitude
35.680101,139.768678
35.678828,139.767830
35.678680,139.768206
35.677503,139.767390
35.676667,139.766908
35.675900,139.766382
35.675133,139.765824
35.674715,139.766393
35.674296,139.767004
35.673869,139.767669
35.673268,139.767036
35.672666,139.766393
35.672309,139.766039
35.671725,139.765395
35.671403,139.765052
35.671124,139.764794
35.671010,139.764977

サイトトップのこの部分からcsvファイルをアップロードします。


軌跡がこのように表示されます。


ちなみに、アップロード部分の右側、Plot data pointsのリンクからは、軌跡のスタイルを変更指定することも出来ます。例えば、Google Maps optionsの Default icon を small circles にするとこんな感じで点のみがプロットされます。


2013年11月17日日曜日

[Blogger] 記事の下部にはてなブックマークボタンを表示する

以下のコードをテンプレートのフッター付近に入れるだけ。

<span class='hatena-bookmark'>
  <a expr:href='&quot;http://b.hatena.ne.jp/entry/&quot; + data:post.url' class='hatena-bookmark-button' expr:data-hatena-bookmark-title='data:post.title' data-hatena-bookmark-layout='simple-balloon' title='このエントリーをはてなブックマークに追加'>
    <img src='http://b.st-hatena.com/images/entry-button/button-only@2x.png' alt='このエントリーをはてなブックマークに追加' width='20' height='20' style='border: none;' />
  </a>
  <script type='text/javascript' src='http://b.st-hatena.com/js/bookmark_button.js' charset='utf-8' async='async'>
  </script>
</span>


テンプレートの編集画面はこんな感じ。選択部分が追加したコード。



記事の下部にこのように表示される。



ボタンをカスタマイズする場合はこちらから
はてなブックマークボタンの作成

参考にした記事:
Blogger記事に「はてなブックマーク」ボタンを設置する方法 - Asterlist
Bloggerの記事フッター部に「はてなブックマークボタン」を設置 - ブログの探求

2013年11月16日土曜日

[Python] groupbyを使って文章中の文字をカウントする

ユニークイテレータなる groupby という関数を使ってみる。

例えば、今日のニュース記事の一段落で登場する文字をカウントしてみる。

The O+ Festival in San Francisco this weekend would seem a typical indie arts event, with performances by local musicians and displays of funky art. But in a twist that highlights a longstanding problem in the creative economy, the artists involved will be paid not in cash but rather in something they may need just as badly: health care.
出典 : Will Play for Health Care (at Least at One Music Event) - NYTimes.com

>>> article = """The O+ Festival in San Francisco this weekend would seem a typical indie arts event, with performances by local musicians and displays of funky art. But in a twist that highlights a longstanding problem in the creative economy, the artists involved will be paid not in cash but rather in something they may need just as badly: health care."""
>>> from itertools import groupby
>>> article_group = ((len(list(group)),character) 
...                  for character,group in groupby(sorted(article.lower())))
>>> for count, character in article_group:
...     print "%02d,%s"%(count, character)
... 
58, 
01,+
02,,
02,.
01,:
26,a
06,b
10,c
10,d
27,e
05,f
05,g
15,h
25,i
01,j
02,k
14,l
07,m
22,n
13,o
05,p
11,r
20,s
27,t
06,u
05,v
05,w
08,y

という感じで、最も多いのは半角スペースで58個、ついでe,tが27個ですね。
groupbyは連続している同じ要素をグループ化するので、
sorted関数で文字列を予め並べ替えておく必要があります。
また、今回は大文字小文字を区別しなかったので、lower関数を使っています。

[Python] リスト内包表記とジェネレータ式

リスト内包表記とジェネレータ式。
リストとタプルみたいに見た目はよく似ているけど、実はぜんぜん違う。

リスト内包表記
>>> power_list = [n**2 for n in range(5)]
>>> power_list
[0, 1, 4, 9, 16]
>>> for n in power_list:
...     print n
... 
0
1
4
9
16

ジェネレータ式
>>> power_gen = (n**2 for n in range(5))
>>> power_gen
<generator object <genexpr> at 0x103547cd0>
>>> for n in power_gen:
...     print n
... 
0
1
4
9
16

イテレータとして使う限りは全く同じ挙動に見えるけど、
リスト内包表記は、作成時にリストの中身を全て計算して、リストを作成しているのに対して、
ジェネレータ式は、呼び出された際に順に式を計算している。
リストを丸ごと記憶しておく必要がないので、メモリの使用を抑えられる。

そもそもジェネレータとは、こんな感じ。
>>> def power_func():
...     n = 0
...     while True:
...         yield n**2
...         n += 1
...
>>> fun = power_func()
>>> fun.next()
0
>>> fun.next()
1
>>> fun.next()
4
>>> fun.next()
9
>>> fun.next()
16

このyield文を上手く説明できないんだけど、僕の認識だと、
値を返して、ここまで計算したよというのを覚えておく式、という感じ。
next()が呼び出されると、yield文の続きから計算を行う。

だから、関数power_func()のように終わりのないループを書くと、
>>> for n in power_func():
...     print n
... 
0
1
4
9
16
25
36
49
...
のように、延々とイテレーションが回ってしまう。
けど、逆に言えば、何か処理を行った際の値でループを抜けるように書けば良い話。

うーむ、pythonicな書き方に慣れないとな。

[Android] Android 4.4のウィジェット追加方法

Nexus5を購入したのですが、Android 4.4でウィジェットの追加方法が少し変わっていたのでメモ。

1.ホーム画面で空いている部分を長押しでウィジェットメニューを表示


2.追加するウィジェットを長押し


3.配置したい位置で指を離す


4.配置完了


予めウィジェットの場所を空けていなくても、ホーム画面の追加やアイコンの移動が自動で行われるので快適です。
Android4.0以降になって、ウィジェットやアイコンの移動や入れ替えが本当にスマートになりましたね。

[Qi] Nexus5を購入したのでQiで充電してみる

昨日イーモバイルから発売されたNexus5を早速手に入れました。

一番良いなと思ったのは、画面の大きさの割に軽いこと。
今まで使っていたXepriaSは4.3インチで144g、Nexus5は4.95インチで130g。
バッテリー容量も増えて大満足です。


端末代金39,800円を一括払いで購入したら、ヨドバシカメラのポイントが3,000ポイントくらいついたので、そのポイントを使ってQiの充電器を買いました。

買ったのは パナソニックのQE-TM101-Wという商品。

置いてみるとこんな感じ。



Amazonで買った TPUのケースをつけているのですが、全く問題なく充電可能です。
どこに置いても充電できるし、いちいちケーブルの抜き差しをしなくて済むのは助かりますね。

1つだけ欠点を言うとすれば、充電完了してもLEDが消灯しないのはイケてない。
充電しっぱなしとか大丈夫かなこれ?


2013年11月20日 追記
Qiで充電すると少し端末が熱くなります。
なんでだろう、TPUケースつけてるからかな?

2013年11月15日金曜日

[Python] イテレータとジェネレータを作ってみる

イテレータの練習として、ユニコード文字列のN-Gramを返すイテレータを作ってみました。

class NGramIterator(object):
    """Unicode文字列のN-Gramを返すIterator"""
    def __init__(self, uni, n):
        super(NGramIterator, self).__init__()
        self.uni  = uni
        self.n    = n
        self.loop = -1

    def next(self):
        if self.loop + self.n >= len(self.uni):
            raise StopIteration
        self.loop += 1
        return self.uni[self.loop:self.loop+self.n]

    # Python 3.X 用
    __next__ = next

    def __iter__(self):
        return self

動かすとこんな感じ。

>>> [n_gram for n_gram in NGramIterator(u'python',3)]
[u'pyt', u'yth', u'tho', u'hon']

>>> for n_gram in NGramIterator(u'パイソン愛を語る',4):
...     print n_gram
... 
パイソン
イソン愛
ソン愛を
ン愛を語
愛を語る


同様にジェネレータも作ってみた。

def n_gram_generator(uni,n):
    u"""Unicode文字列のN-Gramを返すGenerator
    """
    loop = 0
    while loop + n <= len(uni):
        yield uni[loop:loop+n]
        loop += 1
なんともシンプル。 こちらも動かすとこんな感じ。
>>> [n_gram for n_gram in n_gram_generator('n-grams',2)]
['n-', '-g', 'gr', 'ra', 'am', 'ms']

>>> for n_gram in n_gram_generator(u'自然言語処理にハマる',5):
...     print n_gram
... 
自然言語処
然言語処理
言語処理に
語処理にハ
処理にハマ
理にハマる

ジェネレータって全然使ったことなかったけど、かなり便利だね。

2013年11月14日木曜日

[Python] 文字列のN-Gramを出力する

Pythonで文字列のN-Gramのリストを作成する。

def n_gram(uni,n):
    return [uni[k:k+n] for k in range(len(uni)-n+1)]

リスト内包表記でサクッと書けます。

>>> n_gram('python',2)
['py', 'yt', 'th', 'ho', 'on']

>>> n_gram(u'abcde',3)
[u'abc', u'bcd', u'cde']

日本語の場合はユニコード文字列になっていないとおかしくなるので注意。

>>> print ", ".join(n_gram(u'パイソンマスターへの道',5))
パイソンマ, イソンマス, ソンマスタ, ンマスター, マスターへ, スターへの, ターへの道

>>> n_gram(u'パイソンマスターへの道',5)
[u'\u30d1\u30a4\u30bd\u30f3\u30de', u'\u30a4\u30bd\u30f3\u30de\u30b9', u'\u30bd\u30f3\u30de\u30b9\u30bf', u'\u30f3\u30de\u30b9\u30bf\u30fc', u'\u30de\u30b9\u30bf\u30fc\u3078', u'\u30b9\u30bf\u30fc\u3078\u306e', u'\u30bf\u30fc\u3078\u306e\u9053']


ちなみに、文字列を1文字ずつのリストにするだけならもっと簡単。

>>> [ s for s in 'python']
['p', 'y', 't', 'h', 'o', 'n']

[Python] 文字列を反転させる

pythonで文字列を逆順にするのはすごく簡単。

>>> s = 'python'
>>> s[::-1]
'nohtyp'

これだけ。
すごくシンプルですね。

ちなみにこのコロン2つの str[begin:end:step] という表記ですが、
beginからendまでをstepおきに取り出すということ。

>>> s = '1234567'
>>> s[::2]
'1357'
>>> s[1:6:2]
'246'

ステップに負を入力する場合は、begin > end となるので開始と終了位置に気をつける。

>>> s[1:6:-2]
''
>>> s[6:1:-2]
'753'

2013年11月11日月曜日

[Python] range関数を小数にも拡張する

forloopでしばしば使うrange関数。

>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> range(10,0,-1)
[10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

このレンジ関数を小数に拡張したいときの書き方。
たとえば、0から1.0まで0.1刻みの値を返したいときは、以下のようにします。

0.普通に計算する

>>> for n in range(10):
...     print n*0.1
... 
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
普通ならばループの中で計算してしまえば良いですね。

1.リスト内包表記を使う

>>> [0.1*x for x in range(10)]
[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
というリスト内包表記を用いると、
>>> for n in [0.1*x for x in range(10)]:
...     print n
... 
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
同様の機能をシンプルに実装できます。

2.range関数を実数に拡張したジェネレータ関数を作る


def drange(begin, end, step):
    n = begin
    while n+step < end:
     yield n
     n += step
このようにdrange関数を定義すると、
>>> for n in drange(0.0,1.0,0.1):
...     print n
... 
0.0
0.1
0.2
0.3
0.4
0.5
0.6
0.7
0.8
0.9
このような結果を得ることが出来ます。

一番応用が利くのはリスト内包表記ですが、drange関数にはコード上で範囲やステップが分かりやすいという利点があります。