Подтвердить что ты не робот

Детерминизм потоков Java 8

Мотивация

Я только что переписал около 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();
}
4b9b3361

Ответ 1

В selectOne вы просто хотите выбрать String s с максимальным рангом value = seed * s.hashCode(); для данного параметра seed.

Проблема заключается в линии "tiebreaking": if (s.compareTo(result) * seed < 0) continue;

Это не детерминированно - для разных порядков элементов он пропускает разные элементы из проверки, и, таким образом, изменение в порядке элементов приводит к изменению результата.

Удалите tiebreaking if, и результат будет нечувствителен к порядку элементов в списке ввода.

Ответ 2

Ошибка в том, что ваш тай-брейк на самом деле не нарушает ничью. Мы должны выбрать s, когда v > value, но вместо этого мы возвращаемся к compareTo(). Это нарушает симметрию сравнения, делая ваш алгоритм зависимым от порядка встреч.

В качестве бонуса, здесь простой тестовый пример для воспроизведения ошибки:

System.out.println(selectOne(Arrays.asList("1", "2"), 4));  // 1
System.out.println(selectOne(Arrays.asList("2", "1"), 4));  // 2