Ottenere una raccolta di valori di indice usando una query LINQ
Domanda
C'è un modo migliore per farlo?
string[] s = {"zero", "one", "two", "three", "four", "five"};
var x =
s
.Select((a,i) => new {Value = a, Index = i})
.Where(b => b.Value.StartsWith("t"))
.Select(c => c.Index);
vale a dire. Sto cercando un modo più efficiente o più elegante per ottenere le posizioni degli articoli corrispondenti ai criteri.
Soluzione
Puoi facilmente aggiungere il tuo metodo di estensione:
public static IEnumerable<int> IndexesWhere<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
int index=0;
foreach (T element in source)
{
if (predicate(element))
{
yield return index;
}
index++;
}
}
Quindi usalo con:
string[] s = {"zero", "one", "two", "three", "four", "five"};
var x = s.IndexesWhere(t => t.StartsWith("t"));
Altri suggerimenti
Se stai solo usando l'esempio come modo per imparare LINQ, ignora questo post.
Non mi è chiaro che LINQ sia in realtà il modo migliore per farlo. Il codice seguente sembra essere più efficiente poiché non è necessario creare un nuovo tipo anonimo. Certo, il tuo esempio potrebbe essere inventato e la tecnica potrebbe essere più utile in un contesto diverso, ad esempio in una struttura di dati in cui potrebbe trarre vantaggio da un indice sul valore, ma il codice seguente è ragionevolmente semplice, comprensibile (nessun pensiero richiesto) e probabilmente più efficiente.
string[] s = {"zero", "one", "two", "three", "four", "five"};
List<int> matchingIndices = new List<int>();
for (int i = 0; i < s.Length; ++i)
{
if (s[i].StartWith("t"))
{
matchingIndices.Add(i);
}
}
Mi sembra perfetto. È possibile salvare un paio di caratteri modificando la selezione in:
.Select((Value, Index) => new {Value, Index})
Nell'elenco di raccolta è inoltre presente il metodo FindIndex per il quale si crea un metodo di eliminazione che può restituire l'indice dalla raccolta. puoi fare riferimento al seguente link in msdn http://msdn.microsoft.com /en-us/library/x1xzf2ca.aspx .
Che ne dici di questo? È simile al poster originale, ma prima seleziono gli indici e quindi costruisco una raccolta che soddisfi i criteri.
var x = s.Select((a, i) => i).Where(i => s[i].StartsWith("t"));
Questo è un po 'meno efficiente di alcune delle altre risposte poiché l'elenco viene ripetuto completamente due volte.
Ho discusso questo interessante problema con un collega e all'inizio ho pensato che la soluzione di JonSkeet fosse eccezionale, ma il mio collega ha sottolineato un problema, vale a dire che se la funzione è un'estensione di IEnumerable<T>
, allora può essere usata dove una raccolta lo implementa.
Con un array, è sicuro dire che l'ordine prodotto con foreach
sarà rispettato (cioè List.ToArray
ripeterà dal primo all'ultimo), ma non sarebbe necessariamente il caso di altre raccolte (Elenco, Dizionario, ecc.), dove <=> non rifletterebbe necessariamente " ordine di immissione " ;. Eppure la funzione è presente e può essere fuorviante.
Alla fine, ho finito con qualcosa di simile alla risposta di tvanfosson, ma come metodo di estensione, per gli array:
public static int[] GetIndexes<T>(this T[]source, Func<T, bool> predicate)
{
List<int> matchingIndexes = new List<int>();
for (int i = 0; i < source.Length; ++i)
{
if (predicate(source[i]))
{
matchingIndexes.Add(i);
}
}
return matchingIndexes.ToArray();
}
Speriamo che <=> rispetti l'ordine dell'ultima operazione ...