バイナリサーチ

AOJの問題を解く途中で作ったのでメモしておく。結局使わなくなったけど。
あらかじめソートされているのが前提。見つからないときは None を返す。

def binary_search(lis, x):
    if len(lis) == 0:
        return None
    else:
        i = len(lis) / 2
        if lis[i] == x:
            return x
        elif x < lis[i]:
            return binary_search(lis[:i], x)
        else:
            return binary_search(lis[i+1:], x)

a = range(100000)
print binary_search(a, 3359)
print binary_search(a, 100001)
print binary_search(a, 0)
print binary_search(a, -1)

実行結果:

^o^ > python binary_search.py
3359
None
0
None

	

素数を無限に列挙するジェネレータ

エラトステネスの篩を応用して書いてみた。

import sys
import itertools

def primes():
    p = []
    p.append(2)
    yield 2
    p.append(3)
    yield 3
    for n in itertools.count(6, 6):
        n1 = n - 1
        for x in p:
            if n1 % x == 0:
                break
            else:
                p.append(n1)
                yield n1
               n2 = n + 1
               for y in p:
                   if n2 % y == 0:
                       break
                   else:
                       p.append(n2)
                       yield n2

n = int(sys.argv[1])
for p in primes():
    if p < n:
        print p
    else:
        break
^o^ > python primes3.py 100
2
3
5
7
11
13
17
19
23
29
31
37
41
43
47
53
59
61
67
71
73
79
83
89
97

ここでは100未満の素数を出力している。さすがに primes3.py 1000000 とかやると遅い。

重複する要素を取り除いだリストと重複する要素だけを取り出したリスト

リストから重複した要素を取り除く

Python には uniq がないらしい。ああ、なんてこった。
というわけで作ってみた。

def uniq(lis):
    r = []
    for i in lis:
        if i not in r:
            r.append(i)
    return r

a = [1, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9]

print 'Original: ', a
print 'Uniq: ', uniq(a)

もうちょっときれいにならないかな。reduceでできそうな気がする。

^o^ > python uniq.py
Original:  [1, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9]
Uniq:      [1, 2, 3, 4, 5, 6, 7, 8, 9]

リストから重複した要素だけを取り出す

今度は逆に、重複した要素だけを取り出してみる。

def overlup(lis):
    r = []
    def func(x, y):
        if x == y and x not in r:
            r.append(x)
        return y
    reduce(func, lis)
    return r

a = [1, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9]

print 'Original: ', a
print 'Overlup: ', overlup(a)
^o^ > python overlup.py
Original:  [1, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9]
Overlup:   [1, 4, 7]

ネストしたforループから脱出するには例外を使う

タイトルで言いたいことがすんでしまった^^)

forループを途中で脱出するにはbreakを使うけど、breakではひとつのループしか脱出してくれない。たとえば次の例ではbreakしても外側のforループは回り続けて、else節が実行される。

for i in range(5):
    for j in range(5):
        k = i + j
        print k
        if k > 5:
            break

        else:
            print "End of nested loop."
^o^ > python nested_loop.py
0
1
2
3
4
1
2
3
4
5
2
3
4
5
6
3
4
5
6
4
5
6
End of nested loop.

外側のforループもいっぺんに脱出したいときには、例外を使う。たとえばこんなふうに:

class EndLoop(Exception):
    pass

try:
    for i in range(5):
        for j in range(5):
            k = i + j
            print k
            if k > 5:
                raise EndLoop
            else:
                print "End of nested loop."
except EndLoop:
    print "Break in loop."

実行すると、ちゃんと途中でふたつのforループを抜け出していることがわかる。

^o^ > python nested_loop2.py
0
1
2
3
4
1
2
3
4
5
2
3
4
5
6
Break in loop.

挿入ソート

Pythonでやってる人を見かけたので俺も。

import random

def insertion_sort(lis):
    for i in range(1,len(lis)):
        tmp = lis.pop(i)
        for j in range(i):
            if tmp < lis[j]:
                lis.insert(j, tmp)
                break
            else:
                lis.insert(i, tmp)
                return lis


l = range(1,10) * 2
random.shuffle(l)
print "unsorted:", 
 l2 = insertion_sort(l)
print "sorted: ", l2

実行結果:

^o^ > python insertion_sort.py
unsorted: [1, 9, 6, 3, 8, 6, 5, 2, 7, 4, 2, 9, 8, 5, 7, 3, 4, 1]
sorted:   [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9]

Python の for文には else をつけることができるんだ。初めて使った。

リストのappendメソッドはNoneを返す

リスト(数列)の差分のリストがほしかったんだけど、はまったのでメモ。
最初、↓こう書いた。

def difference(lis):
    d = reduce(lambda x, y: (x[0].append(y-x[1]), y), lis, ([],0))
    d[0].pop(0)
    return d[0]

a = range(10)
diff = difference(a)
print diff

2行目のx[0].append(y-x[1])で差分を蓄積していってそれを返している(3行目は余分な初期値との差分があるからそれを取り除いている)。
ところが実行するこうだ。

^o^ > python difference.py
Traceback (most recent call last):
  File "difference.py", line 9, in 
    diff = difference(a)
  File "difference.py", line 3, in difference
    d = reduce(lambda x, y: (x[0].append(y-x[1]), y), lis, ([],0))
  File "difference.py", line 3, in 
    d = reduce(lambda x, y: (x[0].append(y-x[1]), y), lis, ([],0))
AttributeError: 'NoneType' object has no attribute 'append'

‘NoneType’のオブジェクトは’append’なんて属性持ってないと。しばらく悩んだけど、原因はタイトルのとおり。
1回目のappendを呼び出したところでNoneが返ってくるので、2回目の呼び出しでは失敗する。

>>> a = range(5)
>>> a
[0, 1, 2, 3, 4]
>>> type(a.append(5))
<type 'NoneType'>

結局、次のように書き換えたらうまくいったけど、関数がひとつ増えてしまった。

def difference2(lis):
    d = reduce(lambda x, y: (add_list(x[0], y-x[1]), y), lis, ([],0))
    d[0].pop(0)
    return d[0]

def add_list(lis, x):
    lis.append(x)
    return lis

a = range(10)
diff = difference2(a)
print diff
^o^ > python difference2.py
[1, 1, 1, 1, 1, 1, 1, 1, 1]

RubyのArray#pushならselfを返してくれるから素直に書けるんだけどなあ。

zip関数

いまさらだけどPython にも zip関数があるのを知った。というか見逃していた。

>>> a = range(5)
>>> a
[0, 1, 2, 3, 4]
>>> b = ['a', 'b', 'c', 'd', 'e']
>>> zip(a, b)
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]

今まで↓こう書いてたよ。ばかみたい。zipのほうがよっぽどすっきりしている。

>>> map(lambda x, y: (x, y), a, b)
[(0, 'a'), (1, 'b'), (2, 'c'), (3, 'd'), (4, 'e')]

とはいえ、違いもある。
zipは長さを短いほうにあわせる。

>>> c = ['foo', 'bar', 'baz']
>>> zip(a, c)
[(0, 'foo'), (1, 'bar'), (2, 'baz')]

mapだと長いほうにあわせて、足りない部分にはNoneが入る。

>>> map(lambda x, y: (x, y), a, c)
[(0, 'foo'), (1, 'bar'), (2, 'baz'), (3, None), (4, None)]

Luhnアルゴリズムでクレジットカードの番号をチェック:Haskell版

こないだやったLuhnアルゴリズムでのチェックをHaskellでもやってみた。

module Main (main) where

import Data.Char

numbers = [
  "5555555555554444",
  "5105105105105100",
  "4111111111111111",
  "4012888888881881",
  "3530111333300000",
  "3566002020360505",
  "30569309025904",
  "38520000023237",
  "378282246310005",
  "371449635398431",
  "378734493671000",
  "6011111111111117",
  "6011000990139424"
]

checkNumber num = sum' `mod` 10 == 0
  where
    sum' = sum $ zipWith add' (cycle [1,2]) (reverse num)
    add' x y = let z = x * digitToInt y
    in
    if z > 9 then z - 9 else z

main = mapM_ (print . checkNumber) numbers

実行結果:

^o^ > runhaskell checkCardNumber.hs
True
True
True
True
True
True
True
True
True
True
True
True
True

Luhnアルゴリズムでクレジットカードの番号をチェック

クレジットカード番号のチェックサムには Luhnアルゴリズムというのが使われているのを知った。

cf. Luhnアルゴリズムでクレジットカードの番号をチェック – brainstorm
cf. Luhnアルゴリズム – Wikipedia

Rubyでやってみた。

# coding: utf-8
#
# Check number with Luhn algorithm

def check_number(digits)
  even = false
  sum = digits.reverse.split(//).inject(0) do |s, d|
    d = d.to_i
    if even
      d = d * 2
      s -= 9 if d > 9
    end
    even = !even
    s += d
  end
  sum % 10 == 0
end


NUMBERS = [
  '5555555555554444',
  '5105105105105100',
  '4111111111111111',
  '4012888888881881',
  '3530111333300000',
  '3566002020360505',
  '30569309025904',
  '38520000023237',
  '378282246310005',
  '371449635398431',
  '378734493671000',
  '6011111111111117',
  '6011000990139424'
]

NUMBERS.each do |digits|
  puts check_number(digits)
end

カード番号の例は↓ここで紹介されているもの。
cf. ECサイトの動作テストに使える、クレジットカードのテスト番号一覧 – Webクリエイターボックス

実行結果:

^o^ > check_card_numbers.rb
true
true
true
true
true
true
true
true
true
true
true
true
true

easy_install

easy_install は Python のパッケージインストーラ、というかコマンドだ。Setuptools や Distribute に含まれている。で、ここがちょっと混乱するところなんだけど、Setuptools を使えばいいのか、それとも Distrubute を使うほうがいいのか、ってこと。Web の情報を見てみると、Setuptools は開発がとまっていて、そこから fork したのが Distribute らしい。

cf. エキPy読書会(第2期) 第1回に行ってきました – 今川館

というわけで、Distribute をインストールしてみた。

Distribute をインストールするには、次のURLからスクリプトをダウンロードして実行すればいい。

http://python-distribute.org/distribute_setup.py

^o^ > wget http://python-distribute.org/distribute_setup.py
^o^ > python distribute_setup.py

なんかいろいろとメッセージが出て、インストールが完了したらしい。
Pythonをインストールしたディレクトリの下にScripts\easy_install.exe というファイルができているので、ここにパスを通せばOK。
これで easy_install コマンドが使えるようになったはず。

^o^ > easy_install

ところが、いざ実行すると「ユーザーアカウント制御」というダイアログがでてきて、「はい」をクリックしても「いいえ」をクリックしてもそのまま終了してしまう。どうやら、Windows 7 のユーザー権限にひっかかっているらしい。どうすりゃいいんだ。

[追記]
どうもファイル名にinstallと入っているのが原因らしい。
cf. http://phpy.readthedocs.org/en/latest/install.html

次のようにすれば回避できるようだ。

^o^ > python -m easy_install
error: No urls, filenames, or requirements specified (see --help)

^o^ > python -m easy_install --help

Global options:
  --verbose (-v)  run verbosely (default)
  --quiet (-q)    run quietly (turns verbosity off)
  --dry-run (-n)  don't actually do anything
  --help (-h)     show detailed help message
  --no-user-cfg   ignore pydistutils.cfg in your home directory

Options for 'easy_install' command:
  --prefix                       installation prefix
  --zip-ok (-z)                  install package as a zipfile
  --multi-version (-m)           make apps have to require() a version
  --upgrade (-U)                 force upgrade (searches PyPI for latest
                                 versions)
  --install-dir (-d)             install package to DIR
  --script-dir (-s)              install scripts to DIR
  --exclude-scripts (-x)         Don't install scripts
  --always-copy (-a)             Copy all needed packages to install dir
  --index-url (-i)               base URL of Python Package Index
  --find-links (-f)              additional URL(s) to search for packages
  --delete-conflicting (-D)      no longer needed; don't use this
  --ignore-conflicts-at-my-risk  no longer needed; don't use this
  --build-directory (-b)         download/extract/build in DIR; keep the
                                 results
  --optimize (-O)                also compile with optimization: -O1 for
                                 "python -O", -O2 for "python -OO", and -O0 to
                                 disable [default: -O0]
  --record                       filename in which to record list of installed
                                 files
  --always-unzip (-Z)            don't install as a zipfile, no matter what
  --site-dirs (-S)               list of directories where .pth files work
  --editable (-e)                Install specified packages in editable form
  --no-deps (-N)                 don't install dependencies
  --allow-hosts (-H)             pattern(s) that hostnames must match
  --local-snapshots-ok (-l)      allow building eggs from local checkouts
  --version                      print version information and exit
  --no-find-links                Don't load find-links defined in packages
                                 being installed
  --user                         install in user site-package
                                 'C:\Users\takatoh\AppData\Roaming\Python\Python2
                                 7\site-packages'

usage: easy_install.py [options] requirement_or_url ...
   or: easy_install.py --help