ラベル ソート の投稿を表示しています。 すべての投稿を表示
ラベル ソート の投稿を表示しています。 すべての投稿を表示

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年6月3日火曜日

Python でリスト内包表記を使ってクイックソート

リスト内包表記の書き方がわかったので、これを使ってクイックソートを書いてみる。クイックソートと言えば、はじめて C で書いたときはちょっとしたミスで、エラーでまくりで嫌になったもんだ。そういえば, VB でも Java の真似して、ソートのためのインターフェイスを implement してクイックソート書いた覚えもあるなぁ  ^^;

 

クイックソートとは

クイックソート - Wikipedia によると、アルゴリズムは、

  1. 適当な数(ピボットという)を選択する (この場合はデータの総数の中央値が望ましい)
  2. ピボットより小さい数を前方、大きい数を後方に移動させる (分割)
  3. 二分割された各々のデータを、それぞれソートする

こうやってみると、なんてシンプルなんだ。。。(@_@;)

 

Haskell でクイックソート

About Haskell の「クイックソート」では、

    qsort []     = []
    qsort (x:xs) = qsort elts_lt_x ++ [x] ++ qsort elts_greq_x
                     where
                       elts_lt_x   = [y | y <- xs, y < x]
                       elts_greq_x = [y | y <- xs, y >= x]

アルゴリズムが素直に表現されていてわかりやすい。

 

Python で書いてみる

Haskell のコードを真似して、

def qsort(list):
    if list == []: return []
    else:
        p = list.pop(0)
        return qsort([x for x in list if x < p]) + \
                [p] + \
                qsort([x for x in list if x >= p])

うーむ、簡潔に書けるものだ。 ^^

では、同じように、 Ruby でも、

def qsort(list)
    if list.empty? then return [] 
    else
        p = list.shift
        return qsort(list.select{|x| x < p}) + 
               [p] + 
               qsort(list.select{|x| x >= p})
    end
end

 

参考

リストの操作については、

一文を複数行へ分ける方法は、