2

我有一组选项,一些正交(可以以任何组合组合),一些排他(只允许从集合中选择一个),并且需要选择一组enum值,以便它们可以按位组合or并提取与位and。我希望or-ing 无效组合是可检测的。

有没有这样的生成工具enums

为清晰起见进行编辑

我正在寻找可以利用某些标志无效的事实来减少使用的位数的东西。我能够检测错误的要求是软的。如果事情搞砸了,我不需要知道使用了什么。

我正在使用 C#,但任何解决方案都应该有帮助。

一个示例模式是:

0011 00
0101 00
1001 00
0110 00
1010 00
1100 00

0000 01
0000 10

得到 6 个独占标志和 2 的正交对成 6 位

快速测试表明 5 位给出 9 个值,6 位给出 20,...

4

7 回答 7

5

我所知道的最好的通用方法与其说是一种工具,不如说是一种约定:定义这样的位标志列表:

FLAG_1                    0x00000001
FLAG_2                    0x00000002
FLAG_3                    0x00000004
FLAG_4                    0x00000008
FLAG_5                    0x00000010
FLAG_6                    0x00000020

它很容易使用,因为数字继续向左移动的 1、2、4、8 模式。

编辑:回应评论。好吧,如果您真的想要将位标志与独占枚举结合起来,那么您基本上要做的就是将位列表的部分分段,以将其视为数字空间。因此,您可以使用 0x1 和 0x2 这两个位,现在您可以使用这两个位来表示 0-3。就像是:

OPT_1_VAL_1               0x00000000
OPT_1_VAL_2               0x00000001
OPT_1_VAL_3               0x00000002
OPT_1_VAL_4               0x00000003
FLAG_1                    0x00000004
FLAG_2                    0x00000008
FLAG_3                    0x00000010
FLAG_4                    0x00000020

您使用的屏蔽逻辑必须更复杂。要查找标志,您可以执行 if(settings & FLAG_1),但对于选项空间,您必须执行 if((settings & OPT_1_VAL_3) == OPT_1_VAL_3)。

于 2009-02-04T22:40:15.450 回答
3

我不知道有什么工具,但这里有一个技巧可以让唯一位枚举更容易生成:

public enum Critters
{
   Amorphous = 0,
   Sloth =     1 << 0,
   Armadillo = 1 << 1,
   Weasel =    1 << 2,
   Crab =      1 << 3,
   Partridge = 1 << 4,
   Parakeet =  1 << 5,
   Rhino =     1 << 6
};
于 2009-02-04T23:04:19.693 回答
3

为了表示一组“独占的”n选项(即必须选择一个),我们至少需要ceil(log2(n))位。例如,optionk可以用kbase-中的数字表示2

为了表示一组“正交n选项(即0, 1, ..., n可以选择任何大小的组合),我们至少需要n位。例如,选项k0, k1, k2可以用二进制数表示,除了 bits 之外,它的位为零0, 1, 2

因此,为了同时表示多个选项集,我们将每个选项集所需的位数相加(取决于它是“独占”还是“正交”)以获得所需的总位数。

简而言之,要选择枚举值,

  • “独占”选项k使用k << r
  • “正交”选项k0, k1, ..., k{n-1}使用0x1 << r, 0x1 << (r+1), ..., 0x1 << (r+n-1)

其中 offsetr是前面的选项集使用的位数。


如何在 Java 中自动执行此构造的示例:

/**
 * Construct a set of enum values, for the given sizes and types 
 * (exclusive vs orthogonal) of options sets.
 *
 * @param optionSetSizes
 *     number of elements in each option set
 * @param isOptionSetExclusive
 *     true if corresponding option set is exclusive, false if
 *     orthogonal
 * @returns
 *     array of m elements representing the enum values, where 
 *     m is the sum of option set sizes. The enum values are 
 *     given in the order of the option sets in optionSetSizes 
 *     and isOptionSetExclusive.
 */ 
int[] constructEnumValues(
        int[] optionSetSizes, 
        boolean[] isOptionSetExclusive)
{
    assert optionSetSizes.length == isOptionSetExclusive.length;

    // determine length of the return value
    int m = 0; 
    for (int i = 0; i < optionSetSizes.length; i++) m += optionSetSizes[i];
    int[] vals = new int[m];

    int r = 0; // number of bits used by the preceding options sets
    int c = 0; // counter for enum values used 

    for (int i = 0; i < optionSetSizes.length; i++)
    {
        // size of this option set
        int n = optionSetSizes[i];                   

        // is this option set exclusive?
        boolean exclusive = isOptionSetExclusive[i]; 

        for (int k = 0; k < n; k++)
        {
            vals[c] = (exclusive) ? (k << r) : (0x1 << (r + k));
            c++;
        }

        r += (exclusive) ? (int) Math.ceil(Math.log(n)/Math.log(2)) : n; 
    } 

    return vals;
}
于 2009-02-05T00:14:59.907 回答
2

...需要选择一组枚举值,以便它们可以组合...

真的需要手动选择它们吗?例如,JavaEnumSet为您完成了繁琐的工作,并为您提供了一个Set用于操作这些标志的接口。

于 2009-02-04T22:39:50.397 回答
2

使用 2 的幂,以便每个标志对应于单个位位置。

于 2009-02-04T22:44:10.917 回答
1

为此,您可以使用标准枚举(在 C# 中)。为此,您需要设置FlagsAttribute,然后专门为这些值编号。代码看起来像这样:

[Flags]
public enum AvailableColours {
    black = 1,
    red = 2,
    green = 4,
    blue = 8,
    white = 16,
}

然后,标准的按位运算符将按预期工作。

[编辑] 嗯,好的,你想生成可能的组合,对吧?您的要求非常具体,所以如果有任何工具接近您想要的,我会感到非常惊讶。我认为您将不得不自己动手。我假设你想要这些作为字符串,对吗?这是一些实用程序代码,至少可以帮助您入门:

public const int BITS_IN_BYTE = 8;
public const int BYTES_IN_INT = sizeof(int);
public const int BITS_IN_INT = BYTES_IN_INT * BITS_IN_BYTE;

/// <summary>
/// Display the bits in an integer
/// </summary>
/// <param name="intToDisplay">The integer to display</param>
/// <returns>A string representation of the bits</returns>
public string IntToBitString(int intToDisplay) {
    StringBuilder sb = new StringBuilder();
    AppendBitString(intToDisplay, sb);
    return sb.ToString();
}

/// <summary>
/// Displays the bits in an integer array
/// </summary>
/// <param name="intsToDisplay">Arrau to display</param>
/// <returns>String representation of the bits</returns>
public string IntArrayToBitString(int[] intsToDisplay) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < intsToDisplay.Length -1; i++) {
        AppendBitString(intsToDisplay[i], sb);
        sb.Append(' ');
    }
    if (intsToDisplay.Length - 1 > 0)
        AppendBitString(intsToDisplay[intsToDisplay.Length - 1], sb);
    return sb.ToString();
}

private void AppendBitString(int intToAppend, StringBuilder sb) {
    for (int j = BITS_IN_INT - 1; j >= 0; j--) {
        sb.Append((intToAppend >> j) & 1);
        if (j % 4 == 0 && j > 1)
            sb.Append(' ');
    }
}

/// <summary>
/// Creates an integer from a bit string. This method can be used
/// to explicitly set bits in an integer during testing.
/// </summary>
/// <example>
/// int i = bitUtil.IntFromBitString("0000 0000 0000 0100");
/// </example>
/// <param name="bitString">String representing the individual bits</param>
/// <returns></returns>
public int IntFromBitString(String bitString) {
    int returnInt = 0;
    int currentBitPos = bitString.Length;
    for (int i = bitString.Length - 1; i >= 0; i--) {
        char c = bitString[i];
        if (Char.IsWhiteSpace(c)) continue;

        if (c == '1') {
            returnInt |= 1 << (bitString.Length - currentBitPos);
        }
        currentBitPos--;
    }
    return returnInt;
}

/// <summary>
/// Tests the status of an individual bit in and integer. It is 0 based starting from the most
/// significant bit. 
/// </summary>
/// <param name="bits">The integer to test</param>
/// <param name="pos">The position we're interested in</param>
/// <returns>True if the bit is set, false otherwise</returns>
public bool IsBitOn(int bits, int pos) {
    int shiftAmnt = (BITS_IN_INT - 1) - pos;
    return ((bits >> shiftAmnt) & 1) == 1;
}

/// <summary>
/// Calculates the number of integers (as in an array of ints) required to
/// store a given number of bits
/// </summary>
/// <param name="bitsNeeded">The total count of required bits</param>
/// <returns>The number of integers required to represent a given bit count</returns>
public int RequiredSizeOfIntArray(int bitsNeeded) {
    return (bitsNeeded / BITS_IN_INT) + (((bitsNeeded % BITS_IN_INT) == 0) ? 0 : 1);
}

/// <summary>
/// Calculates which array element would hold the individual bit for a given bit position
/// </summary>
/// <param name="bitPos">The position of the interesting bit</param>
/// <returns>An index into an array of integers</returns>
public int ArrayPositionForBit(int bitPos) {
    return bitPos / BITS_IN_INT;
}

/// <summary>
/// Sets an individual bit to a given value
/// </summary>
/// <param name="bits">The integer containing the bits</param>
/// <param name="pos">The position in the integer to set</param>
/// <param name="isSet">True for on, False for off</param>
public void SetBit(ref int bits, int pos, bool isSet) {
    int posToSet = (BITS_IN_INT - 1) - pos;
    if (isSet)
        bits |= 1 << posToSet;
    else
        bits &= ~(1 << posToSet);
}

/// <summary>
/// Converts an array of integers into a comma seperated list
/// of hexidecimal values.
/// </summary>
/// <param name="bits">The array of integers</param>
/// <returns>String format</returns>
public String IntArrayToHexString(int[] bits) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < bits.Length - 1; i++) {
        sb.Append(bits[i].ToString("X"));
        sb.Append(',');
    }
    if (bits.Length > 0) {
        sb.Append(bits[bits.Length - 1].ToString("X"));
    }
    return sb.ToString();
}

/// <summary>
/// Parses a comma seperated list of hexidecimal values and
/// returns an array of integers for those values
/// </summary>
/// <param name="hexString">Comma seperated hex values</param>
/// <returns>integer array</returns>
public int[] HexStringToIntArray(String hexString) {
    string[] hexVals = hexString.Split(new char[] {','});
    int[] retInts = new int[hexVals.Length];
    for (int i = 0; i < hexVals.Length; i++) {
        retInts[i] = Int32.Parse(hexVals[i], System.Globalization.NumberStyles.HexNumber);
    }
    return retInts;
}
于 2009-02-04T22:39:12.363 回答
1

您确定需要使用位域吗?

根据我的经验,具有一组布尔数据成员的类几乎总是最佳选择。

我听说过使用位域而不是更大的布尔值(通常是一个字节)的唯一论点是它应该更快。与所有优化一样,在不测量性能的情况下进行是一个坏主意。

一旦它被封装在一个类中,如果您决定更改表示以进行优化,您就可以做到这一点,而不会遇到很多其他代码。

于 2009-02-05T00:37:44.933 回答