番石榴:如何从列表和单个元素创建一个显式排序?

时间:2021-06-22 20:48:17

In Guava, given a Collection<E> and an element e of type E that I know is in the collection, I'd like to create a custom Ordering<E> that sorts e first and then the rest of the collection. However, the way to get there seems awfully complicated:

在Guava中,给定一个集合 和E类型的元素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?

What I'm wishing for is either something like this:

我希望的是这样的:

Ordering.explicit(first);

(I'd like this to sort first to the beginning and retain the order of all other elements, but the docs say the resulting Ordering will throw a ClassCastException for elements not explicitly listed.)

(我希望从一开始就进行排序,并保留所有其他元素的顺序,但文档中说,结果排序将为未显式列出的元素抛出ClassCastException。)

Or like this:

或者像这样:

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

(But this would fail because first would be a duplicate value)

(但这会失败,因为第一个是重复的值)

Can anybody come up with a concise way of doing what I want?

有人能想出一个简洁的方法来做我想做的事吗?

BTW, it doesn't have to be an Ordering, it could also be a workaround for creating an Iterable in the specified Order, but again, this is very complicated:

顺便说一下,它不需要是一个排序,它也可以是一个可以在指定的顺序中创建迭代的方法,但是,这是非常复杂的:

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

7 个解决方案

#1


9  

Well, here's one way to do it, but you may not find it much better.

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

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

ComparisonChain文档

Relevant Guava feature request -- please add any details.

相关的Guava功能请求——请添加任何细节。

#2


1  

Perhaps this answer isn't easier/less complicated than what you already have but at least it can be re-used :)

也许这个答案并不比你已经拥有的简单/简单,但至少可以重复使用:

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));

#3


1  

Simply use NullsFirstOrdering as your template and create a ordering that sorts the first element, delegating to another ordering for everything else:

简单地使用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);
  }
}

You then can chain orderings easily: Ordering.from(new ItemFirstComparator("oranges", Ordering.allEqual())).

然后,您可以轻松地进行链式排序:order .from(new ItemFirstComparator, order . allequal()))。

Edit

Changed the code to use Comparator instead of Ordering, the rest stays the same.

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

#4


1  

If you look at the source for com.google.common.collect.ExplicitOrdering, it maintains a map holding the rank of each item, and compare simply compares the ranks. You can do the same thing yourself, but forcing the designated first item's rank to -1, which is before all other items.

如果您查看com.google.common.collec . indeitorder的源代码,它将维护一个包含每个条目的秩的映射,并简单地比较它们的秩。你自己也可以做同样的事情,但是把指定的第一项的级别强制到-1,这是在所有其他项目之前。

If you have a List (as the question's title states), Java 8 streams make building the map moderately convenient:

如果您有一个列表(如问题的标题所示),那么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);

If you have only a Collection (as the question's body states), you'll need to use a for loop to build the map:

如果您只有一个集合(如问题的主体状态),您将需要使用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);

You can turn the Comparator into an Ordering with Ordering.from, as usual.

您可以像往常一样将比较器转换为具有order .from的排序。

#5


1  

This is more convenient and less repetitive if you have more special values:

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

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;
    }
}

You can use it in a comparison chain like

你可以在比较链中使用它

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

It will sort the elements as specified in the PriorityComparator, other elements are reported as being equal.

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

It's also easy to require T to be comparable and use that as the default value instead:

也很容易要求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);
    }
}

#6


0  

If you were considering using explicit ordering to begin with, it kind of assumes your list did not have duplicates. At which point, FluentIterable and .toSet() can make this trivial. Duplicates will be simply ignored (as opposed to erroring out).

如果一开始就考虑使用显式排序,它会假定您的列表没有重复。此时,FluentIterable和. toset()可以使这一点变得微不足道。重复将被简单地忽略(而不是出错)。

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

IMO, your first suggestion is actually not that bad, and also works if your list has duplicate values non-first. If you use a FluentIterable though, it looks better, as you can concat mixed Iterable and elements types:

IMO,您的第一个建议实际上并没有那么糟糕,而且如果您的列表具有非优先级的重复值,那么它也是有效的。如果您使用FluentIterable,它看起来会更好,因为您可以使用混合迭代和元素类型:

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

The catch here though is that if you have more than 1 "first" element, you'll lose the copies.

但这里的问题是,如果有超过1个“first”元素,就会丢失副本。

The fix is trivial though:

不过,这个修正是微不足道的:

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);

This requires iterating your collection twice, but the algorithm is trivial, and probably faster than anything Comparator based. If I had to code review such an implementation, I'd accept this without batting an eye, because it's super readable/maintainable, and I'd have 100% faith it works as intended.

这需要对集合进行两次迭代,但该算法非常简单,可能比基于比较器的任何算法都要快。如果我必须对这样的实现进行代码检查,我就会接受它,而不会对它大惊小怪,因为它是超级可读/可维护的,而且我100%相信它能按预期工作。

If all else fails though, manual iteration never hurt anyone:

如果其他方法都失败了,手动迭代不会伤害任何人:

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);

Finally, note that since these use FluentIterable, getting a collection (ImmutableList) out of these is as trivial as appending .toList() to you FluentIterable.

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

#7


0  

This also sounds like a "ranking" sort, where objects that "are first" have a higher weigh: So the 1-liner ordering would be:

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

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

#1


9  

Well, here's one way to do it, but you may not find it much better.

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

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

ComparisonChain文档

Relevant Guava feature request -- please add any details.

相关的Guava功能请求——请添加任何细节。

#2


1  

Perhaps this answer isn't easier/less complicated than what you already have but at least it can be re-used :)

也许这个答案并不比你已经拥有的简单/简单,但至少可以重复使用:

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));

#3


1  

Simply use NullsFirstOrdering as your template and create a ordering that sorts the first element, delegating to another ordering for everything else:

简单地使用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);
  }
}

You then can chain orderings easily: Ordering.from(new ItemFirstComparator("oranges", Ordering.allEqual())).

然后,您可以轻松地进行链式排序:order .from(new ItemFirstComparator, order . allequal()))。

Edit

Changed the code to use Comparator instead of Ordering, the rest stays the same.

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

#4


1  

If you look at the source for com.google.common.collect.ExplicitOrdering, it maintains a map holding the rank of each item, and compare simply compares the ranks. You can do the same thing yourself, but forcing the designated first item's rank to -1, which is before all other items.

如果您查看com.google.common.collec . indeitorder的源代码,它将维护一个包含每个条目的秩的映射,并简单地比较它们的秩。你自己也可以做同样的事情,但是把指定的第一项的级别强制到-1,这是在所有其他项目之前。

If you have a List (as the question's title states), Java 8 streams make building the map moderately convenient:

如果您有一个列表(如问题的标题所示),那么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);

If you have only a Collection (as the question's body states), you'll need to use a for loop to build the map:

如果您只有一个集合(如问题的主体状态),您将需要使用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);

You can turn the Comparator into an Ordering with Ordering.from, as usual.

您可以像往常一样将比较器转换为具有order .from的排序。

#5


1  

This is more convenient and less repetitive if you have more special values:

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

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;
    }
}

You can use it in a comparison chain like

你可以在比较链中使用它

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

It will sort the elements as specified in the PriorityComparator, other elements are reported as being equal.

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

It's also easy to require T to be comparable and use that as the default value instead:

也很容易要求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);
    }
}

#6


0  

If you were considering using explicit ordering to begin with, it kind of assumes your list did not have duplicates. At which point, FluentIterable and .toSet() can make this trivial. Duplicates will be simply ignored (as opposed to erroring out).

如果一开始就考虑使用显式排序,它会假定您的列表没有重复。此时,FluentIterable和. toset()可以使这一点变得微不足道。重复将被简单地忽略(而不是出错)。

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

IMO, your first suggestion is actually not that bad, and also works if your list has duplicate values non-first. If you use a FluentIterable though, it looks better, as you can concat mixed Iterable and elements types:

IMO,您的第一个建议实际上并没有那么糟糕,而且如果您的列表具有非优先级的重复值,那么它也是有效的。如果您使用FluentIterable,它看起来会更好,因为您可以使用混合迭代和元素类型:

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

The catch here though is that if you have more than 1 "first" element, you'll lose the copies.

但这里的问题是,如果有超过1个“first”元素,就会丢失副本。

The fix is trivial though:

不过,这个修正是微不足道的:

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);

This requires iterating your collection twice, but the algorithm is trivial, and probably faster than anything Comparator based. If I had to code review such an implementation, I'd accept this without batting an eye, because it's super readable/maintainable, and I'd have 100% faith it works as intended.

这需要对集合进行两次迭代,但该算法非常简单,可能比基于比较器的任何算法都要快。如果我必须对这样的实现进行代码检查,我就会接受它,而不会对它大惊小怪,因为它是超级可读/可维护的,而且我100%相信它能按预期工作。

If all else fails though, manual iteration never hurt anyone:

如果其他方法都失败了,手动迭代不会伤害任何人:

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);

Finally, note that since these use FluentIterable, getting a collection (ImmutableList) out of these is as trivial as appending .toList() to you FluentIterable.

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

#7


0  

This also sounds like a "ranking" sort, where objects that "are first" have a higher weigh: So the 1-liner ordering would be:

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

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