LINQ语句,其中结果计数用于表达式的条件

时间:2009-03-23 07:49:38

标签: vb.net linq

O'LINQ-fu大师,请帮忙。

我有一个要求,我必须使用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。感谢其他人的建议。

2 个答案:

答案 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)
);

如果要求(无论出于何种原因)

  • 赞成列表末尾的项目
  • 允许更多项目超过请求(5而不是3,但有时只有)

然后我会编写foreach循环。