3

3 番目のオブジェクトのプロパティに基づいてオブジェクトのコレクションを並べ替える方法が必要です。簡単なケースを使って説明しようと思います。

Person オブジェクトがあるとします。

class Person {
    String firstName;
    String lastName;
    ...
}

そして、特定の人物を基準にして Person のコレクションをソートしたいと考えています。例: John Doe を見つけたい人物です。見つからない場合は、最も「似ている」人物を並べ替えたコレクションの一番上に配置します。

類似性は次のように定義されます。名前のみが一致する場合は、姓のみが一致する場合よりも一致度が高くなります。もちろん、両方が一致すればビンゴです。

解決策を思いつきましたが、それが完璧かどうかはわかりません。アイデアは、次のような Comparator を使用することです。

public static class PersonComparator implements Comparator<Person> {
    String firstName;
    String lastName;

    public PersonComparator(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public int compare(Person p1, Person p2) {
        int p1Match = calcMatch(p1);
        int p2Match = calcMatch(p2);

        int result = p1Match - p2Match;
        if (result == 0) {
            //not very sure about this part
            result = p1.firstName.compareTo(p2.firstName);
            if (result == 0) {
                result = p1.lastName.compareTo(p2.lastName);
            }
        }
        return result;
    }

    public int calcMatch(Person p) {
        StringBuilder builder = new StringBuilder();
        builder.append(firstName.equals(p.firstName) ? "1" : "0");
        builder.append(lastName.equals(p.lastName) ? "1" : "0");
        return Integer.parseInt(builder.toString(), 2);
    }
}

したがって、人物 1 の名が一致し、姓が一致しない場合、彼はバイナリ一致 '10' を整数 2 に変換し、人物 2 の名と姓の両方が一致する場合、バイナリ値は '11' を 3 に変換します。 .compareTo は単純に 2 - 3 = -1 を返し、1 が 2 よりも「小さい」ことを示します。

ただし、両方の人の姓名が探しているものと一致しない場合はどうすればよいですか。一致した「バイナリ値」は同じであり、0 を返すことは、2 人の人物が互いに等しいことを示します (たとえば、少なくとも TreeSet に対して)。そのようなコンパレータが TreeSet で使用されると、2 人のうちの 1 人だけが結果セットに残ります。

これは望ましい動作ではないため、両方の人の結果が同じ一致値になる場合、2 人のフィールドの比較に基づいて、compareTo によって返される値を計算します。

次の簡単なテスト ケースを実行すると、例が示されます。

public static void main(String[] args) {
    List<Person> persons = new ArrayList<Person>();
    persons.add(new Person("Pietje", "Puk"));
    persons.add(new Person("Jan", "Jansen"));
    persons.add(new Person("John", "Doe")); 

    Comparator<Person> comparator = new PersonComparator("John", "Doe")
    int firstCompare = comparator.compare(persons.get(0), persons.get(1));
    int secondCompare = comparator.compare(persons.get(1), persons.get(2));
    int thirdCompare = comparator.compare(persons.get(0), persons.get(2));
    System.out.println(firstCompare + " vs " + secondCompare + " vs " + thirdCompare);

    TreeSet<Person> personsSet = new TreeSet<Person>(comparator);
    personsSet.addAll(persons);
    personsSet.add(new Person("Baby", "Doe"));
    personsSet.add(new Person("John", "Roe"));
    personsSet.add(new Person("Jane", "Doe"));

    int i = 0;
    for (Person person : personsSet) {
        System.out.println(i++ + ") " + person + " [" + comparator.calcMatch(person) + "]");
    }
}

上記のコードを実行すると、次のようになります。

6 対 -3 対 -3

0) ヤン・ヤンセン [0]

1) ピーチェ・プク [0]

2) ベイビードゥ [1]

3) ジェーン・ドウ [1]

4) ジョン・ロー [2]

5) ジョン・ドウ [3]

最初の比較は名に基づいており (Pietje Puk 対 Jan Jansen)、結果は 6 でした。また、ピボット (Pietje Puk と John Doe) と比較した姓に基づいており、同様に -3 という結果になりました。

コードでコメントされているように、両方のフィールドが同様に一致するが異なる値を持つ compareTo の問題の解決策についてはわかりません。「一致」コードは常に 0 から 3 までの値を計算するため、「フィールド比較」ははるかに高い値を持つ可能性があり、これらの数値を「混合」することが適切かどうかはわかりません。

誰かが同様の問題に直面したことがありますか、または私のソリューションが契約に準拠しており、欠陥がないことを確認できますか? 理想的には、TreeSet で使用できるコンパレーターが必要なため、compareTo は、人が実際に等しくない場合にのみ 0 を返す必要があります。

私が持っている別の解決策は、「ピボット」を「通常の」「人」オブジェクトとしてツリーセットに配置し、compareTo メソッドに提供された 2 人の人のフィールドに基づいて単純なコンパレータを使用することです。コレクションが並べ替えられたら、ピボット オブジェクトを検索すると、その近くにある要素が最も一致することがわかります。ただし、このソリューションは本当にエレガントに聞こえず、常に適用できるとは限りません。

4

5 に答える 5

1

このアプローチは正しいように思えますが、2 つの注意点があります。

  1. 単純に 0 と 1 を追加するだけでよいのに、なぜ StringBuilder と解析を使用して一致を計算するのでしょうか?
  2. 2 つの異なる Person インスタンスの姓と名が同じ場合はどうなるでしょうか。コンパレーターによってそれらが等しいと見なされるようにしたいですか、それともそうではありませんか? System.identityHashCode()そうでない場合は、膨大な数のインスタンスと巨大なメモリがない限り、それらを比較することを検討してください。絶対に確実にしたい場合は、Guava のOrdering.arbitrary()コンパレーターを使用してそれらを比較してください。これにより、2 人が同じインスタンスである場合にのみ同等であることが保証されます。
于 2013-08-16T21:47:55.247 に答える
1

を並べ替えたくないようですPersonが、優先順位を付けてください。

を に入れることをお勧めしPersonますPriorityQueue。そこを使用するComparatorと、目的の結果を得ることができるはずです。ただし、キューの先頭は、指定された順序に関して最小の要素を持つ要素になるため、代わりに負の値を使用する必要がある場合があります。

于 2013-08-16T21:49:37.660 に答える