【问题标题】:Sort a list of lists using comparator使用比较器对列表列表进行排序
【发布时间】:2018-09-10 05:48:05
【问题描述】:

我正在考虑使用比较器整理列表列表(在 ArrayLists 上)。订单最大的地方。所有子列表的大小始终相同。

例如,一个列表

[[4,5,6], [7,9,10], [4,7,8], [1,2,3], [7,9,12]]

这应该

[[7,9,12], [7,9,10], [4,7,8], [4,5,6], [1,2,3]]

我有类似的东西,但只按每个列表中的第一项排序

List<List<Integer>> list = Arrays.asList(
                Arrays.asList(4,5,6),
                Arrays.asList(7,9,10), 
                Arrays.asList(4,7,8),
                Arrays.asList(1,2,3), 
                Arrays.asList(7,9,12));

list.sort((l1, l2) -> l2.get(0).compareTo(l1.get(0)));

产生:

[[7, 9, 10], [7, 9, 12], [4, 5, 6], [4, 7, 8], [1, 2, 3]]

如何编写一个比较器,如果前面的项目相等,则按列表中的下一个项目排序?

例如,[7, 9, 10], [7, 9, 12] 应该去比较两个 7,然后是两个 9,然后是 10 和 12。例如,[4, 5, 6], [4, 7, 8] 应该继续比较两个 4,然后再比较 4 和 7 并停止。

【问题讨论】:

  • 这与字典顺序相同。您将如何使用List&lt;String&gt; 实现它?
  • 创建附加方法,该方法将遍历列表元素对,直到找到不相等的对。对于那对返回他们比较的结果。如果这样的对不存在,则返回 0,这将表示相等的列表。

标签: java algorithm sorting oop functional-programming


【解决方案1】:

您可以这样定义比较器:

Comparator<List<Integer>> comparator = (list1, list2) -> {
       for (int i = 0; i < list1.size(); i++) {
            int value = Integer.compare(list2.get(i), list1.get(i));
            if (value != 0)
                return value;
       }
       return 0;
};

或:

Comparator<List<Integer>> comparator = (list1, list2) -> 
IntStream.range(0, list1.size())
         .map(i -> Integer.compare(list2.get(i), list1.get(i)))
         .filter(value -> value != 0)
         .findFirst()
         .orElse(0);

然后排序:

list.sort(comparator);

更新

您可以通过创建返回比较器的自定义泛型函数来进一步概括这一点,即:

static <T extends Comparable<T>> Comparator<List<T>> comparator(){
       return (o1, o2) -> IntStream.range(0, o1.size())
                                   .map(i -> o2.get(i).compareTo(o1.get(i)))
                                   .filter(value -> value != 0)
                                   .findFirst()
                                   .orElse(0);
}

现在你可以这样做了:

List<List<Integer>> integerList = Arrays.asList(
                Arrays.asList(4,5,6),
                Arrays.asList(7,9,10),
                Arrays.asList(4,7,8),
                Arrays.asList(1,2,3),
                Arrays.asList(7,9,12));

integerList.sort(comparator()); // sort list of integers descending

List<List<String>> stringList = Arrays.asList(
                Arrays.asList("a","b","c"),
                Arrays.asList("d","e","f"),
                Arrays.asList("g","h","i"));

stringList.sort(comparator()); // sort list of strings descending 

等等……

注意 - 我使用的是 JDK 9+,所以我不知道类型推断在 之前 到 JDK 8 的版本中是否足够好。

【讨论】:

  • 第一个投票数增加。第二次扬起眉毛。
  • @Marco13 谢谢,我刚刚有几分钟的空闲时间,决定为什么不让比较器通用。 :)
  • 我仍然更喜欢基于列表的方法而不是基于流的方法。此外,在概括它时,通常可以通过将匹配的 Comparator 传递到方法中来避免依赖 Comparable 的元素,而 Comparator#naturalOrder 在专用于 Comparable 元素的方法中。我试图在我的回答中勾勒出这个想法。
  • 对于通用版本,我宁愿创建一个包装参数Comparator的工厂函数,而不是依赖Comparable
【解决方案2】:

其实你是按照Lists的第一个元素排序的:

list.sort((l1, l2) -> l2.get(0).compareTo(l1.get(0)));

要么比较所有元素,但请注意,如果您的 List 的长度不同,您也必须处理它。

或者将List的元素转换成Strings然后比较这个String:

list.sort((l1, l2) -> l2.toString()
                        .compareTo(l1.toString()));

这种方式的优势在于即使ListString 大小不同。

【讨论】:

    【解决方案3】:

    由于您想按降序对整数进行排序,因此您首先创建一个比较器来执行此操作,然后将其多次链接在一起以创建列表的比较器:

    Comparator<Integer> intReversed = Integer::compare;
    intReversed = intReversed.reversed(); // integer reversed order comparator
    
    // Chain comparators for 3 indices (could also do this in a loop for lists of unknown size)
    // Each step compares the extracted element using the given comparator:
    Comparator<List<Integer>> comp = Comparator.comparing(l -> l.get(0), intReversed);
    comp = comp.thenComparing(l -> l.get(1), intReversed);
    comp = comp.thenComparing(l -> l.get(2), intReversed);
    
    list.sort(comp);        
    System.out.println(list); // [[7, 9, 12], [7, 9, 10], [4, 7, 8], [4, 5, 6], [1, 2, 3]]
    

    【讨论】:

    • 有趣 - 这基本上是循环展开此处的其他选项。我想知道这对性能有什么影响。
    • 如果有 50 个元素呢?
    • @AniketSahrawat 您需要在某个时间点循环 - 为什么不循环一次 在比较器之外 而不是在每个比较操作中循环 n lg(n) 次?我不确定性能影响是什么,但我认为没有理由不让他们有很大的机会成为好人。
    • @AniketSahrawat 然后在循环中构建比较器。例如:ideone.com/KjcOIo 或者这不是你要问的?
    【解决方案4】:

    您尝试实现的排序顺序称为Lexicographical order

    要按此顺序比较两个列表,您可以简单地遍历列表,并比较相应的元素。只要一次比较产生一个非零值,就可以返回该值。如果所有元素都相等,则返回零。

    所以你可以用一个泛型方法来实现这个比较的核心:

    private static <T> int compareLexicographically(
        List<? extends T> list0,
        List<? extends T> list1, 
        Comparator<? super T> comparator)
    

    基于此方法,您可以组装不同类型的比较器。对于Integer 值(以及实现Comparable 接口的其他类型),您可以使用Comparator#naturalOrder 比较器作为最后一个参数。对于其他类型,您可以输入不同的比较器。例如,您还可以随时选择从中创建reversed comparator

    这是一个示例,展示了如何实现和使用这种方法来对Integer 对象、String 对象或自定义对象进行排序(这里是一个简单的Person 类,通过它们的getName 进行比较)。

    import java.util.Arrays;
    import java.util.Comparator;
    import java.util.List;
    
    public class LexicographicalComparator
    {
        public static void main(String[] args)
        {
            exampleWithIntegers();
            exampleWithStrings();
            exampleWithPersons();
        }
    
        private static void exampleWithIntegers()
        {
            List<List<Integer>> list = Arrays.asList(
                Arrays.asList(4, 5, 6), 
                Arrays.asList(7, 9, 10),
                Arrays.asList(4, 7, 8), 
                Arrays.asList(1, 2, 3),
                Arrays.asList(7, 9, 12));
    
            Comparator<List<Integer>> comparator = lexicographicalComparator();
            list.sort(comparator.reversed());
    
            System.out.println("Integers, descending:");
            list.forEach(System.out::println);
        }
    
        private static void exampleWithStrings()
        {
            List<List<String>> list = Arrays.asList(
                Arrays.asList("B", "B", "C"), 
                Arrays.asList("C", "B", "B"),
                Arrays.asList("B", "C", "A"), 
                Arrays.asList("A", "C", "B"),
                Arrays.asList("C", "B", "A"));
    
            Comparator<List<String>> comparator = lexicographicalComparator();
            list.sort(comparator);
    
            System.out.println("Strings, ascending:");
            list.forEach(System.out::println);
        }
    
        private static void exampleWithPersons()
        {
            class Person 
            {
                String name;
                Person(String name)
                {
                    this.name = name;
                }
                String getName()
                {
                    return name;
                }
    
                @Override
                public java.lang.String toString()
                {
                    return name;
                }
            }
    
            List<List<Person>> list = Arrays.asList(
                Arrays.asList(new Person("B"), new Person("B"), new Person("C")), 
                Arrays.asList(new Person("C"), new Person("B"), new Person("B")),
                Arrays.asList(new Person("B"), new Person("C"), new Person("A")), 
                Arrays.asList(new Person("A"), new Person("C"), new Person("B")),
                Arrays.asList(new Person("C"), new Person("B"), new Person("A")));
    
            Comparator<List<Person>> comparator = 
                lexicographicalComparator(Comparator.comparing(Person::getName));
            list.sort(comparator);
    
            System.out.println("Persons, by name, ascending:");
            list.forEach(System.out::println);
        }
    
    
    
        private static <T extends Comparable<? super T>> Comparator<List<T>> 
            lexicographicalComparator()
        {
            return (list0, list1) -> 
                compareLexicographically(list0, list1, Comparator.naturalOrder());
        }
    
        private static <T> Comparator<List<T>> lexicographicalComparator(
            Comparator<? super T> comparator)
        {
            return (list0, list1) -> 
                compareLexicographically(list0, list1, comparator);
        }
    
        private static <T> int compareLexicographically(
            List<? extends T> list0,
            List<? extends T> list1, 
            Comparator<? super T> comparator)
        {
            if (list0.size() < list1.size())
            {
                return -1;
            }
            if (list0.size() > list1.size())
            {
                return 1;
            }
            for (int i = 0; i < list0.size(); i++)
            {
                T t0 = list0.get(i);
                T t1 = list1.get(i);
                int value = comparator.compare(t0, t1);
                if (value != 0)
                {
                    return value;
                }
            }
            return 0;
        }
    }
    

    您可能仍需考虑极端情况。最重要的是:

    • 当列表大小不同时会发生什么?
    • 当列表包含null 元素时会发生什么?

    第一个目前基本上是首先按列表的大小对列表进行排序,这可能是也可能不是你想要的。

    通过将Comparator#nullsFirstComparator#nullsLast 比较器传递给外部方法可以轻松处理后者,但您必须注意这一点。

    【讨论】:

      猜你喜欢
      • 2013-02-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-07-09
      • 2014-05-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多