【问题标题】:Guava: How to create an explicit Ordering from a List and a single element?Guava:如何从列表和单个元素创建显式排序?
【发布时间】:2013-01-18 16:21:36
【问题描述】:

在 Guava 中,给定一个 Collection<E> 和一个 E 类型的元素 e,我知道该元素在集合中,我想创建一个自定义 Ordering<E>,它首先对 e 进行排序,然后是其余的集合。但是,到达那里的方法似乎非常复杂:

Collection<String> values = ImmutableList.of("apples", "oranges", "pears");
String first = "oranges";

List<String> remainingValues = newArrayList(values);  // this
remainingValues.remove(first);                        // seems
Ordering<String> myOrdering =                         // very
    Ordering.explicit(first, remainingValues.toArray( // complicated!
        new String[remainingValues.size()]));         // is there an easier way?

我想要的是这样的:

Ordering.explicit(first);

(我希望将first 排序到开头并保留所有其他元素的顺序,但文档说生成的排序将为未明确列出的元素抛出ClassCastException。)

或者像这样:

Ordering.explicit(first, values.toArray(/* etc */));

(但这会失败,因为first 会是重复值)

谁能想出一个简洁的方法来做我想做的事?

顺便说一句,它不一定是Ordering,它也可以是在指定订单中创建Iterable 的解决方法,但同样,这非常复杂:

Iterable<String> sorted = Iterables.concat(
                             ImmutableList.of(first),
                             Iterables.filter(values, not(equalTo(first))));

【问题讨论】:

  • 在链接的 javaDoc 上,allEqual 下的示例以及 nullsFirst/Last 方法引起了我的注意。我想知道您是否有一个要提取的值的比较器小于其他任何值,链接到假设所有其他值都相等的排序?
  • @Charlie 是的,这可行,但实际上不会比我正在做的事情简单
  • 这是真的...但是通用的 itemFirst/itemLast 可能会为番石榴本身提供一个很好的补丁...如果接受然后:Ordering.allEqual().itemFirst("oranges") 将非常简单:-D

标签: java guava comparator


【解决方案1】:

嗯,这是一种方法,但您可能不会发现它更好。

final String special = "oranges";
Collections.sort(
    list,
    new Comparator<String>() {
      public int compare(String left, String right) {
        return ComparisonChain.start()
            .compareTrueFirst(left.equals(special), right.equals(special))
            .compare(left, right)
            .result();
      }
    });

ComparisonChain docs

Relevant Guava feature request -- 请添加任何详细信息。

【讨论】:

  • 当然,我知道这种方式,它(除了比较链)实际上是传统的 Java 方式。但是:我喜欢使用 Guava,因为在大多数情况下,它实际上让生活更轻松。这并没有让任何事情变得更容易,而且我不喜欢破坏性的 Collections.sort 调用。
  • 嗯,因为没有一个答案似乎令人满意,所以这个得到了建议提交问题的复选标记。我最终可能会这样做。
【解决方案2】:

如果你有更多的特殊值,这更方便,重复更少:

class PriorityComparator<T> implements Comparator<T> {
    private final List<T> values;

    public PriorityComparator(T... values) {
        this.values = Arrays.asList(values);
    }

    @Override public int compare(T o1, T o2) {
        int idx1 = values.indexOf(o1);
        int idx2 = values.indexOf(o2);
        if (idx1 > -1) {
            return idx2 > -1 ? idx1 - idx2 : -1;
        }
        return idx2 > -1 ? 1 : 0;
    }
}

您可以在比较链中使用它,例如

return ComparisonChain.start()
    .compare(left, right, new PriorityComparator<>("oranges", "apples"))
    .compare(left, right)
    .result();

它将按照PriorityComparator中指定的元素对元素进行排序,其他元素被报告为相等。

要求T 具有可比性并将其用作默认值也很容易:

class PriorityComparator2<T extends Comparable<T>> implements Comparator<T> {
    private final List<T> values;

    public PriorityComparator2(T... values) {
        this.values = Arrays.asList(values);
    }

    @Override public int compare(T o1, T o2) {
        int idx1 = values.indexOf(o1);
        int idx2 = values.indexOf(o2);
        if (idx1 > -1) {
            return idx2 > -1 ? idx1 - idx2 : -1;
        }
        return idx2 > -1 ? 1 : o1.compareTo(o2);
    }
}

【讨论】:

    【解决方案3】:

    也许这个答案并不比你已经拥有的更容易/不那么复杂,但至少它可以重复使用:)

    class FirstOrdering<T extends Comparable> extends Ordering<T> {
    
        private T first;
    
        public FirstOrdering(T first) {
            this.first = first;
        }
        @Override
        public int compare(@Nullable T left, @Nullable T right) {
            // TODO Nullchecks...
            if (first.equals(left)) return -1;
            if (first.equals(right)) return 1;
            return left.compareTo(right);
        }
    }
    
    final String first = "B";
        new FirstOrdering(first).
                sortedCopy(Arrays.asList("A", "D", "E", first));
    

    【讨论】:

      【解决方案4】:

      只需使用NullsFirstOrdering 作为您的模板,并创建一个排序第一个元素的排序,将其他所有内容委托给另一个排序:

      public class ItemFirstComparator<T> implements Comparator<T> implements Serializable {
        private final Comparator<? super T> comparator;
        private final Object item;
      
        ItemFirstComparator(Object item, Comparator<? super T> comparator) {
          this.item = item;
          comparator = checkNotNull(comparator);
        }
      
        @Override public int compare(@Nullable T left, @Nullable T right) {
          if (left == right) {
            return 0;
          }
          if (Objects.equals(left, item)) {
            return -1;
          }
          if (Objects.equals(right, item)) {
            return 1;
          }
          return comparator.compare(left, right);
        }
      }
      

      然后您可以轻松地链接订购:Ordering.from(new ItemFirstComparator("oranges", Ordering.allEqual()))

      编辑

      将代码更改为使用 Comparator 而不是 Ordering,其余保持不变。

      【讨论】:

      • 子类排序看起来像是一种代码味道。我很惊讶这甚至是可能的。
      • @SeanPatrickFloyd 虽然可以进行子类排序,但我同意你的观点,即它很臭。因此,我将代码示例更改为 Comparable。
      • 子类化肯定被过度使用了,但 Ordering 至少被设计为子类化:“你也可以跳过比较器步骤,直接扩展 Ordering。” docs.guava-libraries.googlecode.com/git/javadoc/com/google/…
      【解决方案5】:

      如果您查看 com.google.common.collect.ExplicitOrdering 的来源,它会维护一个包含每个项目排名的地图,compare 只是比较排名。您可以自己做同样的事情,但将指定的第一个项目的排名强制为 -1,这在所有其他项目之前。

      如果您有一个列表(如问题标题所述),Java 8 流使构建地图相当方便:

      Map<T, Integer> rankMap = IntStream.range(0, list.size()).boxed().collect(
          Collectors.toMap(list::get, i -> list.get(i).equals(first) ? -1 : i));
      Comparator<T> cmp = Comparator.comparing(rankMap::get);
      

      如果您只有一个 Collection(如问题正文所述),则需要使用 for 循环来构建地图:

      Map<T, Integer> rankMap = new HashMap<>(coll.size());
      int rank = 0;
      for (T t : coll)
          rankMap.put(t, t.equals(first) ? -1 : rank++);
      Comparator<T> cmp = Comparator.comparing(rankMap::get);
      

      您可以像往常一样使用 Ordering.from 将 Comparator 变成 Ordering。

      【讨论】:

        【解决方案6】:

        如果您开始考虑使用显式排序,则假设您的列表没有重复项。在这一点上,FluentIterable.toSet() 可以使这变得微不足道。重复项将被简单地忽略(而不是出错)。

        Iterable<String> sorted = FluentIterable.of(first).append(values).toSet();
            or
        ImmutableList<String> sorted =
            FluentIterable.of(first).append(values).toSet().toList();
        

        IMO,您的第一个建议实际上并没有那么糟糕,如果您的列表包含非第一个重复值,它也可以工作。但是,如果您使用 FluentIterable,它看起来会更好,因为您可以连接混合的 Iterable 和元素类型:

        Iterable<String> others = Iterables.filter(values, not(equalTo(first)));
        Iterable<String> sorted = FluentIterable.of(first).append(others);
        

        这里的问题是,如果您有超过 1 个“第一个”元素,您将丢失副本。

        虽然修复很简单:

        Iterable<String> firsts = Iterables.filter(values, equalTo(first)));
        Iterable<String> others = Iterables.filter(values, not(equalTo(first));
        Iterable<String> sorted = FluentIterable.from(firsts).append(others);
        

        这需要对您的集合进行两次迭代,但该算法很简单,并且可能比任何基于 Comparator 的算法都快。如果我必须对这样的实现进行代码审查,我会毫不犹豫地接受它,因为它具有超级可读性/可维护性,而且我 100% 相信它可以按预期工作。

        如果一切都失败了,手动迭代永远不会伤害任何人:

        List<String> firsts = new ArrayList<>();
        List<String> others = new ArrayList<>();
        values.forEach(element -> (first.equal(element) ? firsts : others).add(element));
        Iterable<String> sorted = FluentIterable.from(firsts).append(others);
        

        最后,请注意,由于这些使用 FluentIterable,因此从中获取集合 (ImmutableList) 就像将 .toList() 附加到 FluentIterable 一样简单。

        【讨论】:

          【解决方案7】:

          这听起来也像“排序”排序,其中“第一”的对象具有更高的权重:因此 1-liner 排序将是:

          Ordering.explicit(true, false).onResultOf(first::equals);
             or the more general
          Ordering.natural().reverse().onResultOf(rankFunction);
          

          【讨论】:

            猜你喜欢
            • 2012-06-09
            • 2012-06-11
            • 1970-01-01
            • 2012-09-24
            • 2020-01-07
            • 1970-01-01
            • 2015-10-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多