web-dev-qa-db-ja.com

複合 'if'条件を記述するより短い方法はありますか?

ただ代わりに of:

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

たとえば、それを行うにはlike

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

短い条件を要約する方法さえありますか?

34
OptimusMaximus

strchr() は、文字がリストにあるかどうかを確認するために使用できます。

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

この場合、switchを使用できます。

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

これはstrchrを使用するよりも少し冗長ですが、関数呼び出しは含まれません。また、CとC++の両方で機能します。

提案した代替構文は、カンマ演算子を使用しているため、期待どおりに機能しないことに注意してください。

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

これは最初にch'A'を比較してから、結果を破棄します。次に、'B'が評価されて破棄され、次に'C'が続き、'F'が評価されるまで続きます。次に、'F'が条件の値になります。ブール値のコンテキストでゼロ以外の値がtrueと評価されるため('F'はゼロ以外)、上記の式は常にtrueになります。

39
dbush

テンプレートを使用すると、次のように自分自身を表現できます。

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's 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;
}
33
Richard Hodges

この過度に回答された質問に対するもう1つの答えは、完全を期すためだけに含めています。ここでのすべての答えの間に、アプリケーションで機能するsomethingが見つかるはずです。

したがって、別のオプションはルックアップテーブルです。

// 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's 'A', 'B', or 'C'.
}

必要に応じて、Cスタイルの静的キャストを非難しますが、仕事は完了します。 std::vector<bool>配列を使用すると、夜も活動を続けることができます。 bool以外の型も使用できます。しかし、あなたはアイデアを得ます。

明らかに、これは例えばwchar_t、マルチバイトエンコーディングでは事実上使用できません。しかし、charの例や、ルックアップテーブルに役立つものについては、そうします。 YMMV。

14
Jason C

任意の文字セットに対して文字をチェックする必要がある場合は、次のように書くことができます。

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

C strchr回答と同様に、C++では文字列を作成し、その内容に対して文字をチェックできます。

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

上記は、'F'および'Z'の場合はtrueを返しますが、'z'または'O'の場合はfalseを返します。このコードは、文字の連続した表現を想定していません。

std::string::find は、文字列内に文字が見つからない場合にstd::string::nposを返すため、これは機能します。

Live on Coliru

編集:

別のC++メソッドがあり、動的割り当てを含まないが、さらに長いコードが必要です。

#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

12
jaggedSpire

1つのオプションは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;
}
6
user6697869

言語ではなくコーディングの実践で問題の解決策があります-リファクタリング

読者がこの答えを正統ではないことに気付くと確信していますが、-リファクタリングは、メソッド呼び出しの背後にある乱雑なコードを隠すことができます。そのメソッドは後でクリーンアップするか、そのままにしておくことができます。

次のメソッドを作成できます。

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

そして、このメソッドは以下のように短い形式で呼び出すことができます。

if (characterIsValid(ch)) ...

非常に多くのチェックでそのメソッドを再利用し、どこでもブール値のみを返します。

6
displayName

シンプルで効果的な解決策として、memchr()を使用できます。

_#include <string.h>

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

memchr()は文字列の最後にヌル文字_'\0'_を見つけるため、このタスクにはstrchr()よりもstrchr()の方が適しています。ケース。

リストが動的または外部であり、その長さが指定されていない場合、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 */
}
_

ただし、上記のすべてのソリューションでは、chchar型であると想定しています。 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? */
}
_

X-Yの回答 現代のシステムの大部分で問題はありません。

今日使用されているほとんどすべての文字エンコードでは、アルファベットが1つの連続した連続したブロックに格納されているという事実を利用できます。 Aの後に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};

したがって、'c' - 'a'は2で、lettervalues[2]はCの文字値である3になります。

Ifステートメントや条件付きロジックは、まったく必要ありません。必要なデバッグはすべて、正しい値を入力したことを確認するためのlettervaluesの証明です。

C++でさらに学習すると、lettervaluesstatic(現在の翻訳単位のみのアクセス)およびconst(変更不可)である必要があることがわかります。おそらくconstexpr(コンパイル時に変更および修正できません)。私が何を話しているのか分からなくても心配しないでください。 3つすべてを後でカバーします。そうでない場合は、それらをグーグル。すべて非常に便利なツールです。

この配列の使用は、次のように簡単です。

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

しかし、これには2つの致命的な盲点があります。

最初は大文字です。 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つは、文字以外の文字の入力です。たとえば、「1」です。 'a' - '1'は、配列にない配列インデックスになります。これは悪いです。運が良ければ、プログラムはクラッシュしますが、プログラムが動作しているかのように見えることを含め、何かが起こる可能性があります。詳細は ndefined Behaviour を参照してください。

幸い、これにも簡単な修正があります。適切な入力のスコアのみを計算します。 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は不可能なWordスコアであるため、ComputeWordScoreを呼び出すユーザーは-1をテストしてユーザーの入力を拒否できます。彼らがそれで何をするかはComputeWordScoreの問題ではありません。一般的に、関数を作成できる愚か者はより優れており、エラーは、決定を行うために必要なすべての情報を持つ最も近いコードによって処理されます。この場合、文字列のどのような読み取りでも、不良文字列の処理方法を決定するタスクが課せられ、ComputeWordScoreはWordスコアの計算を続行できます。

2
user4581301

この特定のケースでは、charが整数であるという事実を使用して、範囲をテストできます。

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

しかし、一般的にこれは残念ながら不可能です。コードを圧縮したい場合は、ブール関数を使用してください

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

ほとんどの簡潔なバージョンがカバーされているので、最適化されたケースをいくつかのヘルパーマクロでカバーして、少し簡潔にします。

たまたま、範囲がlongあたりのビット数に収まる場合は、ビットマスクを使用してすべての定数を組み合わせることができ、値が範囲内にあり、変数のビットマスクがゼロでないことをビットごとにANDで確認すると、定数ビットマスク。

_/* 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 ) );
}
_

すべてのa#値は定数なので、コンパイル時に1つのビットマスクに結合されます。これにより、1つの減算と1つの比較が1つの範囲、1つのシフト、1つのビットごとに残ります...

_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と同様の最適化を達成する関数の。

1
technosaurus