web-dev-qa-db-ja.com

Javaでは、あるリストを別のリストに基づいてどのようにソートしますか?

私はこれに似た他のいくつかの質問を見てきましたが、私の問題を解決するものを見つけることができませんでした。

私のユースケースはこれです:ユーザーは最初にアイテムのリストを持っています(listA)。彼らはアイテムを再注文し、その順序(listB)を保持しますが、制限のため、バックエンドで順序を保持できないため、listAを取得した後にソートする必要があります。

基本的に、2つのArrayList(listAとlistB)があります。 1つはリストが特定の順序である必要があり(listB)、もう1つはアイテムのリストがあります(listA)。 listBに基づいてlistAをソートします。

44
Debacle
_Collections.sort(listB, new Comparator<Item>() {
    public int compare(Item left, Item right) {
        return Integer.compare(listA.indexOf(left), listA.indexOf(right));
    }
});
_

しかし、これは非常に非効率的であり、おそらくlistAから_Map<Item, Integer>_を作成して、アイテムの位置をより速く検索する必要があります。

グアバには、それを行うためのすぐに使える比較器があります: Ordering.explicit()

61
JB Nizet

Java 8:

Collections.sort(listToSort, 
    Comparator.comparing(item -> listWithOrder.indexOf(item)));

またはそれ以上:

listToSort.sort(Comparator.comparingInt(listWithOrder::indexOf));
38
Thermech

listBを並べ替える順序を定義するlistAリストがあるとします。これは単なる例ですが、データ型の自然な順序ではなく、リストによって定義される順序を示しています。

_List<String> listB = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday",
    "Thursday", "Friday", "Saturday");
_

ここで、listAをこの順序に従ってソートする必要があるとしましょう。これは_List<Item>_であり、Itemにはpublic String getWeekday()メソッドがあります。

listBのすべての値を、インデックスなどの簡単にソートできるものにマップする_Map<String, Integer>_を作成します。つまり、_"Sunday"_ => _0_、... 、_"Saturday"_ => _6_。これにより、すばやく簡単に検索できます。

_Map<String, Integer> weekdayOrder = new HashMap<String, Integer>();
for (int i = 0; i < listB.size(); i++)
{
    String weekday = listB.get(i);
    weekdayOrder.put(weekday, i);
}
_

次に、Mapを使用して注文を作成するカスタム_Comparator<Item>_を作成できます。

_public class ItemWeekdayComparator implements Comparator<Item>
{
    private Map<String, Integer> sortOrder;

    public ItemWeekdayComparator(Map<String, Integer> sortOrder)
    {
        this.sortOrder = sortOrder;
    }

    @Override
    public int compare(Item i1, Item i2)
    {
        Integer weekdayPos1 = sortOrder.get(i1.getWeekday());
        if (weekdayPos1 == null)
        {
            throw new IllegalArgumentException("Bad weekday encountered: " +
               i1.getWeekday());
        }
        Integer weekdayPos2 = sortOrder.get(i2.getWeekday());
        if (weekdayPos2 == null)
        {
            throw new IllegalArgumentException("Bad weekday encountered: " +
               i2.getWeekday());
        }
        return weekdayPos1.compareTo(weekdayPos2);
    }
}
_

その後、カスタムlistAを使用してComparatorをソートできます。

_Collections.sort(listA, new ItemWeekdayComparator(weekdayOrder));
_
12
rgettman

JB Nizetの答えのスピードの改善(彼が自分でした提案から)。この方法では:

  • 1000個のアイテムリストを100回並べ替えると、単体テストの速度が10倍向上します。

  • 10000のアイテムリストを100回並べ替えると、ユニットテストの速度が140倍(37秒ではなくバッチ全体で265ミリ秒)向上します。

この方法は、両方のリストが同一でない場合にも機能します。

/**
 * Sorts list objectsToOrder based on the order of orderedObjects.
 * 
 * Make sure these objects have good equals() and hashCode() methods or
 * that they reference the same objects.
 */
public static void sortList(List<?> objectsToOrder, List<?> orderedObjects) {

    HashMap<Object, Integer> indexMap = new HashMap<>();
    int index = 0;
    for (Object object : orderedObjects) {
        indexMap.put(object, index);
        index++;
    }

    Collections.sort(objectsToOrder, new Comparator<Object>() {

        public int compare(Object left, Object right) {

            Integer leftIndex = indexMap.get(left);
            Integer rightIndex = indexMap.get(right);
            if (leftIndex == null) {
                return -1;
            }
            if (rightIndex == null) {
                return 1;
            }

            return Integer.compare(leftIndex, rightIndex);
        }
    });
}
9
Frank

以下は、_2n_だけ時間の複雑さを増すが、望むものを達成するソリューションです。また、並べ替えに使用する他のリストRが一様に比較可能であれば、並べ替えるリストLにComparable要素が含まれているかどうかは関係ありません。

_public HeavyPair<L extends Comparable<L>, R> implements Comparable<HeavyPair<L, ?>> {
    public final L left;
    public final R right;

    public HeavyPair(L left, R right) {
        this.left = left;
        this.right = right;
    }

    public compareTo(HeavyPair<L, ?> o) {
        return this.left.compareTo(o.left);
    }

    public static <L extends Comparable<L>, R> List<R> sort(List<L> weights, List<R> toSort) {
        assert(weights.size() == toSort.size());
        List<R> output = new ArrayList<>(toSort.size());
        List<HeavyPair<L, R>> workHorse = new ArrayList<>(toSort.size());
        for(int i = 0; i < toSort.size(); i++) {
            workHorse.add(new HeavyPair(weights.get(i), toSort.get(i)))
        }
        Collections.sort(workHorse);
        for(int i = 0; i < workHorse.size(); i++) {
            output.add(workHorse.get(i).right);
        }
        return output;
    }
}
_

ただし、このコードを書いている間に私が使用したひどい慣習はすみません。私は急いでいた。

HeavyPair.sort(listB, listA);を呼び出すだけです

編集:この行を修正しましたreturn this.left.compareTo(o.left);。今では実際に動作します。

5
Axoren

リストをソートし、最初の配列リストに加えられた変更に応じて別のリストに変更を加える方法の例を次に示します。このトリックは失敗することはなく、リスト内のアイテム間のマッピングを保証します。このトリックを使用するには、両方のリストのサイズが同じでなければなりません。

    ArrayList<String> listA = new ArrayList<String>();
    ArrayList<String> listB = new ArrayList<String>();
    int j = 0;
    // list of returns of the compare method which will be used to manipulate
    // the another comparator according to the sorting of previous listA
    ArrayList<Integer> sortingMethodReturns = new ArrayList<Integer>();

    public void addItemstoLists() {
        listA.add("Value of Z");
        listA.add("Value of C");
        listA.add("Value of F");
        listA.add("Value of A");
        listA.add("Value of Y");

        listB.add("this is the value of Z");
        listB.add("this is the value off C");
        listB.add("this is the value off F");
        listB.add("this is the value off A");
        listB.add("this is the value off Y");

        Collections.sort(listA, new Comparator<String>() {

            @Override
            public int compare(String lhs, String rhs) {
                // TODO Auto-generated method stub
                int returning = lhs.compareTo(rhs);
                sortingMethodReturns.add(returning);
                return returning;
            }

        });
        // now sort the list B according to the changes made with the order of
        // items in listA
        Collections.sort(listB, new Comparator<String>() {

            @Override
            public int compare(String lhs, String rhs) {
                // TODO Auto-generated method stub

                // comparator method will sort the second list also according to
                // the changes made with list a
                int returning = sortingMethodReturns.get(j);
                j++;
                return returning;
            }

        });

    }
2

問題:別のリストにあるフィールドのすべての可能な値の1つに基づいてPojoのリストをソートします。

この解決策を見てください、これがあなたが達成しようとしているものかもしれません:

import Java.util.ArrayList;
import Java.util.Collections;
import Java.util.Comparator;
import Java.util.List;

public class Test {

  public static void main(String[] args) {
    List<Employee> listToSort = new ArrayList<>();
    listToSort.add(new Employee("a", "age11"));
    listToSort.add(new Employee("c", "age33"));
    listToSort.add(new Employee("b", "age22"));
    listToSort.add(new Employee("a", "age111"));
    listToSort.add(new Employee("c", "age3"));
    listToSort.add(new Employee("b", "age2"));
    listToSort.add(new Employee("a", "age1"));

    List<String> listWithOrder = new ArrayList<>();
    listWithOrder.add("a");
    listWithOrder.add("b");
    listWithOrder.add("c");

    Collections.sort(listToSort, Comparator.comparing(item -> 
    listWithOrder.indexOf(item.getName())));
    System.out.println(listToSort);
  }

}


class Employee {
  String name;
  String age;

  public Employee(String name, String age) {
    super();
    this.name = name;
    this.age = age;
  }

  public String getName() {
    return name;
  }

  public String getAge() {
    return age;
  }

  @Override
  public String toString() {
    return "[name=" + name + ", age=" + age + "]";
  }
}

出力[[name = a、age = age11]、[name = a、age = age111]、[name = a、age = age1]、[name = b、age = age22]、[name = b、age = age2] ]、[name = c、age = age33]、[name = c、age = age3]]

1
NoisyBoy

Java 8:

listB.sort((left, right) -> Integer.compare(list.indexOf(left), list.indexOf(right)));

または

listB.sort(Comparator.comparingInt(item -> list.indexOf(item)));
1
Arif Acar

設定に応じて機能する別のソリューションは、listBにインスタンスを格納するのではなく、listAからインデックスを格納することです。これは、次のようにlistAをカスタムソートリスト内にラップすることで実行できます。

public static class SortedDependingList<E> extends AbstractList<E> implements List<E>{
    private final List<E> dependingList;
    private final List<Integer> indices;

    public SortedDependingList(List<E> dependingList) {
        super();
        this.dependingList = dependingList;
        indices = new ArrayList<>();
    }

    @Override
    public boolean add(E e) {
        int index = dependingList.indexOf(e);
        if (index != -1) {
            return addSorted(index);
        }
        return false;
    }

    /**
     * Adds to this list the element of the depending list at the given
     * original index.
     * @param index The index of the element to add.
     * 
     */
    public boolean addByIndex(int index){
        if (index < 0 || index >= this.dependingList.size()) {
            throw new IllegalArgumentException();
        }
        return addSorted(index);
    }

    /**
     * Returns true if this list contains the element at the
     * index of the depending list.
     */
    public boolean containsIndex(int index){
        int i = Collections.binarySearch(indices, index);
        return i >= 0;
    }

    private boolean addSorted(int index){
        int insertIndex = Collections.binarySearch(indices, index);
        if (insertIndex < 0){
            insertIndex = -insertIndex-1;
            this.indices.add(insertIndex, index);
            return true;
        }
        return false;
    }

    @Override
    public E get(int index) {
        return dependingList.get(indices.get(index));
    }

    @Override
    public int size() {
        return indices.size();
    }
}

次に、このカスタムリストを次のように使用できます。

public static void main(String[] args) {
    class SomeClass{
        int index;
        public SomeClass(int index) {
            super();
            this.index = index;
        }
        @Override
        public String toString() {
            return ""+index;
        }
    }

    List<SomeClass> listA = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        listA.add(new SomeClass(i));
    }
    SortedDependingList<SomeClass> listB = new SortedDependingList<>(listA);
    Random Rand = new Random();

    // add elements by index:
    for (int i = 0; i < 5; i++) {
        int index = Rand.nextInt(listA.size());
        listB.addByIndex(index);
    }

    System.out.println(listB);

    // add elements by identity:
    for (int i = 0; i < 5; i++) {
        int index = Rand.nextInt(listA.size());
        SomeClass o = listA.get(index);
        listB.add(o);
    }
    System.out.println(listB);      
}

もちろん、このカスタムリストは、元のリストの要素が変更されない限り有効です。変更が可能な場合、元のリストへの変更を何らかの方法でリッスンし、カスタムリスト内のインデックスを更新する必要があります。

また、SortedDependingListは現在、listAからの要素の2回目の追加を許可していないことに注意してください。

SortedDependingListに何かを追加する好ましい方法は、要素のインデックスを既に知っており、sortedList.addByIndex(index);を呼び出して追加することです。

1
Balder

これを行う1つの方法は、リストをループし、listAに含まれている場合にアイテムを一時リストに追加することです。

List<?> tempList = new ArrayList<?>();
for(Object o : listB) {
    if(listA.contains(o)) {
        tempList.add(o);
    }
}
listA.removeAll(listB);
tempList.addAll(listA);
return tempList;
1
Runemoro

あなたが望むものを完全には明確にしませんが、これが状況の場合:A:[c、b、a] B:[2,1,0]

そして、あなたはそれらの両方をロードしてから生成したい:C:[a、b、c]

じゃあこれ?

List c = new ArrayList(b.size());
for(int i=0;i<b.size();i++) {
  c.set(b.get(i),a.get(i));
}

それには余分なコピーが必要ですが、私はそれを適所に置くとはるかに効率が悪くなり、すべての種類が明確ではないと思います:

for(int i=0;i<b.size();i++){
    int from = b.get(i);
    if(from == i) continue;
    T tmp = a.get(i);
    a.set(i,a.get(from));
    a.set(from,tmp);
    b.set(b.lastIndexOf(i),from); 
}

私もテストしていないことに注意してください。

1
Brad Tofel

オブジェクト参照を同じにする必要がある場合は、listA newを初期化できます。

listA = new ArrayList(listB)
0
Khinsu

同じ問題が発生しました。
順序付けられたキーのリストがあり、キーの順序に従ってリスト内のオブジェクトを順序付ける必要があります。
私のリストは、N ^ 2の時間の複雑さを持つソリューションを使用できなくするのに十分な長さです。
私の解決策:

<K, T> List<T> sortByOrder(List<K> orderedKeys, List<T> objectsToOrder, Function<T, K> keyExtractor) {
    AtomicInteger ind = new AtomicInteger(0);
    Map<K, Integer> keyToIndex = orderedKeys.stream().collect(Collectors.toMap(k -> k, k -> ind.getAndIncrement(), (oldK, newK) -> oldK));
    SortedMap<Integer, T> indexToObj = new TreeMap<>();
    objectsToOrder.forEach(obj -> indexToObj.put(keyToIndex.get(keyExtractor.apply(obj)), obj));
    return new ArrayList<>(indexToObj.values());
}

時間の複雑さはO(N * Log(N))です。
このソリューションでは、ソートするリスト内のすべてのオブジェクトに個別のキーがあると想定しています。そうでない場合は、単にSortedMap<Integer, T> indexToObj by SortedMap<Integer, List<T>> indexToObjList

0
Alexander
import Java.util.Comparator;
import Java.util.List;

public class ListComparator implements Comparator<String> {

    private final List<String> orderedList;
    private boolean appendFirst;

    public ListComparator(List<String> orderedList, boolean appendFirst) {
        this.orderedList = orderedList;
        this.appendFirst = appendFirst;
    }

    @Override
    public int compare(String o1, String o2) {
        if (orderedList.contains(o1) && orderedList.contains(o2))
            return orderedList.indexOf(o1) - orderedList.indexOf(o2);
        else if (orderedList.contains(o1))
            return (appendFirst) ? 1 : -1;
        else if (orderedList.contains(o2))
            return (appendFirst) ? -1 : 1;
        return 0;
    }
}

この汎用コンパレータを使用して、他のリストに基づいてリストをソートできます。たとえば、appendFirstがfalseの場合、以下が出力されます。

順序付きリスト:[a、b]

順不同リスト:[d、a、b、c、e]

出力:[a、b、d、c、e]

0
Utsav Shah

Tim Heroldが書いたように、オブジェクト参照が同じでなければならない場合、listBをlistAにコピーするだけです。

listA = new ArrayList(listB);

または、listAが参照するリストを変更したくない場合:

listA.clear();
listA.addAll(listB);

参照が同じではないが、listAとlistBのオブジェクト間に同等の関係がある場合、カスタムComparatorを使用してlistAをソートし、listBでオブジェクトを見つけ、listBのインデックスをソートキーとして使用できます。ブルートフォースがlistBを検索する単純な実装は、パフォーマンスに関しては最適ではありませんが、機能的には十分です。

0
Jason C

非常に非効率的なルックアップを避けるために、listBのアイテムにインデックスを付け、それに基づいてlistAをソートする必要があります。

Map<Item, Integer> index = IntStream.range(0, listB.size()).boxed()
  .collect(Collectors.toMap(listB::get, x -> x));

listA.sort((e1, e2) -> Integer.compare(index.get(c1), index.get(c2));
0
Duarte Meneses

これを試して。以下のコードは、特定のタイプを指定しなかったため、listAがObjectsのリストであるシナリオの一般的な目的です。

Object[] orderedArray = new Object[listA.size()];

for(int index = 0; index < listB.size(); index ++){
    int position = listB.get(index); //this may have to be cast as an int
    orderedArray[position] = listA.get(index);
}
//if you receive UnsupportedOperationException when running listA.clear()
//you should replace the line with listA = new List<Object>() 
//using your actual implementation of the List interface
listA.clear(); 
listA.addAll(orderedArray);
0
M7Jacks

2つのリストに同じ要素が含まれていることが保証されている場合、異なる順序でのみ、List<T> listA = new ArrayList<>(listB)を使用できます。これはO(n)時間の複雑さです。そうでない場合、Collections.sort()を使用して多くの回答が表示されますが、O(2n)ランタイムを保証する代替方法があります。理論的にはsortの最悪時間よりも高速です。 O(nlog(n))の複雑さ、コストは2n ストレージ

Set<T> validItems = new HashSet<>(listB);
listA.clear();
listB.forEach(item -> {
  if(validItems.contains(item)) {
    listA.add(item);
  }
});
0
aarbor

IMO、あなたは何か他のものを持続する必要があります。完全なlistBではなく、somethingです。ユーザーが変更したアイテムのインデックスにすぎない場合があります。

0
ArturoTena