我有一组选项,一些是正交的(可以以任何组合进行组合),一些是互斥的(只允许使用该组中的一个),并且需要选择一组
enum
值,以便它们可以与位组合-wise 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 个值,...
我所知道的最好的通用方法与其说是一种工具,不如说是一种约定:像这样定义位标志列表:
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)。
为了表示一组“独占”的
n
选项(即必须选择一个),我们至少需要ceil(log2(n))
位。例如,选项 k
可以由 base-k
中的数字 2
表示。
为了表示一组“n
”选项的“
正交”(即可以选择
0, 1, ..., n
大小的任意组合),我们至少需要n
位。例如,选项 k0, k1, k2
可以用除 0, 1, 2
之外的位均为零的二进制数来表示。
因此,为了同时表示多个选项集,我们将每个选项集所需的位数相加(取决于它是“独占”还是“正交”)以获得所需的总位数。
简而言之,要选择枚举值,
k
使用 k << r
k0, k1, ..., k{n-1}
使用 0x1 << r, 0x1 << (r+1), ..., 0x1 << (r+n-1)
其中 offset
r
是前面选项集使用的位数。
如何自动化此构建的示例,在 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;
}
我不知道有什么工具,但这里有一个技巧可以使唯一位枚举更容易生成:
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
};
使用 2 的幂,以便每个标志对应于一个位位置。
您可以使用标准枚举(在 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;
}
您确定需要使用位字段吗?
根据我的经验,具有一组布尔数据成员的类几乎总是最佳选择。
我听过的关于使用位字段而不是更大的布尔值(通常是一个字节)的唯一论点是它据说更快。与所有优化一样,在不测量性能的情况下进行优化是一个坏主意。
一旦将其封装在类中,如果您决定更改表示形式以进行优化,则无需执行大量其他代码即可完成此操作。