我有一个要求,我必须使用VB.NET中的Target.AddRange()从IEnumerable(Of T)(让我们称之为Source)将项添加到List(Of T)(让我们称之为Target)。
Target.AddRange(Source.TakeWhie(Function(X, Index) ?))
? part是一个棘手的条件,类似于:只要尚未列举的计数不等于将列表填充到所需的最小值,然后随机决定是否应该采用当前项目,否则采取项目 Somethig喜欢......
Source.Count() - Index = _minimum_required - _curr_count_of_items_taken _
OrElse GetRandomNumberBetween1And100() <= _probability_this_item_is_taken
' _minimum_required and _probability_this_item_is_taken are constants
混淆部分是每次满足TakeWhile语句时都需要递增_curr_count_of_items_taken。我该怎么做呢?
我也对使用任何其他LINQ方法(Aggregate,Where等)而不是TakeWhile的解决方案持开放态度。
如果所有其他方法都失败了,那么我将回到使用旧的for-loop =)
但希望有一个LINQ解决方案。提前感谢任何建议。
编辑:根据要求提供良好的旧循环版本:
Dim _source_total As Integer = Source.Count()
For _index As Integer = 0 To _source_total - 1
If _source_total - _index = MinimumRows - Target.Count _
OrElse NumberGenerator.GetRandomNumberBetween1And100 <= _possibility_item_is_taken Then
Target.Add(Source(_index))
End If
Next
EDITDIT: 大卫的无副作用答案接近我所需要的,同时保持可读性。也许他是唯一一个能理解我传达不良的伪代码的人。事后看来,OrderBy(GetRandomNumber)非常出色。我只需要将Take(3)部分更改为Take(MinimumRequiredPlusAnOptionalRandomAmountExtra)并在结尾处删除OrderBy和Select。感谢其他人的建议。
答案 0 :(得分:5)
基本上你需要引入副作用。
在C#中,这相对容易 - 您可以使用lambda表达式来更新捕获的变量。在VB中,这可能仍然存在,但我不想猜测语法。我不完全了解你的情况(听起来有点倒退),但你可以这样做:
C#会是这样的:
int count = 0;
var query = source.TakeWhile(x => count < minimumRequired ||
rng.Next(100) < probability)
.Select(x => { count++; return x; });
target.AddRange(query);
每次实际拍摄物品时,计数都会递增。
请注意,我怀疑您实际上需要Where
而不是TakeWhile
- 否则第一次rng
给出一个高数字时,序列将结束。
编辑:如果你不能直接使用副作用 ,你可能会使用可怕的黑客攻击。我没试过,但是......
public static T Increment<T>(ref int counter, T value)
{
counter++;
return value;
}
...
int count = 0;
var query = source.TakeWhile(x => count < minimumRequired ||
rng.Next(100) < probability)
.Select(x => Increment(ref count, x));
target.AddRange(query);
换句话说,您将副作用放入单独的方法中,并使用计数器的pass-by-reference调用该方法。不知道它是否适用于VB,但可能值得一试。另一方面,循环可能更简单......
作为完全接近它的不同方式,您的源是否已经是内存中的集合,您可以廉价地迭代它?如果是这样,请使用:
var query = Enumerable.Concat(source.Take(minimumRequired),
source.Skip(minimumRequired)
.TakeWhile(condition));
换句话说,绝对抓住前n个元素,然后重新开始,跳过前n个元素,并根据条件接受其余的元素。
答案 1 :(得分:3)
如果你的任务是从50个随机图像的集合中提取3个随机图像,这很有用。
target.AddRange( source.OrderBy(GetRandomNumber).Take(3) );
如果您需要保留订单,那么添加起来并不难:
target.AddRange( source
.Select( (x, i) => new {x, i})
.OrderBy(GetRandomNumber)
.Take(3)
.OrderBy( z => z.i)
.Select( z => z.x)
);
如果要求(无论出于何种原因)
然后我会编写foreach循环。