Como você obtém o índice da iteração atual de um loop foreach?

939

Existe alguma construção de linguagem rara que não encontrei (como as poucas que aprendi recentemente, algumas sobre Stack Overflow) em C # para obter um valor que representa a iteração atual de um loop foreach?

Por exemplo, atualmente faço algo assim, dependendo das circunstâncias:

int i = 0;
foreach (Object o in collection)
{
    // ...
    i++;
}
Matt Mitchell
fonte
1
a recuperação de conversão foreach geralmente não me será mais otimizada do que apenas usar o acesso baseado em índice em uma coleção, embora em muitos casos seja igual. O objetivo do foreach é tornar seu código legível, mas (geralmente) adiciona uma camada de indireção, que não é gratuita.
27710 Brian
8
Eu diria que o objetivo principal foreaché fornecer um mecanismo de iteração comum para todas as coleções, independentemente de serem indexáveis ​​( List) ou não ( Dictionary).
Brian Gideon
2
Oi Brian Gideon - concordo definitivamente (isso foi há alguns anos atrás e eu era muito menos experiente na época). No entanto, embora Dictionarynão seja indexável, uma iteração de Dictionaryo percorre em uma ordem específica (ou seja, um Enumerador é indexável pelo fato de produzir elementos sequencialmente). Nesse sentido, poderíamos dizer que não estamos procurando o índice dentro da coleção, mas o índice do elemento enumerado atual na enumeração (isto é, se estamos no primeiro, quinto ou último elemento enumerado).
Matt Mitchell
4
O foreach também permite que o compilador pule limites verificando o acesso de cada matriz no código compilado. Usar for com um índice fará com que o tempo de execução verifique se o acesso ao índice é seguro.
IvoTops 22/08/12
1
Mas é falso. Se você não alterar a variável de iteração de um loop for no loop, o compilador saberá quais são seus limites e não precisará checá-los novamente. Esse é um caso tão comum que qualquer compilador decente o implementará.
Jim Balter

Respostas:

552

O foreaché para iterar sobre coleções implementadas IEnumerable. Isso é feito chamando GetEnumeratora coleção, que retornará um Enumerator.

Este enumerador possui um método e uma propriedade:

  • MoveNext ()
  • Atual

Currentretorna o objeto em que o Enumerator está atualmente, MoveNextatualiza Currentpara o próximo objeto.

O conceito de um índice é estranho ao conceito de enumeração e não pode ser feito.

Por esse motivo, a maioria das coleções pode ser percorrida usando um indexador e a construção do loop for.

Eu prefiro usar um loop for nessa situação em comparação com o rastreamento do índice com uma variável local.

FlySwat
fonte
165
"Obviamente, o conceito de um índice é estranho ao conceito de enumeração e não pode ser feito." - Isso não faz sentido, como as respostas de David B e bcahill deixam claro. Um índice é uma enumeração em um intervalo, e não há razão para que não se possa enumerar duas coisas em paralelo ... é exatamente isso que a forma de indexação de Enumerable.Select faz.
Jim Balter
11
Exemplo de código básico:for(var i = 0; i < myList.Count; i ++){System.Diagnostics.Debug.WriteLine(i);}
Chad Hedgcock 14/04
22
@ JimBalter: Esse é o primeiro link que li. Não vejo como isso apóia sua posição. Também não atiro palavras ad-homs e divisivas às pessoas ao responder. Cito como exemplo o seu uso de "absurdo", "confusão extraordinária", "completamente errado e confuso", "recebi 37 votos positivos" etc. etc. (acho que seu ego está um pouco em risco aqui.) gostaria de pedir educadamente que você não brinque com os outros com o seu 'argumentum ad verecundiam' e, em vez disso, gostaria de incentivá-lo a pensar em maneiras de apoiar as pessoas aqui no StackOverflow. Eu diria que Jon Skeet é um cidadão modelo aqui a esse respeito.
Pretzel
11
Pretzel: O seu uso de Jon Skeet como cidadão modelo está enganado, pois ele vai brigar (e rebaixar) alguém que não concorda com ele, como eu experimentei. Jim Balter: Alguns de seus comentários foram excessivamente agressivos e você pode ter apresentado seus pontos com menos raiva e mais educação.
precisa saber é o seguinte
7
O argumento de @Pretzel Jim é que, desde que você possa mapear os elementos para uma sequência de números inteiros, poderá indexá-lo. Que a própria classe não armazene um índice não vem ao caso. Além disso, que a lista ligada faz ter uma ordem só reforça a posição de Jim. Tudo o que você precisa fazer é numerar cada elemento em ordem. Especificamente, você pode conseguir isso incrementando uma contagem enquanto itera ou pode gerar uma lista de números inteiros com o mesmo comprimento e depois compactá-los (como na zipfunção do Python ).
Jpmc26 /
666

Ian Mercer postou uma solução semelhante a esta no blog de Phil Haack :

foreach (var item in Model.Select((value, i) => new { i, value }))
{
    var value = item.value;
    var index = item.i;
}

Isso fornece o item ( item.value) e seu índice ( item.i) usando essa sobrecarga de LINQsSelect :

o segundo parâmetro da função [inside Select] representa o índice do elemento de origem.

O new { i, value }está criando um novo objeto anônimo .

As alocações de heap podem ser evitadas usando ValueTuplese você estiver usando o C # 7.0 ou posterior:

foreach (var item in Model.Select((value, i) => ( value, i )))
{
    var value = item.value;
    var index = item.i;
}

Você também pode eliminar item.usando destruturação automática:

<ol>
foreach ((MyType value, Int32 i) in Model.Select((value, i) => ( value, i )))
{
    <li id="item_@i">@value</li>
}
</ol>
bcahill
fonte
9
Essa solução é boa para o caso de um modelo do Razor em que a limpeza do modelo é uma preocupação de design não trivial e você também deseja usar o índice de todos os itens enumerados. No entanto, lembre-se de que a alocação de objetos do 'empacotamento' adiciona despesa (no espaço e no tempo) além do incremento (inevitável) de um número inteiro.
David Bullock
6
@mjsr A documentação está aqui .
Thorkil Holm-Jacobsen
19
Desculpe - isso é inteligente, mas é realmente mais legível do que criar um índice fora do foreach e incrementá-lo a cada loop?
Jbyrd 17/10
13
Com as versões mais recentes do C #, você também usa tuplas, para ter algo assim: foreach (var (item, i) no Model.Select ((v, i) => (v, i))) Permite que você acesse o item e o índice (i) diretamente dentro do loop for com desconstrução da tupla.
Haukman
5
Alguém pode me explicar por que essa é uma boa resposta (mais de 450 votos positivos no momento da redação)? Tanto quanto posso ver, é mais difícil de entender do que simplesmente incrementar um contador; portanto, é menos sustentável, usa mais memória e provavelmente é mais lento. Estou esquecendo de algo?
Rich N
182

Finalmente, o C # 7 tem uma sintaxe decente para obter um índice dentro de um foreachloop (ou seja, tuplas):

foreach (var (item, index) in collection.WithIndex())
{
    Debug.WriteLine($"{index}: {item}");
}

Um pequeno método de extensão seria necessário:

public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self)       
   => self.Select((item, index) => (item, index)); 
user1414213562
fonte
8
Essa resposta é subestimado, tendo as tuplas é muito mais limpo
Todd
7
Modificada para coleções alça nulos:public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> self) => self?.Select((item, index) => (item, index)) ?? new List<(T, int)>();
2Toad
Agradável. Eu realmente gosto mais desta solução.
precisa saber é o seguinte
Esta é a melhor resposta
w0ns88 28/01
2
Pode ser útil chamar o método Enumeratedpara ser mais reconhecível para pessoas acostumadas a outros idiomas (e talvez trocar a ordem dos parâmetros da tupla também). Não que isso WithIndexnão seja óbvio de qualquer maneira.
FernAndr 30/01
115

Poderia fazer algo assim:

public static class ForEachExtensions
{
    public static void ForEachWithIndex<T>(this IEnumerable<T> enumerable, Action<T, int> handler)
    {
        int idx = 0;
        foreach (T item in enumerable)
            handler(item, idx++);
    }
}

public class Example
{
    public static void Main()
    {
        string[] values = new[] { "foo", "bar", "baz" };

        values.ForEachWithIndex((item, idx) => Console.WriteLine("{0}: {1}", idx, item));
    }
}
Brad Wilson
fonte
12
Isso "realmente" não resolve o problema. A idéia é boa, mas não evitar a variável de contagem adicional
Atmocreations
Isso não funciona se tivermos uma instrução de retorno dentro do nosso loop for, se você alterar "ForEachWithIndex" para isso, então não é genérico, melhor escrever um regular para o laço
Shankar Raju
Sua chamada ForEachWithIndex é equivalente a esta usando o Linq Select que recebe uma sequência e um índice:values.Select((item, idx) => { Console.WriteLine("{0}: {1}", idx, item); return item; }).ToList();
user2023861
95

Discordo dos comentários de que um forloop é uma escolha melhor na maioria dos casos.

foreaché uma construção útil e não substituível por um forloop em todas as circunstâncias.

Por exemplo, se você tiver um DataReader e percorrer todos os registros usando um, foreachele automaticamente chama o método Dispose e fecha o leitor (que pode fechar a conexão automaticamente). Portanto, é mais seguro, pois evita vazamentos de conexão, mesmo que você esqueça de fechar o leitor.

(Claro que é uma boa prática sempre fechar os leitores, mas o compilador não será capaz de capturá-lo se você não o fizer - você não pode garantir que fechou todos os leitores, mas pode aumentar a probabilidade de não perder conexões obtendo o hábito de usar o foreach.)

Pode haver outros exemplos de chamadas implícitas do Disposemétodo sendo úteis.

Mike Nelson
fonte
2
Obrigado por apontar isso. Bastante sutil. Você pode obter mais informações em pvle.be/2010/05/foreach-statement-calls-dispose-on-ienumerator e msdn.microsoft.com/en-us/library/aa664754(VS.71).aspx .
Mark Meuer
+1. Eu estava escrevendo mais detalhadamente sobre como foreaché diferente for(e mais próximo while) do Programmers.SE .
Arseni Mourzenko
64

Resposta literal - aviso, o desempenho pode não ser tão bom quanto usar um intpara rastrear o índice. Pelo menos é melhor do que usar IndexOf.

Você só precisa usar a sobrecarga de indexação de Select para agrupar cada item da coleção com um objeto anônimo que conhece o índice. Isso pode ser feito contra qualquer coisa que implemente IEnumerable.

System.Collections.IEnumerable collection = Enumerable.Range(100, 10);

foreach (var o in collection.OfType<object>().Select((x, i) => new {x, i}))
{
    Console.WriteLine("{0} {1}", o.i, o.x);
}
Amy B
fonte
3
O único motivo para usar OfType <T> () em vez de Cast <T> () é se alguns itens na enumeração podem falhar em uma conversão explícita. Para objeto, isso nunca será o caso.
Dahlbyk
13
Claro, exceto pelo outro motivo para usar o OfType em vez do Cast - que é que eu nunca uso o Cast.
217 Amy B
36

Usando LINQ, C # 7 e o System.ValueTuplepacote NuGet, você pode fazer isso:

foreach (var (value, index) in collection.Select((v, i)=>(v, i))) {
    Console.WriteLine(value + " is at index " + index);
}

Você pode usar a foreachconstrução regular e poder acessar o valor e o índice diretamente, não como membro de um objeto, e mantém os dois campos apenas no escopo do loop. Por esses motivos, acredito que esta é a melhor solução se você puder usar o C # 7 e System.ValueTuple.

Pavel
fonte
Qual é a diferença da resposta do usuário1414213562 ?
Edward Brey
@EdwardBrey, suponho que não. Essa pergunta tem muitas respostas, provavelmente eu apenas a perdi ou não percebi o que estava fazendo porque ele separou parte da lógica em um método de extensão.
Pavel
1
Isso é diferente porque .Select é interno do LINQ. Você não precisa escrever sua própria função? Você precisará que o VS instale "System.ValueTuple".
Anton
33

Usando a resposta do @ FlySwat, eu vim com esta solução:

//var list = new List<int> { 1, 2, 3, 4, 5, 6 }; // Your sample collection

var listEnumerator = list.GetEnumerator(); // Get enumerator

for (var i = 0; listEnumerator.MoveNext() == true; i++)
{
  int currentItem = listEnumerator.Current; // Get current item.
  //Console.WriteLine("At index {0}, item is {1}", i, currentItem); // Do as you wish with i and  currentItem
}

Você obtém o enumerador usando GetEnumeratore depois faz um loop usando um forloop. No entanto, o truque é criar a condição do loop listEnumerator.MoveNext() == true.

Como o MoveNextmétodo de um enumerador retorna true se houver um próximo elemento e ele puder ser acessado, a condição do loop fará com que o loop pare quando ficarmos sem elementos para iterar.

Gezim
fonte
10
Não há necessidade de comparar listEnumerator.MoveNext () == true. É como perguntar ao computador se verdadeiro == verdadeiro? :) Apenas diga se listEnumerator.MoveNext () {}
Zesty
9
@ Zesty, você está absolutamente correto. Eu senti que é mais legível adicioná-lo neste caso, especialmente para pessoas que não estão acostumadas a inserir nada além de i <blahSize como condição.
Gezim
@ Gezim Não me importo com os predicados aqui, mas entendo que isso não parece um predicado.
John Dvorak
1
Você deve descartar o enumerador.
Antonín Lejsek
@ AntonínLejsek O enumerador não implementa IDisposable.
Edward Brey
27

Não há nada errado em usar uma variável de contador. De fato, se você usa for, foreach whileou do, uma variável de contador deve em algum lugar ser declarada e incrementada.

Portanto, use esse idioma se não tiver certeza se possui uma coleção indexada adequadamente:

var i = 0;
foreach (var e in collection) {
   // Do stuff with 'e' and 'i'
   i++;
}

Caso contrário, use este caso você saiba que sua coleção indexável é O (1) para acesso ao índice (para o qual será Arraye provavelmente List<T>(a documentação não diz), mas não necessariamente para outros tipos (como LinkedList)):

// Hope the JIT compiler optimises read of the 'Count' property!
for (var i = 0; i < collection.Count; i++) {
   var e = collection[i];
   // Do stuff with 'e' and 'i'
}

Nunca deve ser necessário operar 'manualmente' IEnumerator, invocando MoveNext()e interrogando Current- foreachestá poupando esse incômodo em particular ... se você precisar pular itens, basta usar um continueno corpo do loop.

E, para ser completo, dependendo do que você estava fazendo com o seu índice (as construções acima oferecem bastante flexibilidade), você pode usar o Parallel LINQ:

// First, filter 'e' based on 'i',
// then apply an action to remaining 'e'
collection
    .AsParallel()
    .Where((e,i) => /* filter with e,i */)
    .ForAll(e => { /* use e, but don't modify it */ });

// Using 'e' and 'i', produce a new collection,
// where each element incorporates 'i'
collection
    .AsParallel()
    .Select((e, i) => new MyWrapper(e, i));

Usamos AsParallel()acima, porque já estamos em 2014 e queremos fazer bom uso desses múltiplos núcleos para acelerar as coisas. Além disso, para o LINQ 'sequencial', você sóForEach()List<T>Array usa um método de extensão e ... e não está claro que usá-lo seja melhor do que fazer um simples foreach, pois você ainda está executando o thread único para obter uma sintaxe mais feia.

David Bullock
fonte
26

Você pode agrupar o enumerador original com outro que contenha as informações do índice.

foreach (var item in ForEachHelper.WithIndex(collection))
{
    Console.Write("Index=" + item.Index);
    Console.Write(";Value= " + item.Value);
    Console.Write(";IsLast=" + item.IsLast);
    Console.WriteLine();
}

Aqui está o código para a ForEachHelperclasse.

public static class ForEachHelper
{
    public sealed class Item<T>
    {
        public int Index { get; set; }
        public T Value { get; set; }
        public bool IsLast { get; set; }
    }

    public static IEnumerable<Item<T>> WithIndex<T>(IEnumerable<T> enumerable)
    {
        Item<T> item = null;
        foreach (T value in enumerable)
        {
            Item<T> next = new Item<T>();
            next.Index = 0;
            next.Value = value;
            next.IsLast = false;
            if (item != null)
            {
                next.Index = item.Index + 1;
                yield return item;
            }
            item = next;
        }
        if (item != null)
        {
            item.IsLast = true;
            yield return item;
        }            
    }
}
Brian Gideon
fonte
Na verdade, isso não retornará o índice do item. Em vez disso, ele retornará o índice dentro da lista enumerada, que pode ser apenas uma sub-lista da lista, fornecendo dados precisos somente quando a sub-lista e a lista tiverem o mesmo tamanho. Basicamente, sempre que a coleção contiver objetos que não sejam do tipo solicitado, seu índice estará incorreto.
Lucas B
7
@ Lucas: Não, mas ele retornará o índice da iteração atual para cada. Essa foi a pergunta.
Brian Gideon
20

Aqui está uma solução que eu acabei de encontrar para esse problema

Código original:

int index=0;
foreach (var item in enumerable)
{
    blah(item, index); // some code that depends on the index
    index++;
}

Código atualizado

enumerable.ForEach((item, index) => blah(item, index));

Método de extensão:

    public static IEnumerable<T> ForEach<T>(this IEnumerable<T> enumerable, Action<T, int> action)
    {
        var unit = new Unit(); // unit is a new type from the reactive framework (http://msdn.microsoft.com/en-us/devlabs/ee794896.aspx) to represent a void, since in C# you can't return a void
        enumerable.Select((item, i) => 
            {
                action(item, i);
                return unit;
            }).ToList();

        return pSource;
    }
mat3
fonte
16

Basta adicionar seu próprio índice. Mantenha simples.

int i = 0;
foreach (var item in Collection)
{
    item.index = i;
    ++i;
}
conterio
fonte
15

Só vai funcionar para uma lista e não para qualquer IEnumerable, mas no LINQ existe o seguinte:

IList<Object> collection = new List<Object> { 
    new Object(), 
    new Object(), 
    new Object(), 
    };

foreach (Object o in collection)
{
    Console.WriteLine(collection.IndexOf(o));
}

Console.ReadLine();

@ Jonathan Eu não disse que era uma ótima resposta, apenas disse que estava apenas mostrando que era possível fazer o que ele pediu :)

@ Graphain Eu não esperaria que fosse rápido - não tenho muita certeza de como funciona, poderia reiterar a lista inteira a cada vez para encontrar um objeto correspondente, o que seria uma enorme quantidade de comparações.

Dito isto, a Lista pode manter um índice de cada objeto junto com a contagem.

Jonathan parece ter uma idéia melhor, se ele elaborar?

Seria melhor apenas manter uma contagem de onde você está na frente, mais simples e mais adaptável.

cadinho
fonte
5
Não tenho certeza sobre a votação pesada. O desempenho certo torna isso proibitivo, mas você respondeu à pergunta!
22416 Matt Mitchell #
4
Outro problema é que ele só funciona se os itens da lista forem exclusivos.
CodesInChaos
14

O C # 7 finalmente nos fornece uma maneira elegante de fazer isso:

static class Extensions
{
    public static IEnumerable<(int, T)> Enumerate<T>(
        this IEnumerable<T> input,
        int start = 0
    )
    {
        int i = start;
        foreach (var t in input)
        {
            yield return (i++, t);
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        var s = new string[]
        {
            "Alpha",
            "Bravo",
            "Charlie",
            "Delta"
        };

        foreach (var (i, t) in s.Enumerate())
        {
            Console.WriteLine($"{i}: {t}");
        }
    }
}
Paul Mitchell
fonte
Sim, e a MS deve estender o CLR / BCL para tornar esse tipo de coisa nativa.
18719 Todd
14

Por que foreach ?!

A maneira mais simples é usar for em vez de foreach se você estiver usando a Lista :

for (int i = 0 ; i < myList.Count ; i++)
{
    // Do something...
}

Ou se você deseja usar foreach:

foreach (string m in myList)
{
     // Do something...
}

Você pode usar isso para conhecer o índice de cada loop:

myList.indexOf(m)
Parsa
fonte
8
A solução indexOf é inválida para lista com duplicatas e também é muito lenta.
tymtam
2
O problema a ser evitado é onde você percorre o IEnumerable várias vezes, por exemplo, para obter a contagem de itens e depois cada item. Isso tem implicações quando o IEnumerable é o resultado de uma consulta ao banco de dados, por exemplo.
David Clarke
2
myList.IndexOf () é O (n), então seu loop será O (n ^ 2).
Patrick Beard
9
int index;
foreach (Object o in collection)
{
    index = collection.indexOf(o);
}

Isso funcionaria para o suporte a coleções IList.

Sachin
fonte
68
Dois problemas: 1) Esta é O(n^2)uma vez na maioria das implementações IndexOfé O(n). 2) Isso falhará se houver itens duplicados na lista.
CodesInChaos
15
Nota: O (n ^ 2) significa que isso pode ser desastrosamente lento para uma grande coleção.
O'Rooney
Grande invenção para usar o método IndexOf! Era isso que eu estava procurando para obter o índice (número) no loop foreach! Big Thx
Mitja Bonca
19
Deus, espero que você não tenha usado isso! :( Ele usa a variável que você não queria criar - na verdade, criará n + 1 ints porque essa função precisa criar uma para retornar também - e esse índice de pesquisa é muito, muito mais lento que . operação de incremento um inteiro em cada passo por que não as pessoas vão votar esta resposta para baixo?
canahari
13
Não use esta resposta, encontrei a dura verdade mencionada em um dos comentários. "Isso falha se houver itens duplicados na lista." !!!
Bruce
9

É assim que eu faço, o que é bom por sua simplicidade / brevidade, mas se você estiver fazendo muito no corpo do loop obj.Value, ele envelhecerá rapidamente.

foreach(var obj in collection.Select((item, index) => new { Index = index, Value = item }) {
    string foo = string.Format("Something[{0}] = {1}", obj.Index, obj.Value);
    ...
}
Ian Henry
fonte
8

Esta resposta: solicite à equipe de idiomas C # o suporte direto ao idioma.

A resposta principal afirma:

Obviamente, o conceito de um índice é estranho ao conceito de enumeração e não pode ser feito.

Embora isso seja verdade na versão atual do idioma C # (2020), esse não é um limite conceitual de CLR / idioma, mas pode ser feito.

A equipe de desenvolvimento de linguagem C # da Microsoft poderia criar um novo recurso de linguagem C #, adicionando suporte para um novo Interface IIndexedEnumerable

foreach (var item in collection with var index)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

//or, building on @user1414213562's answer
foreach (var (item, index) in collection)
{
    Console.WriteLine("Iteration {0} has value {1}", index, item);
}

Se foreach ()for usado e with var indexestiver presente, o compilador espera que a coleção de itens declare IIndexedEnumerableinterface. Se a interface estiver ausente, o compilador pode preencher a origem com um objeto IndexedEnumerable, que adiciona o código para rastrear o índice.

interface IIndexedEnumerable<T> : IEnumerable<T>
{
    //Not index, because sometimes source IEnumerables are transient
    public long IterationNumber { get; }
}

Posteriormente, o CLR pode ser atualizado para ter um rastreamento de índice interno, usado apenas se a withpalavra-chave for especificada e a origem não implementar diretamenteIIndexedEnumerable

Por quê:

  • O Foreach parece mais agradável e, em aplicativos de negócios, os loops de foreach raramente são um gargalo de desempenho
  • O Foreach pode ser mais eficiente na memória. Ter um pipeline de funções em vez de converter para novas coleções a cada etapa. Quem se importa se usa mais alguns ciclos de CPU quando há menos falhas de cache da CPU e menos coletas de lixo?
  • Exigir que o codificador adicione código de rastreamento de índice estraga a beleza
  • É muito fácil de implementar (por favor, Microsoft) e é compatível com versões anteriores

Embora a maioria das pessoas aqui não seja funcionário da Microsoft, esta é uma resposta correta, você pode pressionar a Microsoft para adicionar esse recurso. Você já pode criar seu próprio iterador com uma função de extensão e usar tuplas , mas a Microsoft pode polvilhar o açúcar sintático para evitar a função de extensão.

Todd
fonte
Aguarde, esse recurso de idioma já existe ou é proposto para o futuro?
Pavel
1
@Pavel Atualizei a resposta para ficar claro. Esta resposta foi fornecida para contrariar a resposta principal que afirma "Obviamente, o conceito de um índice é estranho ao conceito de enumeração e não pode ser feito".
Todd
6

Se a coleção for uma lista, você pode usar List.IndexOf, como em:

foreach (Object o in collection)
{
    // ...
    @collection.IndexOf(o)
}
ssaeed
fonte
13
E agora o algoritmo é O (n ^ 2) (se não for pior). Eu pensaria com muito cuidado antes de usar isso. Seu também uma cópia de 's resposta @crucible
BradleyDotNET
1
@BradleyDotNET está exatamente certo, não use esta versão.
Oskar
1
Tenha cuidado com isso! Se você tiver um item duplicado em sua lista, ele obterá a posição do primeiro!
Sonhja
5

Melhor usar a continueconstrução segura de palavras-chave como esta

int i=-1;
foreach (Object o in collection)
{
    ++i;
    //...
    continue; //<--- safe to call, index will be increased
    //...
}
user426810
fonte
5

Você pode escrever seu loop assim:

var s = "ABCDEFG";
foreach (var item in s.GetEnumeratorWithIndex())
{
    System.Console.WriteLine("Character: {0}, Position: {1}", item.Value, item.Index);
}

Depois de adicionar o seguinte método struct e extensão.

O método struct e extension encapsula a funcionalidade Enumerable.Select.

public struct ValueWithIndex<T>
{
    public readonly T Value;
    public readonly int Index;

    public ValueWithIndex(T value, int index)
    {
        this.Value = value;
        this.Index = index;
    }

    public static ValueWithIndex<T> Create(T value, int index)
    {
        return new ValueWithIndex<T>(value, index);
    }
}

public static class ExtensionMethods
{
    public static IEnumerable<ValueWithIndex<T>> GetEnumeratorWithIndex<T>(this IEnumerable<T> enumerable)
    {
        return enumerable.Select(ValueWithIndex<T>.Create);
    }
}
Satisfeito
fonte
3

Minha solução para esse problema é um método de extensão WithIndex(),

http://code.google.com/p/ub-dotnet-utilities/source/browse/trunk/Src/Utilities/Extensions/EnumerableExtensions.cs

Use-o como

var list = new List<int> { 1, 2, 3, 4, 5, 6 };    

var odd = list.WithIndex().Where(i => (i.Item & 1) == 1);
CollectionAssert.AreEqual(new[] { 0, 2, 4 }, odd.Select(i => i.Index));
CollectionAssert.AreEqual(new[] { 1, 3, 5 }, odd.Select(i => i.Item));
ulrichb
fonte
Eu usaria um structpara o par (índice, item).
CodesInChaos
3

Por interesse, Phil Haack acabou de escrever um exemplo disso no contexto de um Delegado de Navalha ( http://haacked.com/archive/2011/04/14/a-better-razor-foreach-loop.aspx )

Ele efetivamente escreve um método de extensão que envolve a iteração em uma classe "IteratedItem" (veja abaixo), permitindo o acesso ao índice e ao elemento durante a iteração.

public class IndexedItem<TModel> {
  public IndexedItem(int index, TModel item) {
    Index = index;
    Item = item;
  }

  public int Index { get; private set; }
  public TModel Item { get; private set; }
}

No entanto, embora isso seja bom em um ambiente que não seja o Razor, se você estiver executando uma única operação (ou seja, uma que possa ser fornecida como um lambda), não será uma substituição sólida da sintaxe de for / foreach em contextos que não sejam do Razor .

Matt Mitchell
fonte
3

Eu não acho que isso deva ser bastante eficiente, mas funciona:

@foreach (var banner in Model.MainBanners) {
    @Model.MainBanners.IndexOf(banner)
}
Bart Calixto
fonte
3

Eu construí isso no LINQPad :

var listOfNames = new List<string>(){"John","Steve","Anna","Chris"};

var listCount = listOfNames.Count;

var NamesWithCommas = string.Empty;

foreach (var element in listOfNames)
{
    NamesWithCommas += element;
    if(listOfNames.IndexOf(element) != listCount -1)
    {
        NamesWithCommas += ", ";
    }
}

NamesWithCommas.Dump();  //LINQPad method to write to console.

Você também pode usar string.join:

var joinResult = string.Join(",", listOfNames);
Warren LaFrance
fonte
Você também pode usar o string.join em c # Por exemplo: var joinResult = string.Join (",", listOfNames); '
Warren LaFrance
1
Alguém pode me explicar o que é essa coisa de O (n * n)?
Axel
@Axel, basicamente significa que as operações necessárias para calcular o resultado aumentam quadraticamente, ou seja, se houver nitens, as operações são n * nou estão localizadas n. pt.wikipedia.org/wiki/…
Richard Hansell
2

Não acredito que haja uma maneira de obter o valor da iteração atual de um loop foreach. Contando a si mesmo, parece ser o melhor caminho.

Posso perguntar por que você gostaria de saber?

Parece que você provavelmente faria uma de três coisas:

1) Obtendo o objeto da coleção, mas neste caso você já o possui.

2) Contando os objetos para posterior processamento posterior ... as coleções têm uma propriedade Count que você pode usar.

3) Configurando uma propriedade no objeto com base em sua ordem no loop ... embora você possa configurá-lo facilmente ao adicionar o objeto à coleção.

Bryansh
fonte
4) O caso em que bati várias vezes é algo diferente que deve ser feito na primeira ou na última passagem - diga uma lista de objetos que você vai imprimir e precisa de vírgulas entre os itens, mas não depois do último.
Loren Pechtel
2

A menos que sua coleção possa retornar o índice do objeto por algum método, a única maneira é usar um contador como no seu exemplo.

No entanto, ao trabalhar com índices, a única resposta razoável para o problema é usar um loop for. Qualquer outra coisa introduz a complexidade do código, sem mencionar a complexidade do tempo e do espaço.

Joseph Daigle
fonte
2

Que tal algo como isso? Observe que myDelimitedString pode ser nulo se myEnumerable estiver vazio.

IEnumerator enumerator = myEnumerable.GetEnumerator();
string myDelimitedString;
string current = null;

if( enumerator.MoveNext() )
    current = (string)enumerator.Current;

while( null != current)
{
    current = (string)enumerator.Current; }

    myDelimitedString += current;

    if( enumerator.MoveNext() )
        myDelimitedString += DELIMITER;
    else
        break;
}
Matt Towers
fonte
Vários problemas aqui. A) cinta extra. B) string concat + = por iteração do loop.
enorl76
2

Acabei de ter esse problema, mas pensar no problema no meu caso deu a melhor solução, não relacionada à solução esperada.

Pode ser um caso bastante comum, basicamente, estou lendo de uma lista de fontes e criando objetos com base neles em uma lista de destinos. No entanto, tenho que verificar se os itens de origem são válidos primeiro e quero retornar a linha de qualquer erro. À primeira vista, desejo inserir o índice no enumerador do objeto na propriedade Current, no entanto, ao copiar esses elementos, conheço implicitamente o índice atual de qualquer maneira, a partir do destino atual. Obviamente, depende do seu objeto de destino, mas para mim era uma Lista e provavelmente implementará o ICollection.

ie

var destinationList = new List<someObject>();
foreach (var item in itemList)
{
  var stringArray = item.Split(new char[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries);

  if (stringArray.Length != 2)
  {
    //use the destinationList Count property to give us the index into the stringArray list
    throw new Exception("Item at row " + (destinationList.Count + 1) + " has a problem.");
  }
  else
  {
    destinationList.Add(new someObject() { Prop1 = stringArray[0], Prop2 = stringArray[1]});
  }
}

Nem sempre é aplicável, mas muitas vezes vale a pena mencionar, eu acho.

Enfim, o ponto é que, às vezes, já existe uma solução não óbvia na lógica que você tem ...

nicodemus13
fonte
2

Eu não tinha certeza do que você estava tentando fazer com as informações do índice com base na pergunta. No entanto, em C #, geralmente você pode adaptar o método IEnumerable.Select para obter o índice do que desejar. Por exemplo, eu poderia usar algo assim para saber se um valor é ímpar ou par.

string[] names = { "one", "two", "three" };
var oddOrEvenByName = names
    .Select((name, index) => new KeyValuePair<string, int>(name, index % 2))
    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);

Isso daria a você um dicionário pelo nome, indicando se o item era ímpar (1) ou par (0) na lista.

Kasey Speakman
fonte