Проверить, содержит ли unordered_set все элементы в другом unordered_set - C++

Я новичок в C++, и меня попросили преобразовать программу Java в C++. Я пытаюсь написать метод для проверки того, что все элементы в unordered_set существуют в другом unordered_set. Я нашел приведенный ниже пример с использованием hash_set, но hash_set устарел, и сейчас рекомендуется использовать unordered_set.

// returns true if one contains all elements in two
bool SpecSet::containsAll(hash_set<Species*> one, hash_set<Species*> two) {
   sort(one.begin(), one.end());
   sort(two.begin(), two.end());
   return includes(one.begin(), one.end(), two.begin(), two.end());
}

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

bool SpecSet::containsAll(unordered_set<Species*> one, unordered_set<Species*> two) {

   return ?;
}

Я был бы очень признателен за помощь в подходе к эффективному выполнению этого.

РЕДАКТИРОВАТЬ: я думаю, это сработает. Кажется, нет более эффективного способа, чем перебрать все пополам.

bool SpecSet::containsAll(unordered_set<Species*> one, unordered_set<Species*> two) {
   if(two.size() > one.size())
   {
      return false;
   }

   for(Species *species : two)
   {
      if(one.find(species) == one.end())
      {
         return false;
      }
   }
   return true;
}

person Steve W    schedule 17.01.2018    source источник
comment
связанные   -  person Caleth    schedule 17.01.2018
comment
Проверять каждый элемент по одному? (возможно, после сравнения размеров, чтобы исключить очевидные случаи «нет»)   -  person Marc Glisse    schedule 17.01.2018
comment
Спасибо Калет/Марк. Ваши советы привели меня к написанию вышеуказанного метода. Я думаю, это лучшее, что я могу сделать?   -  person Steve W    schedule 17.01.2018
comment
Я не уверен на 100%, но это кажется лучшим способом. Сложность линейна, так как find является постоянным временем.   -  person ArmanHunanyan    schedule 17.01.2018
comment
Вы, вероятно, не хотите передавать свои наборы по значению, если они слишком велики для преобразования в упорядоченные наборы.   -  person Toby Speight    schedule 17.01.2018
comment
Нет волшебных кнопок, которые можно нажать и получить мгновенный ответ, содержит ли один unordered_set все значения из другого. Руководство, по одному, поиск - единственный способ сделать это; исключая специальные случаи использования в порядке наборов небольших значений, и в этом случае некоторые дурачества могут быть выполнены с битовыми масками и т. д. Кроме того, это кажется быстрее, чем на основе сортировки, которая выглядит как O(n log n), в то время как ручной поиск кажется быть O(n). Просто потому, что что-то делается с помощью меньшего количества кода, не обязательно означает, что это быстрее.   -  person Sam Varshavchik    schedule 17.01.2018
comment
Различные входные данные могут привести к разным временным затратам на такую ​​задачу. Можно перебирать все элементы и рекомендуется использовать тривиальный алгоритм, который вы описали сами.   -  person AntiMoron    schedule 17.01.2018
comment
Это не связано с вашим вопросом, но я хотел бы упомянуть об этом: во-первых, избегайте использования необработанных указателей (mytype*) и вместо этого используйте интеллектуальные указатели en.cppreference.com/book/intro/smart_pointers. Они стоят больше (ну, почти ничего) для манипулирования необработанными указателями, но предотвратят много утечек памяти. Во-вторых, вы можете использовать ключевое слово auto для вывода типа выражения присваивания, что особенно интересно при выполнении цикла: for (auto& specie : species ){...}) и облегчить чтение вашего кода.   -  person Felix Bertoni    schedule 26.11.2019


Ответы (2)


Для несортированных коллекций нет более быстрого алгоритма, чем перебирать меньшую коллекцию, проверяя, является ли каждый элемент частью большей. Это будет естественным образом масштабироваться как O(n), где n — размер предполагаемого подмножества, поскольку мы выполняем операцию поиска O(1) n раз.


Вот демонстрационный код с тестами:

#include <unordered_set>

template <typename T>
bool is_subset_of(const std::unordered_set<T>& a, const std::unordered_set<T>& b)
{
    // return true if all members of a are also in b
    if (a.size() > b.size())
        return false;

    auto const not_found = b.end();
    for (auto const& element: a)
        if (b.find(element) == not_found)
            return false;

    return true;
}
int main()
{
    const std::unordered_set<int> empty{ };
    const std::unordered_set<int> small{ 1, 2, 3 };
    const std::unordered_set<int> large{ 0, 1, 2, 3, 4 };
    const std::unordered_set<int> other{ 0, 1, 2, 3, 9 };

    return 0
        +  is_subset_of(small, empty) // small ⊄ ∅
        + !is_subset_of(empty, small) // ∅ ⊂ small
        +  is_subset_of(large, small) // large ⊄ small
        + !is_subset_of(small, large) // small ⊂ large
        +  is_subset_of(large, other) // large ⊄ other
        +  is_subset_of(other, large) // other ⊄ large
        + !is_subset_of(empty, empty) // ∅ ⊂ ∅
        + !is_subset_of(large, large) // x ⊂ x, ∀x
        ;
}

Эквивалент, использующий стандартный алгоритм вместо написания явного цикла:

#include <algorithm>
#include <unordered_set>

template <typename T>
bool is_subset_of(const std::unordered_set<T>& a, const std::unordered_set<T>& b)
{
    // return true if all members of a are also in b
    auto const is_in_b = [&b](auto const& x){ return b.find(x) != b.end(); };

    return a.size() <= b.size() && std::all_of(a.begin(), a.end(), is_in_b);
}

(очевидно, используя один и тот же main() для тестов)


Обратите внимание, что мы передаем наборы по ссылке, а не по значению, так как вы указали, что наборы слишком велики для их копирования и сортировки.

person Toby Speight    schedule 17.01.2018

Отказ от ответственности: это не самый эффективный подход. Это попытка решения, которое было бы таким же универсальным и гибким, как std::includes, но при этом поддерживало бы неупорядоченные диапазоны итераторов. Он не ограничен std::unordered_set и должен работать для любого другого контейнера, например, std::vector или std::list.


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

Глядя на возможные реализации std::includes, можно реализовать версию для неупорядоченных диапазонов. Например так:

template<class InputIt1, class InputIt2>
bool includes_unordered(
    InputIt1 first1, InputIt1 last1,
    InputIt2 first2, InputIt2 last2)
{
    for (; first2 != last2; ++first2)
    {
        InputIt1 it1;
        for (it1 = first1; it1 != last1; ++it1)
        {
            if(*first2 == *it1)
                break;
        }
        if (it1 == last1)
            return false;
    }
    return true;
}

Примечание. оптимизация сравнения размеров контейнеров не выполняется для поддержки контейнеров неуникальных объектов. Но при необходимости это можно сделать с помощью std::distance.

А вот версия с оператором эквивалентности:

template<class InputIt1, class InputIt2, class Equivalence>
bool includes_unordered(
    InputIt1 first1, InputIt1 last1,
    InputIt2 first2, InputIt2 last2,
    Equivalence equiv)
{
    for (; first2 != last2; ++first2)
    {
        InputIt1 it1;
        for (it1 = first1; it1 != last1; ++it1)
        {
            if(equiv(*first2, *it1))
                break;
        }
        if (it1 == last1)
            return false;
    }
    return true;
}

Небольшой живой пример

Тогда includes_unordered можно использовать так же, как std::includes.

person AMA    schedule 17.01.2018
comment
Оба диапазона должны быть отсортированы для использования с std::includes - person ArmanHunanyan; 17.01.2018
comment
Это не сработает, потому что диапазоны должны быть отсортированы. unordered_set не отсортировано. - person user2807083; 17.01.2018
comment
Конечно, вы правы, ребята, я быстро рисовал. Я обновил ответ. - person AMA; 17.01.2018
comment
Похоже, это алгоритм O(m_×_n), где m и n — размеры двух наборов. Хотя это будет работать, это далеко не эффективно. - person Toby Speight; 17.01.2018
comment
@TobySpeight правильно, ваш ответ более эффективен. Это просто мой взгляд на то, что было бы таким же универсальным и гибким, как std::includes. Я добавил короткий отказ от ответственности. - person AMA; 17.01.2018