2009-03-23 2 views
2

O 'LINQ-fu masters, aidez-nous s'il vous plaît.Instruction LINQ où le nombre de résultats est utilisé dans l'état de l'expression

J'ai une exigence où je dois ajouter des éléments dans une liste (Of T) (appelons-la cible) à partir d'un IEnumerable (Of T) (appelons-le Source) en utilisant Target.AddRange() dans VB.NET .

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

Le? La partie est une condition délicate qui est quelque chose comme: Tant que le nombre non encore énuméré n'est pas égal à ce qui est nécessaire pour remplir la liste au minimum requis, décidez aléatoirement si l'élément actuel doit être pris, sinon prenez l'élément. somethig comme ...

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 

La partie confondant est que _curr_count_of_items_taken doit être incrémentée chaque fois que l'instruction TakeWhile est satisfaite. Comment ferais-je cela?

Je suis également ouvert à une solution qui utilise toutes les autres méthodes LINQ (Aggregate, Where, etc.) au lieu de TakeWhile.

Si tout le reste échoue alors je vais revenir à l'aide d'un bon vieux pour boucle =)

Mais en espérant qu'il y est une solution LINQ. Merci d'avance pour toute suggestion.

EDIT: Bon vieux pour la version en boucle comme demandé:

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: David vient de sans effets secondaires réponse ferme à ce que je besoin tout en restant lisible. Peut-être qu'il est le seul à pouvoir comprendre mon pseudo-code mal communiqué =). Le OrderBy (GetRandomNumber) est brillant avec le recul. J'ai juste besoin de changer la partie Take (3) en Take (MinimumRequiredPlusAnOptionalRandomAmountExtra) et laisser tomber OrderBy et Select à la fin. Merci au reste pour des suggestions.

+0

J'ai supprimé ma réponse plutôt confuse parce que je ne peux pas comprendre votre exigence. Serait-il possible pour vous d'écrire la version while-loop dans votre question, donc nous avons un point de départ clair? –

Répondre

3

Si votre tâche consiste à extraire 3 images aléatoires d'une collection de 50 images aléatoires, cela fonctionne très bien.

target.AddRange(source.OrderBy(GetRandomNumber).Take(3)); 

Si vous avez besoin de préservation de l'ordre, ce n'est pas trop difficile d'ajouter:

target.AddRange(source 
    .Select((x, i) => new {x, i}) 
    .OrderBy(GetRandomNumber) 
    .Take(3) 
    .OrderBy(z => z.i) 
    .Select(z => z.x) 
); 

Si les exigences sont (pour une raison quelconque)

  • articles favorables à la fin de la liste
  • permettre plus d'articles à travers que demandé (5 au lieu de 3, mais seulement som etimes)

puis j'écrirais la boucle foreach.

+0

Ooh, cette version de conservation d'ordre est belle. –

5

Vous devez introduire un effet de bord, fondamentalement. En C#, cela est relativement facile - vous pouvez utiliser une expression lambda qui met à jour une variable capturée. En VB cela peut encore être possible, mais je ne voudrais pas deviner la syntaxe. Je ne tout à fait comprendre votre état (ça sonne un peu en arrière), mais vous pouvez faire quelque chose comme:

Le C# serait quelque chose comme:

int count = 0; 

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

target.AddRange(query); 

Le nombre sera incrémenté à chaque fois qu'un l'article est réellement pris.

Notez que je soupçonne que vous voulez réellement Where au lieu de TakeWhile - sinon la première fois que le rng donne un nombre élevé, la séquence se termine.

EDIT: Si vous ne pouvez pas utiliser les effets secondaires directement vous pouvez être en mesure d'utiliser un hack horrible. Je ne l'ai pas essayé, mais ...

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

En d'autres termes, vous mettez du côté effet dans une méthode distincte, et appeler la méthode utilisant un passage par référence pour le compteur.Aucune idée si cela fonctionnerait en VB, mais cela vaut peut-être la peine d'essayer. D'autre part, une boucle pourrait être plus simple ...

En tant que complètement de manière différente, votre source est-elle déjà une collection en mémoire, que vous pouvez parcourir à peu de frais? Dans ce cas, il suffit d'utiliser:

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

En d'autres termes, saisir certainement les premiers n éléments, puis à nouveau commencer, sauter les n premiers éléments et prendre le reste en fonction de la condition.

+0

+1 parce que c'est fondamentalement la même réponse que j'ai donnée! Je ne suis pas sûr d'avoir compris l'exigence, mais je pense que Lambdas dans VB est limité à des expressions pures, pas à des blocs de déclaration, donc ça ne va probablement pas être joli. –

+0

Jon, merci pour la suggestion. J'avais pensé à la manière d'effet secondaire mais VB.NET n'autorise pas les lambdas multi-instructions. Donc, ne peut pas faire le {count ++; return x;} part sans un peu de piratage qui le rendrait probablement plus verbeux que le for-loop = /. D'autres idées? – Fung

+0

Ajout d'autres options. –

Questions connexes