web-dev-qa-db-ja.com

見出しを8つの方向に分類するときにif / else ifチェーンを回避するにはどうすればよいですか?

私は次のコードを持っています:

if (this->_car.getAbsoluteAngle() <= 30 || this->_car.getAbsoluteAngle() >= 330)
  this->_car.edir = Car::EDirection::RIGHT;
else if (this->_car.getAbsoluteAngle() > 30 && this->_car.getAbsoluteAngle() <= 60)
  this->_car.edir = Car::EDirection::UP_RIGHT;
else if (this->_car.getAbsoluteAngle() > 60 && this->_car.getAbsoluteAngle() <= 120)
  this->_car.edir = Car::EDirection::UP;
else if (this->_car.getAbsoluteAngle() > 120 && this->_car.getAbsoluteAngle() <= 150)
  this->_car.edir = Car::EDirection::UP_LEFT;
else if (this->_car.getAbsoluteAngle() > 150 && this->_car.getAbsoluteAngle() <= 210)
  this->_car.edir = Car::EDirection::LEFT;
else if (this->_car.getAbsoluteAngle() > 210 && this->_car.getAbsoluteAngle() <= 240)
  this->_car.edir = Car::EDirection::DOWN_LEFT;
else if (this->_car.getAbsoluteAngle() > 240 && this->_car.getAbsoluteAngle() <= 300)
  this->_car.edir = Car::EDirection::DOWN;
else if (this->_car.getAbsoluteAngle() > 300 && this->_car.getAbsoluteAngle() <= 330)
  this->_car.edir = Car::EDirection::DOWN_RIGHT;

ifsチェーンを避けたい。それは本当にいです。これを書く別の、おそらくもっときれいな方法はありますか?

111
Oraekia
#include <iostream>

enum Direction { UP, UP_RIGHT, RIGHT, DOWN_RIGHT, DOWN, DOWN_LEFT, LEFT, UP_LEFT };

Direction GetDirectionForAngle(int angle)
{
    const Direction slices[] = { RIGHT, UP_RIGHT, UP, UP, UP_LEFT, LEFT, LEFT, DOWN_LEFT, DOWN, DOWN, DOWN_RIGHT, RIGHT };
    return slices[(((angle % 360) + 360) % 360) / 30];
}

int main()
{
    // This is just a test case that covers all the possible directions
    for (int i = 15; i < 360; i += 30)
        std::cout << GetDirectionForAngle(i) << ' ';

    return 0;
}

これは私がそれをする方法です。 (私の以前のコメントによる)。

177
Borgleader

map::lower_bound を使用して、マップの各角度の上限を保存できます。

以下の作業例:

#include <cassert>
#include <map>

enum Direction
{
    RIGHT,
    UP_RIGHT,
    UP,
    UP_LEFT,
    LEFT,
    DOWN_LEFT,
    DOWN,
    DOWN_RIGHT
};

using AngleDirMap = std::map<int, Direction>;

AngleDirMap map = {
    { 30, RIGHT },
    { 60, UP_RIGHT },
    { 120, UP },
    { 150, UP_LEFT },
    { 210, LEFT },
    { 240, DOWN_LEFT },
    { 300, DOWN },
    { 330, DOWN_RIGHT },
    { 360, RIGHT }
};

Direction direction(int angle)
{
    assert(angle >= 0 && angle <= 360);

    auto it = map.lower_bound(angle);
    return it->second;
}

int main()
{
    Direction d;

    d = direction(45);
    assert(d == UP_RIGHT);

    d = direction(30);
    assert(d == RIGHT);

    d = direction(360);
    assert(d == RIGHT);

    return 0;
}
71
Steve Lorimer

各要素が30度のブロックに関連付けられている配列を作成します。

Car::EDirection dirlist[] = { 
    Car::EDirection::RIGHT, 
    Car::EDirection::UP_RIGHT, 
    Car::EDirection::UP, 
    Car::EDirection::UP, 
    Car::EDirection::UP_LEFT, 
    Car::EDirection::LEFT, 
    Car::EDirection::LEFT, 
    Car::EDirection::DOWN_LEFT,
    Car::EDirection::DOWN, 
    Car::EDirection::DOWN, 
    Car::EDirection::DOWN_RIGHT, 
    Car::EDirection::RIGHT
};

次に、角度/ 30で配列にインデックスを付けることができます。

this->_car.edir = dirlist[(this->_car.getAbsoluteAngle() % 360) / 30];

比較や分岐は必要ありません。

ただし、結果は元のわずかに外れます。境界線の値、つまり30、60、120などは、次のカテゴリに配置されます。たとえば、元のコードでは、UP_RIGHTの有効な値は31から60です。上記のコードは、UP_RIGHTに30から59を割り当てます。

これを回避するには、角度から1を引きます。

this->_car.edir = dirlist[((this->_car.getAbsoluteAngle() - 1) % 360) / 30];

これにより、30のRIGHT、60のUP_RIGHTなどが得られます。

0の場合、式は(-1 % 360) / 30になります。これは-1 % 360 == -1-1 / 30 == 0が有効であるため、まだ0のインデックスを取得しています。

C++標準 のセクション5.6は、この動作を確認します。

4バイナリ/演算子は商を生成し、バイナリ%演算子は最初の式を2番目の式で除算した剰余を生成します。 /または%の第2オペランドがゼロの場合、動作は未定義です。整数オペランドの場合、/演算子は、小数部分が破棄された代数商を生成します。商a/bが結果の型で表現できる場合、(a/b)*b + a%baと等しくなります。

編集:

このような構造の読みやすさと保守性に関して多くの質問がありました。 motoDrizztの答えは、より保守性が高く、「ugい」ほどではない元の構造を単純化する良い例です。

彼の答えを拡張して、三項演算子を利用する別の例を示します。元の投稿の各ケースは同じ変数に割り当てられているため、この演算子を使用すると読みやすくなります。

int angle = ((this->_car.getAbsoluteAngle() % 360) + 360) % 360;

this->_car.edir = (angle <= 30)  ?  Car::EDirection::RIGHT :
                  (angle <= 60)  ?  Car::EDirection::UP_RIGHT :
                  (angle <= 120) ?  Car::EDirection::UP :
                  (angle <= 150) ?  Car::EDirection::UP_LEFT :
                  (angle <= 210) ?  Car::EDirection::LEFT : 
                  (angle <= 240) ?  Car::EDirection::DOWN_LEFT :
                  (angle <= 300) ?  Car::EDirection::DOWN:  
                  (angle <= 330) ?  Car::EDirection::DOWN_RIGHT :
                                    Car::EDirection::RIGHT;
57
dbush

そのコードはいものではなく、シンプルで実用的で読みやすく、理解しやすいものです。独自の方法で分離されるため、日常生活で誰も対処する必要はありません。そして、誰かがそれをチェックしなければならない場合-多分彼はどこか他の場所で問題のためにあなたのアプリケーションをデバッグしているので-それは彼がコードとそれが何をするかを理解するのに2秒かかるでしょう。

私がそのようなデバッグを行っていたなら、あなたの機能が何をするのか理解しようとして5分を費やす必要がないのはうれしいことです。この点で、他のすべての機能は完全に失敗します。デバッグ時に人々が深く分析してテストすることを余儀なくされる複雑な混乱の中で、単純で忘れがちなバグのないルーチンを変更するためです。私自身、プロジェクトマネージャーとして、開発者が単純なタスクを実行し、単純で無害な方法で実装するのではなく、非常に複雑な方法で実装するのに時間を浪費することに強く腹を立てます。あなたがそれについて考えて浪費し、SOに尋ねるたびに、物事のメンテナンスと読みやすさを悪化させるためだけに考えてください。

とは言っても、コードには一般的な間違いがあり、コードの読みやすさが大幅に低下します。また、いくつかの改善点を簡単に実行できます。

int angle = this->_car.getAbsoluteAngle();

if (angle <= 30 || angle >= 330)
  return Car::EDirection::RIGHT;
else if (angle <= 60)
  return Car::EDirection::UP_RIGHT;
else if (angle <= 120)
  return Car::EDirection::UP;
else if (angle <= 150)
  return Car::EDirection::UP_LEFT;
else if (angle <= 210)
  return Car::EDirection::LEFT;
else if (angle <= 240)
  return Car::EDirection::DOWN_LEFT;
else if (angle <= 300)
  return Car::EDirection::DOWN;
else if (angle <= 330)
  return Car::EDirection::DOWN_RIGHT;

これをメソッドに入れて、戻り値をオブジェクトに割り当て、メソッドを折りたたみ、残りの部分についてはそのことを忘れます。

追伸330のしきい値を超える別のバグがありますが、それをどのように処理したいかわからないため、修正しませんでした。


更新後

コメントによると、もしあれば、elseを取り除くことさえできます:

int angle = this->_car.getAbsoluteAngle();

if (angle <= 30 || angle >= 330)
  return Car::EDirection::RIGHT;

if (angle <= 60)
  return Car::EDirection::UP_RIGHT;

if (angle <= 120)
  return Car::EDirection::UP;

if (angle <= 150)
  return Car::EDirection::UP_LEFT;

if (angle <= 210)
  return Car::EDirection::LEFT;

if (angle <= 240)
  return Car::EDirection::DOWN_LEFT;

if (angle <= 300)
  return Car::EDirection::DOWN;

if (angle <= 330)
  return Car::EDirection::DOWN_RIGHT;

私はそれをしませんでした。特定のポイントは自分の好みの問題になると思うので、私の答えの範囲は「コードのugさ」についてのあなたの懸念に異なる視点を与えることでした。とにかく、私が言ったように、誰かがコメントでそれを指摘しました、そして、私はそれを示すことは理にかなっていると思います。

49
motoDrizzt

擬似コードで:

angle = (angle + 30) %360; // Offset by 30. 

したがって、カテゴリとして0-6060-9090-150、...があります。 90度の各象限では、1つの部分に60、1つの部分に30があります。

i = angle / 90; // Figure out the quadrant. Could be 0, 1, 2, 3 

j = (angle - 90 * i) >= 60? 1: 0; // In the quardrant is it perfect (eg: RIGHT) or imperfect (eg: UP_RIGHT)?

index = i * 2 + j;

適切な順序で列挙を含む配列のインデックスを使用します。

39
Bijay Gurung
switch (this->_car.getAbsoluteAngle() / 30) // integer division
{
    case 0:
    case 11: this->_car.edir = Car::EDirection::RIGHT; break;
    case 1: this->_car.edir = Car::EDirection::UP_RIGHT; break;
    ...
    case 10: this->_car.edir = Car::EDirection::DOWN_RIGHT; break;
}
18
Caleth

少し特殊なケースである最初のifを無視すると、残りのものはすべてまったく同じパターンに従います:min、max、direction;擬似コード:

if (angle > min && angle <= max)
  _car.edir = direction;

この実際のC++の作成は次のようになります。

enum class EDirection {  NONE,
   RIGHT, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT, DOWN, DOWN_RIGHT };

struct AngleRange
{
    int min, max;
    EDirection direction;
};

これで、ifsの束を書くのではなく、さまざまな可能性をループするだけです。

EDirection direction_from_angle(int angle, const std::vector<AngleRange>& angleRanges)
{
    for (auto&& angleRange : angleRanges)
    {
        if ((angle > angleRange.min) && (angle <= angleRange.max))
            return angleRange.direction;
    }

    return EDirection::NONE;
}

throwing returnではなく、NONEingが別のオプションです)。

それからあなたは電話するでしょう:

_car.edir = direction_from_angle(_car.getAbsoluteAngle(), {
    {30, 60, EDirection::UP_RIGHT},
    {60, 120, EDirection::UP},
    // ... etc.
});

この手法は、 データ駆動型プログラミング として知られています。 ifsの束を取り除くことに加えて、他のコードを再作業することなく、方向を追加する(NNWなど)か、数を減らす(左、右、上、下)ことを簡単に使用できます。


(最初の特別なケースの処理は、「読者のための演習」として残されています。:-))

16
Ðаn

angle / 30のルックアップテーブルに基づいて提案されたバリアントはおそらく望ましいですが、ハードコード化されたバイナリ検索を使用して比較の数を最小限に抑える代替方法があります。

static Car::EDirection directionFromAngle( int angle )
{
    if( angle <= 210 )
    {
        if( angle > 120 )
            return angle > 150 ? Car::EDirection::LEFT
                               : Car::EDirection::UP_LEFT;
        if( angle > 30 )
            return angle > 60 ? Car::EDirection::UP
                              : Car::EDirection::UP_RIGHT;
    }
    else // > 210
    {
        if( angle <= 300 )
            return angle > 240 ? Car::EDirection::DOWN
                               : Car::EDirection::DOWN_LEFT;
        if( angle <= 330 )
            return Car::EDirection::DOWN_RIGHT;
    }
    return Car::EDirection::RIGHT; // <= 30 || > 330
}
12
x4u

重複を本当に避けたい場合は、これを数式として表現できます。

まず、@ GeekのEnumを使用していると仮定します

Enum EDirection { RIGHT =0, UP_RIGHT, UP, UP_LEFT, LEFT, DOWN_LEFT,DOWN, DOWN_RIGHT}

これで、整数数学を使用して列挙型を計算できます(配列は必要ありません)。

EDirection angle2dir(int angle) {
    int d = ( ((angle%360)+360)%360-1)/30;
    d-=d/3; //some directions cover a 60 degree arc
    d%=8;
    //printf ("assert(angle2dir(%3d)==%-10s);\n",angle,dir2str[d]);
    return (EDirection) d;
}

@motoDrizztが指摘しているように、簡潔なコードは必ずしも読み取り可能なコードではありません。数学として表現すると、いくつかの方向がより広い弧をカバーすることを明示するという小さな利点があります。この方向に進みたい場合は、コードを理解するのに役立つアサートを追加できます。

assert(angle2dir(  0)==RIGHT     ); assert(angle2dir( 30)==RIGHT     );
assert(angle2dir( 31)==UP_RIGHT  ); assert(angle2dir( 60)==UP_RIGHT  );
assert(angle2dir( 61)==UP        ); assert(angle2dir(120)==UP        );
assert(angle2dir(121)==UP_LEFT   ); assert(angle2dir(150)==UP_LEFT   );
assert(angle2dir(151)==LEFT      ); assert(angle2dir(210)==LEFT      );
assert(angle2dir(211)==DOWN_LEFT ); assert(angle2dir(240)==DOWN_LEFT );
assert(angle2dir(241)==DOWN      ); assert(angle2dir(300)==DOWN      );
assert(angle2dir(301)==DOWN_RIGHT); assert(angle2dir(330)==DOWN_RIGHT);
assert(angle2dir(331)==RIGHT     ); assert(angle2dir(360)==RIGHT     );

重複を追加したアサートを追加したが、アサートの重複はそれほど悪くありません。一貫性のない主張がある場合は、すぐにわかります。アサートは、配布する実行可能ファイルを膨張させないように、リリースバージョンからコンパイルできます。それにもかかわらず、このアプローチはおそらく、コードをugくするのではなく最適化する場合に最も適切です。

2
gmatht

私はパーティーに遅れていますが、列挙フラグと範囲チェックを使用して、きちんとしたことをすることができます。

enum EDirection {
    RIGHT =  0x01,
    LEFT  =  0x02,
    UP    =  0x04,
    DOWN  =  0x08,
    DOWN_RIGHT = DOWN | RIGHT,
    DOWN_LEFT = DOWN | LEFT,
    UP_RIGHT = UP | RIGHT,
    UP_LEFT = UP | LEFT,

    // just so we be clear, these won't have much use though
    IMPOSSIBLE_H = RIGHT | LEFT, 
    IMPOSSIBLE_V = UP | DOWN
};

チェック(擬似コード)、角度が絶対(0〜360)であると仮定します。

int up    = (angle >   30 && angle <  150) * EDirection.UP;
int down  = (angle >  210 && angle <  330) * EDirection.DOWN;
int right = (angle <=  60 || angle >= 330) * EDirection.Right;
int left  = (angle >= 120 && angle <= 240) * EDirection.LEFT;

EDirection direction = (Direction)(up | down | right | left);

switch(direction){
    case RIGHT:
         // do right
         break;
    case UP_RIGHT:
         // be honest
         break;
    case UP:
         // whats up
         break;
    case UP_LEFT:
         // do you even left
         break;
    case LEFT:
         // 5 done 3 to go
         break;
    case DOWN_LEFT:
         // your're driving me to a corner here
         break;
    case DOWN:
         // :(
         break;
    case DOWN_RIGHT:
         // completion
         break;

    // hey, we mustn't let these slide
    case IMPOSSIBLE_H:
    case IMPOSSIBLE_V:
        // treat your impossible case here!
        break;
}
1
Leonardo Pina