0

我想预先分配一个List<List<double>>. 我知道我可以像这样预先分配一维列表:

List<double> MyList = new List<double>(SomeSize);

是否可以对嵌套列表执行此操作?在 C++ 中,我会这样做:

vector<vector<double>> MyList(OuterSize, vector<double>(InnerSize));

我似乎找不到在 C# 中执行此操作的有效方法,但我确信有一种方法......

4

3 回答 3

4

没有这样的方法,因为它不是一段连续的记忆。您必须自己编程 - 遍历外部维度。

List 不能与 C++ 数组真正相提并论。您为它分配内存,这意味着您避免了 List 的增长,但是 List 以 Count=0 开头,并且您必须添加元素,您无法通过索引器访问它。并且容量初始化是可选的!如果您提前知道最终长度,这只会稍微提高性能。

如果你和一个固定长度的数组相处,你可以分配一个多维数组,如

    int[,] x = new int[4,5];

立刻。

于 2020-01-07T22:05:22.417 回答
3

预分配列表的唯一方法是实例化它。您当然可以实例化外部列表,但是如果您想预先分配内部列表,您也必须实例化它们,这意味着您必须预先填充外部列表。

你可以使用

var list = Enumerable.Range(1, OuterSize)
     .Select( x => new List<T>(InnerSize) )
     .ToList();

这将创建一个包含Outersize元素的列表,每个元素都包含一个空列表,该列表已被预先分配以包含InnerSize元素。

但是,使用这种方法,您仍然必须将项目添加到内部列表中。如果你想ElementAt立即使用,那是行不通的。您必须预先填充内部列表。你可以使用:

var list = Enumerable.Range(1, OuterSize)
    .Select
    (
        x => Enumerable.Range(1, InnerSize)
                 .Select( y => default(T) )
                 .ToList()
    )
    .ToList();

这将实例化一个OuterSize元素列表,每个元素都是一个InnerSize元素列表(所有元素都具有相同的 null/默认值)。

于 2020-01-07T22:09:13.410 回答
1

您的 C++ 代码使用 的vector“填充构造函数”,它用代表第二维的列表实例化集合的第一维的所有元素。

List<T>C#对象没有允许指定初始容量的填充构造函数。现有的填充构造函数保证列表对于IEnumerable<T>参数提供的项目具有“足够的”容量,但它不保证容量将与参数可枚举的基数紧密匹配(部分原因是可枚举在设计上不会暴露它们的基数,因此与容量完全匹配的唯一方法是一次调整列表的底层数组一个元素的大小)。

您可以使用小 Linq 分两行执行此操作,三行使用传统循环,通过构造所需容量的空列表,然后添加第二维的对象,每个对象都初始化为所需的容量:

var myList = new List<List<T>>(5);
//Option A: Linq
myList.AddRange(Enumerable.Repeat(0, 5).Select(x => new List<string>(4)));
//Option B: Loop
for(i=0;i<5;i++)
    myList.Add(new List<string>(4));

在某种程度上,您的 C++ 将执行与其中任何一个类似的操作,只是 C# 中没有任何东西可以在构造函数后面抽象它。


分解第一个选项,该List<T>.AddRange()方法将 an 的每个元素添加IEnumerable<T>到列表中,并根据需要调整大小(它不必在这里,因为我们正在添加到指定的容量)。所以我们需要生成一个可枚举的列表序列。好吧,有一个 Linq 方法。该Enumerable.Repeat()方法生成一个可枚举的序列,该序列将指定的值重复指定的次数。但是,如果我们将“new List()”指定为要重复的值,则该构造函数将仅执行一次(在调用Repeat()以评估要传递的参数值之前),并且我们将获得对单个List重复四次的相同引用. 相反,我们想要实例化四个列表,因此我们需要将该构造函数定义为一行可重复的代码,

没有重载Enumerable.Repeat()接受 aFunc<T>并返回一个IEnumerable<T>; 如果你想用这个方法重复一个函数,你会得到一个IEnumerable<Func<T>>. 但是,该Select<T>()函数可以在输入的可枚举序列上调用,并生成 lambda 语句的所有结果的可枚举序列(一种匿名委托方法定义的形式),给定输入的每个可枚举元素作为参数(我们忽略它,因为这是垃圾;我们关心的输入可枚举是它有 5 个元素)。所以现在我们有一个Enumerable<List<string>>父 List 将进入其内部集合。

是的,我本可以更聪明,做一些类似的事情:

myList.AddRange(Enumerable.Repeat(()=>new List<string>(4), 5).Select(x => x()));

这做同样的事情,只是Repeat()方法重复的不是垃圾数据,它是对执行对象构造的 lambda 的引用,然后在Select()方法的 lambda 中调用它。但是,我现在有两个 lambda,它们被实现为包含此代码的类的私有公式命名函数,这给生成的对象添加了比我们真正需要的更多的麻烦。编译器也无法Repeat()从 lambda 语句中推断出函数的通用输出类型,因此上述代码无法编译;我必须明确指定泛型类型,并且这行代码(以及作为一种语言的 C#)已经足够冗长了。

于 2020-01-07T22:53:19.220 回答