【问题标题】:Custom Comparator自定义比较器
【发布时间】:2017-11-02 04:17:44
【问题描述】:

给定以下列表:"A", "B", "C", "D", "E", "F", "G"

我需要一个进行以下排序的比较器:

  • 指定某个元素(例如"D"
  • 从元素开始
  • 按照原始顺序依次跟随原始列表的所有以下元素
  • 后跟原始列表中所有前面的元素以原始顺序

结果将是:"D", "E", "F", "G", "A", "B", "C"


请注意,我知道我可以做类似以下的事情:

List<String> following = myList.subList(myList.indexOf("D") + 1, myList.size());
List<String> preceding = myList.subList(0, myList.indexOf("D"));
List<String> newList = Stream.of(Collections.singletonList("D"), following, preceding)
                       .flatMap(List::stream)
                       .collect(Collectors.toList());

在这个问题中,我明确指的是Comparator 实现。


很明显,它必须将列表和元素作为参数,我只是不清楚比较算法本身:

private static class MyComparator<T> implements Comparator<T> {

  private final List<T> list;
  private final T element;

  private MyComparator(List<T> list, T element) {
    this.list = list;
    this.element = element;
  }

  @Override
  public int compare(T o1, T o2) {
    // Not clear
  }
}

【问题讨论】:

  • 如果有相同的int应该是0,如果o1o1之前,应该是negative,相反的应该是positive(有待确认,可能是相反的)。但是T 没有扩展任何东西,你只有Object 提供的东西,所以没什么可比较的。嗯,这是在Comparator 中解释的
  • 我很清楚比较器的工作原理,只是不清楚如何在此处为给定的排序算法生成这些数字 :)
  • 你确定你的设计是正确的吗?在我看来,您需要一个基于树的数据结构。
  • "我需要一个比较器来进行以下排序" => 这句话是你困惑的第一点。比较器只比较两个元素。它不进行任何排序,尽管它有助于排序算法获得“正确”的排序顺序。事实上,这个问题对我来说有点不清楚。也许您可以向我们展示您将如何使用这样的比较器?
  • 好吧,由于比较器用于通过将每个元素与下一个元素进行比较来“冒泡排序”列表,这并不容易......我想如果你给比较器“元素“这应该改变逻辑,这可能是可能的,但我认为这不值得

标签: java list sorting comparator


【解决方案1】:

我想这就是你想要的:

class ImposedOrder<T> implements Comparator<T> {

    private final List<T> list;
    private final int startIndex;

    ImposedOrder(List<T> list, T startElement) {
        this.list = new ArrayList<>(list);
        this.startIndex = list.indexOf(startElement);
        if (startIndex < 0) {
            throw new IllegalArgumentException();
        }
    }

    @Override
    public int compare(T t1, T t2) {
        int t1Index = list.indexOf(t1);
        int t2Index = list.indexOf(t2);
        return Integer.compare(adjust(t1Index), adjust(t2Index));
    }

    private int adjust(int rawIndex) {
        if (rawIndex >= startIndex) {
            return rawIndex;
        }
        return rawIndex + list.size();
    }
}

一些额外的验证可能是为了避免强加的重复订单列表。

使用indexOf 的线性搜索不会给您带来很好的性能,但对于小订单列表可能就足够了。否则,您可以在 Comparator 的构造函数中将元素映射到其调整后的索引,而不是保存强制排序列表的副本。

像这样:

class ImposedOrder<T> implements Comparator<T> {

    private final Map<T, Integer> map;
    private final int startIndex;

    ImposedOrder(List<T> list, T startElement) {
        this.startIndex = list.indexOf(startElement);
        if (startIndex < 0) {
            throw new IllegalArgumentException();
        }
        this.map = IntStream.range(0, list.size())
                .boxed()
                .collect(Collectors.toMap(
                        list::get,
                        i -> adjust(startIndex, list.size(), i)
                ));
    }

    @Override
    public int compare(T t1, T t2) {
        Integer t1Index = map.get(t1);
        Integer t2Index = map.get(t2);
        return t1Index.compareTo(t2Index);
    }

    private static int adjust(int startIndex, int size, int rawIndex) {
        if (rawIndex >= startIndex) {
            return rawIndex;
        }
        return rawIndex + size;
    }
}

【讨论】:

    【解决方案2】:

    在这里使用Comparator 没有多大意义,因为它的效率非常低(因为您必须在原始列表中找到 2 个比较元素的索引,这将导致每次比较都需要线性时间)。

    如果列表包含重复项,它可能会失败。

    但是,既然你问过,这样的事情可能会奏效:

    public int compare(T o1, T o2) {
        if (o1.equals(o2)
            return true;
        int i1 = list.indexOf(o1);
        int i2 = list.indexOf(o2);
        int ie = list.indexOf(element); // this can be done in the constructor of the Comparator
        // now you have to check whether i1 and i2 are smaller than or larger than
        // ie, and based on that determine which of the corresponding elements should come
        // first
    }
    

    或者,直接调用

    Collections.rotate(list,list.size() - list.indexOf(element));
    

    【讨论】:

      【解决方案3】:

      如果我问对了你该怎么办Collections.sort(myList, myComparator);

      那么我可以建议您使用已定义的整理器:

      List<String> myList = Arrays.asList("A", "B", "C", "D", "E", "F", "G");
      System.out.println(myList);
      String rules = "< d,D < e,E < f,F < g,G < a,A < b,B < c,C";
      
      RuleBasedCollator ruleBasedCollator = new RuleBasedCollator(rules);
      Collections.sort(myList, ruleBasedCollator);
      System.out.println(myList);
      

      这里的规则是"&lt; d,D &lt; e,E &lt; f,F &lt; g,G &lt; a,A &lt; b,B &lt; c,C",这意味着哪些字符的权重比其他字符高……其余的和往常一样是一种排序方法

      输出

      [A、B、C、D、E、F、G]

      [D、E、F、G、A、B、C]

      【讨论】:

      • 我认为这不是所要求的
      • @nilesh 你是对的,现在答案更正了!
      • 不是正确的输出:"D", "E", "F", "G", "A", "B", "C",“D”之后的所有内容都应该跟随它。但是我不知道Collator,谢谢!
      • 不完全是我的预期,但RuleBasedCollator 很有趣,不知道这个!谢谢!
      【解决方案4】:

      你需要比较元素的索引。

      private static class MyComparator<T> implements Comparator<T> {
      
          private final List<T> list;
          private final int elementIndex;
      
          private MyComparator(List<T> list, T element) {
              // keep original order
              this.list = new ArrayList<>(list);
              this.elementIndex = list.indexOf(element);
          }
      
          @Override
          public int compare(T o1, T o2) {
              int i1 = list.indexOf(o1);
              int i2 = list.indexOf(o2);
              if (i1 == elementIndex) {
                  // "shift" first element left
                  return -1;
              } else if (i2 == elementIndex) {
                  // "shift" secont element left
                  return 1;
              }
              boolean left1 = i1 < elementIndex;
              boolean left2 = i2 < elementIndex;
              if (left1 != left2) {
                  // different part
                  return i2 - elementIndex;
              } else {
                  // same part
                  return i1 - i2;
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        我知道这是可能的,但它很混乱...... 而且不安全

        将第一个块推到末尾的想法是添加一个String 以强制这些值更大。这样,如果与“ZA”相比,“A”大于“G”。

            List<String> list = Arrays.asList(new String[]{ "A", "B", "C", "D", "E", "F"});
            final String split = "D";
            Collections.sort(list, new Comparator<String>(){
                public int compare(String o1, String o2) {
                    //Prepend with a big String value (like "ZZZZ") if it is before the value `split`.
                    if(o1.compareTo(split) < 0)
                        o1 = "ZZZZ" + o1;
                    if(o2.compareTo(split) < 0)
                        o2 = "ZZZZ" + o2;
        
                    return o1.compareTo(o2); //then compare
                }
            });
        
            System.out.println(list);
        

        [D、E、F、A、B、C]

        这是不安全的,因为添加一个更大的安全值可能会很复杂,我考虑过使用Character.MAX_VALUE,但我不确定这会更安全。好吧,使用 split 值本身可能会更简单..

        【讨论】:

        • 如果泛型类型是MyClass,我认为你会遇到一些麻烦,尤其是不会覆盖toString
        • @StefanWarminski 我不会,我使用了String,因为问题将它用于List。对于其他Class。可以添加一些通用性相同的逻辑,但这仍然需要一个接口来获取要比较的类型值。如果您使用toString,这将起作用,但输出将取决于方法本身,无法覆盖,因此它将根据类的名称和哈希码进行排序(我认为它基本上是输出)
        【解决方案6】:

        这个想法是根据列表中的原始索引比较元素的权重-如果当前元素的索引> =指定某个元素的索引(例如“D”),则应该移动新列表中的索引向左,“D”的索引正是所需的偏移量。

        int weightO1 = list.indexOf(o1) - list.indexOf(element);
        

        在相反的情况下 (list.indexOf(o1)

        int weightO1 = list.indexOf(o1) + list.size();
        

        我会在本地方法中封装重量计算:

           int weight (T o) {
            return int weight = list.indexOf(o) < list.indexOf(element) ? list.indexOf(o) + list.size() : list.indexOf(o) - list.indexOf(element);
           }
        

        所以 compare 方法的实现是这样的:

        @Override
          public int compare(T o1, T o2) {
             return weight(o1) - weight(o2); 
          }
        

        【讨论】:

          猜你喜欢
          • 2016-10-20
          • 2018-12-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-04
          相关资源
          最近更新 更多