【问题标题】:How can I sort Arrays in a Collection?如何对集合中的数组进行排序?
【发布时间】:2016-07-12 13:04:10
【问题描述】:

我有一个对象列表。这些对象(除其他外)有一个私有 int 数组(如果有帮助,我可以将它转移到一个列表中)。这个数组有一个公共的 Getter。所有数组的大小相同。

我想根据对象的数组对对象进行排序,如下所示:

Unsorted:
{[0, 1, 4, 5], 
 [0, 0, 2, 3],
 [0, 1, 1, 2]}

Sorted:
{[0, 0, 2, 3],
 [0, 1, 1, 2],
 [0, 1, 4, 5]}

字面意思(称为字典式):

  • 比较每个数组的第一个整数
  • 如果它们相等,则比较每个数组的下一个 int(依此类推)
  • 如果它们不相等,则比较的结果就是最终结果。

我设法搜索它们,例如只有数组的第一个元素带有普通的 Comparator 但我不知道如何搜索它们。

【问题讨论】:

  • 要明确:我的数组是 int[] 而不是 Integer[]
  • 这真的很有趣,但是...您没有向我们展示您尝试过的内容。是什么阻止您编写代码?
  • 如果数组是私有的,您希望如何使用它们对您的对象进行排序?您无法比较私有属性。
  • 所有数组的大小都一样吗?
  • 实际的标准是哪个数组比另一个数组大还是小?总和?它们的熵(它们的排序程度)?他们的最小值/最大值? ...在澄清之前投反对票 - 这个问题太模糊(广泛)。

标签: java sorting collections


【解决方案1】:

我有一个对象集合(最好是某种列表) [...] 现在我想根据对象的数组对对象进行排序

为了使它有意义,所讨论的Collection 必须是保持顺序并允许您重新排序元素的那个。就高级 Collections 接口而言,只有 List 具有所需的属性,因此我们假设您的 Collection 确实是 List

List 进行排序的标准方法是使用两种Collections.sort() 方法之一。一种要求列表元素实现Comparable,另一种更一般地说,要求您提供一个实现Comparator 的对象,用于确定所需的对象相对顺序。

数组不实现Comparable(这相当于说它们没有“自然顺序”),但包含它们的对象的类可以这样做。然而,最好的形式可能是编写一个单独的 Comparator 类来实现您想要的顺序,并使用该类的实例。

【讨论】:

    【解决方案2】:

    如果我理解正确,下面的简单方法应该可以工作:

    public class SortArrays {
    
        public static void main(String[] args) {
            List<int[]> listOfArrays = new ArrayList<>(4);
            listOfArrays.add(new int[]{0, 1, 4, 5});
            listOfArrays.add(new int[]{0, 0, 2, 3});
            listOfArrays.add(new int[]{0, 1, 1, 2});
            Collections.sort(listOfArrays, (o1, o2) -> {
                for (int i = 0; i < o1.length; i++) {
                    if (o1[i] < o2[i])
                        return -1;
                    if (o1[i] > o2[i])
                        return 1;
                }
                return 0;
            });
            listOfArrays.forEach(a -> System.out.println(Arrays.toString(a)));
        }
    }
    

    它产生:

    [0, 0, 2, 3]
    [0, 1, 1, 2]
    [0, 1, 4, 5]
    

    这就是你所期望的。

    【讨论】:

    • 如果 o2 比 o1 长会怎样?
    • 结果未定义;)。我已经问过 OP,如果在这种情况下需要确定的东西,我会更新这个答案。
    • 我的意思是更短;)
    【解决方案3】:

    一个不错的 Java 8 解决方案是

    static final Comparator<CustomObject> COMPARATOR = (o1, o2) -> {
        int[] arr1 = o1.getArray();
        int[] arr2 = o2.getArray();
        return IntStream.range(0, arr1.length)
                        .map(i -> Integer.compare(arr1[i], arr2[i]))
                        .filter(i -> i != 0)
                        .findFirst()
                        .orElse(0);
    };
    

    然后,给定一个List&lt;CustomObject&gt;,你可以这样做

    list.sort(COMPARATOR);
    

    Comparator 仅适用于相同长度的数组。您可能需要修改它)。

    【讨论】:

    • 虽然我喜欢这个解决方案的_Java8_ness,但我对它的运行时间方面并不感到特别兴奋。 map 函数需要进行所有整数比较,而一旦我们发现第一对不相等的值,我们就可以将其余部分短路。
    • @KedarMhaswade 不,它会短路,你可以贴一个peek 看看你是否不相信我。
    【解决方案4】:

    假设你的类看起来像这样,你不能修改它。

    final class MyClass
    {
      private final int[] key;
    
      MyClass(int[] key)
      {
        this.key = key.clone();
      }
    
      public int[] getKey()
      {
        return key.clone();
      }
    }
    

    然后,您可以通过实现Comparator 接口为MyClass 的实例定义排序。更笼统地说,我实际上要为int[] 实现一个比较器:

    final class IntArrayComparator
      implements Comparator<int[]>
    {
    
      @Override
      public int compare(int[] a, int[] b)
      {
        int n = Math.min(a.length, b.length);
        for (int idx = 0; idx < n; ++idx) {
          if (a[idx] != b[idx])
            return (a[idx] < b[idx]) ? -1 : +1;
        }
        return a.length - b.length;
      }
    }
    

    鉴于这个新的比较器和您现有的类型,很容易排序:

    final class Test
    {
      public static void main(String... arg)
      {
        /* Create your list somehow... */
        List<MyClass> list = new ArrayList<>();
        list.add(new MyClass(new int[]{0, 1, 4, 5}));
        list.add(new MyClass(new int[]{0, 0, 2, 3}));
        list.add(new MyClass(new int[]{0, 1, 1, 2}));
    
        /* Now sort the list. */
        list.sort(Comparator.comparing(MyClass::getKey, new IntArrayComparator()));
    
        /* Display the result... */
        list.stream().map(MyClass::getKey).map(Arrays::toString).forEach(System.out::println);
      }
    }
    

    你应该得到输出:

    [0, 0, 2, 3] [0, 1, 1, 2] [0, 1, 4, 5]

    我使用的comparing() 工厂方法有两个参数:Function 从列表中的每个对象中“提取”一个键,以及一个可以比较提取的键的Comparator。如果提取的键具有自然顺序,并且实现了Comparable(例如,它是String),则不需要指定Comparator

    或者,如果您可以修改MyClass,您可以通过实现Comparable 并将int[] 比较移动到其compareTo() 方法来给它一个自然顺序,如其他答案所示。然后你可以使用不带参数的sort() 方法。

    【讨论】:

      【解决方案5】:

      考虑这个对象:

      public class Foo {
      
          private int[] values;
      
          public Foo(int[] values) { 
              this.values = values;
          }
      }
      

      制作对象列表:

      ArrayList<Foo> foos = new ArrayList<>();
      foos.add(new Foo(new int[] {0, 1, 4, 5}));
      foos.add(new Foo(new int[] {0, 0, 2, 3}));
      foos.add(new Foo(new int[] {0, 1, 1, 2}));
      

      现在我们想要对我们的对象列表进行排序,所以我们要做的第一件事就是让我们的对象实现Comparable。你应该阅读更多what the Comparable interface ishow to implement it

      public class Foo implements Comparable<Foo> {
          ...
      }
      

      此时你的编译器会抱怨你没有实现接口所需的所有方法。就这样吧。

      public class Foo implements Comparable<Foo> {
          ...
          public int compareTo(Foo f) {
              // Return a negative integer if this < f
              // Return zero if this == f
              // Return a positive integer if this > f
          }
      }
      

      就实际比较逻辑而言,您的问题对于您用于比较两个对象的确切规则并不是很具体。但是,从示例中,我可以猜测这些是您的规则:

      • 比较每个数组的第一个数字。
        • 如果它们相等,请使用每个数组的下一个数字重复此过程。
        • 否则,本次比较的结果为最终结果。

      如果一个数组比另一个数组短,您并没有真正指定要做什么。我将把实际的算法写给你(尽管其他答案似乎已经为你做了)。但是,我要指出,因为 values 是一个私有字段,除非您为该字段实现 getter 或将其公开,否则您将无法使用该字段进行比较。

      public int[] getValues() { return values; }
      

      【讨论】:

        【解决方案6】:

        如果您的数组是 private 并且您确实想提供一个访问器方法,那么为该类实现 Comparable 并执行类似以下代码的操作。

        如果您有访问器方法,请在 Comparator 中执行此操作。

        此代码does not assume arrays to be of same size 因此适用于不同大小的数组。请根据需要进行更改。

        PS - 未编译/测试

        public int compareTo(Object o) {
                CustomClass other = (CustomClass ) o;
        
                int i = 0;
                while (i <= numbers.length && i <= other.numbers.length) {
                    if (numbers[i] < other.numbers[i])
                        return -1;
                    else if (numbers[i] > other.numbers[i])
                        return 1;
                    ++i;
                }
        
                if (numbers.length < other.numbers.length)
                    return -1;
                else if (numbers.length > other.numbers.length)
                    return 1;
        
                return 0;
            }
        

        【讨论】:

          【解决方案7】:

          为了对 int 数组列表进行排序,您需要一个对 int 数组执行字典顺序比较的函数。这通常在 Java 中表示为Comparator&lt;int[]&gt;。实现这种功能的常用方法是从左到右比较对应的值,直到发现不匹配;这种不匹配决定了顺序。如果到达数组的末尾而没有发现不匹配,则通常认为较短的数组小于较长的数组。如果长度相等,并且所有值都相等,则认为数组相等。

          其他答案为此函数使用了匿名内部类或 lambda,但对于这样的事情,我更喜欢具有正确“形状”的普通方法,即接受两个 int[] 参数并返回一个 int这是比较的结果。这使它可以用作方法引用的目标。

          int arrayCompare(int[] a, int[] b) {
              int len = Math.min(a.length, b.length);
              for (int i = 0; i < len; i++) {
                  int c = Integer.compare(a[i], b[i]);
                  if (c != 0) {
                      return c;
                  }
              }
              return a.length - b.length;
          }
          

          注意小心使用Integer.compare() 而不是减法,避免溢出的潜在问题。用法如下:

              List<int[]> arrays = Arrays.asList(
                  new int[] { 0, 1, 4, 5 },
                  new int[] { 0, 0, 2, 3 },
                  new int[] { 0, 1, 1, 2 }
              );
          
              arrays.sort(this::arrayCompare);
              arrays.forEach(a -> System.out.println(Arrays.toString(a)));
          

          在 JDK 9 中,字典数组比较函数已添加到 Arrays 类中。见Arrays.compare(int[], int[])。其他原始类型和引用类型也有重载。 int[] 重载替换了我上面写的arrayCompare 函数,所以你可以重写排序调用如下:

              arrays.sort(Arrays::compare);
          

          Arrays 类中比较函数的优点是它们可以由 JVM 专门处理,例如可以使用向量指令来加速数组比较。

          对于您的具体问题,您似乎没有int[] 的列表,但您有一个MyObject 类型的对象列表,这些对象有一个您想用作排序键的int[] .假设获取数组的方法是调用方法getArray()。您可以按如下方式对列表进行排序:

              myObjects.sort(Comparator.comparing(MyObject::getArray, Arrays::compare));
          

          【讨论】:

          • JDK9 是否支持按字典顺序比较列表?如果是这样,你能传入一个 Comparator 来说明应该如何比较单个元素吗?
          • @PaulBoddington 不在 JDK 中。但是 Guava 有 Ordering.lexicographical() 可以做到这一点。
          猜你喜欢
          • 1970-01-01
          • 2017-11-20
          • 2014-08-29
          • 2011-04-04
          • 2016-08-28
          • 1970-01-01
          • 2017-11-01
          • 1970-01-01
          相关资源
          最近更新 更多