在 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】:

所以对我来说,要求是将originalListorderedList 进行排序。 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&lt;T&gt; listA = new ArrayList&lt;&gt;(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&lt;Integer, T&gt; indexToObj 替换为SortedMap&lt;Integer, List&lt;T&gt;&gt; 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&lt;Item, Integer&gt; 以更快地查找项目的位置。

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),请使用onResultOfOrdering.explicit(ids).onResultOf(item -&gt; 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&lt;Item&gt;,而Item 有一个public String getWeekday() 方法。

创建一个Map&lt;String, Integer&gt;,将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&lt;Item&gt;,使用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);
    

然后您可以使用自定义的ComparatorlistA 进行排序。

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中的元素添加到末尾。例如,如果listA0, 1, 6, 4, 5listB0, 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 中,如何根据另一个列表对一个列表进行排序?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据另一个列表中元组元素的顺序对元组列表进行排序?

根据另一个列表中的值对列表进行排序

如何根据另一个数组的顺序对对象数组进行排序?

根据另一个列表中定义的元素位置对嵌套列表进行排序[关闭]

根据使用 Jexcel 选择的另一个下拉列表更改下拉列表值

如何根据GPA对数组列表进行升序排序? arraylist 包含一个对象数组。 (Java)[重复]