Сочетание этого ответа с идеями этой темы , в частности этого ответа , чтобы создать эффективное, но удобочитаемое решение, вы можете использовать
static boolean unorderedEquals(Collection<?> coll1, Collection<?> coll2) {
if(coll1.size() != coll2.size()) return false;
Map<Object, Integer> freq = new HashMap<>();
for(Object o: coll1) freq.merge(o, 1, Integer::sum);
for(Object o: coll2)
if(freq.merge(o, -1, Integer::sum) < 0) return false;
return true;
}
Первый цикл создает карту частот, как в связанном ответе, но вместо построения второй карты, чтобы выполнить дорогостоящее сравнение, второй цикл уменьшает счетчики для каждого вхождения, возвращаясь немедленно, если счет стал отрицательным.Метод merge
плавно обрабатывает случай отсутствия ключей.
Поскольку в начале метода было проверено, что оба списка имеют одинаковый размер, после увеличения и уменьшения общий счет должен быть равен нулю.,Поскольку мы доказали, что нет отрицательных чисел, поскольку мы сразу же вернули их, также не может быть положительных ненулевых значений.Таким образом, мы можем вернуть true
после второго цикла без дальнейших проверок.
Поддержка произвольных Iterable
с, которые отличаются от Collection
необязательным использованием метода size()
, немного сложнее, так кактогда мы не можем выполнить предварительную проверку и, следовательно, должны поддерживать счет:
static boolean unorderedEquals(Iterable<?> iter1, Iterable<?> iter2) {
Map<Object, Integer> freq = new HashMap<>();
int size = 0;
for(Object o: iter1) {
freq.merge(o, 1, Integer::sum);
size++;
}
for(Object o: iter2)
if(--size < 0 || freq.merge(o, -1, Integer::sum) < 0) return false;
return size == 0;
}
Если мы хотим избежать накладных расходов на бокс, мы должны прибегнуть к изменчивому значению для карты, например
static boolean unorderedEquals(Collection<?> coll1, Collection<?> coll2) {
if(coll1.size() != coll2.size()) return false;
Map<Object, int[]> freq = new HashMap<>();
for(Object o: coll1) freq.computeIfAbsent(o, x -> new int[1])[0]++;
int[] absent = { 0 };
for(Object o: coll2) if(freq.getOrDefault(o, absent)[0]-- == 0) return false;
return true;
}
Но я не думаю, что он окупится.Для небольшого числа вхождений бокс будет повторно использовать экземпляры Integer
, тогда как нам нужен отдельный объект int[]
для каждого отдельного элемента при использовании изменяемых значений.
Но использование compute
может быть интересно для Iterable
решение, при использовании его как
static boolean unorderedEquals(Iterable<?> coll1, Iterable<?> coll2) {
Map<Object, int[]> freq = new HashMap<>();
for(Object o: coll1) freq.computeIfAbsent(o, x -> new int[1])[0]++;
int[] absent = {};
for(Object o: coll2)
if(freq.compute(o, (key,c) -> c == null || c[0] == 0? absent:
--c[0] == 0? null: c) == absent) return false;
return freq.isEmpty();
}
, которое удаляет записи с карты, когда их количество достигает нуля, поэтому нам нужно только проверить карту на пустоту в конце.