Laden...

C# Tuning Werteeinlesealgorithmus, der doppelte Einträge verhindern soll

Erstellt von lechiffre vor 10 Jahren Letzter Beitrag vor 10 Jahren 2.885 Views
L
lechiffre Themenstarter:in
94 Beiträge seit 2013
vor 10 Jahren
C# Tuning Werteeinlesealgorithmus, der doppelte Einträge verhindern soll

Hi Leute folgende Frage.

Ich lese aus einer Textdatei werte aus die im 5 Minuten Takt über ein monat
z.B

0.0 30.04.13 19:20

Jetzt lese ich die datensätz in ein datatable.

Schön und gut.

Aber es geht viel Zeit flöten beim Vergleichen ob die Zeitpunkte schon in der Datatable vorhanden sind.(siehe code)

Dazu ist zu sagen das ich 19 Textdatein habe wo werte im 5 Minuten takt drin stehen , aber leider nicht immer im gleichen 5 minuten takt d.h es kann auch dazu kommen das mal werte fehlen etc.

Deswegen gebe ich im ersten schritt vor das die Column Zeitpunkt erstmal gefüllt wird im 5 minuten Takt und dann erst add ich die werte.

Und da hängt es : Der Vergleich ob der Zeitstempel schon in der Row steht zieht nach dem Performance Profiler am meisten Zeit.

Hier der Code vielleicht hat ja jemand nen Performanceschub 😃 würde mich sehr freuen über eine ausgibige diskussion.

 
var ci = CultureInfo.InvariantCulture.Clone() as CultureInfo;
            ci.NumberFormat.NumberDecimalSeparator = ".";
            var count = 0;
            var StartDate = new DateTime(2013,8, 31, 23, 55, 0);
            var EndDate = new DateTime(2013, 10, 1, 0, 0, 0);
            DateTime start, stop;
            var dt = MakeTableRegendaten2(19);
            while (StartDate.AddMinutes(MinuteInterval) < EndDate)
            {
                StartDate = StartDate.AddMinutes(MinuteInterval);
                var dtrow = dt.NewRow();
                dtrow = dt.NewRow();
                dtrow[0] = StartDate;
                dtregendaten.Rows.Add(dtrow);
            }
            start = DateTime.Now;
            foreach (var files in Directory.GetFiles(@"*"))
            {
                var info = new FileInfo(files);
                var fileName = Path.GetFileName(info.FullName);
                var reader = new StreamReader(files);
                string line;
                while ((line = reader.ReadLine()) != null)
                {
                    string[] separator = { " ","\t" };
                    var items = line.Split(separator, StringSplitOptions.RemoveEmptyEntries);
                    if (items.Length == 4)
                    {
                        var dt = Convert.ToDateTime(items[2] + " " + items[3]);
                        foreach ( var row in dt.Rows.Cast<DataRow>().Where(row =>(DateTime)row["Zeitpunkt"] == dt))
                        {
                            var _rwert = decimal.Parse(items[1], ci); // 1.1 löl ;) 
                            count++;
                            row[fileName.Substring(0, 2))] = _rwert;
                            Console.WriteLine(count);
                        }
                    }
                    
                }

                reader.Close();
                reader.Dispose();

            }

P
992 Beiträge seit 2007
vor 10 Jahren

Nimm ein Dictionary in dem du dir die Werte merkst, welche schon drin stehen.

49.485 Beiträge seit 2005
vor 10 Jahren

Hallo lechiffre,

bzw. noch besser eine HashSet. Die Add-Methode liefert zurück, ob der Eintrag schon vorhanden war.

herbivore

L
lechiffre Themenstarter:in
94 Beiträge seit 2013
vor 10 Jahren

Ich werde es mal mit dem HashSet versuchen danke für die anregungen!

😃

1.361 Beiträge seit 2007
vor 10 Jahren

Hi LeChiffre,

was du vorhast, ist identisch mit einem Full Outer Join.
Du könntest dafür sogar die Enumerable.Join Methode nutzen, allerdings geht diese pauschal nur für 2 Collections und außerdem nutzt diese es nicht aus, wenn deine Eingangslisten vorsortiert sind.

Und wenn man davon ausgeht, dass innerhalb einer jeden Datei die Einträge nach der Zeit sortiert sind, dann kannst du ähnlich wie beim Merge-Schritt im Merge-Sort deine Dateien On-The-Fly zusammenbringen, in dem du gemeinsam über alle Dateien gleichzeitig iterierst.

Dann sparst du dir das vorherige Erzeugen, das Verwalten eines HashSets, etc...
Vielleicht ist das ja was für dich.
Ich habe es zwar nicht getestet, es könnte aber auch schneller sein.

Hier ist mal eine Implementierung für solch einen SortedFullOuterJoin:

using System.Linq;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;

public class Joiner
{
    class Iterator<T>
    {
        internal T Item;
        internal bool HasItem;
        internal IEnumerator<T> Enumerator;

        internal Iterator(IEnumerable<T> xs){
            this.Enumerator = xs.GetEnumerator();
            this.MoveNext();
        }

        internal void MoveNext()
        {
            this.HasItem = this.Enumerator.MoveNext();
            if (this.HasItem)
                this.Item = this.Enumerator.Current;
        }
    }

    public static IEnumerable<IList<T>> SortedFullOuterJoin<T,K>(IEnumerable<IEnumerable<T>> xs, Func<T,K> key)
        where K : IComparable<K>, IEquatable<K>
    {
        var iterators = new List<Iterator<T>>();

        try
        {
            foreach(var x in xs)
                iterators.Add(new Iterator<T>(x));

            while (iterators.Any(e => e.HasItem))
            {        
                K minKey = iterators
                                .Where(e => e.HasItem)
                                .Min(e => key(e.Item));

                var minIterators = iterators
                                    .Where(e => e.HasItem && key(e.Item).Equals(minKey));
                
                yield return minIterators
                                .Select(e => e.Item)
                                .ToArray();

                foreach (var e in minIterators)
                        e.MoveNext();
            }
        }
        finally
        {
            foreach(var it in iterators)
                it.Enumerator.Dispose();
        }
    } 
}


class MainClass
{
    

    class Data
    {
        public int Key { get; set; }
        public string Value { get; set; }

        override public string ToString()
        {
            return String.Format("{0}:{1}", this.Key, this.Value);
        }
    }


    public static void Main()
    {
        var first = new Data[]{
                        new Data{Key=0, Value="a"},
                        new Data{Key=1, Value="b"},
                        //new Data{Key=2, Value="c"},
                        new Data{Key=3, Value="d"},
                        //new Data{Key=4, Value="e"},
                        //new Data{Key=5, Value="f"},
                    };

        var second = new Data[]{
                        new Data{Key=0, Value="A"},
                        //new Data{Key=1, Value="B"},
                        new Data{Key=2, Value="C"},
                        new Data{Key=3, Value="D"},
                        //new Data{Key=4, Value="E"},
                        new Data{Key=5, Value="F"},
                    };

        var third = new Data[]{
                        //new Data{Key=0, Value="Aa"},
                        new Data{Key=1, Value="Bb"},
                        new Data{Key=2, Value="Cc"},
                        new Data{Key=3, Value="Dd"},
                        new Data{Key=4, Value="Ee"},
                        new Data{Key=5, Value="Ff"},
                    };

        var xs = new Data[][]{first, second, third};

        var joined = Joiner.SortedFullOuterJoin(xs, x=>x.Key);
        foreach(var es in joined)
        {
            var text = String.Join(",", es.Select(e => e.Value.ToString()));
            Console.WriteLine(text);
        }
    }
}

Das Beispiel liefert auf der Konsole:


a,A
b,Bb
C,Cc
d,D,Dd
Ee
F,Ff

Deinen Code könnte man dann wie folgt umschreiben: (du musst das aber wohl erst noch zum kompilieren bringen 😉 )


public class RainMeasurement
{
    public string GroupID {get;set;}
    public DateTime Date {get;set;}
    public decimal Value {get;set;}
} 

public static IEnumerable<RainMeasurement> ReadRainFile(string fileName)
{
    var ci = CultureInfo.InvariantCulture.Clone() as CultureInfo;
    ci.NumberFormat.NumberDecimalSeparator = ".";

    var groupID = Path.GetFileName(new FileInfo(fileName).FullName).Substring(0, 2);

    string[] separators = { " ","\t" };
    foreach(var line in File.ReadLines(fileName))
    {
        var items = line.Split(separators, StringSplitOptions.RemoveEmptyEntries);
        if(items.Length == 4)
        {
            yield return new RainMeasurement{ 
                                GroupID = groupID,
                                Date = Convert.ToDateTime(items[2] + " " + items[3]),
                                Value = decimal.Parse(items[1], ci)};
        }
    }
}

public static void YourMain()
{
    var dtregendaten = MakeTableRegendaten2(19);
  
    var fileNames = Directory.GetFiles(@"*");
    var multiRainItems = fileNames.Select(ReadRainFile);


    var joined = Joiner.SortedFullOuterJoin(multiRainItems, item=>item.Date);
    foreach(var group in joined)
    {
        var dtrow = dt.NewRow();
        dtrow[0] = group[0].Date;
        foreach(var item in group)
            dtrow[item.GroupID] = item.Value;
        dtregendaten.Rows.Add(dtrow);
    }

}

Beste Grüße
zommi

L
lechiffre Themenstarter:in
94 Beiträge seit 2013
vor 10 Jahren

Boaaaaaah fetten applaus 😄

Das dauert jetzt nicht länger als nen wimpern schlag!

Vielen dank!

Ich habe mich mal mit der Joiner Klasse jetzt näher auseinander gesetzt super sache!

VIELEN VIELEN dank!

Gruß

1.361 Beiträge seit 2007
vor 10 Jahren

Und es kommt auch noch das richtige raus? 😃
Wenn etwas auf einmal zuu schnell geht... dann macht mich sowas immer skeptisch 😉