How to populate/instantiate a C# array with a single value?
我知道C中的值类型的实例化数组会自动填充该类型的默认值(例如,对于bool为false,对于int为0等)。
有没有一种方法可以用不是默认值的种子值自动填充数组?在创建之后还是内置方法(比如Java的数组?fILE())?假设我想要一个默认为真的布尔数组,而不是假数组。是否有一种内置的方法来实现这一点,或者您只需要使用for循环迭代整个数组?
1 2 3 4 5 6 7 8 9 10 |
必须遍历数组并将每个值"重置"为"真"似乎效率很低。这附近还有吗?也许通过翻转所有的值?
在输入这个问题并考虑之后,我猜想默认值只是C处理这些对象在后台的内存分配的结果,所以我想这可能是不可能的。但我还是想确定一下!
1 | Enumerable.Repeat(true, 1000000).ToArray(); |
不知道框架方法,但您可以编写一个快速帮助器来为您完成它。
1 2 3 4 5 | public static void Populate<T>(this T[] arr, T value ) { for ( int i = 0; i < arr.Length;i++ ) { arr[i] = value; } } |
创建一个具有一千个
1 | var items = Enumerable.Repeat<bool>(true, 1000).ToArray(); // Or ToList(), etc. |
同样,您可以生成整数序列:
2对于大数组或可变大小的数组,您可能应该使用:
1 | Enumerable.Repeat(true, 1000000).ToArray(); |
对于小数组,可以使用C 3中的集合初始化语法:
1 |
集合初始化语法的好处是,您不必在每个槽中使用相同的值,并且可以使用表达式或函数来初始化槽。另外,我认为您可以避免将阵列插槽初始化为默认值的成本。例如:
1 |
如果数组太大,则应使用BitArray。它对每个bool使用1位,而不是字节(如bools数组中的字节),还可以使用位运算符将所有位设置为true。或者只是初始化为true。如果你只需要做一次,那只会花费更多。
1 2 3 4 5 |
好吧,经过一点谷歌搜索和阅读,我发现了:
1 2 |
这当然更接近我要找的。但我不确定这是否比在for循环中遍历原始数组并只更改值要好。事实上,经过一次快速测试后,它看起来慢了大约5倍。所以不是一个很好的解决方案!
不幸的是,我不认为有直接的方法,但是我认为您可以为数组类编写一个扩展方法来实现这一点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | class Program { static void Main(string[] args) { int[] arr = new int[1000]; arr.Init(10); Array.ForEach(arr, Console.WriteLine); } } public static class ArrayExtensions { public static void Init<T>(this T[] array, T defaultVaue) { if (array == null) return; for (int i = 0; i < array.Length; i++) { array[i] = defaultVaue; } } } |
或者…您可以简单地使用倒置逻辑。让
代码样本
1 2 3 4 5 6 7 8 | // bool[] isVisible = Enumerable.Repeat(true, 1000000).ToArray(); bool[] isHidden = new bool[1000000]; // Crazy-fast initialization! // if (isVisible.All(v => v)) if (isHidden.All(v => !v)) { // Do stuff! } |
并行实现呢
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | public static void InitializeArray<T>(T[] array, T value) { var cores = Environment.ProcessorCount; ArraySegment<T>[] segments = new ArraySegment<T>[cores]; var step = array.Length / cores; for (int i = 0; i < cores; i++) { segments[i] = new ArraySegment<T>(array, i * step, step); } var remaining = array.Length % cores; if (remaining != 0) { var lastIndex = segments.Length - 1; segments[lastIndex] = new ArraySegment<T>(array, lastIndex * step, array.Length - (lastIndex * step)); } var initializers = new Task[cores]; for (int i = 0; i < cores; i++) { var index = i; var t = new Task(() => { var s = segments[index]; for (int j = 0; j < s.Count; j++) { array[j + s.Offset] = value; } }); initializers[i] = t; t.Start(); } Task.WaitAll(initializers); } |
当只初始化一个数组时,这段代码的力量是看不到的,但我认为您绝对应该忘记的"纯"的。
下面的代码结合了小副本和数组的简单迭代。大副本的副本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public static void Populate<T>( T[] array, int startIndex, int count, T value ) { if ( array == null ) { throw new ArgumentNullException("array" ); } if ( (uint)startIndex >= array.Length ) { throw new ArgumentOutOfRangeException("startIndex","" ); } if ( count < 0 || ( (uint)( startIndex + count ) > array.Length ) ) { throw new ArgumentOutOfRangeException("count","" ); } const int Gap = 16; int i = startIndex; if ( count <= Gap * 2 ) { while ( count > 0 ) { array[ i ] = value; count--; i++; } return; } int aval = Gap; count -= Gap; do { array[ i ] = value; i++; --aval; } while ( aval > 0 ); aval = Gap; while ( true ) { Array.Copy( array, startIndex, array, i, aval ); i += aval; count -= aval; aval *= 2; if ( count <= aval ) { Array.Copy( array, startIndex, array, i, count ); break; } } } |
使用int[]数组的不同数组长度的基准是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 2 Iterate: 1981 Populate: 2845 4 Iterate: 2678 Populate: 3915 8 Iterate: 4026 Populate: 6592 16 Iterate: 6825 Populate: 10269 32 Iterate: 16766 Populate: 18786 64 Iterate: 27120 Populate: 35187 128 Iterate: 49769 Populate: 53133 256 Iterate: 100099 Populate: 71709 512 Iterate: 184722 Populate: 107933 1024 Iterate: 363727 Populate: 126389 2048 Iterate: 710963 Populate: 220152 4096 Iterate: 1419732 Populate: 291860 8192 Iterate: 2854372 Populate: 685834 16384 Iterate: 5703108 Populate: 1444185 32768 Iterate: 11396999 Populate: 3210109 |
第一列是数组大小,然后是使用简单迭代(@jaredpared实现)进行复制的时间。这个方法的时间在那之后。这些是使用四个整数的结构数组的基准
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 2 Iterate: 2473 Populate: 4589 4 Iterate: 3966 Populate: 6081 8 Iterate: 7326 Populate: 9050 16 Iterate: 14606 Populate: 16114 32 Iterate: 29170 Populate: 31473 64 Iterate: 57117 Populate: 52079 128 Iterate: 112927 Populate: 75503 256 Iterate: 226767 Populate: 133276 512 Iterate: 447424 Populate: 165912 1024 Iterate: 890158 Populate: 367087 2048 Iterate: 1786918 Populate: 492909 4096 Iterate: 3570919 Populate: 1623861 8192 Iterate: 7136554 Populate: 2857678 16384 Iterate: 14258354 Populate: 6437759 32768 Iterate: 28351852 Populate: 12843259 |
这也有效……但可能是不必要的
1 2 |
如果您计划只设置数组中的一些值,但大多数时候想要获取(自定义)默认值,可以尝试如下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public class SparseArray<T> { private Dictionary<int, T> values = new Dictionary<int, T>(); private T defaultValue; public SparseArray(T defaultValue) { this.defaultValue = defaultValue; } public T this [int index] { set { values[index] = value; } get { return values.ContainsKey(index) ? values[index] ? defaultValue; } } } |
您可能需要实现其他接口以使其有用,例如数组本身上的接口。
无法将数组中的所有元素设置为单个操作,除非该值是元素类型的默认值。
例如,如果它是一个整数数组,您可以通过一个操作将它们全部设置为零,如下所示:
我知道我参加晚会迟到了,但有个主意。编写一个包装器,该包装器具有与包装值之间的转换运算符,以便它可以用作包装类型的代理。这实际上是受到了@l33t愚蠢的回答的启发。
首先(来自C++)我意识到在C语言中,当构建数组的元素时,不调用默认的CtoR。相反——即使存在用户定义的默认构造函数!--所有数组元素都初始化为零。那的确让我吃惊。
因此,一个包装类,它只提供一个默认的cTor与期望值将工作的数组在C++中,但不是在C.*。解决方法是让包装器类型在转换时将0映射到所需的种子值。这样一来,零初始化值就可以用seed进行初始化,从而达到所有实际目的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | public struct MyBool { private bool _invertedValue; public MyBool(bool b) { _invertedValue = !b; } public static implicit operator MyBool(bool b) { return new MyBool(b); } public static implicit operator bool(MyBool mb) { return !mb._invertedValue; } } static void Main(string[] args) { MyBool mb = false; // should expose false. Console.Out.WriteLine("false init gives false:" + !mb); MyBool[] fakeBoolArray = new MyBool[100]; Console.Out.WriteLine("Default array elems are true:" + fakeBoolArray.All(b => b) ); fakeBoolArray[21] = false; Console.Out.WriteLine("Assigning false worked:" + !fakeBoolArray[21]); fakeBoolArray[21] = true; // Should define ToString() on a MyBool, // hence the !! to force bool Console.Out.WriteLine("Assigning true again worked:" + !!fakeBoolArray[21]); } |
此模式适用于所有值类型。例如,如果需要使用4进行初始化等,则可以将int的0映射到4。
我很想在C++中做一个模板,提供种子值作为模板参数,但是我理解在C语言中是不可能的。还是我错过了什么?(当然,在C++映射中根本不需要,因为可以提供一个默认的CCTR,它将被调用数组元素。)
FWWW,这里有一个C++等价物:HTTPS://IDENNE.COM/WG8YYH。
如果可以反转逻辑,可以使用
1 2 3 4 5 | int upperLimit = 21; double optimizeMe = Math.Sqrt(upperLimit); bool[] seiveContainer = new bool[upperLimit]; Array.Clear(seiveContainer, 0, upperLimit); |
在创建数组的地方创建一个私有类,并为它创建一个getter和setter。除非您需要数组中的每个位置都是唯一的,比如随机的,否则使用int?作为数组,然后在get上,如果位置等于空,则填充该位置并返回新的随机值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | IsVisibleHandler { private bool[] b = new bool[10000]; public bool GetIsVisible(int x) { return !b[x] } public void SetIsVisibleTrueAt(int x) { b[x] = false //!true } } |
或使用
1 2 3 4 | public void SetIsVisibleAt(int x, bool isTrue) { b[x] = !isTrue; } |
二传手。
关于这个问题还有更多的答案(副本?)问题:在c中,memset的等价物是什么?
有人对替代方案进行了基准测试(包括不安全的版本,但他们没有尝试
这里是另一个appraoch和
或
1 2 3 |