5分でPythonは便利と思えるレシピ

January 01, 2011 at 05:58 PM | View Comments

@nishioが嘆いていたので5分でpythonが便利と分かると思われるスクリプトを書いてみました。

Pythonで5分で便利なことをするレシピ - 西尾泰和のはてなダイアリー

5分で便利と言っても対象が誰かによって違うと思うのですが、元ネタがPHPプログラマ対象のようでしたので、PHPプログラマやプログラムを普段書かない人向けです。

元ネタはPHPっぽくPythonを使っていましたが、今回はバッチプログラム風です。Webアプリを作る上でPHPは便利だと思うのですが、ファイル操作したり集計したりするときにPHPは向いていないと思います。PHPプログラマの人は普段なにを使っているのでしょうか??

5分でわかる Python を知らない人が Python の便利さを学べる記事をかいたよ | HIROKI.JP

プログラムをやってみたい!と言う人はだいたいPHPから入るみたいですが、

  • apacheなどのWebサーバーの管理もするのか?
  • 作ったWebアプリを公開するのか?セキュリティ大丈夫??
  • phpはWeb以外のことをやろうとすると激しく面倒

ということからも初心者用プログラム = PHP というのに疑問を感じます。Webアプリを公開するってハードルがとてつもなく高くないですか??

そんなわけで、PHPオンリープログラマやプログラムを始める人はWebアプリより、普段の手でやると面倒な仕事を自動化できるようなプログラムを書けるようになるといいと思います。

linuxやマックなら、シェルやperlなどいろいろあるのでPythonで無くてもいいと思いますが、PythonはWindowsでも簡単にインストールできて完全に動くので便利、オススメです。

今回は良く使うファイル操作のバッチプログラムを書いてみました。

元ディレクトリを再帰的に探して、特定の拡張子のファイルをコピー先ディレクトリにコピーするプログラムです。

Pythonのファイル操作はいろいろあるのですが、os.walkは再帰的にディレクトリを渡り歩く関数で非常に便利です。Pythonを使い始めの頃からずっと愛用しています。

16.1. os ? 雑多なオペレーティングシステムインタフェース ? Python v2.6.2 documentation

今回作ったプログラムは

% python filecopy.py 元ディレクトリ コピー先ディレクトリ 拡張子

という使い方をします。

#! /usr/bin/env python
# vim: fileencoding=utf8

import os
import sys
import shutil
import logging as logger

def copyfile(path, to_path, ext):
    """
    pathの中の全ての拡張子がextのファイルを
    to_pathにコピー
    """
    logger.debug("args path %s to_path %s ext %s " % (path, to_path, ext))
    # os.walk で path内のディレクトリを全部なめる
    # ディレクトリのなかにディレクトリがあっても一つ一つ取得してくれる
    for root, dirs, flist in os.walk(path):
        # flistはファイルの配列なので一つ一つ処理
        for fname in flist:
            logger.debug("root:%s fname:%s" % (root, fname))
            # hoge.JPG のようなファイル名から拡張子を取得
            # 大文字は小文字にする
            # ex)
            # os.path.splitext(fname) で ("hoge", ".JPG") に分解
            # [-1] で .JPG  (上の要素の一番最後を取得
            # .lower() で .jpg ( すべて小文字に変換
            # [1:] で jpg (2番目の文字から最後までを取得
            file_ext = os.path.splitext(fname)[-1].lower()[1:]
            logger.debug("file ext %s " % file_ext)
            # 拡張子が一致したら処理
            if ext.lower() == file_ext:
                # root(c:\temp\hogeのような親ディレクトリ)とhoge.JPG を 合体させる
                filepath = os.path.join(root, fname)
                # to_pathにコピー
                shutil.copy2(filepath, to_path)
                logger.info("copy %s to %s" % (filepath, to_path))


# if __name__ == "main": でバッチとして実行できる
if __name__ == "__main__":
    # sys.argv には 実行ファイル名 オプション ,,,, のリストが入っている
    if len(sys.argv) < 3:
        print r"usage copyfile.py c:\temp\hoge c:\temp\huga jpg"
        sys.exit()
    path = sys.argv[1]
    to_path = sys.argv[2]
    ext = sys.argv[3]
    copyfile(path, to_path, ext)

今回は拡張子でファイルを選択しましたが、慣れてきたら

  • 正規表現のパターンを渡す
  • ファイルを返すgenerator関数と処理をする関数を分ける

などすると面白いと思います。

categories: python
Read and Post Comments

PythonのP.I.Lでビットマップ白黒に変換する

October 25, 2010 at 04:46 PM | View Comments

Pythonの画像操作ライブラリ P.I.L で白黒のbitmapの変換方法が分からなくてハマッたのでメモ。

#! /usr/bin/env python
#! vim: fileencoding=utf8

import Image

def tobitmap(imagepath, outputfilename):
    im = Image.open(imagepath)
    im = im.convert("1") #白黒に変換
    im.tobitmap() #bitmapに変換
    wf = open(outputfilename, "wb")
    im.save(wf)
    wf.close()


if __name__ == "__main__":
    path = r"C:\temp\image"
    tobitmap(r"C:\temp\image\ruko.jpg", r"C:\temp\image\ruko.bmp")

ちなみに

im = im.convert("1")

im.convert("1")

と勘違いしてハマッた。

ruko
categories: python
Read and Post Comments

Pythonで日付の計算はdateutilを使う

April 05, 2010 at 10:00 PM | View Comments

Pythonで日付の計算をするのにはdatetimeやcalendarなどの標準モジュールがあるのですが、easy_install などでインストールできる dateutilモジュールが便利です。

が、使い方を忘れがちなので、よく使うものをまとめてみました。

非常に便利なのでPythonを使っている人はかならずインストールしておきましょう。

#! /usr/bin/env python
#! vim: fileencoding=utf8

import datetime

def test_relativedelta():
    """
    datetime.date型の加算減算のテスト
    monthsなどsが付くと加算、
    monthなどsが付かない場合は指定した数字になる

    >>> from dateutil import relativedelta
    >>> d = datetime.date(2009, 11, 3)
    >>> d + relativedelta.relativedelta(months=+1)
    datetime.date(2009, 12, 3)
    >>> d + relativedelta.relativedelta(month=2, days=+3)
    datetime.date(2009, 2, 6)
    >>> d + relativedelta.relativedelta(months=+2, day=20)
    datetime.date(2010, 1, 20)

    月末のテスト
    >>> d + relativedelta.relativedelta(months=+3, day=99)
    datetime.date(2010, 2, 28)
    """


def test_parser():
    """
    文字列からdatetime.datetimeを生成するテスト
    ありがちなフォーマットなら指定なしでparseしてくれる

    >>> from dateutil import parser
    >>> parser.parse("2010-01-22")
    datetime.datetime(2010, 1, 22, 0, 0)
    >>> parser.parse("2010/01/22")
    datetime.datetime(2010, 1, 22, 0, 0)
    >>> parser.parse("20100122")
    datetime.datetime(2010, 1, 22, 0, 0)
    >>> parser.parse("2010 01 22")
    datetime.datetime(2010, 1, 22, 0, 0)
    """

def test_rrule():
    """
    特定の期間の月曜日を取得するテスト
    >>> from dateutil.rrule import rrule, MO, WEEKLY, MONTHLY
    >>> list(rrule(WEEKLY, byweekday=(MO),
    ...   dtstart=datetime.datetime(2010,2,21),
    ...   until=datetime.datetime(2010,4,20)))
    [datetime.datetime(2010, 2, 22, 0, 0), datetime.datetime(2010, 3, 1, 0, 0), datetime.datetime(2010, 3, 8, 0, 0), datetime.datetime(2010, 3, 15, 0, 0), datetime.datetime(2010, 3, 22, 0, 0), datetime.datetime(2010, 3, 29, 0, 0), datetime.datetime(2010, 4, 5, 0, 0), datetime.datetime(2010, 4, 12, 0, 0), datetime.datetime(2010, 4, 19, 0, 0)]

    特定期間の月最初の月曜日のリストを取得するテスト
    >>> list(rrule(MONTHLY, byweekday=(MO(1)), dtstart=datetime.datetime(2010,2,21), until=datetime.datetime(2010,7,20)))
    [datetime.datetime(2010, 3, 1, 0, 0), datetime.datetime(2010, 4, 5, 0, 0), datetime.datetime(2010, 5, 3, 0, 0), datetime.datetime(2010, 6, 7, 0, 0), datetime.datetime(2010, 7, 5, 0, 0)]

    特定期間の月最後の月曜日のリストを取得するテスト
    >>> list(rrule(MONTHLY, byweekday=(MO(-1)), dtstart=datetime.datetime(2010,2,21), until=datetime.datetime(2010,7,20)))
    [datetime.datetime(2010, 2, 22, 0, 0), datetime.datetime(2010, 3, 29, 0, 0), datetime.datetime(2010, 4, 26, 0, 0), datetime.datetime(2010, 5, 31, 0, 0), datetime.datetime(2010, 6, 28, 0, 0)]

    """

def _test():
    import doctest
    doctest.testmod()


if __name__ == "__main__":
    _test()

ちなみに上のように、関数のコメントにテストを書くのはdoctestと呼ばれるもので、こちらはPython標準の機能です。

5.2 doctest -- 対話モードを使った使用例の内容をテストする

categories: python
Read and Post Comments

集合知プログラミング 2章1

January 18, 2010 at 11:06 PM | View Comments

買って積み本になっていた集合知プログラミングを少しやってみたのでメモです。

集合知プログラミング
Toby Segaran
オライリージャパン
売り上げランキング: 15874

サンプルコードはPythonで書かれていて、

http://examples.oreilly.com/9780596529321/PCI_Code.zip

からダウンロードできます。

今回やったのは以下の部分

2章 推薦を行う
       2.1 協調フィルタリング
       2.2 嗜好の収集
       2.3 似ているユーザを探し出す
               2.3.1 ユークリッド距離によるスコア
               2.3.2 ピアソン相関によるスコア
               2.3.3 どちらの類似性尺度を利用すべきなのか?
               2.3.4 評者をランキングする
       2.4 アイテムを推薦する

ユーグリット距離とピアソン相関です。

データはPythonの以下のように辞書型(ハッシュ)になっていて、データが多いときはデータベースでやるといいよと書いてあったので、今回はsqlite3でやることにしました。

critics={'Lisa Rose': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.5,
    'Just My Luck': 3.0, 'Superman Returns': 3.5, 'You, Me and Dupree': 2.5,
    'The Night Listener': 3.0},
    'Gene Seymour': {'Lady in the Water': 3.0, 'Snakes on a Plane': 3.5,
        'Just My Luck': 1.5, 'Superman Returns': 5.0, 'The Night Listener': 3.0,
        'You, Me and Dupree': 3.5},
    'Michael Phillips': {'Lady in the Water': 2.5, 'Snakes on a Plane': 3.0,
        'Superman Returns': 3.5, 'The Night Listener': 4.0},
    'Claudia Puig': {'Snakes on a Plane': 3.5, 'Just My Luck': 3.0,
        'The Night Listener': 4.5, 'Superman Returns': 4.0,
        'You, Me and Dupree': 2.5},
    'Mick LaSalle': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
        'Just My Luck': 2.0, 'Superman Returns': 3.0, 'The Night Listener': 3.0,
        'You, Me and Dupree': 2.0},
    'Jack Matthews': {'Lady in the Water': 3.0, 'Snakes on a Plane': 4.0,
        'The Night Listener': 3.0, 'Superman Returns': 5.0, 'You, Me and Dupree': 3.5},
    'Toby': {'Snakes on a Plane':4.5,'You, Me and Dupree':1.0,'Superman Returns':4.0}}

sqlalchemyを使ってテーブルの作成とデータのINSERT、サンプルプログラムと同じようにユーグリット距離とピアソン相関の計算をしています。

sqlalchemyはわざわざCREATE TABLE~~のようにSQLを書かなくても自動でテーブルを作ってくれるので便利です。

#! /usr/bin/env python
#! vim: fileencoding=utf8

import os

from math import pow, sqrt

from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, String, ForeignKey, Float
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation, backref

#_Debug = True
_Debug = False

BASE_PATH = os.path.dirname(os.path.abspath(__file__))
DATA_PATH = os.path.join(BASE_PATH, 'data')
if not os.path.exists(DATA_PATH):
    os.makedirs(DATA_PATH)
DB_FILE = os.path.join(DATA_PATH, 'data.db')


engine = create_engine('sqlite:///%s' % DB_FILE, echo=_Debug)
Base = declarative_base()
metadata = Base.metadata

class User(Base):

    __tablename__ = "users_table"

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String)

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return "<User<' %s'>" % self.name


class Movie(Base):

    __tablename__ = "movies_table"

    id = Column(Integer, primary_key=True)
    name = Column(String, nullable=False)

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return "<Movie<' %s'>" % self.name

class Score(Base):

    __tablename__ = "scores_table"

    id = Column(Integer, primary_key=True)
    user_id = Column(Integer, ForeignKey('users_table.id'))
    movie_id = Column(Integer, ForeignKey('movies_table.id'))
    score = Column(Float)

    def __init__(self, user, movie, score):
        self.user = user
        self.movie = movie
        self.score = score

    def get_score(self):
        return self.score
        #return self.score /10.0

    def __repr__(self):
        return "<Score<' %s' ' %s' ' %s'>" % (
                self.user.name,
                self.movie.name,
                self.score)

Score.user = relation(User, backref=backref('scores', lazy='dynamic'))
Score.movie = relation(Movie, backref=backref('scores', lazy='dynamic'))
users_table = User.__tablename__
movies_table = Movie.__tablename__
score_table = Score.__tablename__

metadata.create_all(engine)

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()


def initial():
    '''
    初期データ挿入
    '''
    users_list = [
        User(i) for i in [
            'Lisa Rose', 'Gene Seymour', 'Michael Phillips', 'Claudia Puig', 'Mick LaSalle',
            'Jack Matthews', 'Toby'
        ]
    ]

    movies_list = [
        Movie(i) for i in [
            'Lady in the Water', 'Snakes on a Plane', 'Just My Luck', 'Superman Returns',
            'You, Me and Dupree', 'The Night Listener'
        ]
    ]

    for u in users_list:
        session.add(u)

    for m in movies_list:
        session.add(m)

    score_list = [
        #La    Sn    Ju    Su    Yo    Th
        [2.5,  3.5,  3.0,  3.5,  2.5,  3.0], #Lisa
        [3.0,  3.5,  1.5,  5.0,  3.5,  3.0], #Gene
        [2.5,  3.0,  None, 3.5,  None, 4.0], #Michael
        [None, 3.5,  3.0,  4.0,  2.5,  4.5], #Claudia
        [3.0,  4.0,  2.0,  3.0,  2.0,  3.0], #Mick
        [3.0,  4.0,  None, 5.0,  3.5,  3.0], #jack
        [None, 4.5,  None, 4.0,  1.0, None] #Toby
    ]

    for u_id, u in enumerate(score_list):
        for m_id, m in enumerate(u):
            if m:
                session.add(Score(
                    user=users_list[u_id],
                    movie=movies_list[m_id],
                    score=m)
                )

    session.commit()

def hoge():
    '''
    ためし
    '''
    #Tobyさんの評価を取得
    u = session.query(User).filter_by(name="Toby").first()
    print u.scores.all()
    #スーパーマンの映画から評価を取得してTobyさんのを表示
    m = session.query(Movie).filter_by(name="Superman Returns").first()
    print m.scores.filter_by(user=u).all()
    #スーパーマンの映画の評価を取得
    for i in m.scores.all():
        print "user %s score %s" % (i.user.name, i.score)

def sim_distance(session, person1, person2):
    """
    person1とperson2の距離を元にした類似性スコアを返す
    """
    from sqlalchemy import or_
    p1 = session.query(User).filter_by(name=person1).first()
    p2 = session.query(User).filter_by(name=person2).first()
    movies = [i.movie for i in session.query(Score).filter(Score.user==p1) if
        i.movie in (j.movie for j in session.query(Score).\
                filter(Score.user==p2))]
    if movies == 0:
        return 0
    sum_of_squares = sum([pow(
        session.query(Score).filter(Score.user==p1).filter(Score.movie==m).\
                first().get_score() -
        session.query(Score).filter(Score.user==p2).filter(Score.movie==m).\
                first().get_score()
        , 2) for m in movies])
    return 1/(1+sum_of_squares)

def sim_person(session, person1, person2):
    """
    person1とperson2のピアソン相関係数を返す
    """
    p1 = session.query(User).filter_by(name=person1).first()
    p2 = session.query(User).filter_by(name=person2).first()
    movies = [i.movie for i in session.query(Score).filter(Score.user==p1) if
                i.movie in (j.movie for j in session.query(Score).\
                        filter(Score.user==p2))]

    count = len(movies)
    if count == 0:
        return 0

    sum1 = sum([s.get_score() for s in session.query(Score).filter(Score.user==p1)
        if s.movie in movies])
    sum2 = sum([s.get_score() for s in session.query(Score).filter(Score.user==p2)
        if s.movie in movies])

    sum1sq = sum([pow(s.get_score(), 2) for s in session.query(Score).filter(Score.user==p1) if s.movie in movies])
    sum2sq = sum([pow(s.get_score(), 2) for s in session.query(Score).filter(Score.user==p2) if s.movie in movies])

    psum = sum([
        session.query(Score).filter(Score.user==p1).filter(Score.movie==m).first().get_score() *
        session.query(Score).filter(Score.user==p2).filter(Score.movie==m).first().get_score()
        for m in movies])

    num = psum-(sum1*sum2/count)
    den = sqrt((sum1sq-pow(sum1, 2)/count)*(sum2sq-pow(sum2, 2)/count))
    if den == 0:
        return 0
    """
    print "num %s" % num
    print "den %s" % den
    print "sum1 %s" % sum1
    print "sum2 %s" % sum2
    print "sum1sq %s" % sum1sq
    print "sum2sq %s" % sum2sq
    print "psum %s" % psum
    """
    return num/den

def topMaches(session, person, n=5, similarity=sim_person):
    scores = [(similarity(session, person, other.name), other.name)
        for other in session.query(User).filter(User.name!=person)]
    scores.sort()
    scores.reverse()
    return scores[:n]


def getRecommendations(session, person, similarity=sim_person):
    from sqlalchemy.sql import exists

    totals = {}
    simSums = {}
    for other in session.query(User).filter(User.name!=person):
        sim = similarity(session, person, other.name)
        #print "sim %s" % sim
        if sim <= 0:
            continue

        for s in other.scores:
            #まだ評価していなものを計算
            if not session.query(Score).\
                    filter(Score.user==session.query(User).\
                    filter(User.name==person).first()).\
                    filter(Score.movie==s.movie).count():

                totals.setdefault(s.movie, 0)
                totals[s.movie] += s.get_score() * sim
                simSums.setdefault(s.movie, 0)
                simSums[s.movie] += sim
    rankings = [(total/simSums[movie], movie.name) for movie, total in totals.iteritems()]
    rankings.sort()
    rankings.reverse()
    return rankings

if __name__ == "__main__":
    #initial() #最初にDBを作ってデータを入れる関数、最初以外はコメントアウト
    #hoge()
    print sim_distance(session, "Lisa Rose", "Gene Seymour")
    print sim_person(session, "Lisa Rose", "Gene Seymour")
    print topMaches(session, 'Toby', n=3)
    print getRecommendations(session, 'Toby')
    print getRecommendations(session, 'Toby', sim_distance)

これを実行すると、sqlite3に

users_table, movies_table, scores_table の3つのテーブルの作成、初期データの挿入、計算のためのSQL文が発行されます。

http://static.flickr.com/4027/4284307397_0d8a367c05.jpg http://static.flickr.com/4014/4285050820_6952b4d93d.jpg http://static.flickr.com/4036/4284307333_1aa87a073d.jpg

実行結果は以下の通りになります。

0.148148148148
0.396059017191
[(0.99124070716192991, u'Lisa Rose'), (0.92447345164190486, u'Mick LaSalle'), (0.89340514744156474, u'Claudia Puig')]
[(3.3477895267131017, u'The Night Listener'), (2.8325499182641614, u'Lady in the Water'), (2.5309807037655649, u'Just My Luck')]
[(3.5002478401415877, u'The Night Listener'), (2.7561242939959363, u'Lady in the Water'), (2.4619884860743739, u'Just My Luck')]

ところが、このプログラム、最初の方にある_DebugをTrueにして、実行しているSELECT文の数を数えると、なんと554個、、、、。

O/Rマッパーは便利だけど集計するには自分でSQL文を打った方が効率が良いです。また、上のプログラムはテーブルと密接してるので、他のテーブルには使えません。

ユーグリッド距離もピアソン相関もscores_tableのような

  • 評価するモノ (例 user_id)
  • 評価されるモノ (例 movie_id)
  • 評価数値 (例 score)

のカラムを持つテーブルがあればいいので、

http://static.flickr.com/4036/4284307333_1aa87a073d.jpg

同じようなテーブルがあれば、そのまま計算できるようなプログラムを書いてみました。main関数で上で作ったデータベースを計算しています。

#! /usr/bin/env python
#! vim: fileencoding=utf8

import os
import sqlite3

from math import sqrt
import logging as logger

PATH = os.path.dirname(os.path.abspath(__file__))
DATA_PATH = os.path.join(PATH, 'data')
DB_FILE = os.path.join(DATA_PATH, 'data.db')

def sim_distance(cur, table, col1, col2, score_col, a_value, b_value):
    """
    a_valueとb_valueの距離を元にした類似性スコアを返す
    """
    sql = """
    SELECT a.%(score_col)s AS a_score, b.%(score_col)s AS b_score
    FROM %(table)s a, %(table)s b
    WHERE a.%(col1)s = %(a_value)s AND a.%(col2)s = b.%(col2)s
    AND b.%(col1)s = %(b_value)s""" % locals()
    logger.debug(sql)
    cur.execute(sql)
    sum_of_sequence = count = 0
    for a_s, b_s in cur:
        count += 1
        sum_of_sequence += pow(a_s * 1.0 - b_s * 1.0, 2)
    if count == 0:
        return 0
    return 1 / (1 + sum_of_sequence)

def sim_person(cur, table, col1, col2, score_col, a_value, b_value):
    """
    a_valueとb_valueのピアソン相関係数を返す
    """
    sql = """
    SELECT a.%(score_col)s AS a_score, b.%(score_col)s AS b_score
    FROM %(table)s a, %(table)s b
    WHERE a.%(col1)s = %(a_value)s AND a.%(col2)s = b.%(col2)s
    AND b.%(col1)s = %(b_value)s""" % locals()
    logger.debug(sql)
    cur.execute(sql)
    sum1 = sum2 = sum1sq = sum2sq = psum = count = 0
    for a_s, b_s in cur:
        count += 1
        sum1 += a_s * 1.0
        sum2 += b_s * 1.0
        sum1sq += pow(a_s * 1.0, 2)
        sum2sq += pow(b_s * 1.0, 2)
        psum += a_s * b_s * 1.0
    if count == 0:
        return 0
    num = psum - (sum1 * sum2 / count)
    den = sqrt((sum1sq-pow(sum1, 2)/count)*(sum2sq-pow(sum2, 2)/count))
    if den == 0:
        return 0
    """
    logger.debug("num %s" % num)
    logger.debug("den %s" % den)
    logger.debug("sum1 %s" % sum1)
    logger.debug("sum2 %s" % sum2)
    logger.debug("sum1sq %s" % sum1sq)
    logger.debug("sum2sq %s" % sum2sq)
    logger.debug("psum %s" % psum)
    """
    return num/den

def topMaches(cur, table, col1, col2, score_col, a_value, n=5, similarity=sim_person):
    """
    a_valueとそれ以外との関係性を計算してもっとも似ているもののリストを返す
    """
    # a_value 以外のcol1を抽出
    sql = "SELECT DISTINCT %(col1)s FROM %(table)s WHERE %(col1)s != %(a_value)s" % locals()
    cur.execute(sql)
    other_list = [i[0] for i in cur.fetchall()]
    scores = [(similarity(cur, table, col1, col2, score_col, a_value, b_value), b_value)
            for b_value in other_list]
    scores.sort()
    scores.reverse()
    return scores[:n]

def getRecommendations(cur, table, col1, col2, score_col, a_value, n=50, similarity=sim_person):
    """
    a_valueになく、他のものにある中で必要と思われるものをリストで返す
    """
    totals = {}
    simSums = {}
    sql = "SELECT DISTINCT %(col1)s FROM %(table)s WHERE %(col1)s != %(a_value)s" % locals()
    logger.debug(sql)
    cur.execute(sql)
    for other in (i[0] for i in cur.fetchall()):
        sim = similarity(cur, table, col1, col2, score_col, a_value, other)
        logger.debug("sim %s" % sim)
        if sim <= 0:
            continue
        #a_valueになくotherにあるcol2とscoreを抽出
        #MySQL4系統では副問合せを使えないのでLEFT JOINを使用
        sql = """
        SELECT a.%(col2)s, a.%(score_col)s FROM %(table)s a
            LEFT OUTER JOIN %(table)s b ON b.%(col1)s = %(a_value)s AND a.%(col2)s = b.%(col2)s
            WHERE a.%(col1)s = %(other)s AND b.%(score_col)s IS NULL""" % locals()
        logger.debug(sql)
        cur.execute(sql)

        for c2, score in cur:
            #logger.debug("%s %s" % (c2, score))
            totals.setdefault(c2, 0)
            totals[c2] += score * sim
            simSums.setdefault(c2, 0)
            simSums[c2] += sim
    rankings = [(total/simSums[c2], c2) for c2, total in totals.iteritems()]
    rankings.sort()
    rankings.reverse()
    return rankings[:n]


def main():
    con = sqlite3.connect(DB_FILE)
    cur = con.cursor()
    args = dict(
            cur=cur,
            table = "scores_table",
            col1 = "user_id",
            col2 = "movie_id",
            score_col = "score",
            a_value = 1,
            b_value = 2)
    print sim_distance(**args)
    print sim_person(**args)
    print topMaches(
            cur=cur,
            table = "scores_table",
            col1 = "user_id",
            col2 = "movie_id",
            score_col = "score",
            a_value = 7,
            n = 3)
    print getRecommendations(
            cur=cur,
            table = "scores_table",
            col1 = "user_id",
            col2 = "movie_id",
            score_col = "score",
            a_value = 7,
            n = 3)

    con.close()

if __name__ == "__main__":
   main()

結果は、

0.148148148148
0.396059017191
[(0.99124070716192991, 1), (0.92447345164190486, 5), (0.89340514744156474, 4)]
[(3.3477895267131017, 6), (2.8325499182641614, 1), (2.5309807037655649, 3)]

他にも手持ちのMySQLのテーブルで試しましたが上手く計算できるようです。

ただ、この計算だけだと実用的な集合知とは思えないデータが出来るので、さらに読み進めていこうと思います。

categories: python
Read and Post Comments

るこさん用twitterクライアント「Rukotter」

October 06, 2009 at 08:59 PM | View Comments

そろそろ7ヶ月目になる我が家の「るこさん」ですが、どういう訳か非常にパソコンが好きです。

たまに触らせてあげてたのですが、vimをやらせてもあまり面白くないのでTwitterをやらせることにしました。

とは言っても0歳児に文字を入力して「Enterキー」を押すと送信されるということを伝えるのは無理なので、るこさん用のTwitterクライアント「Rukotter」をwxPythonで作りました。

http://bitbucket.org/ueblog/wxpythonapps/src/tip/rukotter/rukotter.py

http://static.flickr.com/2441/3987177120_0ae614549b.jpg

python,wxpython,python-twitter,simplejsonがあれば動くと思います。

特徴は

  • 全画面表示
  • 特定の文字数を超えると自動的に送信される(今のところ60文字)
  • 同じ繰り返し文字が3字以上続くと3字に自動的に3字に変換される(ずっと同じキーばかり押していることが多いため)。例 aaaaaaabbdcddddddddd → aaabbdcddd

です。

これで「るこさん」が適当にキーを連打しているだけで自動的にTwitterにPOSTできます。

Windowsキーを押されるといろいろ困るのでアプリ起動時にWindowsキーを無効にする機能もつけようと思ったのですが、

keyhacというソフトを起動すると何の設定なしに無効になるので、これを手動で起動してからるこさんに遊ばせるようにしています。

Pythonスクリプトでキーカスタマイズ [ keyhac ]

ふつうWindowsのキーカスタマイズはレジストリを弄ったり面倒なのですが、このソフトはレジストリを弄らないし、終了したらすぐに元の状態に戻るし、かなりオススメです。

「るこさん」はひたすらキーを叩きまくり喜んでいるようなので何よりです。

http://www.youtube.com/watch?v=Pf1rszhdgSc

文章になっていない無意味な文字の羅列をネットに流すのはどうかと思ったけど、あまり深く考えないようにしました。

フォローするようなものではないですが、「るこさん」のTwitterは

http://twitter.com/ruko_ueblog

になります。

wxPythonは今回はじめて使ったのですが1ファイルでGUIアプリが作れるのは分かっていたけどやはり感動的、しかし、Visual Studioなどの開発ソフトならIDEでドラッグしたりクリックしたりすれば自動的に生成されるフォームやボタンなどの部分を1つ1つ書くのは正直なところ面倒でした。

とは言っても、今までコマンドプロンプトから実行していたちょっとしたツールをGUI化するに便利なので、ちょくちょく使おうと思います。

py2exe化も挑戦してますが、もっと上手い方法がありそうなので後でまたチャレンジしようと思います。

categories: python, ruko
Read and Post Comments

1以上100未満の『2個の素数の積』である整数

May 12, 2009 at 02:53 AM | View Comments

Python Hack-a-thon#1 の参加フォームに

問題: 1以上100未満の『2個の素数の積』である整数を列挙しなさい (回答は任意です)

という問題があったのでやってみました。

Generatorだけを使っているので無駄にリストを作ってメモリを使いすぎないようにしています。limitを100000くらいにしても割と普通に動きます。

ただ、2回目のLoopで素数の計算を何度もやっているのが無駄、、、GeneratorはDeepcopyや、一つ前に戻すのが難しい、、、itertools.teeでも上手くかけませんでした orz...... 1以上100未満の『2個の素数の積』である整数

import math
from itertools import ifilter, count

limit = 100

def sieve(limit=None, start=2):
    g = count(2)
    g = ifilter(lambda x, start=start: x >= start, g) #start以上の素数を返すFliter
    while True:
        prime = g.next()
        if limit and prime > limit: # limitを超えたらStopIteration
            raise StopIteration
        yield prime
        g = ifilter(lambda x, prime=prime: x % prime, g) # primeで割り切れるとFalse、割り切れないとTrueを返すlambdaをフィルターに追加

for i in sieve(int(math.sqrt(limit))): # limit が100なら 10がlimit
    for k in sieve(limit/i, i):
        print "%s * %s = %s" % (i, k, (i*k))

素数に関しては以前書いたEntryとおり、4 TopCoderさんのGeneratorを使っています。

ueBLOG | Pythonで素数生成

この素数ジェネレーターは今まで見てきたPythonのコードの中でもかなり良いと思うコードなので、何をしてるか分からない人はリンク先の解説を見るといいでしょう。

今回は引数limitでStopIteration、引数start以上の数を返すフィルターを追加しました。

素数の積なので、たとえば 2,3,5で考えると

2*2 2*3 2*5
3*2 3*3 3*5
5*2 5*3 5*5

の計算をしなくてはいけません、しかし、2*3 と3*2は同じ値になるので、一度だけ計算すればOKです。

2*2 2*3 2*5
  3*3 3*5
    5*5

よって、2回目のループは 最初のループの値 i以上の数値にすれば重複しないで済むので、素数のGeneratorに追加した引数startにiを渡してあげます。

次に、2回目のループをどこまで計算するかですが、1回目のループ i * k が limit (100)以上の場合は不要になります。よって limit(100) / iまでやればOKです。

最後に最初のループをどこまでやるかです、、、

これは先ほどの 2, 3, 5の例をみれば k >= iの場合、 常に最初の i * k は i * iと同じになるので i*iが100以内になるような数、10まで計算すればOKということになります。これをpythonで汎用的に書くと int(math.sqrt(limit)) ということになります。

10 * 10 = 100なんだから、11 * 11は100以上になるから計算の無駄ということですね。

結果

2 * 2 = 4
2 * 3 = 6
2 * 5 = 10
2 * 7 = 14
2 * 11 = 22
2 * 13 = 26
2 * 17 = 34
2 * 19 = 38
2 * 23 = 46
2 * 29 = 58
2 * 31 = 62
2 * 37 = 74
2 * 41 = 82
2 * 43 = 86
2 * 47 = 94
3 * 3 = 9
3 * 4 = 12
3 * 5 = 15
3 * 7 = 21
3 * 11 = 33
3 * 13 = 39
3 * 17 = 51
3 * 19 = 57
3 * 23 = 69
3 * 29 = 87
3 * 31 = 93
5 * 5 = 25
5 * 6 = 30
5 * 7 = 35
5 * 8 = 40
5 * 9 = 45
5 * 11 = 55
5 * 13 = 65
5 * 17 = 85
5 * 19 = 95
7 * 7 = 49
7 * 8 = 56
7 * 9 = 63
7 * 10 = 70
7 * 11 = 77
7 * 12 = 84
7 * 13 = 91
categories: python
Read and Post Comments

RE:RE:*hogeとか**mapとか

March 11, 2009 at 08:03 AM | View Comments

RE:pythonの引数にある*hogeとか**mapとか - Djangoへの片思い日記 より

http://d.hatena.ne.jp/jYoshiori/20090310/1236685438

僕は

>>> import datetime
>>> import time
>>> datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
datetime.datetime(2008, 3, 10, 0, 0)

のようにやっていたのですが、strptimeだかstrftimeだか忘れがちだったので

>>> value = '2008-03-10'
>>> datetime(*map((lambda x: int(x)),value.split('-')))
datetime.datetime(2008, 3, 10, 0, 0)

の方が分りやすいかもしれない。Python使いの人はmapよりリスト内包記法の方が馴染みやすいので自分で書くなら

>>> datetime.datetime(*[int(i) for i in value.split('-')])
datetime.datetime(2008, 3, 10, 0, 0)

かな、、

もっともformatを指定してもらうなら最初のやりかたが必要

>>> def str_to_date(datestring, format="%Y-%m-%d"):
....     return datetime.date(*time.strptime(datestring, format)[:3])
....
>>> str_to_date('2008-03-10')
datetime.date(2008, 3, 10)
>>> str_to_date('080310', '%y%m%d')
datetime.date(2008, 3, 10)

ちなみに*argsと**kwargsの有意義な使い方は関数デコレーターではないでしょうか?

中でも引数を束縛した関数を返してくれる(カリー化)のfunctools.partialは良い例だと思います。

6.6 functools -- 高階関数と呼び出し可能オブジェクトの操作 (python2.5以上)

実際のコードはもっと複雑なようですが、ドキュメントの説明にもあるように

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

と等価なコードになっています。

実際つかってみると、

>>> basic_str_to_date = partial(str_to_date, format="%Y-%m-%d")
>>> custom_str_to_date = partial(str_to_date, format="%y%m%d")
>>> basic_str_to_date('2008-10-01')
datetime.date(2008, 10, 1)
>>> basic_str_to_date('2008-11-01')
datetime.date(2008, 11, 1)
>>> custom_str_to_date("081001")
datetime.date(2008, 10, 1)
>>> custom_str_to_date("081101")
datetime.date(2008, 11, 1)

のようにformatを束縛した関数を返してくれます。*argsと**kwordsを使うことで、第一引数がどんな関数でも対処できるようになっています。

ちなみにdjango.utils.functional.curryもほぼ同じコードです。

よくよく良く考えたら、この例えは

django.utils.functional - スコトプリゴニエフスク通信 そのままでした。すみません。

categories: python
Read and Post Comments

NKF_pythonで半角文字の変換

March 06, 2009 at 08:00 PM | View Comments

前回の続き、

ueBLOG | Pythonで機種依存文字を含む文字列を半角から全角へ

NFK_pythonにはguessという文字コードを判別してくれる関数があるのですが、

>>> b = "ナイトウあいう".decode("utf8").encode("sjis") #僕のipythonの内部コードがutf8のためutf8でdecode
>>> b
'ナイトウ\x82\xa0\x82\xa2\x82\xa4'
>>> print b
ナイトウあいう
>>> nkf.guess(b)
'Shift_JIS'

これは半角だけの文字だと上手く判別してくれません。

>>> a = "ナイトウ".decode("utf8").encode("sjis")
>>> nkf.guess(a)
'EUC-JP'

しかも、全角文字が入っているものは文字コード変換できるけど、半角だけだと上手くいきません

>>> print nkf.nkf("-sx", b)
ナイトウあいう
>>> print nkf.nkf("-sx", a)
轍蝶

これはやはり元のコードを知っていないとだめなようです。

-J -E -S -W
期待される入力コードの性質を指定する。 -J ISO-2022-JP を仮定する。 -E 日本語 EUC (AT&T) を仮定する。 -S Shift_JIS を仮定する。 いわゆる半角カナ (JIS X 0201 片仮名)も受け入れる。 -W UTF-8 を仮定する。

というわけで、sjisを期待するためにSをオプションでつければちゃんと表示されました。

>>> print nkf.nkf("-sxS", a)
ナイトウ

文字コードが自動判別できないとなると、nkfを使う理由が少し、、、、、

オレ、今度生まれるときはマルチバイト文字がない国に生まれるんだ、、、、、

categories: python
Read and Post Comments

Next Page »
track feed ueblog