私はこれに似た他のいくつかの質問を見てきましたが、私の問題を解決するものを見つけることができませんでした。
私のユースケースはこれです:ユーザーは最初にアイテムのリストを持っています(listA)。彼らはアイテムを再注文し、その順序(listB)を保持しますが、制限のため、バックエンドで順序を保持できないため、listAを取得した後にソートする必要があります。
基本的に、2つのArrayList(listAとlistB)があります。 1つはリストが特定の順序である必要があり(listB)、もう1つはアイテムのリストがあります(listA)。 listBに基づいてlistAをソートします。
_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()
Java 8:
Collections.sort(listToSort,
Comparator.comparing(item -> listWithOrder.indexOf(item)));
またはそれ以上:
listToSort.sort(Comparator.comparingInt(listWithOrder::indexOf));
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));
_
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);
}
});
}
以下は、_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);
。今では実際に動作します。
リストをソートし、最初の配列リストに加えられた変更に応じて別のリストに変更を加える方法の例を次に示します。このトリックは失敗することはなく、リスト内のアイテム間のマッピングを保証します。このトリックを使用するには、両方のリストのサイズが同じでなければなりません。
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;
}
});
}
問題:別のリストにあるフィールドのすべての可能な値の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]]
Java 8:
listB.sort((left, right) -> Integer.compare(list.indexOf(left), list.indexOf(right)));
または
listB.sort(Comparator.comparingInt(item -> list.indexOf(item)));
設定に応じて機能する別のソリューションは、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つの方法は、リストをループし、listAに含まれている場合にアイテムを一時リストに追加することです。
List<?> tempList = new ArrayList<?>();
for(Object o : listB) {
if(listA.contains(o)) {
tempList.add(o);
}
}
listA.removeAll(listB);
tempList.addAll(listA);
return tempList;
あなたが望むものを完全には明確にしませんが、これが状況の場合: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);
}
私もテストしていないことに注意してください。
オブジェクト参照を同じにする必要がある場合は、listA newを初期化できます。
listA = new ArrayList(listB)
同じ問題が発生しました。
順序付けられたキーのリストがあり、キーの順序に従ってリスト内のオブジェクトを順序付ける必要があります。
私のリストは、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
。
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]
Tim Heroldが書いたように、オブジェクト参照が同じでなければならない場合、listBをlistAにコピーするだけです。
listA = new ArrayList(listB);
または、listAが参照するリストを変更したくない場合:
listA.clear();
listA.addAll(listB);
参照が同じではないが、listAとlistBのオブジェクト間に同等の関係がある場合、カスタムComparator
を使用してlistAをソートし、listBでオブジェクトを見つけ、listBのインデックスをソートキーとして使用できます。ブルートフォースがlistBを検索する単純な実装は、パフォーマンスに関しては最適ではありませんが、機能的には十分です。
非常に非効率的なルックアップを避けるために、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));
これを試して。以下のコードは、特定のタイプを指定しなかったため、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);
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);
}
});
IMO、あなたは何か他のものを持続する必要があります。完全なlistBではなく、somethingです。ユーザーが変更したアイテムのインデックスにすぎない場合があります。