ラベル イテレータ の投稿を表示しています。 すべての投稿を表示
ラベル イテレータ の投稿を表示しています。 すべての投稿を表示

2008年6月14日土曜日

Python の シーケンス型に慣れる

1. 配列の操作

プログラミングは、習うより慣れろ。

そう、避けるべきは黙読で、すべきは朗読です。文書は声に出して読まなければ身につきません。とはいっても、ソースコードまで音読していたらそれはそれで間抜けです。ソースコードは音読ではなく書き写す、すなわち写経するのがいいでしょう。

(本気でやるなら黙読は避けて朗読すべき: Days on the Moon  より)

新しく言語をはじめる場合、最初に確認する書き方の一つに「配列」がある。

配列操作の比較表: Ruby, Python, JavaScript, Perl, C++ - bkブログによると、

プログラムを書いていると、他のプログラミング言語の記憶とごっちゃになって、「配列の後ろに要素を追加するのは push だっけ、 append だっけ」などと混乱することがあります。

これを見ながら、写経することにした。 ( ̄人 ̄)ナムナム

a = [3,2,1]
b = [10,200,30]

# 大きさ
print "len(a) :", len(a)

# 空か?
if len(a) == 0: print "empty"
else:           print "not empty"
# 末尾に追加
a.append(4)
print a

# 挿入
a.insert(2, 100)
print a

# 末尾の要素を取り出す
print a.pop()
print a

# 先頭の要素を取り出す ※
print a.pop(0)
print a

# 別の配列を足す
a.extend(b)
print a

# 特定の値を削除する
a.remove(100)
print a

# 特定の位置にある要素を削除する
del(a[2])
print a
# 特定の値の数をカウントする
print a.count(3)
print a.count(0)

# 特定の要素を含んでいるか調べる
print 3 in a
print 4 in a
# 先頭の要素
print a[0]
# 末尾の要素
print a[-1]

# 特定の区間の要素
print a[1:3]    # 1 ~ 2 まで
# sorted は要素自体を並び換えない
print "sorted(a) :", sorted(a)
print a

# sort は配列自体を並び換える
print a
a.sort()
print a

# 要素を末尾から辿る
for elem  in reversed(a):
    print elem

# reversed は要素自体を操作しない
for x in reversed(a):
    print x
print a

# reverse は要素を自体を逆順にする
a.reverse()
print a
# 配列の要素を、特定の文字列でつなげる
print ', '.join("abc")
print ', '.join(str(x) for x in a)

# 要素を全てクリアする
b = a
a[:] = []
print a
print b

 

uniq

Python で、Ruby の Array#uniq に相当するものは、組み込み関数の set(), list() を使い、 list(set(リスト)) とする。

set([iterable])

集合を表現するset 型オブジェクトを返します。

list([sequence])

sequence の要素と同じ要素をもち、かつ順番も同じなリストを返します。

 

join

Python と Ruby の違うところは、 join が 文字列のメソッドとして定義されていること。

Python はなぜ String#join か - TokuLog 改めChumbyとどきました日記 によると

これ、理由は明解で、「join メソッドは文字列の処理だから」です。

それ以下でもそれ以上でもない。

文字列の処理をするメソッドを、どんなものでも入る Array Class のメソッドとしてはやすなんてとんでもない!

そう考えるのが Pythonista クオリティ

(via Python の join が str のメソッドになっている理由 - odz buffer)

 

2. Python は配列クラスに定義されているメソッドが少ない

こうやって見ると、Ruby は語彙が豊富で何でもありの言語だとすると、Python は正当な表記しか許さない律儀な言語。

「今の若者の言葉の乱れは...」

とでも言いそうな雰囲気を感じる。

認知的経済性から考えると、Python の方が言語としてシンプルで良い。独特の言い回しも許容する、言語の豊かさという視点から見たら、Ruby は使いやすい。

Python には、

「要素をリストの先頭に入れる」関数

に特別な名前がない。insert() による表現の一部になっている。これに対して、Ruby では unshift() という特別な名前が与えられている。

同様に Python では

「リストの先頭の要素を取り出す」関数

は pop() メソッドで代替する。Ruby では shift() という名称が与えられている。

「要素の取得」

においても、Ruby では .first, .last という特別な名前が与えられている。

Ruby は人が使う言葉に沿うようなメソッド名が付けられる傾向があるようように感じる。Python は、「あるもので間に合わせろ」ということか。

String クラスで定義されているメソッドの数を比較からも、そういう雰囲気が伝わってくる。

 

3. 文字列とリスト

Python では、文字列を for ループに投入すると、一文字ずつ処理が行われる。

for c in "hoge":
    print c

for c in u"ほげほげ":
    print c

これに対して、 Ruby では、hoge という一つの文字列として処理される。

for c in "hoge"
  puts c
end

一文字ずつ処理を行いたい場合は、split や scan を使って文字列を分割しなくてはならない。

"hoge".split(//).each do |c|
  puts c
end

"hoge".scan(/./).each do |c|
  puts c
end

# 文字コードが Shift_JIS の場合
"ほげほげ".split(//s).each do |c|
  puts c
end

 

3. シーケンス型

list() 関数に、次のような説明がある。

例えば、list('abc')['a', 'b', 'c'] および list(1, 2, 3)[1, 2, 3] を返します。

これを見て、 Haskell を思い出した。

ふつうのHaskellプログラミング によると、

実は、 Haskell では文字列もリストなのです。文字列は文字のリストとして表現されていて、特別な文字列は存在しません。ですから、リストの処理を覚えてしまえば文字列処理も身についたことになります。 (p36)

Haskell では、 "abc" は、['a', 'b', 'c'] と同じ。なぜなら、String 型は [Char] 型の別名に過ぎないため。

Python のライブラリリファレンスに、組み込み型の説明がされている。

ここに、リスト文字列の上位にある

「シーケンス型」

というものが定義されている。

上記で写経した sorted, reversed メソッドの位置付けも、これを見ると理解できる。

結構、イテレータ型が重要。

2008年6月9日月曜日

Python のイテレータ (2) - Ruby の Enumerable との比較

Python のイテレータ の続き。

1. Ruby の Enumerable モジュールのメソッドを分類する

Ruby でイテレータを書くときは、要素を保持するコンテナとなるクラスで、

  1. Enumerable モジュールをインクルードし、
  2. each メソッドを定義する。

イテレートされる、要素となるクラスでは、

  1. Comparable モジュールをインクルードし、
  2. <=> メソッドを定義して、比較可能にしておく。

Enumerable - Rubyリファレンスマニュアル には、便利なメソッドがいくつも定義されている。これを適当に分類する。

  • 要素取得
    • select , find , grep , reject
      • max , max_by , min , min_by
  • 関数の適用
    • map
    • inject
  • ソート
    • sort , sort_by ,
  • 存否確認
    • all? , any? , include?
  • 走査
    • each_with_index
  • その他
    • to_a , partition , zip

 

2. Python の組み込み関数で、iterable を引数に取る関数を分類する

上記の Ruby の Enumerable モジュールに相当するものは、 Python ではどこに定義されているのだろう?

組み込み関数 (2.1 Built-in Functions) を見ると、 iterable を引数として取るものがある。

iterable とは、enumerate() の説明の中に、

iterable はシーケンス型、イテレータ型、あるいは反復をサポートする他のオブジェクト型でなければなりません。

  • 要素取得
    • filter
      • max , min
  • 関数の適用
    • map
    • reduce
      • sum
  • ソート
    • sorted
  • 存否確認
    • all , any
  • 走査
    • enumerate
  • データ構造
    • list , set ,frozenset, tuple
  • その他
    • zip

mapfilter を合わせたものが、リスト内包表記 [x for x in S if 条件式] 。

シーケンス型 における x in S , x not in S という書き方もある。

 

関連記事

2008年6月3日火曜日

Python のイテレータ

1. ジェネレータを理解するためには、イテレータから

Python における リスト内包表記 を理解したので、次は、9.9 ジェネレータ (generator)

ジェネレータは、イテレータを作成するための簡潔で強力なツールです。

と説明があるので、ジェネレータを理解する前に、

イテレータ

について確認する。

 

2. イテレータ の実装方法と使い方

9.8 イテレータ (iterator) によると、その役割は for 文と連携することにある。

for 文を使うとほとんどの コンテナオブジェクトにわたってループを行うことができます

080329-004Python のイテレータは、Java の For-each Loop に似ている。

Ruby のイテレータ (2) - Enumerable で考えた同じ例を、Python のイテレータで実装してみる。例の内容は、

ex. 「人」が「グループ」 に所属している。「人」は `名前' と `年齢' を属性として持つ。

 

要素のとなるクラスの定義

まずは、 Person クラスから定義する。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return self.name + " " + str(self.age)

__str__ は、print 文から呼出すために作成。

3.3.1 基本的なカスタマイズ によると、

__str__(self)

組み込み関数 str() および print 文によって呼び出され、オブジェクトを表す ``非公式の'' 文字列を計算します。

Python では、9.6 プライベート変数 に、

クラスプライベート (class-private) の識別子に関して限定的なサポートがなされています。__spam (先頭に二個以上の下線文字、末尾に高々一個の下線文字) という形式の識別子、テキスト上では _classname__spam へと置換されるようになりました。

とある。ここでは、上記の点について考慮しないことにする。

 

イテレータ となるクラスに __iter__(), next() メソッドを実装

次に、 Person の集合に対する責務を持つ Group クラスを定義する。ここで、__iter__ と next() を定義することによって、 for ループで使用可能となる。2.3.5 イテレータ型 によると、

イテレータオブジェクト自体は以下の 2 のメソッドをサポートする必要があります。これらのメソッドは 2 つ合わせて イテレータプロトコル を成します:

__iter__()

イテレータオブジェクト自体を返します。このメソッドはコンテナとイテレータの両方をfor および in 文で使えるようにするために必要です。...

next()

コンテナ内の次の要素を返します。もう要素が残っていない場合、例外 StopIteration を送出します。...

今回は、Group クラスのオブジェクト自体がイテレートオブジェクトになるようにした。イメージとして以下の通り。プロトコルをインターフェイスのように解釈。 (追記 2009.11.25)

091125-002.png

class Group:
    def __init__(self):
        self.persons = []
        self.index = 0      # next() が返す要素のインデックス

    def add(self, person):
        self.persons.append(person)
        return self

    def __iter__(self):
        u""" next() メソッドの定義されているイテレータオブジェクトを返す """
        return self

    def next(self):
        u""" コンテナ内の次の要素を返す

        呼出される度に次の要素を返す。
        次の要素がないときは、StopIteration 例外を投げる。
        """
        if self.index >= len(self.persons):
            self.index = 0
            raise StopIteration
        result = self.persons[self.index]
        self.index += 1
        return result

この実装だと、要素の最後までイテレートせずに途中でイテレートをやめてしまうと、次回のイテレートで途中からイテレートすることになる。イテレート用の別のクラスを作成した方がいいだろうか (?_?)

Person オブジェクトを追加するためのメソッド add では、メソッドチェーンできるように、自身を返すようにした。

ジェネレータで実装したものはこちら → Python のジェネレータ (1) - 動作を試す

 

イテレータ プロトコル を実装するクラスに for ループ を適用

では、このクラスを使って、 for ループを適用してみる。

group = Group().add(Person("Tarou", 21)).add(
                    Person("Hanako", 15)).add(
                    Person("Jiro", 15))

for person in group:
    print person.name

for a in group:
    print a.age

# __str__ が定義されているので、これで出力できる。
for person in group:
    print person

 

イテレータ プロトコル を実装するクラスを リスト内包記法 で使う

さて、Python ではイテレータを定義しても、Ruby の Enumerable モジュールをインクルードしたときのように、「いくつかのメソッドがもれなくプレゼント」ということはないようだ。 ^^; ただし、リスト内包表記を使えば、Ruby の map, select を使ったときのように、簡潔な表現ができる。

# 20歳より小さい年齢の人のリスト
print [x.name for x in group if x.age < 20]

# 'T' または 'k' を名前に含む人のリスト
import re
print [person.name for person in group if re.search('T|k', person.name)]

print [person.name for person in group
            if [char for char in person.name if char in ['T', 'k']]]

 

3. イテレータ プロトコル を実装するクラスを ソート

次に、Ruby のイテレータ (2) - Enumerable では、 Group クラスのオブジェクトに対して、 sort() を呼びだしている。これは、 Enumerable の sort() が、 Group クラスの each() を利用して実装している事による。 Python では残念ながら、そのような実装になっていない。

しかし、2.1 組み込み関数 に、イテレータが定義されたクラスをソートするための関数が定義されている。 ^^

sorted(iterable[, cmp[, key[, reverse]]])

ソートについては、以下のサイトを参考に。

 

要素クラスに比較メソッド __cmp__(self, other) を実装

上記の「2 クラスの比較」によると、ソート時におけるクラスのオブジェクトの比較は、

__cmp__ メソッドを使えば、比較方法を定義できます。

Person クラスに、以下のようにメソッドを追加した。これは Ruby で言う、 Comparable モジュールをインクルードして、 <=> を定義しているのに相当。

イメージとしては、以下のように、比較用のインターフェイスを実装した感じ。(追記 2009.11.25)

091125-003.png

    def __cmp__(self, other):
        result = cmp(self.age, other.age)
        if result != 0:
            return result
        else:
            return cmp(self.name, other.name)

cmp() については、2.1 組み込み関数 を参照。

追記 (2010.1.5) : if の検査で 0 は偽と見なされる条件式を使うなら、次のように書ける。

    def __cmp__(self, other):
        result = cmp(self.age, other.age)
        return result if result else cmp(self.name, other.name)

__cmp__ が定義してあると、例えば、次のように比較ができるようになる。

# Person オブジェクトの比較
# Person クラスに __cmp__(self, other) が定義されていること
p = Person('a', 25)
print  Person('b', 20) < p < Person('d', 30)

 

リスト内包記法 と sorted()

Group に対して、リスト内包記法を使ってソートした Person オブジェクトのリストを取得してみる。全体としては、以下のようなイメージ。(追記 2009.11.25)

091125-004.png

print [person.name for person in sorted(group)]

print [person.name for person in sorted(group,
            lambda x,y: cmp(y.age, x.age))]

前者は、Person クラスに定義された比較基準を元にして、後者は、比較基準を与える関数を渡してソートをした。

 

リスト内包記法 と reduce()

最後に、Ruby の inject に相当する reduce() を使って、全員の年齢の合計を求めてみる。

print reduce(lambda x,y: x+y, [person.age for person in group])

 

関連記事