tr1::unordered_set объединение и пересечение

Как сделать пересечение и объединение наборов типа tr1::unordered_set в С++? Я не могу найти много упоминаний об этом.

Любая ссылка и код будут высоко оценены. Спасибо большое.

Обновление: я просто догадался, что tr1::unordered_set должен предоставлять функцию для пересечения, объединения, разности... Поскольку это основная операция множеств. Конечно, я могу написать функцию сам, но мне просто интересно, есть ли встроенные функции из tr1. Спасибо большое.


person Community    schedule 22.05.2009    source источник


Ответы (3)


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

Мне приходит в голову, что «наивный» подход к перебору хэша A и поиску каждого элемента в хэше B должен на самом деле дать вам почти оптимальную производительность, поскольку последовательные поиски в хеше B будут выполняться в одном и том же хэш-сегменте (при условии, что оба хэши используют одну и ту же хеш-функцию). Это должно дать вам достойную локальность памяти, даже несмотря на то, что эти сегменты почти наверняка реализованы в виде связанных списков.

Вот некоторый код для unordered_set_difference(), вы можете настроить его, чтобы сделать версии для объединения наборов и различий наборов:

template <typename InIt1, typename InIt2, typename OutIt>
OutIt unordered_set_intersection(InIt1 b1, InIt1 e1, InIt2 b2, InIt2 e2, OutIt out) {
    while (!(b1 == e1)) {
        if (!(std::find(b2, e2, *b1) == e2)) {
            *out = *b1;
            ++out;
        }

        ++b1;
    }

    return out;
}

Предполагая, что у вас есть два unordered_set, x и y, вы можете поместить их пересечение в z, используя:

unordered_set_intersection(
    x.begin(), x.end(),
    y.begin(), y.end(),
    inserter(z, z.begin())
);

В отличие от ответа bdonlan, это будет работать для любого ключа типов и любой комбинации типов контейнеров (хотя использование set_intersection(), конечно, будет быстрее, если исходные контейнеры отсортированы).

ПРИМЕЧАНИЕ. Если ведра заполнены, вероятно, быстрее скопировать каждый хэш в vector, отсортировать их и set_intersection() там, поскольку поиск в ведре, содержащем n элементов, выполняется O(n).

person Community    schedule 22.05.2009
comment
+1 Отличный ответ. Было бы интересно протестировать этот код. На самом деле может быть быстрее (если наборы больше, но не слишком велики) скопировать их в отсортированный набор и запустить std::set_intersection(). - person paxos1977; 22.05.2009
comment
Спасибо ceretullis. Да, я подозреваю, что это было бы быстрее, если бы ведра имели высокую загрузку, хотя в этом случае я подозреваю, что копирование их в векторы и их сортировка будут еще быстрее, просто потому, что меньше накладных расходов на память и не требуется погоня за указателем. (Сортировка вектора и создание отсортированного множества — это O(nlog n).) - person j_random_hacker; 22.05.2009
comment
Я немного обеспокоен. Мы уверены, что std::find будет хорошо работать с итераторами в set? Разве поиск не будет просто перебирать каждый элемент во втором наборе, в то время как мы хотим, чтобы он использовал хэш для зацикливания? Не должна ли функция просто взять ссылку на заданный объект, а затем использовать метод .count? - person Aaron McDaid; 03.12.2011
comment
Хороший вопрос @AaronMcDaid. Я предположил, что контейнеры, позволяющие выполнять быстрый поиск, такие как set и unordered_set, будут предоставлять перегруженные шаблоны для std::find(), но, конечно же, это не упоминается в (старом) стандарте или какой-либо документации, которую я мог найти в Интернете, для которой требуются только базовые InputIterator, и поэтому они будут линейными. время. Интеллектуальный std::find() был бы возможен только в том случае, если бы для быстрого поиска было достаточно знать пару итераторов, а это, вероятно, не так... - person j_random_hacker; 04.12.2011
comment
... Следуя вашему предложению, было бы лучше написать бесплатную функцию шаблона intelligently_find<T>(), которая принимает ссылку на контейнер (вместо пары итераторов), дает ей перегрузки для контейнеров, которые позволяют быстрый поиск, и пусть в противном случае она возвращается к std::find(). - person j_random_hacker; 04.12.2011
comment
-1 к этому ответу: я провел несколько экспериментов, чтобы подтвердить, что std::find работает медленно, и поэтому вместо этого я голосую за ответ @bdonlan. ideone.com/Lr64p (Спасибо, @j_random_hacker) - person Aaron McDaid; 05.12.2011
comment
Вот set_union для unordered_set: std::copy( std::begin(uset2), std::end(uset2), std::inserter( uset1, std::end(uset1) ) ); - person metal; 05.02.2014
comment
Если загруженность корзины велика, у вас проблемы с производительностью не только для объединения/пересечения; может быть, ваша хэш-функция плохая? - person curiousguy; 03.08.2015

В этом нет ничего особенного - для пересечения просто просмотрите каждый элемент одного и убедитесь, что он находится в другом. Для объединения добавьте все элементы из обоих входных наборов.

Например:

void us_isect(std::tr1::unordered_set<int> &out,
        const std::tr1::unordered_set<int> &in1,
        const std::tr1::unordered_set<int> &in2)
{
    out.clear();
    if (in2.size() < in1.size()) {
        us_isect(out, in2, in1);
        return;
    }
    for (std::tr1::unordered_set<int>::const_iterator it = in1.begin(); it != in1.end(); it++)
    {
        if (in2.find(*it) != in2.end())
            out.insert(*it);
    }
}

void us_union(std::tr1::unordered_set<int> &out,
        const std::tr1::unordered_set<int> &in1,
        const std::tr1::unordered_set<int> &in2)
{
    out.clear();
    out.insert(in1.begin(), in1.end());
    out.insert(in2.begin(), in2.end());
}
person Community    schedule 22.05.2009
comment
Вы можете ускорить случай пересечения большого множества с малым, перебирая маленькое множество и проверяя принадлежность к большому. - person Dave; 22.05.2009
comment
В us_union выполнение out = in1; должно быть более эффективным, чем очистка и вставка из диапазона итератора, потому что нет необходимости проверять наличие дубликатов при вставке. В us_isect out.clear() может идти после условия, которое проверяет меньший контейнер, потому что нет необходимости очищать его дважды. Я бы просто использовал in2.count(*it) вместо in2.find(*it) != in2.end() - person Jonathan Wakely; 03.05.2016

на основе предыдущего ответа: версия С++ 11, если набор поддерживает функцию быстрого поиска find() (эффективно перемещаются возвращаемые значения)

/** Intersection and union function for unordered containers which support a fast lookup function find()
 *  Return values are moved by move-semantics, for c++11/c++14 this is efficient, otherwise it results in a copy
 */

namespace unorderedHelpers {

    template<typename UnorderedIn1, typename UnorderedIn2,
             typename UnorderedOut = UnorderedIn1>
    UnorderedOut makeIntersection(const  UnorderedIn1 &in1, const  UnorderedIn2 &in2)
    {
        if (in2.size() < in1.size()) {
            return makeIntersection<UnorderedIn2,UnorderedIn1,UnorderedOut>(in2, in1);
        }

        UnorderedOut out;
        auto e = in2.end();
        for(auto & v : in1)
        {
            if (in2.find(v) != e){
                out.insert(v);
            }
        }
        return out;
    }

    template<typename UnorderedIn1, typename UnorderedIn2,
             typename UnorderedOut = UnorderedIn1>
    UnorderedOut makeUnion(const UnorderedIn1 &in1, const UnorderedIn2 &in2)
    {
        UnorderedOut out;
        out.insert(in1.begin(), in1.end());
        out.insert(in2.begin(), in2.end());
        return out;
    }
}
person Community    schedule 03.08.2015