Каков наилучший способ найти общие элементы из 2 комплектов? - PullRequest
1 голос
/ 07 июня 2019

Недавно у меня было интервью, и мне задали один вопрос.

У меня есть 2 сета, около 1 миллиона записей в каждом. Я должен найти общий элемент в 2 комплектах.

Мой ответ:

Я создам новый пустой набор. И я дал ему решение ниже, но он не был доволен этим. Он сказал, что есть 1 миллион записей, поэтому решение не будет хорошим.

public Set<Integer> commonElements(Set<Integer> s1, Set<Integer> s2) {
    Set<Integer> res = new HashSet<>();
     for (Integer temp : s1) {
        if(s2.contains(temp)) {
            res.add(temp);
        }
     }
     return res;
}

Какой лучший способ решить эту проблему?

Ответы [ 3 ]

2 голосов
/ 07 июня 2019

Прежде всего: чтобы определить пересечение двух наборов, вам абсолютно необходимо просмотреть все записи хотя бы одного из двух наборов (чтобы выяснить, находится ли он в другом наборе).Вокруг нет магии, которая могла бы сказать вам, что менее чем за O (мин (размер (s1), размер (s2)) . Период.

Следующее, что нужно сказать интервьюеру:«1 миллион записей. Вы, должно быть, шутите. Это 2019 год. Любая приличная часть оборудования хрустит двумя миллионами менее чем за секунду».

Затем вы кратко упоминаете, что существуют различные встроенные способырешить эту проблему, а также различные сторонние библиотеки. Но вы избежите ошибки, которую допускают два других ответа: указание на библиотеку, которая вычисляет пересечение, равно вовсе что-то, что вы продаете как «решение» этогоВопрос.

Вы видите, что касается кодирования: интерфейс Java-набора имеет easy решение для этого: s1.retainAll(s2) вычисляет объединение двух наборов, так как удаляет все элементы из s1, которыене в s2.

Очевидно, вы должны упомянуть в интервью, что это изменит s1.

В случае, если требование не изменить s1 илиs2, ваше решениежизнеспособный путь, и нет ничего, что можно сделать с затратами времени выполнения.Если это все, вы можете вызвать size() для обоих наборов и итерировать тот, который имеет меньше записей.

В качестве альтернативы, вы можете сделать

Set<String> result = new HashSet<>(s1);
return result.retain(s2);

, но вВ конце вы должны перебрать один набор и для каждого элемента определить, находится ли он во втором наборе.

Но, конечно, реальный ответ на такие вопросы всегда всегда показывает интервьюеру, что вы в состоянии разбить проблему на различные ее аспекты.Вы обрисовываете основные ограничения, обрисовываете различные решения и обсуждаете их плюсы и минусы.Я, например, ожидал, что вы сядете и, возможно, напишите такую ​​программу:

public class Numbers {    
    private final static int numberOfEntries = 20_000_000;
    private final static int maxRandom = numberOfEntries;

    private Set<Integer> s1;
    private Set<Integer> s2;

    @Before
    public void setUp() throws Exception {
        Random random = new Random(42);
        s1 = fillWithRandomEntries(random, numberOfEntries);
        s2 = fillWithRandomEntries(random, numberOfEntries);
    }

    private static Set<Integer> fillWithRandomEntries(Random random, int entries) {
        Set<Integer> rv = new HashSet<>();
        for (int i = 0; i < entries; i++) {
            rv.add(random.nextInt(maxRandom));
        }
        return rv;
    }

    @Test
    public void classic() {
        long start = System.currentTimeMillis();
        HashSet<Integer> intersection = new HashSet<>();
          s1.forEach((i) -> {
           if (s2.contains(i))
             intersection.add(i);
        });
        long end = System.currentTimeMillis();
        System.out.println("foreach duration: " + (end-start) + " ms");
        System.out.println("intersection.size() = " + intersection.size());
    }


    @Test
    public void retainAll() {
        long start = System.currentTimeMillis();
        s1.retainAll(s2);
        long end = System.currentTimeMillis();
        System.out.println("Retain all duration: " + (end-start) + " ms");
        System.out.println("intersection.size() = " + s1.size());
    }

    @Test
    public void streams() {
        long start = System.currentTimeMillis();
        Set<Integer> intersection = s1.stream().filter(i -> s2.contains(i)).collect(Collectors.toSet());
        long end = System.currentTimeMillis();
        System.out.println("streaming: " + (end-start) + " ms");
        System.out.println("intersection.size() = " + intersection.size());
    }

    @Test
    public void parallelStreams() {
        long start = System.currentTimeMillis();
        Set<Integer> intersection = s1.parallelStream().filter(i -> s2.contains(i)).collect(Collectors.toSet());
        long end = System.currentTimeMillis();
        System.out.println("parallel streaming: " + (end-start) + " ms");
        System.out.println("intersection.size() = " + intersection.size());
    }
}

Первое замечание: я решил запустить с 20 миллионами записей.Я начал с 2 миллионов, но все три теста будут работать намного ниже 500 мс.Вот распечатка для 20 миллионов на моем Mac Book Pro:

foreach duration: 9304 ms
intersection.size() = 7990888 
streaming: 9356 ms
intersection.size() = 7990888
Retain all duration: 685 ms
intersection.size() = 7990888
parallel streaming: 6998 ms
intersection.size() = 7990888

Как и ожидалось: все пересечения имеют одинаковый размер (потому что я посеял генератор случайных чисел, чтобы получить сопоставимые результаты).

И сюрприз: модификация s1 на месте ... безусловно, самый дешевый вариант.Скорость потоковой передачи превосходит коэффициент , равный 10. Также обратите внимание: параллельная потоковая передача здесь происходит быстрее.При работе с 1 миллионом записей поток последовательный был быстрее.

Поэтому я изначально упомянул упоминание "1 миллион записей не является проблемой производительности".Это очень важное утверждение, поскольку оно говорит интервьюеру, что вы не из тех людей, которые тратят часы на микрооптимизацию несуществующих проблем с производительностью.

1 голос
/ 07 июня 2019

вы можете использовать

CollectionUtils

это от apache

CollectionUtils.intersection(Collection a,Collection b)
0 голосов
/ 07 июня 2019
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...