Laden...

foreach iteration gleichzeitig über mehrere arrays

Erstellt von renexxl vor 18 Jahren Letzter Beitrag vor 18 Jahren 7.498 Views
R
renexxl Themenstarter:in
51 Beiträge seit 2005
vor 18 Jahren
foreach iteration gleichzeitig über mehrere arrays

Hallo!

Bei mir häufen sich die Fälle, in denen ich mit foreach über mehrere arrays gleichzeitig iterieren möchte, um redundanten Code zu vermeiden.
Beispiel:


int[] numbers0 = { 1, 2, 3 };
int[] numbers1 = { 4, 5, 6 };

foreach (int i in numbers0)
{
    System.Console.WriteLine(i);
}

foreach (int i in numbers1)
{
    System.Console.WriteLine(i);
}

Stattdessen möchte ich in einer foreach Schleife beide arrays gleichzeitig abarbeiten.
Dies könnte z.B. folgendermaßen aussehen:


foreach (int i in numbers0 + numbers1)
{
    System.Console.WriteLine(i);
}

Insbesondere bei vielen arrays erzeugt man schnell redundanten Code.

Ich könnte mir natürlich ein temporäres Array erzeugen und numbers0 und numbers1 dort reinkopieren. Dies würde aber unnötigen Overhead erzeugen.

  • Rene
4.506 Beiträge seit 2004
vor 18 Jahren

Hallo renexxl,

in Deinen Fällen würde ich schlicht auf die Foreach-Anweisung verzichten und eine klassische For-Schleife verwenden:


for (int i=0; i<numbers0.Count; i++)
{
   Console.Writeline(numbers0[i]);
   Console.Writeline(numbers1[i]);
}

Das Ganze funktioniert aber nur, wenn beide arrays gleich viele Einträge haben (um genau zu sein muss numbers1 genausoviele, oder mehr Einträge besitzen).

Ciao
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo norman_timo,

dein Code verändert aber die Reihenfolge der Ausgabe und löst daher das eigentliche Problem nicht.

Hallo renexxl,

du kannst dir natürlich einen eigenen Enumerator schreiben, aber vielleicht ist auch schon die grundlegende Speicherung unangemessen. Also das der "Fehler" im Design schon darin liegt, dass du mehrere Arrays verwendest. Das ist aber schwierig zu beurteilen, ohne die Aufgabenstellung zu kennen.

herbivore

6.862 Beiträge seit 2003
vor 18 Jahren

Deine foreach Schleife wird doch eh zu ner for Schleife optimiert wenn du primitive Datentypen nimmst. Von daher ists nur syntaktischer Zucker 😉 Foreach macht nur Sinn wenn du die Funktionen aus IEnumerator und IEnumeralbe implementierst.

Baka wa shinanakya naoranai.

Mein XING Profil.

S
1.047 Beiträge seit 2005
vor 18 Jahren

Bei mir häufen sich die Fälle, in denen ich mit foreach über mehrere arrays gleichzeitig iterieren möchte, um redundanten Code zu vermeiden.

was heißt denn gleichzeitig?

Insbesondere bei vielen arrays erzeugt man schnell redundanten Code.

warum schreibst du dir nicht einfach eine methode die als parameter ein array bekommt und dieses dann ausgibt?

Ich könnte mir natürlich ein temporäres Array erzeugen und numbers0 und numbers1 dort reinkopieren. Dies würde aber unnötigen Overhead erzeugen.

was soll das werden?

R
renexxl Themenstarter:in
51 Beiträge seit 2005
vor 18 Jahren

Deine foreach Schleife wird doch eh zu ner for Schleife optimiert wenn du primitive Datentypen nimmst.

Mein Beispiel benutzt einen primitiven Datentyp, in der Praxis nutze ich zumeist nicht primitive Datentypen. Was würde es mir bringen direkt eine for-Schleife zu benutzen?

was heißt denn gleichzeitig?

Mit "gleichzeitig" meine ich "sequentiell innerhalb !einer! foreach-Schleife".

warum schreibst du dir nicht einfach eine methode die als parameter ein array bekommt und dieses dann ausgibt?

Wäre eine Möglichkeit. Ich bin aber der Meinung, dass mein Vorschlag (mit dem +) den Code noch lesbarer macht.

was soll das werden?

Man könnte sich eine Methode schreiben, welche Arrays als Parameter erhält und ein Array als Konkatenation dieser arrays zurückgibt. Ueber das konkatenierte Array kann man dann in einer foreach-Schleife iterieren.

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo renexxl,

hier kommt die Klasse MetaCollection, mit der du genau das tun kannst, was du willst.

EDIT: Weiter unten gibt es eine typsichere Implementierung, die vorzuziehen ist.


foreach (int i in new MetaCollection (numbers0, numbers1)) {
   System.Console.WriteLine (i);
}


using System;
using System.Collections;

public class MetaCollection : IEnumerable
{
   private ICollection [] _aicoll;

   internal class MetaCollectionEnumerator : IEnumerator
   {
      private MetaCollection _mcoll;
      private int            _iPos;
      private IEnumerator    _ienum;

      internal MetaCollectionEnumerator (MetaCollection mcoll)
      {
         _mcoll = mcoll;
         Reset ();
      }

      public Object Current
      {
         get { return _ienum == null ? null : _ienum.Current; }
      }

      public bool MoveNext ()
      {
         while (_ienum == null || !_ienum.MoveNext ()) {
            if (++_iPos >= _mcoll._aicoll.Length) { //EDIT: Siehe meine Anmerkung in dem Beitrag weiter unten
               return false;
            }
            _ienum = _mcoll._aicoll [_iPos].GetEnumerator ();
         }

         return true;
      }

      public void Reset ()
      {
         _iPos = -1;
         _ienum = null;
      }

   }

   public MetaCollection (params ICollection [] aicoll)
   {
      _aicoll = aicoll;
   }

   public IEnumerator GetEnumerator ()
   {
      return new MetaCollectionEnumerator (this);
   }
}

abstract class App
{
   public static void Main (string [] astrArg)
   {
      int [] numbers0 = { 1, 2, 3 };
      int [] numbers1 = { 4, 5, 6 };

      foreach (int i in new MetaCollection (numbers0, numbers1)) {
         Console.WriteLine (i);
      }
   }
}

herbivore

S
1.047 Beiträge seit 2005
vor 18 Jahren

also herbivores lösung ist ja mal echt extraklasse 👍

@topicersteller
wär natürlic halles einfacher wenn du im einganstpost schon gesagt hättest was du wirklich machen willst 😉

4.506 Beiträge seit 2004
vor 18 Jahren

Hallo Herivore,

dein Code verändert aber die Reihenfolge der Ausgabe und löst daher das eigentliche Problem nicht.

ich hatte das anders verstanden. Ich dachte renexxl wollte die Daten einfach mit einer durchzählung abgreifen. Ich hatte mir keine Gedanken über die Reihenfolge gemacht, da ich der Meinung war, dass wenn ich die Daten in einem Durchgang abgreifen will, dass die Reihenfolge keine Rolle spielt.

Wenn dem natürlich so ist, dann hast Du vollkommen Recht. Daher war meine Lösung von einem anderen Ansatz ausgegangen.

Aber Deine MetaCollection hat echt was für sich 😉

Ciao
Norman-Timo

A: “Wie ist denn das Wetter bei euch?”
B: “Caps Lock.”
A: “Hä?”
B: “Na ja, Shift ohne Ende!”

6.862 Beiträge seit 2003
vor 18 Jahren

Mag schön aussehen, aber mehr als eine auf ICollection typisierte eigene Collection ist es auch nicht 🙂 Aber auf sowas muss man auch erstmal kommen.

Des eigentliche Problem ist ja, dass das Konstrukt nicht typsicher ist, geb mal als Input zwei verschiedentypige Arrays an und schon krachts. Wäre noch nen ausbaufähiger Punkt.

Gruß Talla

Baka wa shinanakya naoranai.

Mein XING Profil.

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo zusammen,

freut ich, dass es euch gefällt. Perfekt ist die Implementierung allerdings nicht. Habt ihr denn den Fehler gefunden, der in meiner Enumerator-Implementierung steckt?

herbivore

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo zusammen,

ist das Interesse so abgeebbt oder war der Fehler so schwer zu finden?

In der while-Schleife, darf _iPos nur erhöht werden, wenn noch eine weitere Collection in _aicoll vorhanden ist. Also


if (_iPos >= _mcoll._aicoll.Length - 1) {
   return false;
}
_ienum = _mcoll._aicoll [++_iPos].GetEnumerator ();

statt


if (++_iPos >= _mcoll._aicoll.Length) {
   return false;
}
_ienum = _mcoll._aicoll [_iPos].GetEnumerator ();

Ansonsten wäre die Forderung an MoveNext

If MoveNext passes the end of the collection, the enumerator is positioned after the last element in the collection and MoveNext returns false. When the enumerator is at this position, subsequent calls to MoveNext also return false until Reset is called.

durch potentielles Überzählen von _iPos nicht gewährleistet, auch wenn der Fehler in der Praxis vermutlich nie zum Tragen gekommen wäre.

Neben diesem Fehler gibt es noch einen Schönheitsfehler, denn die Klasse MetaCollection sollte außer IEnumerable noch ICollection und ICloneable implementieren, um dem Namensbestandteil 'Collection' gerecht zu werden. Die andere Alternative wäre nur die Klasse MetaCollectionEnumerator zu schreiben, analog dem Beispiel MovableEnumerator in Objekte identifizieren .

herbivore

R
renexxl Themenstarter:in
51 Beiträge seit 2005
vor 18 Jahren

Hi Herbivore!

Deine Lösung ist genau das, was ich gesucht habe. Die Idee ist wirklich sehr gut. Respekt!

Ich habe deine Klasse in meiner Applikation eingebaut und bislang keinen Fehler festgestellt. Funktioniert alles wie erwartet.

Vielen Dank!

  • Rene
S
8.746 Beiträge seit 2005
vor 18 Jahren

Schöne Klasse. Fällt mir auch spontan ein Verwendungszweck ein: Wenn man File-Filter verwendet kommt man bei mehreren, gleichzeitig anzuwendenden Filtern in die Breduille, weil .NET nur Einzel-Filter unterstützt. Als Konsequenz muss man mehrere Einzelcollections abiterieren. Mit der Lösung deutlich einfacher.

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo Talla,

Des eigentliche Problem ist ja, dass das Konstrukt nicht typsicher ist, geb mal als Input zwei verschiedentypige Arrays an und schon krachts. Wäre noch nen ausbaufähiger Punkt.

Naja, ob es kracht, hängt davon ab, welchen Typ die Elementvariable im foreach hat. Solange die typkompatibel zu allen vorhandenen Elementen ist, kracht es nicht.

Ich habe trotzdem mal eine typsichere MetaCollection geschrieben. 🙂

Anm.: Da IEnumerator<T2> von IEnumerator erbt und beide eine Current-Methode deklarieren, die sich nur im Rückgabewert unterscheidet, kommt man nicht umhin, mindestens eine Current-Methode interface-explizit zu definieren. Deshalb habe ich im Unterschied zu der untypisierten MetaCollection gleich alle Methoden interface-explizit deklariert.


using System;
using System.Collections;
using System.Collections.Generic;

public class MetaCollection<T> : IEnumerable<T>
{
   private ICollection<T> [] _aicoll;

   internal class MetaCollectionEnumerator<T2> : IEnumerator<T2>
   {
      private MetaCollection<T2> _mcoll;
      private int                _iPos;
      private IEnumerator<T2>    _ienum;

      internal MetaCollectionEnumerator (MetaCollection<T2> mcoll)
      {
         _mcoll = mcoll;
         Reset ();
      }

      T2 IEnumerator<T2>.Current
      {
         get { return _ienum.Current; }
      }

      Object IEnumerator.Current
      {
         get { return ((IEnumerator<T2>)this).Current; }
      }

      bool IEnumerator.MoveNext ()
      {
         while (_ienum == null || !_ienum.MoveNext ()) {
            if (_iPos >= _mcoll._aicoll.Length - 1) {
               return false;
            }
            _ienum = _mcoll._aicoll [++_iPos].GetEnumerator ();
         }

         return true;
      }

      void IEnumerator.Reset ()
      {
         Reset ();
      }

      private void Reset ()
      {
         _iPos = -1;
         _ienum = null;
      }

      void IDisposable.Dispose ()
      {
         // warum IEnumerator<T> von IDisposable erbt, wird wohl
         // Microsofts Geheimnis bleiben, zumal das IEnumerable<T> 
         // und auch ICollection<T> nicht tun.
      }
   }

   public MetaCollection (params ICollection<T> [] aicoll)
   {
      _aicoll = aicoll;
   }

   IEnumerator<T> IEnumerable<T>.GetEnumerator ()
   {
      return new MetaCollectionEnumerator<T> (this);
   }

   IEnumerator IEnumerable.GetEnumerator ()
   {
      return ((IEnumerable<T>)this).GetEnumerator ();
   }
}

abstract class App
{
   public static void Main (string [] astrArg)
   {
      int [] numbers0 = { 1, 2, 3 };
      int [] numbers1 = { 4, 5, 6 };

      foreach (int i in new MetaCollection<int> (numbers0, numbers1)) {
         Console.WriteLine (i);
      }
   }
}

herbivore

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo zusammen,

eine überarbeitete Version der MetaCollection findet ihr als Iter.Join in Hilfreiche Iteratoren / Improving Foreach.

herbivore