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

Сравнение трюков в С++

Класс:

class foo{
public:
    int data;
};

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

Конечно, я могу написать if(data==num1|| data == num2|| data ==num3.....), но, честно говоря, я чувствую себя больным, когда пишу data == каждый раз, когда сравниваю его с числом.

Итак, я надеюсь, что смогу написать что-то вроде этого:

if(data is equal to one of these(num1,num2,num3,num4,num5...))
    return true;
else
    return false;

Я хочу реализовать этот оператор, data is equal to one of these(num1, num2, num3, num4, num5...)

Вот мой подход:

#include <stdarg.h>
bool is_equal_to_one_of_these(int count,...){
    int i;
    bool equal = false;
    va_list arg_ptr;
    va_start(arg_prt,count);
    for(int x=0;x<count;x++){
        i = va_arg(arg_ptr,int);
        if( i == data ){
            equal = true;
            break;
        }
    }
    va_end(arg_ptr);
    return equal;
}

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

Есть ли у кого-то лучшее представление?

4b9b3361

Ответ 1

Простой способ

Самый простой способ - написать оболочку функции-члена, которая называется in() вокруг std::find, с парой итераторов для поиска данных. Я написал простую функцию-член template<class It> in(It first, It last) для этого

template<class It>
bool in(It first, It last) const
{
    return std::find(first, last, data) != last;
}

Если у вас нет доступа к источнику foo, вы можете записать не-членные функции подписи template<class T> bool in(foo const&, std::initializer_list<T>) и т.д. и называть его как

in(f, {1, 2, 3 });

Трудный путь

Но отпустите полностью за борт: просто добавьте еще две перегрузки public:

  • один принимает параметр std::initializer_list, который вызывает предыдущий стераторами begin() и end() соответствующего аргумента списка инициализатора.
  • один для произвольного контейнера в качестве ввода, который будет немного терять диспетчеризацию еще две private перегрузки хедера detail_in():
    • одна перегрузка делает трюк SFINAE с возвращаемым типом возврата decltype(c.find(data), bool()), который будет удален из набора перегрузки, если в рассматриваемом контейнере c нет функции-члена find(), и возвращается bool в противном случае ( это достигается за счет злоупотребления запятой внутри decltype)
    • одна резервная перегрузка, которая просто принимает итераторы begin() и end() и передает на исходный in() два итератора

Поскольку теги для хелпера detail_in() образуют иерархию наследования (так же, как и стандартные теги итератора), первая перегрузка будет соответствовать ассоциативным контейнерам std::set и std::unordered_set и их нескольким кузенам. Все остальные контейнеры, включая C-массивы, std::array, std::vector и std::list, будут соответствовать второй перегрузке.

#include <algorithm>
#include <array>
#include <initializer_list>
#include <type_traits>
#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>

class foo
{
public:
    int data;

    template<class It>
    bool in(It first, It last) const
    {
        std::cout << "iterator overload: ";
        return std::find(first, last, data) != last;
    }

    template<class T>
    bool in(std::initializer_list<T> il) const
    {
        std::cout << "initializer_list overload: ";
        return in(begin(il), end(il));
    }

    template<class Container>
    bool in(Container const& c) const 
    {
        std::cout << "container overload: ";
        return detail_in(c, associative_container_tag{});    
    }

private:
    struct sequence_container_tag {};
    struct associative_container_tag: sequence_container_tag {};

    template<class AssociativeContainer>
    auto detail_in(AssociativeContainer const& c, associative_container_tag) const 
    -> decltype(c.find(data), bool())
    {
        std::cout << "associative overload: ";
        return c.find(data) != end(c);    
    }

    template<class SequenceContainer> 
    bool detail_in(SequenceContainer const& c, sequence_container_tag) const
    {
        std::cout << "sequence overload: ";
        using std::begin; using std::end;
        return in(begin(c), end(c));    
    }
};

int main()
{
    foo f{1};
    int a1[] = { 1, 2, 3};
    int a2[] = { 2, 3, 4};

    std::cout << f.in({1, 2, 3}) << "\n";
    std::cout << f.in({2, 3, 4}) << "\n";

    std::cout << f.in(std::begin(a1), std::end(a1)) << "\n";
    std::cout << f.in(std::begin(a2), std::end(a2)) << "\n";

    std::cout << f.in(a1) << "\n";
    std::cout << f.in(a2) << "\n";

    std::cout << f.in(std::array<int, 3>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::array<int, 3>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::vector<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::vector<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::set<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::unordered_set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::unordered_set<int>{ 2, 3, 4 }) << "\n";    
}

Живой пример, который - для всех возможных контейнеров - печатает 1 и 0 для оба набора чисел.

Варианты использования для перегрузки std::initializer_list предназначены для тестирования членов для небольших наборов чисел, которые вы явно выписываете при вызове кода. Он имеет сложность O(N), но избегает любых распределений кучи.

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

Но если у вас есть только контейнер последовательности с числами, вы можете также бросить это в класс, и он будет счастливо вычислять членство для вас в O(N) времени.

Ответ 2

Есть много способов сделать это с помощью STL.

Если у вас есть невероятно большое количество элементов, и вы хотите проверить, является ли ваш данный элемент членом этого набора, используйте set или unordered_set. Они позволяют вам проверять членство в log n и постоянном времени соответственно.

Если вы сохраните элементы в отсортированном массиве, то binary_search также проверит членство в log n время.

Для небольших массивов linear search может, однако, преформировать значительно быстрее (поскольку нет ветвления). Линейный поиск может даже сделать 3-8 сравнений за время, когда бинарный поиск "прыгает" . Это сообщение в блоге предполагает наличие точки безубыточности примерно на 64 элементах, ниже которых линейный поиск может быть быстрее, хотя это, очевидно, зависит на реализацию STL, оптимизацию компилятора и предсказание ветвей вашей архитектуры.

Ответ 3

Если data действительно является интегральным или перечислимым типом, вы можете использовать switch:

switch (data) {
  case 1:
  case 2:
  case 2000:
  case 6000:
  case /* whatever other values you want */:
    act_on_the_group();
    break;
  default:
    act_on_not_the_group();
    break;
}

Ответ 4

Ответы с использованием std::initializer_list прекрасны, но я хочу добавить еще одно возможное решение, которое именно то, что вы пытаетесь использовать с этим вариатором C в безопасном и современном виде: Использование С++ 11 variadic шаблоны

template<typename... NUMBERS>
bool any_equal( const foo& f , NUMBERS&&... numbers )
{
    auto unpacked = { numbers... };

    return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
           != std::end( unpacked );
};

Конечно, это работает только в том случае, если все переданные значения имеют один и тот же тип. Если список инициализаторов unpacked не выводится и не инициализируется.

Тогда:

bool equals = any_equal( f , 1,2,3,4,5 );

РЕДАКТИРОВАТЬ: Вот метафайла are_same, чтобы все переданные числа имели один и тот же тип:

template<typename HEAD , typename... TAIL>
struct are_same : public and_op<std::is_same<HEAD,TAIL>::value...>
{};

Где and_op выполняет n-ary логическое и:

template<bool HEAD , bool... TAIL>
struct and_op : public std::integral_constant<bool,HEAD && and_op<TAIL...>::value>
{};

template<>
struct and_op<> : public std::true_type
{};

Это позволяет принудительно использовать номера одного и того же типа:

template<typename... NUMBERS>
bool any_equal( const foo& f , NUMBERS&&... numbers )
{
    static_assert( all_same<NUMBERS...>::value , 
                   "ERROR: You should use numbers of the same type" );


    auto unpacked = { numbers... };

    return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
           != std::end( unpacked );
};

Ответ 5

Любая оптимизация будет зависеть от свойств сравниваемого набора чисел.

Если существует определенная верхняя граница, вы можете использовать std::bitset. Тестирование членства (то есть индексация в битете, который ведет себя как массив), - это O (1), а также несколько быстрых инструкций. Это часто лучшее решение до пределов в сотни, хотя в зависимости от приложения миллионы могут быть практичными.

Ответ 6

Это не очень, но это должно работать:

class foo {
    bool equals(int a) { return a == data; }
    bool equals(int a, int b) { return (a == data) || (b == data); }
    bool equals(int a, int b, int c) {...}     
    bool equals(int a, int b, int c, int d) {...} 
private:
    int data; 
}

И так далее. Это даст вам точный синтаксис, который вам нужен. Но если вы после полностью переменного количества аргументов, то может быть как вектор, так и std:: initalizer:

Смотрите: http://en.cppreference.com/w/cpp/utility/initializer_list

Этот пример показывает это в действии:

#include <assert.h>
#include <initializer_list>

class foo {
public:
        foo(int d) : data(d) {}
        bool equals_one_of(std::initializer_list<int> options) {
                for (auto o: options) {
                        if (o == data) return true;
                }
                return false;
        }
private:
        int data;
};

int main() {
        foo f(10);
        assert(f.equals_one_of({1,3,5,7,8,10,14}));
        assert(!f.equals_one_of({3,6,14}));
        return 0;
}

Ответ 7

У кого-нибудь есть идея? спасибо за обмен!

Для этого существует стандартный альгитрит:

using std::vector; // & std::begin && std::end

// if(data is equal to one of these(1,2,3,4,5,6))
/* maybe static const */vector<int> criteria{ 1, 2, 3, 4, 5, 6 };
return end(criteria) != std::find(begin(criteria), end(criteria), data);

Изменить: (все в одном месте):

bool is_equal_to_one_of_these(int data, const std::vector<int>& criteria)
{
    using std::end; using std::begin; using std::find;
    return end(criteria) != find(begin(criteria), end(criteria), data);
}

auto data_matches = is_equal_to_one_of_these(data, {1, 2, 3, 4, 5, 6});

Edit:

Я предпочитаю интерфейс в терминах вектора вместо списка инициализаторов, потому что он более мощный:

std:vector<int> v = make_candidate_values_elsewhere();
auto data_matches = is_equal_to_one_of_these(data, v);

Интерфейс (с помощью вектора) не ограничивает вас для определения значений, где вы вызываете is_equal_to_one_of_these.

Ответ 8

set - хороший вариант, но если вы действительно хотите сворачивать свой собственный, initializer_list является убедительным:

bool is_in( int val, initializer_list<int> lst )
{
    for( auto i : lst )
        if( i == val ) return true;
    return false;
}

использование тривиально:

is_in( x, { 3, 5, 7 } ) ;

это O (n) ты, установить/неупорядочен быстрее

Ответ 9

Я бы рекомендовал использовать стандартный контейнер, например std::vector, но это все равно подразумевает линейную сложность с наихудшим временем выполнения O(N).

class Foo{
public:
    int data;
    bool is_equal_to_one_of_these(const std::vector<int>& arguments){
        bool matched = false;
        for(int arg : arguments){ //if you are not using C++11: for(int i = 0; i < arguments.size(); i++){
            if( arg == data ){ //if you are not using C++11: if(arguments[i] == data){
                matched = true;
            }
        }
        return matched;
    }
};

std::vector<int> exampleRange{ {1,2,3,4,5} };
Foo f;
f.data = 3;
std::cout << f.is_equal_to_one_of_these(exampleRange); // prints "true"

Ответ 10

Если данные, num1,.. num6 находятся между 0 и 31, вы можете использовать

int match = ((1<<num1) | (1<<num2) | ... | (1 << num6));
if( ( (1 << data) & match ) != 0 ) ...

Если num1 to num6 являются константами, компилятор будет вычислять совпадение во время компиляции.