在 Java 中,如何根据另一个列表对一个列表进行排序?
Posted
技术标签:
【中文标题】在 Java 中,如何根据另一个列表对一个列表进行排序?【英文标题】:In Java how do you sort one list based on another? 【发布时间】:2013-08-10 09:20:24 【问题描述】:我已经看到其他几个与此类似的问题,但我还没有真正找到任何可以解决我的问题的问题。
我的用例是这样的:用户最初有一个项目列表(listA)。他们重新排序项目并希望保留该订单(listB),但是,由于限制,我无法在后端保留订单,因此我必须在检索后对 listA 进行排序。
所以基本上,我有 2 个 ArrayList(listA 和 listB)。一个具有列表的特定顺序(listB),另一个具有项目列表(listA)。我想根据 listB 对 listA 进行排序。
【问题讨论】:
当您说您无法在“后端”保留订单时,您是什么意思?如果您能提供预期输入和输出的示例,将会很有帮助。 你的列表有相同的元素吗? @Debacle:请澄清两件事:1)listA和listB之间是否存在1:1的对应关系? 2) listA 和 listB 是否包含对相同对象的引用,还是仅包含与 equals() 等效的对象? 我会重新表述这个问题,但我不知道实际要求的是什么......更好的示例数据也会很有帮助。 @Debacle 在 listA 的后端允许哪些操作?我的意思是 swapItems(), removeItem(), addItem(), setItem() ?? 【参考方案1】:List<String> listA;
Comparator<B> comparator = Comparator.comparing(e -> listA.indexOf(e.getValue()));
//call your comparator inside your list to be sorted
listB.stream().sorted(comparator)..
【讨论】:
【参考方案2】:所以对我来说,要求是将originalList
与orderedList
进行排序。 originalList
总是包含来自orderedList
的所有元素,但反之则不然。没有新元素。
fun <T> List<T>.sort(orderedList: List<T>): List<T>
return if (size == orderedList.size)
orderedList
else
var keepIndexCount = 0
mapIndexed index, item ->
if (orderedList.contains(item))
orderedList[index - keepIndexCount]
else
keepIndexCount++
item
附:我的情况是我有列表,用户可以通过拖放进行排序,但有些项目可能会被过滤掉,所以我们保留隐藏项目的位置。
【讨论】:
【参考方案3】:这是一个将时间复杂度增加2n
的解决方案,但可以实现您想要的。它也不关心您要排序的 List R
是否包含 Comparable 元素,只要您用来对其进行排序的另一个 List L
是一致的 Comparable 即可。
public class 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);
。现在它确实有效了。
【讨论】:
【参考方案4】:使用 Java 8:
Collections.sort(listToSort,
Comparator.comparing(item -> listWithOrder.indexOf(item)));
或更好:
listToSort.sort(Comparator.comparingInt(listWithOrder::indexOf));
【讨论】:
只是比第一个版本简单【参考方案5】:如果保证两个列表包含相同的元素,只是顺序不同,则可以使用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);
);
【讨论】:
【参考方案6】:问题:根据另一个列表中存在的字段的所有可能值之一对 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 + "]";
输出 [[姓名=a, 年龄=age11], [姓名=a, 年龄=age111], [姓名=a, 年龄=age1], [姓名=b, 年龄=age22], [姓名=b, 年龄=age2] , [name=c, age=age33], [name=c, age=age3]]
【讨论】:
如果 item.getName() 返回 null ,它将在排序后首先出现。如何让它最后。?【参考方案7】: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]
【讨论】:
这是对列表进行排序的一种非常好的方法,并且澄清一下,使用 appendFirst=true 调用会将列表排序为 [d, c, e, a, b]【参考方案8】:为避免非常低效的查找,您应该索引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));
【讨论】:
【参考方案9】:刚刚遇到同样的问题。 我有一个有序键的列表,我需要根据键的顺序对列表中的对象进行排序。 我的列表足够长,使得时间复杂度为 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
替换为SortedMap<Integer, List<T>> indexToObjList
。
【讨论】:
【参考方案10】:在 java 8 上试试这个:
listB.sort((left, right) -> Integer.compare(list.indexOf(left), list.indexOf(right)));
或
listB.sort(Comparator.comparingInt(item -> list.indexOf(item)));
【讨论】:
【参考方案11】:JB Nizet 答案的速度改进(来自他自己提出的建议)。用这个方法:
对包含 1000 个项目的列表进行 100 次排序可提高我的速度 10 倍 单元测试。
对包含 10000 个项目的列表进行 100 次排序可将我的速度提高 140 倍(整个批次为 265 毫秒而不是 37 秒) 单元测试。
当两个列表不相同时,此方法也可以使用:
/**
* 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);
);
【讨论】:
最佳答案!也很容易扩展类似的问题!【参考方案12】: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>
以更快地查找项目的位置。
Guava 有一个现成的比较器可以做到这一点:Ordering.explicit()
【讨论】:
O(n) 查找大约发生 O(nlogn) 次?那是 O(n^2 logn)!你没开玩笑。 请注意,Integer.compare 仅适用于 java 7。在 java 6 或更低版本中,您需要使用Integer.valueOf(listA.indexOf(left)).compareTo(Integer.valueOf(listA.indexOf(right)))
第二个Item
参数旁边多了一个,
关于Ordering
,下面是它的外观示例:Collections.sort(listA, Ordering.explicit(listB))
。如果您现有的订单仅包含待排序对象的字段(例如 ID),请使用onResultOf
:Ordering.explicit(ids).onResultOf(item -> item.getId())
。【参考方案13】:
这里是一个示例,说明如何对列表进行排序,然后根据对第一个数组列表所做的更改在另一个列表中进行更改。这个技巧永远不会失败,并确保列表中的项目之间的映射。两个列表的大小必须相同才能使用此技巧。
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;
);
【讨论】:
【参考方案14】:试试这个。下面的代码适用于 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);
【讨论】:
【参考方案15】:IMO,你需要坚持别的东西。可能不是完整的列表 B,而是 something。可能只是用户更改的项目的索引。
【讨论】:
【参考方案16】:在 Java 中,有一组类可用于对列表或数组进行排序。以下大多数示例都将使用列表,但同样的概念也适用于数组。一个示例将显示这一点。
我们可以通过创建一个整数列表来使用它,并使用 Collections.sort() 对它们进行排序。 Collections (Java Doc) 类(Java Collection Framework 的一部分)提供了一个静态方法列表,我们可以在处理诸如 list、set 等集合时使用这些方法。所以简而言之,我们可以通过简单地调用 java.util.Collections.sort(the list) 来对列表进行排序,如下例所示:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class example
public static void main(String[] args)
List<Integer> ints = new ArrayList<Integer>();
ints.add(4);
ints.add(3);
ints.add(7);
ints.add(5);
Collections.sort(ints);
System.out.println(ints);
上面的类创建了一个包含四个整数的列表,并使用集合排序方法对这个列表进行排序(在一行代码中),我们不必担心排序算法。
【讨论】:
【参考方案17】:根据您的设置,另一个可能有效的解决方案不是将实例存储在 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 中的元素 - 在这方面,它实际上就像 listA 中的一组元素一样,因为这通常是您在这样的设置中想要的。
向 SortedDependingList 添加内容的首选方法是已经知道元素的索引并通过调用 sortedList.addByIndex(index); 来添加它
【讨论】:
【参考方案18】:假设您有一个listB
列表,该列表定义了您要对listA
进行排序的顺序。这只是一个示例,但它演示了由列表定义的顺序,而不是数据类型的自然顺序:
List<String> listB = Arrays.asList("Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday");
现在,假设listA
需要按照这个顺序进行排序。这是一个List<Item>
,而Item
有一个public String getWeekday()
方法。
创建一个Map<String, Integer>
,将listB
中所有内容的值映射到可以轻松排序的内容,例如索引,即"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);
然后您可以创建自定义Comparator<Item>
,使用Map
创建订单:
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);
然后您可以使用自定义的Comparator
对listA
进行排序。
Collections.sort(listA, new ItemWeekdayComparator(weekdayOrder));
【讨论】:
【参考方案19】:不完全清楚你想要什么,但如果是这种情况: 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);
请注意,我也没有测试,可能有一个标志被翻转了。
【讨论】:
【参考方案20】:就像 Tim Herold 写的,如果对象引用应该相同,您可以将 listB 复制到 listA,或者:
listA = new ArrayList(listB);
如果您不想更改 listA 所指的列表,则可以这样做:
listA.clear();
listA.addAll(listB);
如果引用不相同但listA 和listB 中的对象之间存在某种等价关系,您可以使用自定义Comparator
对listA 进行排序,该Comparator
在listB 中查找对象并使用其在listB 中的索引作为排序键。暴力搜索 listB 的幼稚实现在性能方面不是最好的,但在功能上就足够了。
【讨论】:
请参阅 JB Nizet 的回答,了解执行此操作的自定义比较器的示例。【参考方案21】:这样做的一种方法是遍历 listB 并在 listA 包含项目时将项目添加到临时列表中:
List<?> tempList = new ArrayList<?>();
for(Object o : listB)
if(listA.contains(o))
tempList.add(o);
listA.removeAll(listB);
tempList.addAll(listA);
return tempList;
【讨论】:
@boxed__l:它将两个列表中包含的元素以相同的顺序排序,并将仅包含在A中的元素添加到末尾。例如,如果listA
是0, 1, 6, 4, 5
和listB
是0, 1, 2, 3, 4
,方法会返回0, 2, 4, 6, 5
这个答案有一个主要问题:您正在将对 listB 中的对象的引用插入到 listA 中,如果两个对象是 equals() 但不引用相同,则这是不正确的行为object - listA 中的原始对象丢失,listA 中的一些引用被 listB 中的引用替换,而不是简单地重新排序 listA。
第二个问题是,如果 listA 和 listB 确实包含对相同对象的引用(当然,这使得第一个问题没有实际意义),并且它们包含相同的对象(正如他所说的 OP 所暗示的那样“重新排序”),那么整个事情就和listA.clear(); listA.addAll(listB);
一样。
第三个主要问题是,在这个函数结束时,你会留下一些非常奇怪的副作用。您返回 tempList
,其中包含来自 listB 和 listA 的对象的可能组合,并且 listA 已经删除了一些对象。如果您首先构建 tempList ,则意味着您希望在没有副作用的情况下完成所有这些操作,但 listA 留下了许多(如果不是全部)对象。
基本上,这个答案是无稽之谈。我知道你的目标是什么,但你需要重新考虑你的目标并编辑这个答案。【参考方案22】:
如果对象引用应该相同,可以初始化listA new。
listA = new ArrayList(listB)
【讨论】:
虽然我不完全确定 OP 到底要求什么,但我还是忍不住得出了这个结论。 第二组可能是第一组的子集。在这种情况下,这个答案有点有效,但只需要是集合的交集(删除缺失的元素)以上是关于在 Java 中,如何根据另一个列表对一个列表进行排序?的主要内容,如果未能解决你的问题,请参考以下文章