ProgrammieraufgabenHier wird regelmäßig eine neue Programmieraufgabe gestellt, die dann gelöst werden soll und in Zusammenarbeit mit den Moderatoren auch besprochen werden kann.
Messwerte verarbeiten
Diskussion: Messwerte verarbeiten im Forum Programmieraufgaben, in der Kategorie Code Kitchen; Anzeige
Bei einem Experiment werden Energieschwankungen im 10ms Abstand gemessen und in eine Datei geschrieben.
Format:
Code:
-1
2
-3
...
Anzeige Bei einem Experiment werden Energieschwankungen im 10ms Abstand gemessen und in eine Datei geschrieben.
Format:
Code:
-1
2
-3
also (negative/positive) Zahlen, jeweils eine pro Zeile.
Diese Daten müssen ausgewertet werden - uns (bzw. die Experimentatoren) insteressieren lokale Maxima - also der/die (falls mehrere gleiche vorhanden) Zeitabschnitt(e), während dessen am meisten Energie abgegeben wurde.
Das sind die Stellen, an denen die Summe der Messwerte am höchsten ist.
Bsp:
Messdaten:
-1,-4,5,2,-4,-3,-3,7,-1,-2,2,-8,7,-1,6,-7,-10,-3,-3,-11,-10,-10,5
lokales Maximum: 7,-1,6 => Summe=12
Bsp2:
-4,2,-3,-1,5,-10,2,-5
l.max: 5
Bsp3:
-4,2,-3,-1,5,-10,2,-5,3,-2,4,-1,2,-10
l.Max:
3-2,4,-1,2 -> Summe=6
Hier sind einige Aufgaben dazu (müssen nicht alle erledigt werden ;) )
1. Schreibe einen kurzen, schönen Code, der aus den übergebenen Werten das lokale Maximum herausfindet und ausgibt.
2.Wenn es N Werte gibt - wieviele Möglichkeiten müssen (abhängig von N) getestet werden?
3.Schreibe eine "echte" Anwendung, die aus einer Datei die Messwerte ausliest und verarbeitet. Hier sollte nicht nur die Zahlenfolge ausgegeben werden, sondern entweder die Position in der Datei (Zeile) oder am besten die zeitliche Position (die Messungen werden im 10ms Abstand vorgenommen). Achtung: es kann durchaus mehrere Maxima geben (wenn die Summe der Werte gleich ist) - dann sollten auch alle ausgegeben werden.
Zum testen des Codes können die Beispiele oben genommen werden,
zum testen der Anwendung siehe Anhänge(jeweils 1000 und 6000 Einträge). Interessant wäre auch die (grobe) Performance - zur Orientierung: Prototype Code in Prolog braucht auf meinem Notebook (1,6 Ghz C2Duo, 1GB Ram, Sicstus 4.04) für messwerte1 ~7 sek, für messwerte2 ~1300 Sek
__________________ Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.
Bei N Messungen gibt es die Möglichkeiten:
N mal Einzelmessung
N-1 mal 2erMessung
...
1 mal N-Messung
also Summe von n=1 bis N von n, was gerade der arithm. Reihe entspricht und sich zu N(N+1)/2 vereinfacht.
Also O(N^2).
Ergebnis zu 3.
Fürs erste hab ich 45 mit einem Auftreten und für das zweite 439 mit zwei.
Aber stimmt deine Zeit? Bei mir dauert nämlich auch das zweite nur wenige Sekunden... (amd64 3700+ singlecore)
1. heißt einfach kein komplettes, optimiertes Programm, sondern eine Funktion/Prozedur/sonstwas, so dass der Algorithmus und Funktionsweise ersichtlich sind
zu 3.
die Zeiten stimmen schon ;). Allerdings verwende ich die naive Vorgehnsweise - das Array wird in Teilarrays zerlegt und jedes einzelne getestet. Das wird einfach bei zu großen Arrays zu einem Speicherzugriffsproblem - schon das alleinige Zerlegen der zweiten Messwertedatei dauert ~20Sek (im Vergleich dazu: das 1 dauert nur ~0.5)
Allerdings war dafür die "Entwicklungszeit" angenehm kurz - zusammen mit Dateiauslesen+Testen so um 25 Minuten :)
Ich werde mir aber ein paar Gedanken dazu machen - schneller gehts aufjedenfall.
Prolog
Code:
%Zerlegt ein Array in Teilsequenzen
subseq([_|T],R):-subseq(T,R).
subseq(L,R):-seq(L,R).
seq([R|_],[R]).
seq([H|T],[H|R]):-seq(T,R).
check(Seq):- sumlist(Seq,Sum), %vergleicht einzelne Teilsequenzen mit
subsequence(_,OldSum),!, %den in DB gespeicherten
(Sum<OldSum
;
(Sum>OldSum -> retractall(subsequence(_,_));true),
asserta(subsequence(Seq,Sum))).
max_subseq([H|T],Result):-
retractall(subsequence(_,_)),
asserta(subsequence(H,H)), %initialisiere DB
forall(subseq([H|T],R),check(R)),
findall((seq(Seq),sum(Sum)),subsequence(Seq,Sum),Result).
PS: Habe übrigens dieselben Werte heraus.
__________________ Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.
Ich iteriere einfach über die Größe (1 bis N, zur Festlegung der Anzahl berücksichtigter Messungen), dann lese ich dementsprechend die ersten Messungen ein, vergleiche, ziehe den linken Wert ab und addiere den neuen rechten Wert (spart das komplette itererieren für jede Auswertung), wieder vergleichen usw. dann das ganze für eine Messung mehr...
zu 3
Wie bei 1 geschrieben, hab ich einen etwas anderen Ansatz, der anscheinent auch um einiges schneller ist. Wie bereits angedeutet, dauert die Messung2 nur knapp 2 Sekunden (mit laden, auswerten und ausgeben).
Die Entwicklungszeit hielt sich auch in Grenzen und mit etwa 150 Zeilen ist der Code auch recht übersichtlich (die eigentliche Auswerte Funktion ist 18 Zeilen lang, der Rest ist laden, Zeitmessung, UI).
Ausgabe:
Ich habe mir folgende Sachen überlegt:
man braucht nur 1 mal zu iterieren, sofern man sich bei der Iteration immer die vorherige Summe merkt und einen Speicher für "Kandidaten" hat.
Ausgangssituation: eine Liste. Beim Durchgang werden die Zahlen addiert.
Merke: vorherige Summe + Sequenz (alternativ Position) für die Ausgabe, speichere eventuelle Kandidaten. Suche am Ende die "größten" Kandidaten heraus.
1. am Ende der Liste ist man fertig
2. Ist die vorherige Summe+aktuelle Zahl < als aktuelle Zahl, so kann man die vorherige Summe sowie Sequenz verwerfen (denn größer wirds nicht).
3. Ist vorherige Summe+aktuelle Zahl > vorherige Summe, so kann man die Zahl zu der aktuellen Sequenz hinzufügen und mit der neuen Summe fortfahren
4.Ist vorherige Summe+aktuelle Zahl =<vorherige Summe, so kann es ja sein, dass es weiterhin noch kleiner wird - also bisheriges Ergebnis als Kandidaten speichern und fortfahren.
Kandidaten Speichern: ob man nur die größte Sequenz oder alles speichert (und später für die Ausgabe filtert/sortiert) ist "Geschmackssache".
1:1 Umsetzung des obigen Algos (in Prolog - wer hätte es gedacht :) ):
Code:
check([],_).
check([Val|T],seq(Sum,_)) :- Sum+Val<Val,check(T,seq(Val,[Val])),!.
check([Val|T],seq(Sum,Seq)) :- NewSum is Sum+Val,
NewSum>Sum,check(T,seq(NewSum,[Val|Seq])),!.
check([Val|T],seq(Sum,Seq)) :- NewSum is Sum+Val,
NewSum=<Sum,
seq(Old,_),!,store(Old,seq(Sum,Seq)),
check(T,seq(NewSum,[Val|Seq])),!.
"Nachteil" des Algorithmus : wenn eine Sequenz als Summe 0 ergibt und direkt darauf die "größte" Sequenz folgt, so wird die "0 Sequenz" nicht beachtet. Wobei das imho ein Definitionsfall ist, der auch berücksichtigt werden kann
file_to_list('messwerte.txt',List),find(List,Result).
List = [-7,1,0,1,-8,4,-4,-7,-2,5|...],
Result = [(sum(45),seq([6,-2,8,8,3,3,-4|...]))] ? ;
no
Ein paar Messungen:
der Code ist zwar nicht ganz optimal (Prädikat check könnte noch etwas optimiert werden, anstatt Sequenzen könnte man nur Anfang+Ende der Position speichern), aber immerhin nun in O(n) und nicht O(n^2).
Code:
?- file_to_list('messwerte.txt',L),benchmark(find(L,R),100,Time),length(L,Messwerteanzahl).
L = [-7,1,0,1,-8,4,-4,-7,-2,5|...],
R = [(sum(45),seq([6,-2,8,8,3,3,-4|...]))],
Time = ms(4.04),
Messwerteanzahl = 1000 ?
Code:
?- file_to_list('messwerte2.txt',L),benchmark(find(L,R),100,Time),length(L,Messwerteanzahl).
L = [-12,5,10,-5,-11,-7,13,7,-10,13|...],
R = [(sum(439),seq([11,16,17,16,6,9,-12|...]))],
Time = ms(32.27),
Messwerteanzahl = 6000 ?
__________________ Noch mal, für alle Pseudo-Geeks: 1+1=0. -> 10 wäre Überlauf!
Selig, wer nichts zu sagen hat und trotzdem schweigt.
Ich habe auch einmal ein kleines Python-Programm gebastelt...
46 bzw. 68ms für die Eingabedateien sind inklusive Einlesen für eine Interpreter-Sprache schon nicht schlecht, denke ich. Die Laufzeit ist O(n).
Nachdem ich das Problem verstanden habe, war die Programmierung nicht weiter schwierig, ich habe mir allerdings keine große Mühe gegeben, das Ganze sonderlich zu optimieren.
python
Code:
import sys
if len(sys.argv) != 2:
print "usage:", sys.argv[0], "<input file>"
exit(1)
f = open(sys.argv[1])
werte = []
for line in f:
werte.append(int(line))
start = 0
best = [[0, 1, werte[0]]]
prev = max(0, best[0][2])
for i in xrange(1, len(werte)):
wert = werte[i]
if prev + wert > best[-1][2]:
best.append([start, i, prev + wert])
if prev + wert > 0:
prev += wert
else:
prev = 0
start = i
i = len(best) - 1
while best[-1][2] == best[i][2] and i > 0:
print "Nach %d ms:" % (best[i][0] * 10),
print best[i][2], werte[best[i][0]:best[i][1]]
i -= 1