随机/随机比较器

Whi*_*cal 2 java sorting collections java-8

有没有办法模拟Collections.shuffle的行为而没有比较器易受排序算法实现的影响,结果是安全的?

我的意思是不打破可比合同等.

Hol*_*ger 11

在不违反合同的情况下实施真正的"洗牌比较器"是不可能的.Comparator合同的一个基本方面是结果是可重复的,因此Comparator必须修复特定实例的排序.

当然,您可以使用混洗操作预先初始化该固定排序,并创建一个比较器,该比较器将精确地建立此排序.例如

List<ElementType> ordering=new ArrayList<>(list);
Collections.shuffle(ordering);

list.sort(Comparator.comparingInt(ordering::indexOf));
Run Code Online (Sandbox Code Playgroud)

虽然这有点无意义.很明显,这个比较器不能用于包含不在ordering列表中的元素的集合.


或者,您可以使用首先没有排序的值的稳定属性作为排序条件,例如哈希码.这可以通过稳定但随机化的变换来增强,例如

public static Comparator<String> randomOrder() {
    ThreadLocalRandom r = ThreadLocalRandom.current();
    int x = r.nextInt(), y = r.nextInt();
    boolean b = r.nextBoolean();
    return Comparator.comparingInt((String s)->s.hashCode()^x)
     .thenComparingInt(s->s.length()^y)
     .thenComparing(b? Comparator.naturalOrder(): Comparator.reverseOrder());
}
Run Code Online (Sandbox Code Playgroud)

 

List<String> list=Arrays.asList("hello", "now", "shuffle", "this", "!");
list.sort(randomOrder());
System.out.println(list);
list.sort(randomOrder());
System.out.println(list);
Run Code Online (Sandbox Code Playgroud)

关键点是每个Comparator实例代表一个随机选择但固定的顺序,我们创建一个新Comparator实例来请求不同的排序.因此,不Comparator违反合同.

请注意,这Comparator看起来有点复杂,因为它必须关注可能的哈希冲突.它将使用length属性(也是随机的)然后对于String具有相同哈希码和长度的s,它将简单地回退到自然或逆序,这不太可能引人注意,因为它只影响这些不常见的对的关系.

如果为没有冲突的值(例如Integer实例)创建这样的比较器或覆盖定义相等的值的所有属性(例如,两者,xya Point),则比较器看起来会更简单.

  • @shmosel 好点。滥用比较器进行洗牌总是有严重的限制(例如从不处理重复项)。我借此机会改进了比较器,另请参阅 https://ideone.com/96TWyv 它现在产生所有排列,但仍然不如真正的洗牌算法,因为排列不具有相等的可能性,因此它可以采取在更大的列表中看到所有之前,确实进行了大量的洗牌。对于快速而肮脏的一次性操作可能就足够了,但一般来说,我从不建议滥用某种排序来进行洗牌。 (3认同)

ben*_*nez 5

当元素的类型未知时,与上一个答案更通用:

public static <T> Comparator<T> shuffle() {
    final Map<Object, UUID> uniqueIds = new IdentityHashMap<>();
    return Comparator.comparing(e -> uniqueIds.computeIfAbsent(e, k -> UUID.randomUUID()));
}
Run Code Online (Sandbox Code Playgroud)

也可以在流中使用:

list.stream().sorted(Streams.shuffle()).collect(Collectors.toList())
Run Code Online (Sandbox Code Playgroud)

有可能是碰撞莫名其妙,所以它可以使用扩展HashSetUUID检查这种情况下,

  • 好的。请注意,您可以将其简化为`Map&lt;Object, UUID&gt; uniqueIds = new IdentityHashMap&lt;&gt;(); return Comparator.comparing(e -&gt; uniqueIds.computeIfAbsent(e, k -&gt; UUID.randomUUID()));`。即使解决冲突也不是那么难,因为它可以类似地完成:`Map&lt;Object, UUID&gt; randomIds = new IdentityHashMap&lt;&gt;(); Map&lt;Object, Integer&gt; uniqueIds = new IdentityHashMap&lt;&gt;(); return Comparator.comparing((T e) -&gt; randomIds.computeIfAbsent(e, k -&gt; UUID.randomUUID())) .thenComparing(e -&gt; uniqueIds.computeIfAbsent(e, k -&gt; uniqueIds.size())); ` (2认同)