给定方向枚举,最小的逐位反转方法是什么?

问题描述 投票:1回答:3

假设我有一个enum Direction,它指定了基数(和心轴间)方向:

public enum Direction
{
    None      = 0,
    Up        = 1 << 1,
    Down      = 1 << 2,
    Left      = 1 << 3,
    Right     = 1 << 4,
    UpLeft    = Up | Left,
    UpRight   = Up | Right,
    DownLeft  = Down | Left,
    DownRight = Down | Right
}

在不引入冲突方向(向上和向下)的情况下,为了反转任意方向,我可以执行的最短逐位运算是什么?

我知道我可以使用三级语句来有效地处理这个问题

public Direction Inverse (Direction d) {
    // Inverse of Direction.None should still be none
    if (d == Direction.None) { return d; }

    Direction dInvert = (d.HasFlag(Direction. Up)) ? Direction.Down : Direction.Up;
    dInvert |= (d.HasFlag(Direction.Left)) ? Direction.Right : Direction.Left;

    return dInvert;
}

但我宁愿学习如何使用二元运算符处理这种“对称枚举反转”。如果没有代码打高尔夫,我能想到的最好的是

dir ^ (Direction.Up | Direction.Down)

但这显然只有在dir不包含任何水平“组件”时才有效。

c# enums
3个回答
7
投票

枚举应该看起来像这样(从1 << 0 == 1开始减少一个):

public enum Direction
{
    None      = 0,
    Up        = 1 << 0,
    Down      = 1 << 1,
    Left      = 1 << 2,
    Right     = 1 << 3,
    UpLeft    = Up | Left,
    UpRight   = Up | Right,
    DownLeft  = Down | Left,
    DownRight = Down | Right
}

让我们分析不同的可能性。这是一个带有标志和相应反转的表(只有最后8位)

+-----------+----------------+----------------+
| Direction | Flags          | Inverted       |
+---------------------------------------------+
| None      | 0000 0000 =  0 | 0000 0000 =  0 |
| Up        | 0000 0001 =  1 | 0000 0010 =  2 |
| Down      | 0000 0010 =  2 | 0000 0001 =  1 |
| Left      | 0000 0100 =  4 | 0000 1000 =  8 |
| Right     | 0000 1000 =  8 | 0000 0100 =  4 |
| UpLeft    | 0000 0101 =  5 | 0000 1010 = 10 |
| UpRight   | 0000 1001 =  9 | 0000 0110 =  6 |
| DownLeft  | 0000 0110 =  6 | 0000 1001 =  9 |
| DownRight | 0000 1010 = 10 | 0000 0101 =  5 |
+-----------+----------------+----------------+

组合方向可以用~dir & 0b1111反转,但我没有看到简单方向的简单按位操作。

让我们看一下十进制值。我们可以尝试推断一个函数。

  inv.
   ^
10 |              X
 9 |                 X
 8 |           X
 7 |
 6 |                          X
 5 |                             X
 4 |                       X
 3 |
 2 |  X
 1 |     X
 0 X--+--+--+--+--+--+--+--+--+--+--> flag
   0  1  2  3  4  5  6  7  8  9 10

这看起来也不简单。

WolframAlpha可以从这些数字对中插入多项式

InterpolatingPolynomial[{{0, 0}, {1, 2}, {2, 1}, {4, 8}, {5, 10}, {6, 9}, {8, 4}, {9, 6},{10, 5}}, x]

结果是:

- (x(x7 - 40 x6 + 686 x5 - 6580 x4 + 37709 x3 - 124180 x2 + 203524 x - 131280))/ 10080

uugh!

我会做一个反转字典

var inversion = new Dictionary<Direction, Direction> {
    [Direction.None]      = Direction.None,
    [Direction.Up]        = Direction.Down,
    [Direction.Down]      = Direction.Up,
    [Direction.Left]      = Direction.Right,
    [Direction.Right]     = Direction.Left,
    [Direction.UpLeft]    = Direction.DownRight,
    [Direction.UpRight]   = Direction.DownLeft,
    [Direction.DownLeft]  = Direction.UpRight,
    [Direction.DownRight] = Direction.UpLeft
}

并获得倒转的方向

Direction invDir = inversion[dir];

最后,我找到了一个使用负枚举值的简单解决方案:

如果你这样声明枚举:

public enum Direction
{
    None = 0,
    Up = 1,
    Down = -1,
    Left = 4,
    Right = -4,
    UpLeft = Up + Left,
    UpRight = Up + Right,
    DownLeft = Down + Left,
    DownRight = Down + Right
}

您可以通过使用二进制补码取负值来获得反转方向 ~d + 1

foreach (Direction d in Enum.GetValues(typeof(Direction))) {
    Console.WriteLine($"{d}  ===> {~d + 1}");
}

打印

无===>无 上来===>下来 DownLeft ===> UpRight 左===>对 UpLeft ===> DownRight DownRight ===> UpLeft 对===>左 UpRight ===> DownLeft 下来===> Up

简单地使用-d不起作用,因为-不能应用于枚举。你必须写(Direction)(-((int)dir))


3
投票

我用两对比特编码方向,一对用于垂直,一对用于水平:

enum Direction
{
    None      = 0b00_00,
    Down      = 0b00_01,
    Up        = 0b00_10,
    Right     = 0b01_00,
    DownRight = 0b01_01,
    UpRight   = 0b01_10,
    Left      = 0b10_00,
    DownLeft  = 0b10_01,
    UpLeft    = 0b10_10
}

static Direction Inverse(Direction dir)
{
    int d = (int)dir;
    Direction ret = (Direction)(((d & 0b01_01) << 1) | 
                                ((d & 0b10_10) >> 1));

    return ret;
}

static bool HasDir(Direction dir, Direction query)
{
    return query == (Direction)((int)query & (int)dir);
}

static bool HasUp(Direction dir)
{
    return HasDir(dir, Direction.Up);
}

static bool HasDown(Direction dir)
{
    return HasDir(dir, Direction.Down);
}

static bool HasRight(Direction dir)
{
    return HasDir(dir, Direction.Right);
}

static bool HasLeft(Direction dir)
{
    return HasDir(dir, Direction.Left);
}

private static string show(Direction dir)
{
    return (HasDown(dir) ? "V" : HasUp(dir) ? "^" : "") +
           (HasLeft(dir) ? "<" : HasRight(dir) ? ">" : "");
}


static void Main(string[] args)
{
    foreach(Direction dir in Enum.GetValues(typeof(Direction)))
    {
        Console.WriteLine(show(dir) + " " + show(Inverse(dir)));
    }

    Console.WriteLine("ciao!");
}

Inverse操作只需交换两对。


0
投票

juharr对你的问题提出了评论,这与我的想法一致。即:您确定将方向表示为四个两位值的组合是否有意义?如您所述,许多可能的组合都是无效的,因为您不能在相同的值中使用相反的方向。相反,两个三位值的组合可能更合适:一个用于表示水平方向,另一个用于表示垂直方向。

这是一个可能是什么样子的例子。 Direction是一个提供产生水平和垂直分量的属性的结构,以及一个简单的反演方法。

public enum HorizontalDirection
{
    Left = -1,
    None = 0,
    Right = 1,
}

public enum VerticalDirection
{
    Up = -1,
    None = 0,
    Down = 1,
}

public struct Direction
{
    public Direction(HorizontalDirection h) : this(h, VerticalDirection.None) { }
    public Direction(VerticalDirection v) : this(HorizontalDirection.None, v) { }

    public Direction(HorizontalDirection h, VerticalDirection v)
    {
        HorizontalComponent = h;
        VerticalComponent = v;
    }

    public HorizontalDirection HorizontalComponent { get; }
    public VerticalDirection VerticalComponent { get; }

    public Direction Invert() => new Direction(
        (HorizontalDirection)(-(int)HorizontalComponent),
        (VerticalDirection)(-(int)VerticalComponent));
}
© www.soinside.com 2019 - 2024. All rights reserved.