2009-03-23 13 views
2

O 'LINQ-fuマスター、お手上げです。結果カウントが式の条件で使用されるLINQステートメント

VB.NETでTarget.AddRange()を使用してIEnumerable(Of T)からList(Of T)(これをTargetと呼ぶ)にアイテムを追加する必要がある。

Target.AddRange(Source.TakeWhie(Function(X, Index) ?)) 

?部分は、次のようなトリッキーな状態です:まだ列挙されていない数がリストを最小限にするのに必要なものと等しくない場合は、現在の項目を取るべきかどうかをランダムに決定します。 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 

交絡部分は_curr_count_of_items_takenがTakeWhile文が満足されるたびにインクリメントする必要があるということです。それをどうやってやりますか?

私はTakeWhileではなく、他のLINQメソッド(集計、場所など)を使用するソリューションにもオープンしています。

すべてが他の後、私は戻ってforループ古き良きを使用することになります失敗した場合=)

しかし、LINQのソリューションがあることを望ん。事前に感謝の意を表します。

EDIT:

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:要求されたとして、古き良きforループバージョン ダビデの無副作用の答えが読める滞在しながら、私は必要なものに閉じています。たぶん彼は私の疎通した擬似コード=を理解できる唯一の人です。 OrderBy(GetRandomNumber)は後天的に見事です。 Take(3)の部分をTake(MinimumRequiredPlusAnOptionalRandomAmountExtra)に変更し、最後にOrderByとSelectを削除するだけです。残りのおかげで提案に感謝します。

+0

私はあなたの要件を理解できないので、私はかなり混乱した答えを削除しました。あなたの質問にwhileループバージョンを書くことは可能でしょうか?明確な出発点がありますか? –

答えて

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

要件が最後に

  • 賛成項目(何らかの理由で)にしている場合リストのうち
  • は、要求されたものより多くのアイテムを許可します(3ではなく5、som etimes)

次にforeachループを作成します。

+0

ああ、その注文保存版は美しいです。 –

5

基本的に副作用を導入する必要があります。

これは比較的簡単です。キャプチャされた変数を更新するラムダ式を使用できます。 VBでこれはまだ可能かもしれませんが、構文を推測するのは嫌です。私はないかなりあなたの条件を理解し(それが逆方向に少し聞こえる)が、あなたのような何かを行うことができます:カウントは毎回インクリメントされます

int count = 0; 

var query = source.TakeWhile(x => count < minimumRequired || 
            rng.Next(100) < probability) 
        .Select(x => { count++; return x; }); 

target.AddRange(query); 

:のようなC#はものになるだろう

をアイテムは実際に撮影されます。私はあなたが実際に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); 

していない、あなたは別のメソッドに副作用を入れ、参照渡しカウンターのために使用してメソッドを呼び出します。VBで動作するかどうかは考えられませんが、試してみる価値があります。一方、ループはもっとシンプルになるかもしれません...

完全にに近づく別の方法として、あなたのソースはすでにインメモリコレクションであり、安価に反復処理できますか?もしそうなら、単に使用:

var query = Enumerable.Concat(source.Take(minimumRequired), 
           source.Skip(minimumRequired) 
            .TakeWhile(condition)); 

言い換えれば、最初のn個の要素をスキップし、条件に基づいて休息を取る、間違いなく最初のn個の要素をつかむし、再度開始します。

+0

+1それは私が与えたのと基本的に同じ答えなので+1!私が要件を理解しているかどうかは不明ですが、VBのLambdasは文ブロックではなく純粋な式に限定されていると思いますので、おそらくそれほど美しくはありません。 –

+0

ジョン、提案に感謝します。私は副作用の方法を考えていたが、VB.NETは複数のステートメントのラムダを許さない。だから、{count ++;おそらくfor-loop = /よりも冗長になるようなハッキングをせずに返すことができます。他のアイデア? – Fung

+0

その他のオプションが追加されました。 –

関連する問題