¿Cuál es la mejor manera de hacer un bucle hacia atrás en C / C # / C ++?

101

Necesito moverme hacia atrás a través de una matriz, así que tengo un código como este:

for (int i = myArray.Length - 1; i >= 0; i--)
{
    // Do something
    myArray[i] = 42;
}

¿Existe una forma mejor de hacer esto?

Actualización: esperaba que tal vez C # tuviera algún mecanismo incorporado para esto como:

foreachbackwards (int i in myArray)
{
    // so easy
}

Actualización 2: hay mejores formas. Rune se lleva el premio con:

for (int i = myArray.Length; i-- > 0; )
{    
    //do something
}
//or
for (int i = myArray.Length; i --> 0; )
{
    // do something
}

que se ve aún mejor en C normal (gracias a Twotymz):

for (int i = lengthOfArray; i--; )
{    
    //do something
}
MusiGenesis
fuente
No estoy seguro de ver por qué alguna de las alternativas es mejor, si la bondad incluye claridad o facilidad de mantenimiento.
dkretz
Cierto. Esperaba encontrar una forma más razonable de hacer esto, ya que tengo que hacerlo con bastante frecuencia.
MusiGenesis
3
La mayor parte de esta pregunta es una especie de revisión de las respuestas a continuación. Sugiera que se reduzca significativamente.
einpoklum
Elimine C y C ++ del título y los hashtags.
jaskmar

Respuestas:

149

Si bien es cierto que es un poco oscuro, diría que la forma más agradable tipográficamente de hacer esto es

for (int i = myArray.Length; i --> 0; )
{
    //do something
}
MusiGenesis
fuente
32
Cuando leí tu respuesta por primera vez, parecía que ni siquiera se compilaría, así que asumí que eras un loco. Pero es exactamente lo que estaba buscando: una mejor forma de escribir un bucle for hacia atrás.
MusiGenesis
3
Creo que i -> 0; es a propósito. Eso es lo que quiere decir con "tipográficamente agradable"
Johannes Schaub - litb
16
Yo mismo lo encontré "tipográficamente confuso". Para mí, el "-" no parece correcto a menos que sea adyacente a la variable que está afectando.
MusiGenesis
26
Eso es demasiado oscuro y confuso. Nunca escribiría algo como esto en el código de producción ...
Mihai Todor
9
¡Aah, el operador va al operador (->) hace el truco!
nawfal
118

En C ++, básicamente tiene la opción entre iterar usando iteradores o índices. Dependiendo de si tiene una matriz simple o una std::vector, utiliza diferentes técnicas.

Usando std :: vector

Usando iteradores

C ++ te permite hacer esto usando std::reverse_iterator:

for(std::vector<T>::reverse_iterator it = v.rbegin(); it != v.rend(); ++it) {
    /* std::cout << *it; ... */
}

Usando índices

El tipo entero sin signo que devuelve std::vector<T>::sizees no siempre std::size_t. Puede ser mayor o menor. Esto es crucial para que funcione el bucle.

for(std::vector<int>::size_type i = someVector.size() - 1; 
    i != (std::vector<int>::size_type) -1; i--) {
    /* std::cout << someVector[i]; ... */
}

Funciona, ya que los valores de los tipos integrales sin signo se definen mediante módulo su recuento de bits. Por lo tanto, si está estableciendo -N, terminará en(2 ^ BIT_SIZE) -N

Usando matrices

Usando iteradores

Estamos usando std::reverse_iteratorpara hacer la iteración.

for(std::reverse_iterator<element_type*> it(a + sizeof a / sizeof *a), itb(a); 
    it != itb; 
    ++it) {
    /* std::cout << *it; .... */
}

Usando índices

Podemos usar con seguridad std::size_taquí, a diferencia de lo anterior, ya que sizeofsiempre devuelve std::size_tpor definición.

for(std::size_t i = (sizeof a / sizeof *a) - 1; i != (std::size_t) -1; i--) {
   /* std::cout << a[i]; ... */
}

Evitar trampas con sizeof aplicado a punteros

En realidad, la forma anterior de determinar el tamaño de una matriz apesta. Si a es en realidad un puntero en lugar de una matriz (lo que sucede con bastante frecuencia y los principiantes lo confundirán), fallará silenciosamente. Una mejor manera es usar lo siguiente, que fallará en tiempo de compilación, si se le da un puntero:

template<typename T, std::size_t N> char (& array_size(T(&)[N]) )[N];

Funciona obteniendo primero el tamaño de la matriz pasada y luego declarando devolver una referencia a una matriz de tipo char del mismo tamaño. charse define para tener sizeofde: 1. Entonces la matriz devuelta tendrá un sizeofde: N * 1, que es lo que estamos buscando, con solo evaluación del tiempo de compilación y cero sobrecarga de tiempo de ejecución.

En lugar de hacer

(sizeof a / sizeof *a)

Cambie su código para que ahora lo haga

(sizeof array_size(a))
Johannes Schaub - litb
fuente
Además, si su contenedor no tiene un iterador inverso, puede usar la implementación de reverse_iterator de boost: boost.org/doc/libs/1_36_0/libs/iterator/doc/…
MP24
su array_size parece funcionar para matrices asignadas estáticamente, pero me falló cuando a era 'new int [7]', por ejemplo.
Nate Parsons
2
sí, esa es la intención :) new int [7] devuelve un puntero. así que sizeof (new int [7]) no devuelve 7 * sizeof (int), pero devolvería sizeof (int *). array_size provoca un error de compilación para ese caso en lugar de funcionar silenciosamente.
Johannes Schaub - litb
también intente array_size (+ statically_allocated_array), que también falla, porque el operador + convierte la matriz en un puntero a su primer elemento. el uso de tamaño normal le daría nuevamente el tamaño de un puntero
Johannes Schaub - litb
En primer lugar, ¿por qué no utilizar endy beginen orden inverso?
Tomáš Zato - Reincorpora a Monica
54

En C # , usando Visual Studio 2005 o posterior, escriba 'forr' y presione [TAB] [TAB] . Esto se expandirá a un forbucle que retrocede a través de una colección.

Es tan fácil equivocarse (al menos para mí), que pensé que sería una buena idea incluir este fragmento.

Dicho esto, me gusta Array.Reverse()/ Enumerable.Reverse()y luego iterar hacia adelante mejor: indican más claramente la intención.

Jay Bazuzi
fuente
41

Yo siempre prefiero código claro contra el ' tipográficamente complacer código'. Por lo tanto, siempre usaría:

for (int i = myArray.Length - 1; i >= 0; i--)  
{  
    // Do something ...  
}    

Puede considerarlo como la forma estándar de realizar un bucle hacia atrás.
Solo mis dos centavos ...

Jack Griffin
fuente
Trabajó. Todavía no he hecho esto en C #, pero gracias.
PCPGMR
Entonces, ¿qué pasa si la matriz es realmente grande (por lo que el índice tiene que ser de tipo sin firmar)? size_t en realidad no está firmado, ¿no?
lalala
Puedes declarar i como uint. Lee la publicación de Marc Gravell.
Jack Griffin
No funcionará para un tipo sin firmar debido a la envoltura, a diferencia del tipográficamente agradable. No está bien.
Ocelot
17

En C # usando Linq :

foreach(var item in myArray.Reverse())
{
    // do something
}
Keltex
fuente
Esta es sin duda la más simple, pero tenga en cuenta que en la versión actual de CLR, invertir una lista es siempre una operación O (n) en lugar de la operación O (1) que podría ser si fuera una IList <T>; consulte connect.microsoft.com/VisualStudio/feedback/…
Greg Beech
1
Parece que .Reverse () hace una copia local de la matriz y luego itera hacia atrás a través de ella. Parece un poco ineficiente copiar una matriz solo para poder recorrerla. Supongo que cualquier publicación con "Linq" es digna de votos a favor. :)
MusiGenesis
Hay una posible compensación, pero luego te encuentras con el problema de que la "respuesta votada" (i -> 0) podría resultar en un código más lento porque (i) debe compararse con el tamaño de la matriz cada vez que se utilizado como en index.
Keltex
1
Si bien estoy de acuerdo en que Linq es excelente, el problema con este enfoque es que un iterador no le permite seleccionar elementos de la matriz de manera selectiva; considere la situación en la que desea navegar a través de una parte de una matriz, pero no la totalidad cosa ...
jesses.co.tt
11

Definitivamente, esa es la mejor manera para cualquier arreglo cuya longitud sea un tipo integral con signo. Para matrices cuyas longitudes son un tipo integral sin signo (por ejemplo, una std::vectoren C ++), entonces necesita modificar ligeramente la condición final:

for(size_t i = myArray.size() - 1; i != (size_t)-1; i--)
    // blah

Si acaba de decir i >= 0, esto siempre es cierto para un entero sin signo, por lo que el ciclo será un ciclo infinito.

Adam Rosenfield
fuente
En C ++, el "i--" se ajustaría automáticamente al valor más alto si fuera 0? Lo siento, tengo problemas de C ++.
MusiGenesis
Asombroso. Ustedes me ayudaron a entender la causa de un error de llenado del disco duro en algo que escribí hace 12 años. Menos mal que no trabajo en C ++.
MusiGenesis
La condición de terminación debe ser: i <myArray.size (). Solo depende de las propiedades de desbordamiento de unsigned int; no detalles de implementación de enteros y conversiones. En consecuencia, es más fácil de leer y funciona en idiomas con representaciones de números enteros alternativos.
ejgottl
Creo que una mejor solución para mí es quedarme en el mundo C # donde no puedo lastimar a nadie.
MusiGenesis
4

Me parece bien. Si el indexador no estaba firmado (uint, etc.), es posible que deba tenerlo en cuenta. Llámame perezoso, pero en ese caso (sin firmar), podría usar una contravariable:

uint pos = arr.Length;
for(uint i = 0; i < arr.Length ; i++)
{
    arr[--pos] = 42;
}

(de hecho, incluso aquí deberías tener cuidado con casos como arr.Length = uint.MaxValue ... tal vez a! = en algún lugar ... por supuesto, ¡ese es un caso muy poco probable!)

Marc Gravell
fuente
Sin embargo, lo de "--pos" es complicado. He tenido compañeros de trabajo en el pasado a los que les gustaba "corregir" aleatoriamente cosas en código que no les parecían del todo bien.
MusiGenesis
1
Luego use .arr.Length-1 y pos--
Marc Gravell
4

En CI me gusta hacer esto:


int i = myArray.Length;
while (i--) {
  myArray[i] = 42;
}

Ejemplo de C # agregado por MusiGenesis:

{int i = myArray.Length; while (i-- > 0)
{
    myArray[i] = 42;
}}
Twotymz
fuente
Me gusta esto. Me tomó uno o dos segundos darme cuenta de que su método funciona. Espero que no le importe la versión C # agregada (las llaves adicionales son para que el índice tenga el mismo alcance que en un bucle for).
MusiGenesis
No estoy seguro de si usaría esto en el código de producción, porque provocaría un "¿WTF es esto?" Universal reacción de quien tuvo que mantenerlo, pero en realidad es más fácil de escribir que un bucle for normal, y no hay signos menos o símbolos> =.
MusiGenesis
1
Lástima que la versión de C # necesita el extra "> 0".
MusiGenesis
No estoy seguro de qué idioma es. pero su primer fragmento de código ciertamente NO es C :) solo para decirle lo obvio. tal vez quisiste escribir C # y html no pudo analizarlo o algo así?
Johannes Schaub - litb
@litb: Supongo que "myArray.Length" no es válido en C (?), pero la parte del ciclo while funciona y se ve muy bien.
MusiGenesis
3

La mejor manera de hacer eso en C ++ es probablemente usar adaptadores de iterador (o mejor, rango), que transformarán la secuencia de manera perezosa a medida que se atraviesa.

Básicamente,

vector<value_type> range;
foreach(value_type v, range | reversed)
    cout << v;

Muestra el rango "rango" (aquí, está vacío, pero estoy bastante seguro de que puede agregar elementos usted mismo) en orden inverso. Por supuesto, simplemente iterar el rango no es de mucha utilidad, pero pasar ese nuevo rango a algoritmos y demás es bastante bueno.

Este mecanismo también se puede utilizar para usos mucho más potentes:

range | transformed(f) | filtered(p) | reversed

Calculará perezosamente el rango "rango", donde la función "f" se aplica a todos los elementos, los elementos para los que "p" no es verdadera se eliminan y finalmente se invierte el rango resultante.

La sintaxis de tubería es la OMI más legible, dado su infijo. La actualización pendiente de revisión de la biblioteca Boost.Range implementa esto, pero también es bastante simple hacerlo usted mismo. Es aún más interesante con un lambda DSEL para generar la función f y el predicado p en línea.


fuente
¿Puede decirnos de dónde tiene "foreach"? ¿Es un #define to BOOST_FOREACH? Se ve lindo todo el camino.
Johannes Schaub - litb
1
// this is how I always do it
for (i = n; --i >= 0;){
   ...
}
Mike Dunlavey
fuente
1

Prefiero un bucle while. Es más claro para mí que disminuir ien la condición de un bucle for

int i = arrayLength;
while(i)
{
    i--;
    //do something with array[i]
}
Petko Petkov
fuente
0

Usaría el código en la pregunta original, pero si realmente quisieras usar foreach y tener un índice entero en C #:

foreach (int i in Enumerable.Range(0, myArray.Length).Reverse())
{
    myArray[i] = 42; 
}
xyz
fuente
-1

Voy a intentar responder mi propia pregunta aquí, pero tampoco me gusta esto:

for (int i = 0; i < myArray.Length; i++)
{
    int iBackwards = myArray.Length - 1 - i; // ugh
    myArray[iBackwards] = 666;
}
MusiGenesis
fuente
En lugar de hacer el .Length - 1 - i cada vez, ¿quizás considere una segunda variable? Ver mi publicación [actualizada].
Marc Gravell
Voté en contra de mi propia pregunta. Duro. No es más tosco que el original.
MusiGenesis
-4

NOTA: Esta publicación terminó siendo mucho más detallada y, por lo tanto, fuera de tema, me disculpo.

Dicho esto, mis compañeros lo leen y creen que es valioso "en algún lugar". Este hilo no es el lugar. Agradecería sus comentarios sobre dónde debería ir esto (soy nuevo en el sitio).


De todos modos, esta es la versión C # en .NET 3.5, lo cual es sorprendente porque funciona en cualquier tipo de colección utilizando la semántica definida. Esta es una medida predeterminada (¡reutilización!), No el rendimiento o la minimización del ciclo de la CPU en el escenario de desarrollo más común, aunque eso nunca parece ser lo que sucede en el mundo real (optimización prematura).

*** Método de extensión que trabaja sobre cualquier tipo de colección y realiza una acción que el delegado espera un valor único del tipo, todo ejecutado sobre cada elemento a la inversa **

Requiere 3.5:

public static void PerformOverReversed<T>(this IEnumerable<T> sequenceToReverse, Action<T> doForEachReversed)
      {
          foreach (var contextItem in sequenceToReverse.Reverse())
              doForEachReversed(contextItem);
      }

¿Versiones anteriores de .NET o desea comprender mejor los componentes internos de Linq? Sigue leyendo .. O no ..

SUPUESTO: En el sistema de tipos .NET, el tipo Array hereda de la interfaz IEnumerable (no el IEnumerable genérico solo IEnumerable).

Esto es todo lo que necesita para iterar de principio a fin, sin embargo, desea moverse en la dirección opuesta. Como IEnumerable funciona en Array de tipo 'objeto', cualquier tipo es válido,

MEDIDA CRÍTICA: Asumimos que si puede procesar cualquier secuencia en orden inverso, es 'mejor' que solo podrá hacerlo en números enteros.

Solución a para .NET CLR 2.0-3.0:

Descripción: Aceptaremos cualquier instancia de implementación de IEnumerable con el mandato de que cada instancia que contiene sea del mismo tipo. Entonces, si recibimos una matriz, la matriz completa contiene instancias de tipo X. Si cualquier otra instancia es de un tipo! = X, se lanza una excepción:

Un servicio singleton:

clase pública ReverserService {private ReverserService () {}

    /// <summary>
    /// Most importantly uses yield command for efficiency
    /// </summary>
    /// <param name="enumerableInstance"></param>
    /// <returns></returns>
    public static IEnumerable ToReveresed(IEnumerable enumerableInstance)
    {
        if (enumerableInstance == null)
        {
            throw new ArgumentNullException("enumerableInstance");
        }

        // First we need to move forwarad and create a temp
        // copy of a type that allows us to move backwards
        // We can use ArrayList for this as the concrete
        // type

        IList reversedEnumerable = new ArrayList();
        IEnumerator tempEnumerator = enumerableInstance.GetEnumerator();

        while (tempEnumerator.MoveNext())
        {
            reversedEnumerable.Add(tempEnumerator.Current);
        }

        // Now we do the standard reverse over this using yield to return
        // the result
        // NOTE: This is an immutable result by design. That is 
        // a design goal for this simple question as well as most other set related 
        // requirements, which is why Linq results are immutable for example
        // In fact this is foundational code to understand Linq

        for (var i = reversedEnumerable.Count - 1; i >= 0; i--)
        {
            yield return reversedEnumerable[i];
        }
    }
}



public static class ExtensionMethods
{

      public static IEnumerable ToReveresed(this IEnumerable enumerableInstance)
      {
          return ReverserService.ToReveresed(enumerableInstance);
      }
 }

[TestFixture] clase pública Testing123 {

    /// <summary>
    /// .NET 1.1 CLR
    /// </summary>
    [Test]
    public void Tester_fornet_1_dot_1()
    {
        const int initialSize = 1000;

        // Create the baseline data
        int[] myArray = new int[initialSize];

        for (var i = 0; i < initialSize; i++)
        {
            myArray[i] = i + 1;
        }

        IEnumerable _revered = ReverserService.ToReveresed(myArray);

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));
    }

    [Test]
    public void tester_why_this_is_good()
    {

        ArrayList names = new ArrayList();
        names.Add("Jim");
        names.Add("Bob");
        names.Add("Eric");
        names.Add("Sam");

        IEnumerable _revered = ReverserService.ToReveresed(names);

        Assert.IsTrue(TestAndGetResult(_revered).Equals("Sam"));


    }

    [Test]
    public void tester_extension_method()
  {

        // Extension Methods No Linq (Linq does this for you as I will show)
        var enumerableOfInt = Enumerable.Range(1, 1000);

        // Use Extension Method - which simply wraps older clr code
        IEnumerable _revered = enumerableOfInt.ToReveresed();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }


    [Test]
    public void tester_linq_3_dot_5_clr()
    {

        // Extension Methods No Linq (Linq does this for you as I will show)
        IEnumerable enumerableOfInt = Enumerable.Range(1, 1000);

        // Reverse is Linq (which is are extension methods off IEnumerable<T>
        // Note you must case IEnumerable (non generic) using OfType or Cast
        IEnumerable _revered = enumerableOfInt.Cast<int>().Reverse();

        Assert.IsTrue(TestAndGetResult(_revered).Equals(1000));


    }



    [Test]
    public void tester_final_and_recommended_colution()
    {

        var enumerableOfInt = Enumerable.Range(1, 1000);
        enumerableOfInt.PerformOverReversed(i => Debug.WriteLine(i));

    }



    private static object TestAndGetResult(IEnumerable enumerableIn)
    {
      //  IEnumerable x = ReverserService.ToReveresed(names);

        Assert.IsTrue(enumerableIn != null);
        IEnumerator _test = enumerableIn.GetEnumerator();

        // Move to first
        Assert.IsTrue(_test.MoveNext());
        return _test.Current;
    }
}
equipo de domain.dot.net
fuente
2
Amigo o amigo: Solo estaba buscando una forma menos torpe de escribir "para (int i = myArray.Length - 1; i> = 0; i--)".
MusiGenesis
1
no hay ningún valor en esta respuesta en absoluto
Matt Melton