2010-04-08 14 views
6

Ho una matrice di elementi in cui l'elemento ha un valore booleano Contrassegnato.utilizzando Linq per partizionare i dati negli array

1 flagged 
2 not flagged 
3 not flagged 
4 flagged 
5 not flagged 
6 not flagged 
7 not flagged 
8 flagged 
9 not flagged 

voglio romperlo in array basati sull'indicatore battente bandiera

uscita>

array 1 {1,2,3} 
array 2 {4,5,6,7} 
array 3 {8,9} 

risposta

0

Non credo che LINQ è adatto per questo molto bene. Potrebbe essere fatto con Aggregate(), ma penso che sarebbe meglio semplicemente fare un ciclo con un foreach() per costruire il risultato.

7

Linq non ha un operatore per questo, ma ho scritto un metodo di estensione che si può essere in grado di utilizzare (nel processo di sottoporlo a MoreLinq, che si dovrebbe anche verificare):

Usando l'operatore di seguito, si può scrivere:

var result = 
    items.Segment((item,prevItem,idx) => item.Flagged) 
     .Select(seq => seq.ToArray()) // converts each sequence to an array 
     .ToList(); 

Ecco il codice del metodo di estensione:

public static IEnumerable<IEnumerable<T>> Segment<T>(IEnumerable<T> sequence, Func<T, T, int, bool> newSegmentIdentifier) 
    { 
     var index = -1; 
     using (var iter = sequence.GetEnumerator()) 
     { 
      var segment = new List<T>(); 
      var prevItem = default(T); 

      // ensure that the first item is always part 
      // of the first segment. This is an intentional 
      // behavior. Segmentation always begins with 
      // the second element in the sequence. 
      if (iter.MoveNext()) 
      { 
       ++index; 
       segment.Add(iter.Current); 
       prevItem = iter.Current; 
      } 

      while (iter.MoveNext()) 
      { 
       ++index; 
       // check if the item represents the start of a new segment 
       var isNewSegment = newSegmentIdentifier(iter.Current, prevItem, index); 
       prevItem = iter.Current; 

       if (!isNewSegment) 
       { 
        // if not a new segment, append and continue 
        segment.Add(iter.Current); 
        continue; 
       } 
       yield return segment; // yield the completed segment 

       // start a new segment... 
       segment = new List<T> { iter.Current }; 
      } 
      // handle the case of the sequence ending before new segment is detected 
      if (segment.Count > 0) 
       yield return segment; 
     } 
    } 
+0

Perché è la condizione nella funzione lambda ' item.Flagged! = prevItem.Flagged '? Dovresti creare un nuovo segmento se il flag cambia da falso a vero. Penso che la condizione dovrebbe essere solo 'item.Flagged', e non dipende affatto dall'elemento precedente. –

+0

@phild: sei corretto, 'item.Flagged' è sufficiente. Avevo fretta e ho interpretato male le aspettative dell'OP. Ho aggiornato la mia risposta. – LBushkin

+0

+1, metodo di estensione molto utile :) –

1

non credo che LINQ è lo strumento giusto per Thi compito di s. Che dire di questo:

public static List<List<T>> PartitionData<T>(T[] arr, Func<T, bool> flagSelector){ 
    List<List<T>> output = new List<List<T>>(); 
    List<T> partition = null; 
    bool first = true; 

    foreach(T obj in arr){ 
     if(flagSelector(obj) || first){ 
      partition = new List<T>(); 
      output.Add(partition); 
      first = false; 
     } 
     partition.Add(obj); 
    } 

    return output; 
} 

Un piccolo esempio, con i dati da Fábio Batistas postale:

var arrayOfElements = new[] { 
    new { Id = 1, Flagged = true }, 
    new { Id = 2, Flagged = false }, 
    new { Id = 3, Flagged = false }, 
    new { Id = 4, Flagged = true }, 
    new { Id = 5, Flagged = false }, 
    new { Id = 6, Flagged = false }, 
    new { Id = 7, Flagged = false }, 
    new { Id = 8, Flagged = true }, 
    new { Id = 9, Flagged = false } 
}; 

var partitioned = PartitionData(arrayOfElements, x => x.Flagged); 
+0

Penso che questo abbia bisogno di un output.Add (currentList); dopo l'istruzione foreach, o l'ultimo array non viene mai aggiunto. – Kenoyer130

+0

L'ho aggiornato e ne ho ricavato una funzione generica. –

2

Considerando:

var arrayOfElements = new[] { 
    new { Id = 1, Flagged = true }, 
    new { Id = 2, Flagged = false }, 
    new { Id = 3, Flagged = false }, 
    new { Id = 4, Flagged = true }, 
    new { Id = 5, Flagged = false }, 
    new { Id = 6, Flagged = false }, 
    new { Id = 7, Flagged = false }, 
    new { Id = 8, Flagged = true }, 
    new { Id = 9, Flagged = false } 
}; 

si può scrivere:

var grouped = 
    from i in arrayOfElements 
    where i.Flagged 
    select 
     (new[] { i.Id }) 
     .Union(arrayOfElements.Where(i2 => i2.Id > i.Id).TakeWhile(i2 => !i2.Flagged).Select(i2 => i2.Id)) 
     .ToArray(); 

Funziona se i tuoi elementi sono ordinati d dall'attributo Id. Se non lo fanno, dovrai iniettare una sequenza sull'array originale, che dovrebbe essere facile da fare anche con linq, così otterrai una sequenza.

Inoltre, una migliore alternativa dovrebbe essere:

// for each flagged element, slice the array, 
// starting on the flagged element until the next flagged element 
var grouped = 
    from i in arrayOfElements 
    where i.Flagged 
    select 
     arrayOfElements 
      .SkipWhile(i2 => i2 != i) 
      .TakeWhile(i2 => i2 == i || !i2.Flagged) 
      .Select(i2 => i2.Id) 
      .ToArray(); 

Si noti che queste risposte siano utilizzando LINQ puro.

+0

Mi piace molto Linq, e lo uso dove posso, ma non pensi che questo sia piuttosto brutto rispetto all'approccio classico (vedi il mio post)? Penso anche che questo sarà peggiore nelle prestazioni. –

+0

Mi sono evoluto un po ', per rimuovere la necessità di ordinarli da Id. Ma sono d'accordo, è peggio per le prestazioni. Mi è piaciuta la risposta @LBushkin, creando un nuovo metodo di estensione per questo. –

+0

+1 Mi piace la tua seconda alternativa. È bello vedere quanti compiti possono essere fatti con linq puro, che non avrei nemmeno pensato di fare in linq. –

5

Ho avuto un problema simile con questo, e risolto usando GroupBy e chiusura.

//sample data 
var arrayOfElements = new[] { 
    new { Id = 1, Flagged = true }, 
    new { Id = 2, Flagged = false }, 
    new { Id = 3, Flagged = false }, 
    new { Id = 4, Flagged = true }, 
    new { Id = 5, Flagged = false }, 
    new { Id = 6, Flagged = false }, 
    new { Id = 7, Flagged = false }, 
    new { Id = 8, Flagged = true }, 
    new { Id = 9, Flagged = false } 
}; 

//this is the closure which will increase each time I see a flagged 
int flagCounter = 0; 

var query = 
    arrayOfElements.GroupBy(e => 
     { 
      if (e.Flagged) 
       flagCounter++; 
      return flagCounter; 
     }); 

Ciò che non è il raggruppamento su un int (flagCounter), che viene incrementato ogni volta che un elemento segnalato viene trovata.
Si prega di notare che questo non funzionerà con AsParallel().

verificare i risultati:

foreach(var group in query) 
{ 
    Console.Write("\r\nGroup: "); 
    foreach (var element in group) 
     Console.Write(element.Id); 
} 

Uscite:

Gruppo: 123
Gruppo: 4567
Gruppo: 89

+0

Approccio molto interessante. Molto diverso dalle altre soluzioni, ma sai, molte strade portano a Roma ... –

+0

Mentre interessante, penso che sia un po '"hacker" introdurre lo stato nella query. Il risultato finale, tuttavia, è bello (un raggruppamento linq). Forse c'è un modo per farlo senza la chiusura? –

+0

@ Fábio: Le chiusure sono una grande aggiunta al C# e sono più adatte per l'uso nei delegati (come in questo caso). La mia conoscenza non può portarmi in una soluzione thread-safe che li usi ancora, ma sicuramente questo codice è leggibile quanto performante e manutenibile. –

Problemi correlati