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

Есть ли более короткий способ написать сложные условия "если"?

Просто вместо of:

if  ( ch == 'A' || ch == 'B' || ch == 'C' || .....

Например, чтобы сделать это как:

if  ( ch == 'A', 'B', 'C', ...

существует даже более короткий способ суммирования условий?

4b9b3361

Ответ 1

strchr() можно использовать, чтобы увидеть, находится ли символ в списке.

const char* list = "ABCXZ";
if (strchr(list, ch)) {
  // 'ch' is 'A', 'B', 'C', 'X', or 'Z'
}

Ответ 2

В этом случае вы можете использовать switch:

switch (ch) {
case 'A':
case 'B':
case 'C':
    // do something
    break;
case 'D':
case 'E':
case 'F':
    // do something else
    break;
...
}

Хотя это немного более подробно, чем при использовании strchr, он не включает никаких вызовов функций. Он также работает как для C, так и для С++.

Обратите внимание, что альтернативный синтаксис, который вы предложили, не будет работать так, как вы могли ожидать, из-за использования оператора запятой:

if  ( ch == 'A', 'B', 'C', 'D', 'E', 'F' )

Это сначала сравнивает ch с 'A', а затем отбрасывает результат. Затем 'B' оценивается и отбрасывается, затем 'C' и т.д. До тех пор, пока 'F' не будет оценен. Тогда 'F' становится значением условного. Поскольку любое ненулевое значение, вычисляемое в true в булевом контексте (и 'F' отличное от нуля), тогда указанное выражение всегда будет истинным.

Ответ 3

Шаблоны позволяют нам выразить себя таким образом:

if (range("A-F").contains(ch)) { ... }

Для этого требуется небольшая сантехника, которую вы можете поместить в библиотеку.

Это действительно делает невероятно эффективным (по крайней мере, на gcc и clang).

#include <cstdint>
#include <tuple>
#include <utility>
#include <iostream>

namespace detail {
    template<class T>
    struct range
    {
        constexpr range(T first, T last)
        : _begin(first), _end(last)
        {}

        constexpr T begin() const { return _begin; }
        constexpr T end() const { return _end; }

        template<class U>
        constexpr bool contains(const U& u) const
        {
            return _begin <= u and u <= _end;
        }

    private:
        T _begin;
        T _end;
    };

    template<class...Ranges>
    struct ranges
    {
        constexpr ranges(Ranges...ranges) : _ranges(std::make_tuple(ranges...)) {}

        template<class U>
        struct range_check
        {
            template<std::size_t I>
            bool contains_impl(std::integral_constant<std::size_t, I>,
                               const U& u,
                               const std::tuple<Ranges...>& ranges) const
            {
                return std::get<I>(ranges).contains(u)
                or contains_impl(std::integral_constant<std::size_t, I+1>(),u, ranges);
            }

            bool contains_impl(std::integral_constant<std::size_t, sizeof...(Ranges)>,
                               const U& u,
                               const std::tuple<Ranges...>& ranges) const
            {
                return false;
            }


            constexpr bool operator()(const U& u, std::tuple<Ranges...> const& ranges) const
            {
                return contains_impl(std::integral_constant<std::size_t, 0>(), u, ranges);
            }
        };

        template<class U>
        constexpr bool contains(const U& u) const
        {
            range_check<U> check {};
            return check(u, _ranges);
        }

        std::tuple<Ranges...> _ranges;
    };
}

template<class T>
constexpr auto range(T t) { return detail::range<T>(t, t); }

template<class T>
constexpr auto range(T from, T to) { return detail::range<T>(from, to); }

// this is the little trick which turns an ascii string into
// a range of characters at compile time. It probably a bit naughty
// as I am not checking syntax. You could write "ApZ" and it would be
// interpreted as "A-Z".
constexpr auto range(const char (&s)[4])
{
    return range(s[0], s[2]);
}

template<class...Rs>
constexpr auto ranges(Rs...rs)
{
    return detail::ranges<Rs...>(rs...);
}

int main()
{
    std::cout << range(1,7).contains(5) << std::endl;
    std::cout << range("a-f").contains('b') << std::endl;

    auto az = ranges(range('a'), range('z'));
    std::cout << az.contains('a') << std::endl;
    std::cout << az.contains('z') << std::endl;
    std::cout << az.contains('p') << std::endl;

    auto rs = ranges(range("a-f"), range("p-z"));
    for (char ch = 'a' ; ch <= 'z' ; ++ch)
    {
        std::cout << ch << rs.contains(ch) << " ";
    }
    std::cout << std::endl;

    return 0;
}

ожидаемый вывод:

1
1
1
1
0
a1 b1 c1 d1 e1 f1 g0 h0 i0 j0 k0 l0 m0 n0 o0 p1 q1 r1 s1 t1 u1 v1 w1 x1 y1 z1 

Для справки, вот мой оригинальный ответ:

template<class X, class Y>
bool in(X const& x, Y const& y)
{
    return x == y;
}

template<class X, class Y, class...Rest>
bool in(X const& x, Y const& y, Rest const&...rest)
{
    return in(x, y) or in(x, rest...);
}

int main()
{
    int ch = 6;
    std::cout << in(ch, 1,2,3,4,5,6,7) << std::endl;

    std::string foo = "foo";
    std::cout << in(foo, "bar", "foo", "baz") << std::endl;

    std::cout << in(foo, "bar", "baz") << std::endl;
}

Ответ 4

Если вам нужно проверить символ на произвольный набор символов, вы можете попробовать написать это:

std::set<char> allowed_chars = {'A', 'B', 'C', 'D', 'E', 'G', 'Q', '7', 'z'};
if(allowed_chars.find(ch) != allowed_chars.end()) {
    /*...*/
}

Ответ 5

Еще один ответ на этот чересчур ответный вопрос, который я просто включаю для полноты. Между всеми ответами здесь вы найдете что-то, что работает в вашем приложении.

Таким образом, другой вариант - это таблица поиска:

// On initialization:
bool isAcceptable[256] = { false };
isAcceptable[(unsigned char)'A'] = true;
isAcceptable[(unsigned char)'B'] = true;
isAcceptable[(unsigned char)'C'] = true;

// When you want to check:
char c = ...;
if (isAcceptable[(unsigned char)c]) {
   // it 'A', 'B', or 'C'.
}

Откажитесь от статических бросков C-стиля, если нужно, но они выполняют свою работу. Я полагаю, вы можете использовать std::vector<bool>, если массивы будут держать вас в ночное время. Вы также можете использовать типы, кроме bool. Но у вас есть идея.

Очевидно, что это становится громоздким, например, wchar_t и практически непригодным для использования с многобайтовыми кодировками. Но для вашего примера char или для всего, что поддается поисковой таблице, это будет сделано. YMMV.

Ответ 6

Аналогично ответу C strchr, в С++ вы можете построить строку и проверить символ на его содержимое:

#include <string>
...
std::string("ABCDEFGIKZ").find(c) != std::string::npos;

Вышеуказанное вернет true для 'F' и 'Z', но false для 'Z' или 'O'. Этот код не предполагает непрерывного представления символов.

Это работает, потому что std::string::find возвращает std::string::npos, когда он не может найти символ в строке.

Live on Coliru

Edit:

Существует еще один метод С++, который не включает динамическое распределение, но включает еще более длинный фрагмент кода:

#include <algorithm> // std::find
#include <iterator> // std::begin and std::end
...
char const chars[] = "ABCDEFGIKZ";
return std::find(std::begin(chars), std::end(chars), c) != std::end(chars);

Это работает аналогично первому фрагменту кода: std::find выполняет поиск заданного диапазона и возвращает определенное значение, если элемент не найден, Здесь указанное конкретное значение - это конец диапазона.

Live on Coliru

Ответ 7

Один из вариантов - unordered_set. Поместите интересующие персонажи в набор. Затем просто проверьте количество символов, о которых идет речь:

#include <iostream>
#include <unordered_set>

using namespace std;

int main() {
  unordered_set<char> characters;
  characters.insert('A');
  characters.insert('B');
  characters.insert('C');
  // ...

  if (characters.count('A')) {
    cout << "found" << endl;
  } else {
    cout << "not found" << endl;
  }

  return 0;
}

Ответ 8

Существует решение вашей проблемы, а не на языке, но в практике кодирования - Рефакторинг.

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

Вы можете создать следующий метод:

private bool characterIsValid(char ch) {
    return (ch == 'A' || ch == 'B' || ch == 'C' || ..... );
}

а затем этот метод можно вызвать в краткой форме:

if (characterIsValid(ch)) ...

Повторно используйте этот метод с таким количеством проверок и только возвращаем логическое значение в любом месте.

Ответ 9

В этом конкретном случае вы можете использовать тот факт, что char является целым числом и проверяет диапазон:

if(ch >= 'A' && ch <= 'C')
{
    ...
}

Но в целом это невозможно, к сожалению. Если вы хотите сжать свой код, просто используйте функцию boolean

if(compare_char(ch))
{
    ...
}

Ответ 10

Ответ X-Y на подавляющее большинство современных систем не беспокоит.

Вы можете воспользоваться тем фактом, что практически каждая кодировка символов, используемая сегодня, хранит алфавит в одном последовательно упорядоченном непрерывном блоке. За ним следуют B, B - C и т.д. На Z. Это позволяет вам делать простые математические трюки на письмах для преобразования буквы в число. Например, буква C минус буква A, 'C' - 'A', равна 2, расстояние между c и a.

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

Мы можем использовать это, чтобы сделать сопоставление буквенных значений с буквами с простым массивом:

//                    a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p, q,r,s,t,u,v,w,x,y, z
int lettervalues[] = {1,3,3,2,1,4,2,4,1,8,5,1,3,1,1,3,10,1,1,1,1,4,4,8,4,10};

So 'c' - 'a' равно 2 и lettervalues[2] приведет к 3, буквенному значению C.

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

По мере того, как вы больше изучаете С++, вы узнаете, что lettervalues должен быть static (текущий доступ только для единицы перевода) и const (не может быть изменен), возможно constexpr (нельзя изменить и зафиксировать во время компиляции). Если вы не знаете, о чем я говорю, не беспокойтесь. Вы покроете все три позже. Если нет, перейдите в Google. Все это очень полезные инструменты.

Использование этого массива может быть таким же простым, как

int ComputeWordScore(std::string in)
{
    int score = 0;
    for (char ch: in) // for all characters in string
    {
        score += lettervalues[ch - 'a'];
    }
    return score;
}

Но у этого есть два фатальных слепых пятна:

Первая - заглавные буквы. Извините Ayn Rand, но "A" не "a", а 'A'-'a' не равен нулю. Это можно решить, используя std::tolower или std::toupper, чтобы преобразовать все входные данные в известный случай.

int ComputeWordScore(std::string in)
{
    int score = 0;
    for (char ch: in) // for all characters in string
    {
        score += lettervalues[std::tolower(ch) - 'a'];
    }
    return score;
}

Другой - это ввод символов, которые не являются буквами. Например, "1". 'a' - '1' приведет к индексу массива, который не находится в массиве. Это плохо. Если вам повезет, ваша программа потерпит крах, но все может случиться, в том числе и выглядеть так, как если бы ваша программа работала. Подробнее читайте Undefined Поведение.

К счастью, это также имеет простое исправление: просто вычислите оценку для хорошего ввода. Вы можете проверить действительные символы алфавита с помощью std::isalpha.

int ComputeWordScore(std::string in)
{
    int score = 0;
    for (char ch: in) // for all characters in string
    {
        if (std::isalpha(ch))
        {
            score += lettervalues[std::tolower(ch) - 'a'];
        }
        else
        {
            // do something that makes sense here. 
        }
    }
    return score;
}

Мое другое было бы return -1;. -1 - это невозможный показатель слова, поэтому любой, кто называет ComputeWordScore, может проверить -1 и отклонить ввод пользователя. То, что они делают с этим, не проблема ComputeWordScore. Как правило, глупец, с которым вы можете сделать функцию, лучше и ошибки, должен обрабатываться ближайшим фрагментом кода, который имеет всю информацию, необходимую для принятия решения. В этом случае независимо от того, что читается в строке, вероятно, будет поручено решить, что делать с плохими строками, а ComputeWordScore может продолжать вычислять оценки слов.

Ответ 11

Для простого и эффективного решения вы можете использовать memchr():

#include <string.h>

const char list[] = "ABCXZ";
if (memchr(list, ch, sizeof(list) - 1)) {
    // 'ch' is 'A', 'B', 'C', 'X', or 'Z'
}

Обратите внимание, что memchr() лучше подходит для strchr() для этой задачи, так как strchr() найдет нулевой символ '\0' в конце строки, что в большинстве случаев неверно.

Если список является динамическим или внешним и его длина не указана, подход strchr() лучше, но вы должны проверить, отличается ли ch от 0, поскольку strchr() найдет его в конце строка:

#include <string.h>

extern char list[];
if (ch && strchr(list, ch)) {
    // 'ch' is one of the characters in the list
}

Еще одно более эффективное, но менее точное C99 решение использует массив:

#include <limits.h>

const char list[UCHAR_MAX + 1] = { ['A'] = 1, ['B'] = 1, ['C'] = 1, ['X'] = 1, ['Z'] = 1 };
if (list[(unsigned char)ch]) {
    /* ch is one of the matching characters */
}

Обратите внимание, однако, что все приведенные выше решения предполагают, что ch имеет тип char. Если ch имеет другой тип, они принимают ложные срабатывания. Вот как это исправить:

#include <string.h>

extern char list[];
if (ch == (char)ch && ch && strchr(list, ch)) {
    // 'ch' is one of the characters in the list
}

Кроме того, остерегайтесь ловушек, если вы сравниваете значения unsigned char:

unsigned char ch = 0xFF;
if (ch == '\xFF') {
    /* test fails if `char` is signed by default */
}
if (memchr("\xFF", ch, 1)) {
    /* test succeeds in all cases, is this OK? */
}

Ответ 12

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

Так получилось, что если ваш диапазон попадает в ваше количество бит в длину, вы можете объединить все ваши константы с помощью битовой маски и просто проверить, что ваше значение попадает в диапазон, а переменная битмаска отлична от нуля при побитом и с постоянной битовой маской.

/* This macro assumes the bits will fit in a long integer type,
 * if it needs to be larger (64 bits on x32 etc...),
 * you can change the shifted 1ULs to 1ULL or if range is > 64 bits,
 * split it into multiple ranges or use SIMD
 * It also assumes that a0 is the lowest and a9 is the highest,
 * You may want to add compile time assert that:
 * a9 (the highest value) - a0 (the lowest value) < max_bits
 * and that a1-a8 fall within a0 to a9
 */
#define RANGE_TO_BITMASK_10(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9) \
  (1 | (1UL<<((a1)-(a0))) | (1UL<<((a2)-(a0))) | (1UL<<((a3)-(a0))) | \
  (1UL<<((a4)-(a0))) | (1UL<<((a5)-(a0))) | (1UL<<((a6)-(a0))) | \
  (1UL<<((a7)-(a0))) | (1UL<<((a8)-(a0))) | (1UL<<((a9)-(a0))) )

/*static inline*/ bool checkx(int x){
    const unsigned long bitmask = /* assume 64 bits */
        RANGE_TO_BITMASK_10('A','B','C','F','G','H','c','f','y','z');
    unsigned temp = (unsigned)x-'A';
    return ( ( temp <= ('z'-'A') ) && !!( (1ULL<<temp) & bitmask ) );
}

Поскольку все значения # являются константами, они будут объединены в 1 битмаску во время компиляции. Это оставляет 1 вычитание и 1 сравнивается для диапазона, 1 сдвига и 1 побитового и... если компилятор не может оптимизировать дальше, получается clang can (он использует инструкцию BTT бит):

checkx:                                 # @checkx
        addl    $-65, %edi
        cmpl    $57, %edi
        ja      .LBB0_1
        movabsq $216172936732606695, %rax # imm = 0x3000024000000E7
        btq     %rdi, %rax
        setb    %al
        retq
.LBB0_1:
        xorl    %eax, %eax
        retq

Он может выглядеть как больше кода на стороне C, но если вы хотите оптимизировать, это похоже на то, что оно может стоить на стороне сборки. Я уверен, что кто-то может стать креативным с макросом, чтобы сделать его более полезным в реальных программных ситуациях, чем это "доказательство концепции".

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

#include <limits.h>
#define INIT_1(v,a) [ a ] = v
#define INIT_2(v,a,...) [ a ] = v, INIT_1(v, __VA_ARGS__)
#define INIT_3(v,a,...) [ a ] = v, INIT_2(v, __VA_ARGS__)
#define INIT_4(v,a,...) [ a ] = v, INIT_3(v, __VA_ARGS__)
#define INIT_5(v,a,...) [ a ] = v, INIT_4(v, __VA_ARGS__)
#define INIT_6(v,a,...) [ a ] = v, INIT_5(v, __VA_ARGS__)
#define INIT_7(v,a,...) [ a ] = v, INIT_6(v, __VA_ARGS__)
#define INIT_8(v,a,...) [ a ] = v, INIT_7(v, __VA_ARGS__)
#define INIT_9(v,a,...) [ a ] = v, INIT_8(v, __VA_ARGS__)
#define INIT_10(v,a,...) [ a ] = v, INIT_9(v, __VA_ARGS__)
#define ISANY10(x,...) ((const unsigned char[UCHAR_MAX+1]){ \
                          INIT_10(-1, __VA_ARGS__) \
                       })[x]

bool checkX(int x){
  return ISANY10(x,'A','B','C','F','G','H','c','f','y','z');
}

Этот метод будет использовать (обычно) 256-байтовую таблицу и поиск, который сводится к gcc следующим образом:

checkX:
        movslq  %edi, %rdi  # x, x
        cmpb    $0, C.2.1300(%rdi)      #, C.2
        setne   %al   #, tmp93
        ret

ПРИМЕЧАНИЕ. Clang также не подходит для таблицы поиска в этом методе, потому что он устанавливает таблицы const, которые встречаются внутри функций в стеке при каждом вызове функции, поэтому вы хотите использовать INIT_10 для инициализации static const unsigned char [UCHAR_MAX+1] вне функции для достижения аналогичной оптимизации gcc.