C ++ в состоянии - PullRequest
       13

C ++ в состоянии

0 голосов
/ 16 ноября 2018

Как проверить, находится ли значение в созданном наборе «на лету».Я ищу какой-то синтаксический сахар, как у нас в python

if s in set(['first','second','third','fourth']):
    print "It's one of first,second,third,fourth";

Как это можно эффективно сделать в C ++?

Ответы [ 7 ]

0 голосов
/ 19 ноября 2018

Я пытался проверить ваши решения, используя инструмент сравнения, чтобы увидеть результаты. Похоже, что создание набора in-situ снижает производительность, поэтому следует избегать такого решения, которое в других случаях довольно быстро в python (нужно также выполнять тесты производительности в python).

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

enter image description here

Для элементов = 4 набор только на 18% быстрее, чем несколько if. enter image description here

При создании набора в каждом if код набора работает почти на 900% медленнее.

enter image description here

Ссылка на эталонный тест http://quick -bench.com / iqQJWxaRgQGh4Sry2YtfxdSqTio (но он может исчезнуть, поэтому я также добавляю проверенный код

static void withSet(benchmark::State& state) {
  std::vector<std::string> l = {"1","2","9","10"};
  int i = 0;
  std::set<std::string> s = {"1","2","3","4","5","6","7","8","9","10"};

  for (auto _ : state) {
    const std::string t = l[i++%l.size()];
    if (s.find(t) != s.end() ) {
      benchmark::DoNotOptimize(t);
    }
  }
}
// Register the function as a benchmark
BENCHMARK(withSet);

static void withIfs(benchmark::State& state) {
  std::vector<std::string> l = {"1","2","9","10"};
  int i = 0;

  for (auto _ : state) {
    const std::string t = l[i++%l.size()];
    if (t == "1" || t == "2" || t == "3" || t == "4" ||
    t == "5" || t == "6" || t == "7" || t == "8" ||
    t == "9" || t == "10"
    ) {
      benchmark::DoNotOptimize(t);
    }
  }
}
BENCHMARK(withIfs);

--- редактировать ---

Кроме того, я понял, что count () в множестве не равен итерации каждого элемента в множестве. Это результат теста между find ()! = End () и count ()

enter image description here

0 голосов
/ 27 ноября 2018

Я публикую альтернативный подход, который не является верным, но показывает хорошие результаты, как показано ниже. Основная идея такая же с @NathanOliver. Идея состоит в инициализации набора данных {"first", ..., "fourth"} во время компиляции с помощью квалификатора static constexpr, чтобы получить прирост производительности во время выполнения:

  • В C ++ 17 и более, std::string_view доступен, и имеет constexpr ctors. Таким образом, для улучшения производительности естественно применять static constexpr std::array из std::string_view.

  • Кроме того, для такого небольшого набора данных ~ 10 наивный линейный поиск с локальностью кэша смежных массивов иногда превосходит другие алгоритмы. Поэтому я намеренно использую std::find здесь.

Это легко сделать с помощью оператора C ++ 17, если с инициализатором , следующим образом:

if(static constexpr 
   std::array<std::string_view, 4>
   v = {"first", "second", "third", "fourth"};
   std::find(v.cbegin(), v.cend(), s) != v.cend())

Это можно представить в виде следующего компактного и переносимого синтаксического макроса сахара:

#include <tuple>
#include <array>
#include <string_view>
#include <algorithm>

#define IF_CONTAINS(str, ...)                                          \
if(static constexpr                                                    \
   std::array<                                                         \
       std::string_view,                                               \
       std::tuple_size<decltype(std::make_tuple(__VA_ARGS__))>::value> \
   v = {__VA_ARGS__};                                                  \
   std::find(v.cbegin(), v.cend(), str) != v.cend())

Этот макрос IF_CONTAINS отлично работает как оператор if следующим образом:

Live DEMO

std::string s = "first";

IF_CONTAINS(s, "first", "second", "third", "fourth") {
    std::cout << s << " is found" << std::endl;
}

Тест производительности 1

Сначала мы проверим эффективность этого подхода с помощью набора данных выше {"first", "second", "third", "fourth"}. Тесты выполняются с использованием Quick C ++ Benchmark, gcc-8.2, C ++ 17 и O3 . Результаты следующие:

  • const std::string s = "first"; ( Live DEMO )

    в 1,1 раза медленнее, чем наивная реализация.

enter image description here

  • const std::string s = "second"; ( Live DEMO )

    3,7 раза быстрее, чем наивная реализация.

enter image description here

  • const std::string s = "third"; ( Live DEMO )

    1,9 раза быстрее, чем наивная реализация.

enter image description here

  • const std::string s = "fourth"; ( Live DEMO )

    в 3,1 раза быстрее, чем наивная реализация.

enter image description here


Тест производительности 2

Наконец, мы тестируем производительность с набором данных {"1", ...,"10"}. Тестовый код такой же, как и у @pawel_j. Этот тест показывает в 2,7 раза более быстрый результат, чем наивная реализация:

Live DEMO

enter image description here

0 голосов
/ 16 ноября 2018

Как это можно эффективно сделать в C ++?[...] Я ищу что-то, что можно сделать в одной строке - в if

В «одной строке - в if» и «эффективно» - разные вещи.

Если вы действительно хотите, чтобы все в строке, я предлагаю решение 0x5453 (также решение Hiroki), которое использует счетчик.

Я предлагаю альтернативу, основанную на emplace() (но работает и с insert())

if ( ! std::set<std::string>{"first", "second", "third", "fourth"}.emplace(s).second )
   std::cout << "It's one of first,second,third,fourth" << std::endl; 

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

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

Если вы хотите эффективности, я предлагаю решение haccks (сделав также const набор, но я подозреваю, что компилятор все равно оптимизирует)

 std::set<std::string> const numSet {"first", "second", "third", "fourth"}; 

 if ( numSet.find(s) != numSet.cend() )
    std::cout << "It's one of first,second,third,fourth" << std::endl;

или, что лучше, избегайте std::set на всех объявленияхпроверяйте использование свертывания шаблонов, как предлагает NathanOliver (вам нужен C ++ 17, но, возможно, потеря короткого замыкания, на самом деле не сложно адаптировать его в C ++ 14).

0 голосов
/ 16 ноября 2018

Если вы хотите, чтобы это было эффективно, вы не захотите создавать контейнер просто для проверки существования значения.Что мы можем сделать, так это использовать сложенные выражения в C ++ 17 и написать функцию типа

template<typename... Args, std::enable_if_t<std::conjunction_v<std::is_same<const char*, Args>...>, bool> = true>
bool string_exists(std::string to_find, Args... args)
{
    return ((to_find == args) || ...);
}

, которая затем позволяет писать оператор if, например

int main()
{
    std::string element;
    // get value of element somehow
    if (string_exists(element, "1", "2", "3", "4"))
        std::cout << "found";
}

И теперь ни контейнер, ни объекты std::string не создаются.Если вы хотите принимать строковые объекты, вы можете изменить функции на

template<typename... Args, std::enable_if_t<std::conjunction_v<std::is_same<const char*, Args>...> ||
                                            std::conjunction_v<std::is_same<std::string, Args>...>, bool> = true>
bool string_exists(std::string to_find, Args... args)
{
    return ((to_find == args) || ...);
}

int main()
{
    std::string element;
    // get value of element somehow
    if (string_exists(element, some, other, string, variables))
        std::cout << "found";
}

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

0 голосов
/ 16 ноября 2018

Я не вижу ничего плохого в использовании find метода

std::set<std::string>aset {"first", "second", "third", "fourth"}; 
std::string s = "third";

if(aset.find(s) != aset.end()){
    std::cout << "It's one of first,second,third,fourth";
}
0 голосов
/ 16 ноября 2018

Как насчет этого:

std::string s = "first";
if(std::set<std::string>{"first","second","third","fourth"}.count(s)>=1){
    std::cout << s << " is found" << std::endl;
}

Кстати, в C ++ 20 и более, я думаю, std::set::contains предпочтительнее.

0 голосов
/ 16 ноября 2018

Что-то вроде этого должно работать:

#include <iostream>
#include <string>
#include <unordered_set>

void foo(const std::string& s) {
    if (std::unordered_set<std::string>{"1", "2", "3", "4"}.count(s) != 0) {
        std::cout << "It's one of 1,2,3,4\n";
    }
}

В C ++ 17 вы можете позволить руководству по вычетам вступать в игру, чтобы очистить аргумент шаблона (обратите внимание, что мне нужно использовать литералы std::string, чтобы избежатьвывод как const char*):

using namespace std::string_literals;
if (std::unordered_set{"1"s, "2"s, "3"s, "4"s}.count(s) != 0) { ... }
...