[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
ラベル 比較 の投稿を表示しています。 すべての投稿を表示
ラベル 比較 の投稿を表示しています。 すべての投稿を表示

2009年10月30日金曜日

Java でオブジェクトの比較とソート

Haskell の代数的データ型を比較、特定の基準でソート を試したので、ついでに Java でも。てゆうか、総称型が導入されてから、JDK で言うと 1.5 以降 Java を触ってないので API のドキュメントを読んでも隔世の感が。。 ^^;

 

「人」クラスの定義

前回と同様、「人」が「名前」「年齢」を持っているとして、

public class Person {

    private String name;
    private int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    String getName() {
        return this.name;
    }

    int getAge() {
        return this.age;
    }

    @Override
    public String toString() {
        return this.name + ":" + this.age;
    }
}

 

Comparable<T> を実装して比較可能に

Person クラスを比較可能にするには、Comparable インターフェイスを実装。

インタフェース Comparable<T>

型パラメータ:
T - the type of objects that this object may be compared to

昔は <T> なんてなかったので、久しぶりに見ると混乱するなぁ~ (+_+)

Haskell で言うなら、

class Eq a => Ord a where

の型変数 a に相当するということか。

 

クラス宣言において、Comparable を implements するとき、後ろに <具体的なクラス名> を入れる。

public class Person implements Comparable<Person> {

Haskell なら、

instance Ord Person where

 

Comparable<Person> を実装するには、compareTo メソッドを書く必要がある。ここでは「人」の年齢が人の順序を決めるものとして扱う。総称型を利用することによって、Object クラスをキャストして使わなくて済むところが以前より楽。

    public int compareTo(Person o) {
        return new Integer(this.age).compareTo(o.getAge());
    }

これで Personクラスのオブジェクトを比較できるようになった。

System.out.println(new Person("Tarou", 10).compareTo(new Person("Saburou", 30)));  // -1

 

ソート

次に上記で定義した比較によるソートを試す。リストを作成するには、ArraysasList を使うのが書きやすい。

public static <T> List<T> asList(T... a)

このメソッドも随分様変りしてるなぁ。。。以前は、

public static List asList(Object[] a)
(Arrays (Java 2 プラットフォーム SE v1.4.0) より)

引数の … は可変長の引数を表わすらしい。(cf. ライトニングJava (9) 可変長引数(3)) static の後の <T> は何を表わしているのか知らないけれど、とにかく使う分には、

        List<Person> persons = Arrays.asList(
                new Person("Saburou", 30),
                new Person("Jiro", 20),
                new Person("Tarou", 10),
                new Person("Hanako", 30));

これをソートするには、Collectionssort を使う。

        Collections.sort(persons);
        System.out.println(persons);   // [Tarou:10, Jiro:20, Saburou:30, Hanako:30]

しかし、型を見ると、

public static <T extends Comparable<? super T>> void sort(List<T> list)

うげぇ (@_@;) ますます分からん。。

 

Comparator でソートする基準を変更

ソートする基準を変えたい場合は、Comparator を使う。

例えば、「年齢が同じ場合、名前順にしたい」なら、

        Collections.sort(persons, new Comparator<Person>() {
            public int compare(Person o1, Person o2) {
                int c = new Integer(o1.getAge()).compareTo(o2.getAge());
                if (c != 0) {
                    return c;
                } else {
                    return o1.getName().compareTo(o2.getName());
                }
            }
        });
        System.out.println(persons);   // [Tarou:10, Jiro:20, Hanako:30, Saburou:30]

これも Comparator<Person> とクラスを指定すると、compare メソッドの引数のクラスが決まるようで、キャストが必要なくなっている。

 

あ~、Java の総称型は完全にスルーしていたので読めない。。パタッ(o_ _)o~†

しかし、これからは Scala の時代になっていくのかなぁ?

 

全体

2009年10月23日金曜日

Haskell の代数的データ型を比較、特定の基準でソート – compare, sortBy

Ruby であるクラスのオブジェクトを比較可能にするには、クラスに Comparable モジュールをインクルードする。Python なら __cmp__ メソッドをクラスに実装
同じように Haskell でも代数的データ型を比較できるようにするには、Ord クラスのインスタンスにする。

比較できるように

代数的データ型の定義
例えば、「人」が「名前」「年齢」を持つことを代数的データ型で表現すると、
data Person = Person { name :: String
                     , age  :: Int
                     } deriving Show

Ord クラスのインスタンスにする
Data.Ord によると、
Minimal complete definition: either compare or <=. Using compare can be more efficient for complex types.
よって、compare メソッドを実装。ただし、ここでは「人」を比較可能にしたとき、その「年齢」のみで順序が決まるものとする。
instance Ord Person where
       compare x y = compare (age x) (age y)
比較対象の「年齢」を表わす age は Int 型で、これは既に Ord 型のインスタンスなので compare メソッドで比較した結果をそのまま返した。

Eq クラスのインスタンスにする必要も
しかし、このままではエラーが表示されてしまう。 (+_+)
    No instance for (Eq Person)
      arising from the superclasses of an instance declaration
Ord クラスは以下のように Eq クラスを継承している。そのため、Person 型は Eq のインスタンスでなくてはならない。
class Eq a => Ord a where
(Data.Ord より)
例えば、同じ年齢なら同値とみなすとしておくなら、
instance Eq Person where
    x == y = age x == age y
(単に名前も含めて同値とするなら、Person 型の定義において導出インスタンスを使い deriving (Show, Eq) のようにする。)
具体的な値で比較してみる。
*Main> Person "Tarou" 30 < Person "Hanako" 30
False
*Main> Person "Tarou" 30 <= Person "Hanako" 30
True
*Main> Person "Tarou" 30 < Person "Hanako" 10
False
*Main> Person "Tarou" 30 > Person "Hanako" 10
True

ソート

Data.Listsort の定義は、
sort :: Ord a => [a] -> [a]
Person 型を Ord クラスのインスタンスにしたので、[Person] に sort を適用できるようになった。ps を以下のように定義して、
ps = [ Person "Saburou" 30
     , Person "Jiro" 20
     , Person "Tarou" 10
     , Person "Hanako" 30
     ]
ソートすると、
*Main> sort ps
[Person {name = "Tarou",   age = 10},
 Person {name = "Jiro",    age = 20},
 Person {name = "Saburou", age = 30},
 Person {name = "Hanako",  age = 30}]

特定の基準でソート

しかし、上記のように定義した場合、インスタンス宣言したときの比較に固定されてしまう。色々な基準でソートしたいなら、Data.ListsortBy 関数で比較方法を引数として渡してソート。
例えば、名前で比較させたい場合、
sortBy (\x y -> compare (name x) (name y)) ps
順序を逆にしたい場合は、x と y を入れかえて、
sortBy (\x y -> compare (name y) (name x)) ps

comparing 関数
Data.Ord には、こういうときに便利な comparing 関数が定義されている。
comparing p x y = compare (p x) (p y)
これを使うと、
sortBy (comparing name) ps
と書くことができる。

flip で逆順
順序を逆にしたい場合は、flip 関数を使い引数を入れかえる。
sortBy (flip $ comparing name) ps

複数の基準でソート
次に、「年齢順に並べるが、同じ年齢の場合は名前順にしたい」のなら、
  print $ sortBy cmp ps where
                 cmp x y = let c = comparing age x y
                           in if c /= EQ then c
                              else comparing name x y

複数の基準をつなげる

ここで、「人」の属性に「性別」も加える。
data Gender = Male | Female deriving (Show, Eq, Ord)

data Person = Person { name   :: String
                     , age    :: Int
                     , gender :: Gender
                     } deriving Show
「性別、年齢、名前」の順にソートするなら、
  print $ sortBy cmp ps where
                 cmp x y = let c = comparing gender x y
                           in if c /= EQ then c
                              else let c = comparing age x y
                                   in if c /= EQ then c
                                      else comparing name x y
先ほどの [Person] 型のリストの内容を以下のように変更。
ps = [ Person "Youko"   30 Female
     , Person "Saburou" 30 Male
     , Person "Jiro"    20 Male
     , Person "Hanako"  30 Female
     , Person "Tarou"   10 Male
     ]
これを上記でソートした結果、
[Person {name = "Tarou",   age = 10, gender = Male},
 Person {name = "Jiro",    age = 20, gender = Male},
 Person {name = "Saburou", age = 30, gender = Male},
 Person {name = "Hanako",  age = 30, gender = Female},
 Person {name = "Youko",   age = 30, gender = Female}]

つなげる関数

それにしても、上記のコードは let, if, let, if,… と長い。 (+_+) これを 「Haskell の State モナド (1) - 状態を模倣する」のように、つなぎの部分を部品化しておき、個々のパーツをくっつけることはできないだろうか?
イメージとしては、次のような形。
  print $ sortBy cmp ps where
                 cmp = comparing gender `comb` 
                       comparing age    `comb` 
                       comparing name

つなぎの関数名を comb とする。最初の二つの関数 comparing gender, comparing age に注目。この二つを引数に取ることを想像すると、
comb cmp1 cmp2 = ...
comparing gender 関数は、比較のための引数を二つ取るので、
comb cmp1 cmp2 = ...             cmp1 x y 
cmp1 の結果を変数に束縛する。
comb cmp1 cmp2 = ...     let c = cmp1 x y 
cmp1 で x, y を引数として取るには、x, y が与えられている必要があるので、無名関数の形で、
comb cmp1 cmp2 = \x y -> let c = cmp1 x y 
cmp1 において比較した結果、同値と見なされなかったら、その比較をこの関数の結果とする。
comb cmp1 cmp2 = \x y -> let c = cmp1 x y 
                         in if c /= EQ then c
そうでなければ、cmp2 で比較する関数を結果とする。
comb cmp1 cmp2 = \x y -> let c = cmp1 x y 
                         in if c /= EQ then c else cmp2 x y

これで先ほどの comb でつなげた関数を実行できるようになる。結果は、
[Person {name = "Tarou",   age = 10, gender = Male},
 Person {name = "Jiro",    age = 20, gender = Male},
 Person {name = "Saburou", age = 30, gender = Male},
 Person {name = "Hanako",  age = 30, gender = Female},
 Person {name = "Youko",   age = 30, gender = Female}]

2008年3月30日日曜日

Ruby のイテレータ (2) – Enumerable と Comparable モジュール

Ruby のブロック付きメソッドとイテレータ - yield の様々な使い方」のつづき

1. 要素を保持する親クラスと、要素となる子クラス

080329-004次のような例を想定する。

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

「グループ」クラスは親クラスで、「人」クラスは要素となる子クラス。

class Person
  attr_reader :name , :age
  def initialize(name,age)
    @name = name
    @age = age
  end
end

親クラスとなる「グループ」は、保持する「人」の集合に対して責務を持っており、「人」を「グループ」に追加する操作を持つとする。

class Group
  def initialize
    @persons = []
  end
  def add(person)
    @persons << person
    self
  end
end

 

2. Enumerable モジュールを親クラスにインクルード

「グループ」クラスには、「人」を追加する操作だけではなく、

  • 特定の「人」を抽出したり
  • 全ての人が、ある条件を満たすのか調べる

ようなメソッドも定義したい。 Ruby では、自分でそのような操作を実装しなくても、「グループ」に Enumerable モジュールをインクルードするだけで、便利なメソッドがいくつも追加される。

Enumerable モジュールを「グループ」にインクルードする方法は、

  1. 「グループ」クラス内に、include Enumerable と記述。
  2. 「グループ」に、「人」の集合の要素を順番に取り出す each メソッドを定義。

このような Enumerable モジュールの使い方を Mixin と呼ぶ。

each メソッドを定義する理由は、Enumerable - Rubyリファレンスマニュアル  によると、

(Enumerable クラスは) 繰り返しを行なうクラスのための Mix-in。このモジュールのメソッドは全て each を用いて定義されているので、インクルードするクラスには each が定義されていなければなりません。

 

each メソッドの実装方法

each メソッドの実装、要素を保持する親クラスから、要素をひとつづつ取り出し、その要素にブロック(関数)を適用すること。以下に、Group クラスの each メソッドの実装を示す。

class Group
  include Enumerable
  def each
    @persons.each do |person|
      yield person
    end
  end
end

each メソッドの中で、yield が使われている。この記述で、yield の呼び出しの際、「グループ」が保持している各々の person を、each メソッドに与えられるブロックに引き渡していることになる。

この書き方でわかりづらい点は、each メソッドの引数にブロックが与えられることが明示されていないこと。メソッドの中で、yield が使われてることで、メソッドにブロックが与えることを想定しているがわかる。

( cf. jutememo's gist: 1350439 — Gist )

 

each メソッドを利用してみる

以下の順に、実際に each メソッドを使ってみる。

  1. 「グループ」オブジェクトを生成し、
  2. 「人」オブジェクトを追加する。
  3. 「グループ」オブジェクトの each メソッドを呼出す。
aGroup = Group.new.
         add(Person.new("Tarou",21)).
         add(Person.new("Hanako",15)).
         add(Person.new("Jiro",15))
                                  

aGroup.each do |person|
  puts person.name
end

 

Enumerable モジュールに定義されている collect , sort を使ってみる

Enumerable を Mixin すると、Enumerable モジュールで定義されている collect, sort などのメソッドを呼出すことができる。この2つのメソッドを試しに使ってみる。

p aGroup.collect{|person| person.age + 10}

aGroup.sort{|a,b| a.age <=> b.age}.each do |person|
  print person.name, person.age, "\n"
end

上記において、sort メソッドの呼び出しのとき、ソートの方法をブロックで渡している。

Enumerable - Rubyリファレンスマニュアル によると、

sort {|a, b| ... } 全ての要素を昇順にソートした配列を生成して返します。 ブロックなしのときは <=> メソッドを要素に対して呼び、その結 果をもとにソートします。

ここでは、Person クラスにおいて、<=> を定義してないので、上記のように、要素を比較する方法を、ブロックを渡す必要があった。

 

3. オブジェクトを比較するクラスに Comparable モジュールをインクルード

Enumerable の Mixin の方法がわかったところで、今度は上記の sort メソッドで利用された、「人」を比較するためのメソッドを「人」クラスに追加してみる。「人」オブジェクトを比較可能にするには、Comparable モジュールを Person クラスに Mixin する。

一例として、「人」を比較するためルールを、以下のように決める。

  1. 「はじめに `年齢' で比較し、
  2. もし同じ年齢だったら、`名前' のアルファベット順で比較する」
class Person
  include Comparable
  attr_reader :name , :age
  def initialize(name,age)
    @name = name
    @age = age
  end
  def <=>(other)
    cmp = @age <=> other.age
    if cmp != 0
      return cmp
    else
      return @name <=> other.name
    end
  end
end

tarou = Person.new('Tarou',21)
jirou = Person.new('Jirou',15)
hanako = Person.new('Hanako',15)

p tarou > jirou
p tarou < jirou
p tarou == jirou

p jirou > hanako
p jirou < hanako
p jirou == hanako

Enumerable モジュールは、each メソッドを利用して、様々なメソッドが定義されている。Comparable モジュールでは、<=> を利用して、いくつかの操作が定義されている。

上記のように、Person に <=> を実装したので、Group に Mixin した Enumerable モジュールの sort メソッドを、ブロックなしで呼出せるようになった。

( cf. jutememo's gist: 1350439 — Gist )

 

4. モジュールは、Template Method パターンを使っている

このようなモジュールのインクルードの動作の仕組みは、「まつもと直伝 プログラミングのオキテ 第9回:ITpro」 に、次のように書かれている。

Rubyのクラス・ライブラリでTemplate Methodパターンを最も活用している部分は,EnumerableモジュールとComparableモジュールでしょう。...

Comparableモジュールは大小比較の基本となる「<=>」メソッドを用いて,各種比較演算を提供しています。「<=>」メソッドの仕様はレシーバと引数を比較して,レシーバの方が大きければ正の整数,等しければゼロ,小さければ負の整数を返すというものです。このメソッドを基礎にして, Comparableモジュールは「==」,「>」,「>=」,「<」,「<=」,「between?」の6つの比較演算を提供しています。

Ruby で書かれたデザインパターン (Template Method) の実装例を見たい場合は、以下を参照にすると良い。

前者は、Module を Mixin して、Template Method を実現しており、後者は、サブクラス化によって実現している。ただし、Mixin とは、

一般にTemplate Methodは継承とセットで語られますが,Enumerableのようにインクルードするだけで継承関係に関わりなく任意のクラスに機能を追加できるのは魅力的です。もっとも,Rubyのインクルードは一種の(制限された)多重継承なので,何の不思議もありませんが。

( cf. まつもと直伝 プログラミングのオキテ 第9回:ITpro )

 

5. Yield はどのように呼び出され、動作しているのか?

Enumerable モジュールは、C で実装されているらしい。

Nabble - [ruby-dev:32712] Re: Enumerable can't take multiple parameters によると、

たとえばEnumerable#collectをRubyで定義する時にどのように書くか、という問題です。現在は
module Enumerable
  def collect
    result = []
    self.each{|x| result.push(yield x)}
    result
  end
end 
と等価になるように定義しています。

先ほど Group クラスで定義した each メソッドを参照しながら、 Enumerable の collect メソッドを見てみる。頭が混乱してくる。。 パタッ(o_ _)o~†

以下のように collect メソッドの呼出しをイメージしてみる。

080330-001

うーん、ややこしい (@_@;)  どこが一番想像しにくいのだろうか?順に動作を追って考えることにした。

  1. Group クラスに Mixin した collect メソッドを、ブロック付きで呼出す。
  2. Enumerable モジュールの collect メソッドの中で、 Group メソッドで定義した each メソッドを呼出す。
  3. Group クラスに定義した each メソッドでは、Person 要素を一つずつ取り出し、each メソッドに渡されたブロックの処理を進める。

例えば、先ほどの実行例で考えると、先頭の要素 `Tarou' が取り出されたとき、渡されたブロックの引数を展開すると、次のような値にが与えられているとイメージする。

{|tarou| result.push (yield tarou)}

ここでややこしいのは、yield が呼ばれていること(4)。

展開すると、こんな感じになるだろうか。

{|tarou| tarou.age + 10}

動作がわかりにくので、上記二つをまとめると、

{|tarou| result.push (tarou.age + 10)}

これを Person の要素分だけ繰り返して、 結果を 配列 result に詰め込んでいく。

 

Enumerable が関知していること

上記のように、個々の動作を追っていくと、何がなんだかわからなくなる。むしろ、Enumerable というモジュールの機能レベルで理解をしていく方がいいかもしれない。

Enumerable とは、 動詞 enumerate (列挙する、数え上げる) に由来する。「数え上げる」とは、対象がどのようなものであっても、複数の要素に対して行う行為を指す。

collect という操作であるなら、

「要素に対して、何らかの操作した結果の、集合を返す」

役割を持つと考える。要素を取り出す仕事は each が行う。

collect の中で呼出されている、each メソッドに渡されるブロック変数 x は、

「 x の中身が何なのか自分は関知しない」

ということを表わしている。その何だかよくわからない任意のものを、自分が呼出されたときに、与えられたブロックに渡し、「何らかの処理」を行わせた結果を受け取る。「何らかの処理」については、 Enumerable 自身は関知しない。ただ、「何らかの処理」の結果を受け取って、配列に詰め込むだけの役割を持つ。

 

6. 他の言語との比較

Haskell の map 関数と比較

Haskell にも、Ruby の Enumerable モジュールの collect に相当する map 関数がある。

map 関数の定義を見てみと、

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

map::(a -> b) -> [a] -> [b]
map f []     = []
map f (x:xs) = f x : map f xs

なんとシンプルな。。 (@_@;)

対象が何であれ、

「要素に対して、関数を適用する」

ということが素直に示されている。この定義を理解しておいてから、 Ruby の Enumerable モジュールの collect メソッドを見ると、何となく同じような形に見えてくる。yield x という文が、要素 x に対して関数を適用していることに等しい。

Haskell が再帰によって、要素に関数を適用するというところと、 Ruby が each によって、個々の要素を取り出し、順にブロックを適用するというところが違うけれど。

 

Java との比較

自分の場合、プログラミングは、 Java から入ったので、 Ruby のモジュールを見たときは違和感があった。Java の視点から見ると、 interface が外面だけでなく、中身も持っている感じ。Ruby はモジュールがあるから、Java よりも抽象的な表現による、コーディング促進されるのだろうか?

Java でオブジェクトを比較する方法を実装する場合、

  1. インターフェイス Comparable の compareTo を実装することによって、オブジェクト同士が比較可能にする。
  2. Arrays.sort, Collections.sort を利用してソートする。
  3. ソートするときに、Comparator を実装した匿名クラスのオブジェクトを渡せば、ソートの方法を変更することができる。これは、Ruby でブロックつきで呼出しているようなものと考えればよい。

ただし、クロージャと匿名インナークラスとの違いについては、MF Bliki: Closure に指摘されている。、

if you're a Java programmer you probably think "I could do that with an anonymous inner class", a C#er would consider a delegate.These mechanisms are similar to closures, but there are two telling differences.(...)

その二点とは、

closures can refer to variables visible at the time they were defined ...

Languages that support closures allow you to define them with very little syntax.

Ruby のブロックと Proc」へつづく…

 

連想事項