Laden...

Mehrdimensionale Arrays mit verschiedenen Dimensionen

Erstellt von ck82 vor 5 Jahren Letzter Beitrag vor 5 Jahren 2.414 Views
C
ck82 Themenstarter:in
34 Beiträge seit 2015
vor 5 Jahren
Mehrdimensionale Arrays mit verschiedenen Dimensionen

Hallo liebes Forum,

ich hoffe ihr könnte einem Anfänger helfen. Ich habe so meine Probleme mit Arrays.
Kurz: Ich importiere eine CSV-Datei als String [][] ([] = Jede Zeile / [][] = jede Zelle.

Nun möchte ich das ganze gerne auf verschiedene Arrays aufteilen.

Dazu würde ich einfach eine weitere Dimension anlegen.

[] = Jedes Array mit bestimmten Werten / [][] Jede Zeile mit bestimmten Werten / [][][] Jede Zelle

deklarieren würde ich wie folgt:

string [][] ArrCSV; (bereits mit Werten gefüllt)
Neu:
string [][][] RueckArr;

Folgendes würde gehen:

RueckArr[0] = ArrCSV;
// Jetzt befindet sich das komplette ArrCSV in der zweiten Dimension von RueckArr

Wenn ich aber gerne die Arrays einzeln importieren möchte, funktioniert das nicht.
Im Grunde müsste der nachfolgende Satz identisch mit vorherigem sein (in einer For-Schleife):

RueckArr[0][i] = ArrCSV[i];

Wenn ich das durchlaufen lassen - kommt eine NullReferenceExeption

Kann mir jemand einen Schubs geben?

16.806 Beiträge seit 2008
vor 5 Jahren

Eine NullReferenceException ermittelt man am einfachsten mit dem Debugger
[Artikel] Debugger: Wie verwende ich den von Visual Studio?

Ansonsten weiß ich nicht ganz, wieso Du das so relativ umständlich machst.
Das wird auch je nach CSV Größe enorm den RAM vollpumpen.

C
ck82 Themenstarter:in
34 Beiträge seit 2015
vor 5 Jahren

Was wäre die weniger umständliche Alternative?

16.806 Beiträge seit 2008
vor 5 Jahren

Ich würde das einfach in eine simple List<string[]> lesen; im Endeffekt auch Zweidimensional, aber viel einfach mit umzugehen.

File.ReadLines("my.csv").Select(x => x.Split(','))

Auf verschiedene Arrays umsetzen heisst halt, dass Du viel Speicher dafür benötigst.
Besser ist es, Projektionen zu nutzen. Ansonsten hast Du alles mehrfach im Speicher.

In C# gibt es (seit 7.2) Memory Access Patterns in Form von Span<T> und Memory<T>.
Es braucht nicht nur viel weniger Speicher, es ist insgesamt viel performanter.

Das mit der dritten Dimension hab ich nicht verstanden; weder wozu Du es brauchst, noch wozu es dienst.
Du schreibst leider auch nur "funktioniert nicht", womit niemand was anfangen kann 😉

L
136 Beiträge seit 2015
vor 5 Jahren

Hallo ck82,

Wie Abt schon schreibt, ist es mit einer Liste bestimmt einfacher.

Kann z.B. so aussehen:



        public List<ModelCSV> ReadCSV(string path, string delimiter)
        {

            using (StreamReader reader = new StreamReader(path))
            {

                var arrCSV = from line in ReadFile(reader, delimiter)
                               select new ModelCSV
                               {
                                   Column1 = line[0],
                                   Column2 = line[1],
                                   Column3 = line[2],
                                   Column4 = line[3]
                                };

                return arrCSV.ToList();
            }

        }

        private IEnumerable<string[]> ReadFile(StreamReader reader, string delimiter)
        {
            while (reader.EndOfStream == false)
            {
                yield return reader.ReadLine().Split(delimiter.ToCharArray());
            }
        }


Dann machst Du Dir noch eine Klasse "ModelCSV" mit den Eigenschaften die Du benötigst und fertig.
-> Denke mal die CSV-Datei hat immer die selbe Struktur....kann also einfach in einer Klasse "ModelCSV" abgebildet werden

Edit benötigte Usings:
using System.Collections.Generic;
using System.Linq;
using System.IO;

Gruss Lhyn

16.806 Beiträge seit 2008
vor 5 Jahren

Prinzipiell korrekter Code; aber unnötig lang.

Da sowieso alles durch ToList() im Speicher materialisiert wird und eine Liste zurück gibst, kann man auch getrost auf das speicherschonende yield verzichten; man hat keinen Gewinn.

Ergibt am Ende


        public List<ModelCSV> ReadCSV(string path, string delimiter)
        {
            IEnumerable<string[]> lines = File.ReadLines(path)
                                                .Select(x => x.Split(delimiter.ToCharArray()));

            IEnumerable<ModelCSV> query = lines.Select(line =>
                new ModelCSV { Column1 = line[0], Column2 = line[1], Column3 = line[2], Column4 = line[3] });

            return query.ToList();
        }

Wenn jedoch der yield-Effekt erwünscht wird, dann ergibt sich

                
        public IEnumerable<ModelCSV> ReadCSV(string path, string delimiter)
        {
            using (StreamReader reader = new StreamReader(path))
            {
                foreach(ModelCSV entry in ReadFile(reader, delimiter)
                    .Select(line => new ModelCSV {Column1 = line[0], Column2 = line[1], Column3 = line[2], Column4 = line[3]}))
                { 
                    yield return entry;
                }
            }
        }

        private IEnumerable<string[]> ReadFile(StreamReader reader, string delimiter)
        {
            string line;
            char[] cut = delimiter.ToCharArray();
            while ((line = reader.ReadLine()) != null)
            {
                yield return line.Split(cut);
            }
        }

Warum EndOfStream unnötigt (oder auf eine gewisse Art sogar falsch) ist: .txt filtern die eine Bedingung erfüllt und die Ergebnisse in eine neue .txt schreiben

3.003 Beiträge seit 2006
vor 5 Jahren

Wenn jedoch der yield-Effekt erwünscht wird, dann ergibt sich
[..]

Jetzt hast du nur die Materialisierung durch ToList() gegen die Materialisierung durch foreach getauscht und hast immer noch keinen "yield-effekt".


public IEnumerable<ModelCSV> ReadCSV(string path, string delimiter)
{
    using (StreamReader reader = new StreamReader(path))
        return ReadFile(reader, delimiter).Select(Parse);
}

private static ModelCSV Parse(string[] input) 
    => new ModelCSV 
    {
        Column1 = input[0], 
        Column2 = input[1], 
        Column3 = input[2], 
        Column4 = input[3]
    };


Ich halte nicht viel davon, weil der reader im Zweifel schon disposed ist, wenn man dann endlich die Liste materialisieren möchte. Schlechter Einsatz für yield.

LaTino
EDIT: typo

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

16.806 Beiträge seit 2008
vor 5 Jahren

Jetzt hast du nur die Materialisierung durch ToList() gegen die Materialisierung durch foreach getauscht und hast immer noch keinen "yield-effekt".

foreach erzeugt keine Materialisierung... 🤔
foreach greift auf GetEnumerator zu bzw. dessen Current und MoveNext.
Da wird nichts aktiv materialisiert - im Gegensatz zu ToList.

Du kannst auch im Debugger beobachten, dass ganz normal - und mit yield - der Enumerator durchlaufen wird.

    using (StreamReader reader = new StreamReader(path))  
        return ReadFile(reader, delimiter).Select(Parse);  

Ich halte nicht viel davon, weil der reader im Zweifel schon disposed ist

Genau das dürfte auch passieren.
Durch das return ReadFile wird das using() verlassen und der Stream geschlossen.
Beim Durchlaufen des Enumerators wird dann im while versucht, auf den geschlossenen Stream zuzugreifen.
Daher ist ein foreach notwendig - das keine Auswirkung auf die Materialisierung von yield hat.

Ich hatte zuerst den gleichen Gedankengang und direkt das IEnumerable zurück gegeben; was aber in nem Runtime Error durch die Zugriffsverletzung mich wachgerüttelt hat.

Schlechter Einsatz für yield.

Sehe ich völlig anders.
Bei korrekter Verwendung ein absolut guter Einsatz von yield.

C
ck82 Themenstarter:in
34 Beiträge seit 2015
vor 5 Jahren

Ich möchte mich bei euch allen für die nützlichen Hinweise und die angeregte Diskussion bedanken.

Besonders aber bei lhyn, die Antwort hat mir am meisten genutzt und passt hervorragend in den Thread "Grundlagen von C#".

Die anderen Überlegungen sind für mich teilweise nur schwer nachzuvollziehen. Auch wenn es, meine ich, in der Diskussion in der ersten Linie um Performance ging. Diese Überlegungen sind aber für mich als Anfänger noch zu weit. Ich möchte zunächst das mein Code funktioniert. Performance kommt bei mir noch in der zweiten Reihe. Aber sicherlich kann ich und bestimmt auch andere von solchen Diskussionen und Überlegungen lernen. Daher freue ich mich über die angeregte Diskussion und die verschiedenen Herangehensweisen.

Nochmals vielen Dank euch allen für eure Unterstützung.

16.806 Beiträge seit 2008
vor 5 Jahren

Besonders aber bei lhyn, die Antwort hat mir am meisten genutzt

Nicht nur Performance...
Beachte, dass der Code einen Fehler hat, auf den ich im Folgepost hingewiesen habe.

L
136 Beiträge seit 2015
vor 5 Jahren

Hallo zusammen,

Sind den bei linq-Abfragen in Verbindung mit Dateizugriff die "using(...)" nicht nötig?
Habe hier im Forum mal gelernt, dass ich diese immer in ein using packen soll 😁

Gruss Lhyn

W
955 Beiträge seit 2010
vor 5 Jahren

Alle Objekte die IDisposable implementieren sollten in ein using gepackt werden. Ausnahmen sind Task und evtl HttpClient.

3.003 Beiträge seit 2006
vor 5 Jahren

Sind den bei linq-Abfragen in Verbindung mit Dateizugriff die "using(...)" nicht nötig?
Habe hier im Forum mal gelernt, dass ich diese immer in ein using packen soll 😄

Wo genau siehst du fehlende usings?

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

L
136 Beiträge seit 2015
vor 5 Jahren

Hallo LaTino,

Dachte in File.ReadLines ist IDisposable implementiert...

Aber dann sollte es doch mindesten in einen:



try
{}
catch()
{}


integriert werden?

Kann ja sein, dass es dieses File nicht gibt oder schon geöffnet ist etc?
-> Oder fängt mir linq diese Probleme ab?
Kann es auch gleich selbst ausprobieren...

Gruss Lhyn

3.003 Beiträge seit 2006
vor 5 Jahren

Du musst gar nichts ausprobieren, der Quellcode der Methoden ist öffentlich zugänglich.


[ResourceExposure(ResourceScope.Machine)]
[ResourceConsumption(ResourceScope.Machine)]
private static String[] InternalReadAllLines(String path, Encoding encoding)
{
    Contract.Requires(path != null);
    Contract.Requires(encoding != null);
    Contract.Requires(path.Length != 0);
 
    String line;
    List<String> lines = new List<String>();
 
    using (StreamReader sr = new StreamReader(path, encoding))
        while ((line = sr.ReadLine()) != null)
            lines.Add(line);
 
    return lines.ToArray();
}

Dein Einwurf mit using war daher Unsinn.

Dass man bei Dateioperationen möglichst prüft, ob die Datei da ist - geschenkt, das war aber auch weder Thema noch von dir moniert.

LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

L
136 Beiträge seit 2015
vor 5 Jahren

Wieder etwas dazugelernt 👍

Danke und Gruss Lhyn