我需要创建一个数字组合列表。数字非常小,因此我可以使用byte
而不是int
。但是,它需要许多嵌套循环才能获得所有可能的组合。我想知道是否有更有效率的方式去做我以后的事情。到目前为止的代码是:
var data = new List<byte[]>();
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
{
data.Add(new [] {a, b, c, d, e, f, g, h, i, j, k, l, m});
}
我正在考虑使用BitArray
之类的东西,但我不确定如何合并它。
任何建议都将不胜感激。或者,也许这是做我想做的最快的方式吗?
修改 快点的几点(道歉我没有把这些放在原帖中):
byte
比使用int
更快,我保证。它在内存使用方面也要好得多,有67m +字节数组而不是ints ConcurrentBag
) - 但是我很高兴被证明是错的:)结论
Caramiriel提供了一个很好的微优化,可以减少循环的时间,所以我已经将答案标记为正确。 Eric还提到预先分配List更快。但是,在这个阶段,似乎嵌套循环实际上是最快的方式(令人沮丧,我知道!)。
如果您想要尝试使用StopWatch
进行基准测试,请在每个循环中使用13个循环计数最多4个 - 这将使列表中的行数大约为67米。在我的机器上(i5-3320M 2.6GHz),优化版本需要大约2.2秒。
答案 0 :(得分:60)
您可以使用结构的属性并提前分配结构。我在下面的示例中删除了一些级别,但我相信您将能够找出具体细节。运行速度比原始速度快5-6倍(发布模式)。
块:
struct ByteBlock
{
public byte A;
public byte B;
public byte C;
public byte D;
public byte E;
}
循环:
var data = new ByteBlock[2*3*4*3*4];
var counter = 0;
var bytes = new ByteBlock();
for (byte a = 0; a < 2; a++)
{
bytes.A = a;
for (byte b = 0; b < 3; b++)
{
bytes.B = b;
for (byte c = 0; c < 4; c++)
{
bytes.C = c;
for (byte d = 0; d < 3; d++)
{
bytes.D = d;
for (byte e = 0; e < 4; e++)
{
bytes.E = e;
data[counter++] = bytes;
}
}
}
}
}
它更快,因为每次将其添加到列表时都不会分配新列表。此外,由于它正在创建此列表,因此需要引用其他所有值(a,b,c,d,e)。您可以假设每个值仅在循环内修改一次,因此我们可以对其进行优化(数据位置)。
另请阅读副作用的评论。
使用T[]
代替List<T>
编辑答案。
答案 1 :(得分:33)
你正在做的是计算(使用可变基数,但仍然在计算)。
由于您使用的是C#,我认为您不希望使用有用的内存布局和数据结构,让您真正优化您的代码。
所以在这里,我发布了一些不同的东西,这可能不适合你的情况,但值得注意的是:如果你真的以稀疏的方式访问列表,这里有一个让你计算线性时间中的第i个元素(而不是其他答案的指数)
class Counter
{
public int[] Radices;
public int[] this[int n]
{
get
{
int[] v = new int[Radices.Length];
int i = Radices.Length - 1;
while (n != 0 && i >= 0)
{
//Hope C# has an IL-opcode for div-and-reminder like x86 do
v[i] = n % Radices[i];
n /= Radices[i--];
}
return v;
}
}
}
您可以这样使用此类
Counter c = new Counter();
c.Radices = new int[] { 2,3,4,3,4,3,3,4,2,4,4,3,4};
现在c[i]
与您的列表相同,请将其命名为l
,l[i]
。
正如您所看到的,您可以轻松避免所有这些循环:)即使您预先计算了所有列表,因为您可以简单地实现Carry-Ripple计数器。
计数器是一门非常研究的科目,如果你愿意,我强烈建议你去寻找一些文献。
答案 2 :(得分:14)
方法1
提高速度的一种方法是,如果您打算继续使用List<byte[]>
,请指定容量。
var data = new List<byte[]>(2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4);
方法2
此外,您可以直接使用System.Array
来获得更快的访问权限。如果你的问题坚持要事先在内存中物理填充每个元素,我推荐这种方法。
var data = new byte[2 * 3 * 4 * 3 * 4 * 3 * 3 * 4 * 2 * 4 * 4 * 3 * 4][];
int counter = 0;
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
data[counter++] = new[] { a, b, c, d, e, f, g, h, i, j, k, l, m };
在我的计算机上完成 596 ms,比所讨论的代码(需要658毫秒)快< 10.4%。
方法3
或者,您可以使用以下技术进行低成本初始化,以便以稀疏方式进行访问。当仅需要一些元素并且预先确定所有元素时,这是特别有利的。此外,当内存不足时,这些技术可能成为处理更大元素时唯一可行的选择。
在这个实现中,每个元素在访问时都是懒散地,即时地确定的。当然,这是以访问期间产生的额外CPU为代价的。
class HypotheticalBytes
{
private readonly int _c1, _c2, _c3, _c4, _c5, _c6, _c7, _c8, _c9, _c10, _c11, _c12;
private readonly int _t0, _t1, _t2, _t3, _t4, _t5, _t6, _t7, _t8, _t9, _t10, _t11;
public int Count
{
get { return _t0; }
}
public HypotheticalBytes(
int c0, int c1, int c2, int c3, int c4, int c5, int c6, int c7, int c8, int c9, int c10, int c11, int c12)
{
_c1 = c1;
_c2 = c2;
_c3 = c3;
_c4 = c4;
_c5 = c5;
_c6 = c6;
_c7 = c7;
_c8 = c8;
_c9 = c9;
_c10 = c10;
_c11 = c11;
_c12 = c12;
_t11 = _c12 * c11;
_t10 = _t11 * c10;
_t9 = _t10 * c9;
_t8 = _t9 * c8;
_t7 = _t8 * c7;
_t6 = _t7 * c6;
_t5 = _t6 * c5;
_t4 = _t5 * c4;
_t3 = _t4 * c3;
_t2 = _t3 * c2;
_t1 = _t2 * c1;
_t0 = _t1 * c0;
}
public byte[] this[int index]
{
get
{
return new[]
{
(byte)(index / _t1),
(byte)((index / _t2) % _c1),
(byte)((index / _t3) % _c2),
(byte)((index / _t4) % _c3),
(byte)((index / _t5) % _c4),
(byte)((index / _t6) % _c5),
(byte)((index / _t7) % _c6),
(byte)((index / _t8) % _c7),
(byte)((index / _t9) % _c8),
(byte)((index / _t10) % _c9),
(byte)((index / _t11) % _c10),
(byte)((index / _c12) % _c11),
(byte)(index % _c12)
};
}
}
}
在我的计算机上完成 897 毫秒(也在方法2 中创建和添加
Array
),这大约是<强>比所讨论的代码慢了36.3%(需要658ms)。
答案 3 :(得分:13)
在我的机器上,这会生成222 ms vs 760 ms(13 for for循环)的组合:
private static byte[,] GenerateCombinations(byte[] maxNumberPerLevel)
{
var levels = maxNumberPerLevel.Length;
var periodsPerLevel = new int[levels];
var totalItems = 1;
for (var i = 0; i < levels; i++)
{
periodsPerLevel[i] = totalItems;
totalItems *= maxNumberPerLevel[i];
}
var results = new byte[totalItems, levels];
Parallel.For(0, levels, level =>
{
var periodPerLevel = periodsPerLevel[level];
var maxPerLevel = maxNumberPerLevel[level];
for (var i = 0; i < totalItems; i++)
results[i, level] = (byte)(i / periodPerLevel % maxPerLevel);
});
return results;
}
答案 4 :(得分:8)
var numbers = new[] { 2, 3, 4, 3, 4, 3, 3, 4, 2, 4, 4, 3, 4 };
var result = (numbers.Select(i => Enumerable.Range(0, i))).CartesianProduct();
使用扩展方法 http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
{
// base case:
IEnumerable<IEnumerable<T>> result =
new[] { Enumerable.Empty<T>() };
foreach (var sequence in sequences)
{
// don't close over the loop variable (fixed in C# 5 BTW)
var s = sequence;
// recursive case: use SelectMany to build
// the new product out of the old one
result =
from seq in result
from item in s
select seq.Concat(new[] { item });
}
return result;
}
答案 5 :(得分:8)
List在内部有一个数组,用于存储值,具有固定长度。当您调用List.Add时,它会检查是否有足够的空间。当它无法添加新元素时,它将创建一个更大尺寸的新数组,复制所有之前的元素,然后添加新元素。这需要相当多的周期。
由于你已经知道了元素的数量,你可以创建正确大小的列表,这应该快得多。
另外,不确定如何访问这些值,但你可以创建一个这样的东西并将图像保存在代码中(从磁盘加载它可能比你现在做的慢。你现在读了多少次/写这个东西?
答案 6 :(得分:5)
这是一种只需要2个循环的不同方式。我们的想法是增加第一个元素,如果这个数字超过了增加下一个元素。
您可以使用currentValues.Clone并将该克隆版本添加到列表中,而不是显示数据。对我而言,这比你的版本跑得快。
byte[] maxValues = {2, 3, 4};
byte[] currentValues = {0, 0, 0};
do {
Console.WriteLine("{0}, {1}, {2}", currentValues[0], currentValues[1], currentValues[2]);
currentValues[0] += 1;
for (int i = 0; i <= maxValues.Count - 2; i++) {
if (currentValues[i] < maxValues[i]) {
break;
}
currentValues[i] = 0;
currentValues[i + 1] += 1;
}
// Stop the whole thing if the last number is over
// } while (currentValues[currentValues.Length-1] < maxValues[maxValues.Length-1]);
} while (currentValues.Last() < maxValues.Last());
答案 7 :(得分:3)
你的所有数字都是编译时间常数。
如何将所有循环展开到列表中(使用您的程序编写代码):
data.Add(new [] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
data.Add(new [] {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
etc.
这至少应该消除for循环的开销(如果有的话)。
我对C#不太熟悉,但似乎有一些序列化对象的方法。如果你刚刚生成了List并以某种形式序列化了怎么办?我不确定反序列化是否比创建List和添加元素更快。
答案 8 :(得分:2)
您是否需要将结果作为数组数组?使用当前设置,内部阵列的长度是固定的,可以用结构替换。这将允许整个事物被保留为一个连续的内存块,并提供更容易访问的元素(不知道你如何使用这个东西)。
下面的方法要快得多(41毫秒对比我盒子上原件的1071毫秒):
struct element {
public byte a;
public byte b;
public byte c;
public byte d;
public byte e;
public byte f;
public byte g;
public byte h;
public byte i;
public byte j;
public byte k;
public byte l;
public byte m;
}
element[] WithStruct() {
var t = new element[3981312];
int z = 0;
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
{
t[z].a = a;
t[z].b = b;
t[z].c = c;
t[z].d = d;
t[z].e = e;
t[z].f = f;
t[z].g = g;
t[z].h = h;
t[z].i = i;
t[z].j = j;
t[z].k = k;
t[z].l = l;
t[z].m = m;
z++;
}
return t;
}
答案 9 :(得分:1)
使用Parallel.For()
运行它怎么样? (结构优化赞誉为 @Caramiriel )。我略微修改了值(a是5而不是2)所以我对结果更有信心。
var data = new ConcurrentStack<List<Bytes>>();
var sw = new Stopwatch();
sw.Start();
Parallel.For(0, 5, () => new List<Bytes>(3*4*3*4*3*3*4*2*4*4*3*4),
(a, loop, localList) => {
var bytes = new Bytes();
bytes.A = (byte) a;
for (byte b = 0; b < 3; b++) {
bytes.B = b;
for (byte c = 0; c < 4; c++) {
bytes.C = c;
for (byte d = 0; d < 3; d++) {
bytes.D = d;
for (byte e = 0; e < 4; e++) {
bytes.E = e;
for (byte f = 0; f < 3; f++) {
bytes.F = f;
for (byte g = 0; g < 3; g++) {
bytes.G = g;
for (byte h = 0; h < 4; h++) {
bytes.H = h;
for (byte i = 0; i < 2; i++) {
bytes.I = i;
for (byte j = 0; j < 4; j++) {
bytes.J = j;
for (byte k = 0; k < 4; k++) {
bytes.K = k;
for (byte l = 0; l < 3; l++) {
bytes.L = l;
for (byte m = 0; m < 4; m++) {
bytes.M = m;
localList.Add(bytes);
}
}
}
}
}
}
}
}
}
}
}
}
return localList;
}, x => {
data.Push(x);
});
var joinedData = _join(data);
_join()
是一种私有方法,定义为:
private static IList<Bytes> _join(IEnumerable<IList<Bytes>> data) {
var value = new List<Bytes>();
foreach (var d in data) {
value.AddRange(d);
}
return value;
}
在我的系统上,此版本运行速度提高约6倍(1.718秒对0.266秒)。
答案 10 :(得分:0)
你的一些数字完全适合整数数字的位数,所以你可以用高级数字“打包”它们:
pthread_join()
当然,这会使代码的可读性降低,但是您保存了一个循环。这可以在每次其中一个数字是2的幂时完成,在你的情况下是七次。
答案 11 :(得分:0)
这是另一种解决方案。在VS之外,它运行速度高达437.5毫秒,比原始代码快了26%(在我的计算机上为593.7):
static List<byte[]> Combinations(byte[] maxs)
{
int length = maxs.Length;
int count = 1; // 3981312;
Array.ForEach(maxs, m => count *= m);
byte[][] data = new byte[count][];
byte[] counters = new byte[length];
for (int r = 0; r < count; r++)
{
byte[] row = new byte[length];
for (int c = 0; c < length; c++)
row[c] = counters[c];
data[r] = row;
for (int i = length - 1; i >= 0; i--)
{
counters[i]++;
if (counters[i] == maxs[i])
counters[i] = 0;
else
break;
}
}
return data.ToList();
}