Мотивация
Я только что переписал около 30 тривиальных парсеров, и мне нужны новые версии, которые ведут себя точно так же, как старые. Поэтому я сохранил их примерные входные файлы и некоторую подпись результатов, полученных старыми анализаторами, для сравнения с новыми. Эта подпись содержит количество успешно обработанных элементов, суммы некоторых хеш-кодов и до 10 псевдослучайно выбранных элементов.
Я подумал, что это хорошая идея, так как равенство хэш-кода суммирует гарантию того, что выходы точно совпадают, а образцы позволяют мне видеть, что неправильно. Я использую только образцы, так как в противном случае он будет очень большой.
Проблема
В принципе, учитывая неупорядоченный набор строк, я хочу получить список из десяти из них, так что, когда коллекция немного изменится, я по-прежнему получаю в основном те же самые образцы в тех же позициях (вход неупорядочен, но выход - это список). Это должно также работать, когда чего-то не хватает, поэтому идеи, подобные принятию 100-го наименьшего элемента, не работают.
ImmutableList<String> selectSome(Collection<String> list) {
if (list.isEmpty()) return ImmutableList.of();
return IntStream.range(1, 20)
.mapToObj(seed -> selectOne(list, seed))
.distinct()
.limit(10)
.collect(ImmutableList.toImmutableList());
}
Итак, я начинаю с чисел от 1 до 20 (так что после distinct
у меня, скорее всего, есть мои 10 образцов), вызовите детерминированную функцию без учета состояния selectOne
(определенную ниже), возвращающую одну строку, максимальную по некоторым смешать критерии, удалить дубликаты, ограничить результат и собрать его с помощью Guava. Все шаги должны быть ИМХО детерминированными и "упорядоченными", но я могу что-то игнорировать. Другая возможность заключается в том, что все мои 30 новых парсеров ошибочны, но это маловероятно, учитывая, что хеши правильны. Более того, результаты анализа выглядят правильно.
String selectOne(Collection<String> list, int seed) {
// some boring mixing, definitely deterministic
for (int i=0; i<10; ++i) {
seed *= 123456789;
seed = Integer.rotateLeft(seed, 16);
}
// ensure seed is odd
seed = 2*seed + 1;
// first element is the candidate result
String result = list.iterator().next();
// the value is the hash code multiplied by the seed
// overflow is fine
int value = seed * result.hashCode();
// looking for s maximizing seed * s.hashCode()
for (final String s : list) {
final int v = seed * s.hashCode();
if (v < value) continue;
// tiebreaking by taking the bigger or smaller s
// this is needed for determinism
if (s.compareTo(result) * seed < 0) continue;
result = s;
value = v;
}
return result;
}
Этот выборка не работает. Я получаю последовательность, подобную
"9224000", "9225000", "4165000", "9200000", "7923000", "8806000", ...
с одним старым парсером и
"9224000", "9225000", "4165000", "3030000", "1731000", "8806000", ...
с новым. Оба результата вполне повторяемы. Для других парсеров это выглядит очень похоже.
Является ли мое использование потоков неправильным? Должен ли я добавить .sequential()
или похожий?
Update
Сортировка коллекции ввода решила проблему:
ImmutableList<String> selectSome(Collection<String> collection) {
final List<String> list = Lists.newArrayList(collection);
Collections.sort(list);
.... as before
}
Что еще не хватает, объясняется, почему.
Объяснение
Как указано в ответах, мой тай-брейк был вскрывающим, так как я пропустил проверку на галстук. Что-то вроде
if (v==value && s.compareTo(result) < 0) continue;
отлично работает.
Я надеюсь, что мой запутанный вопрос может быть, по крайней мере, полезен для тех, кто ищет "последовательную выборку". Это не было связано с Java 8.
Я должен был использовать Guava ComparisonChain
или лучше Java 8 arg max, чтобы избежать моей глупой ошибки:
String selectOne(Collection<String> list, int seed) {
.... as before
final int multiplier = 2*seed + 1;
return list.stream()
.max(Comparator.comparingInt(s -> multiplier * s.hashCode())
.thenComparing(s -> s)) // <--- FOOL-PROOF TIEBREAKER
.get();
}