Entwickler-Ecke

Algorithmen, Optimierung und Assembler - Konzept gesucht: Zeitanteile ermitteln


Narses - Di 07.03.17 19:23
Titel: Konzept gesucht: Zeitanteile ermitteln
Moin!

Komischer Titel, sorry, mir fällt kein besserer ein. :( Ich brauche bitte mal etwas Input, wie man dieses Problem grundsätzlich angeht. Es geht um folgendes:Wie geht man sowas an, ohne sich in Fallunterscheidungen zu verlieren?! :gruebel: Irgendwie habe ich da grade ein Brett vorm Kopp! :autsch: Ideen? :lupe:

cu
Narses


Delete - Di 07.03.17 19:41

- Nachträglich durch die Entwickler-Ecke gelöscht -


Ralf Jansen - Di 07.03.17 20:46

Zitat:
Wie geht man sowas an, ohne sich in Fallunterscheidungen zu verlieren?!


Kommt drauf an ;) Wenn deine Beispiele soweit treffend sind das jeder Tag sich immer gleich verhält, also ein Montag immer wie ein Montag usw. ohne Regeln für besondere Montage dann würde ich so vorgehen.

A. Für jeden ganzen Wochentag die an diesem Tag zu berücksichtigende Zeit bestimmen (7 Konstanten unabhängig von deinem konkreten Zeitraum also was vorberechnetes)
B. Aus deinem konkreten Zeitraum die Randbeißer rausrechnen. Also die beiden Tage am Anfang und Ende die nicht vollständig abgebildet sind und die einzeln behandeln.
C. Anhand der nun kompletten Tage und der Info vom Startdatum(und dessen Wochentag) und der Anzahl folgender Tage kann ich das dann simpel die Anzahl der jeweiligen Wochentage ermitteln und das mit den Konstanten aus A ausmultiplizieren. (17 Montage mal 14400 Sekunden + 18 Dienstage * 14400 Sekunden usw.)
D. Jetzt die beiden anteiligen Tage(die Randbeißer) anhand ihres jeweiligen Wochentags noch dazurechnen. Je nachdem wie komplex die Regeln sind, mehrere zu berücksichtigende gewertete Zeiträume am Tag oder sowas, steckt hier etwas Komplexität. Wäre aber eigentlich der selbe Algo den man auch für A verwenden würde.

Das sollte man so halbwegs sauber in handhabbaren Einzelteilen zerlegen und dann lösen können.


Boldar - Mi 08.03.17 05:33

Oder, wenn es viele/unregelmäßige Regeln gibt, eventuell iterativ mit Regeln aus einem Array:
Jeweils Funktion, die für jeden Zeitpunkt die nächste Intervallgrenze ermittelt plus die Zeit bis dahin zurückgibt. Diese Funktion dann solange ausführen, bisder Zielpinkt erreicht ist, und wenn Man sich in einem Gewertetem intervall befindet, die Zeit zum Ergebnis addieren.
Ich kann das nachher mal versuchen in (Pseudo-)code zu gießen, falls das jetzt zu unverständlich war.

So könnte man jedenfalls auf unregelmäßige Regeln behandeln, die sich nicht Wöchentlich wiederholen.


jfheins - Mi 08.03.17 22:47

Da sich deine Regeln alle auf ganze Tage beziehen, würde ich den Zeitraum erst mal nach Tagen unterteilen. Dann kannst du für jeden Tag eine Überschneidung zwischen dem Zeitintervall und der interessanten Zeit berechnen. Also Top-Down programmiert in etwa so:


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
Voraussetzung: Mal orgendlich zusammenfassen
class TimeSpan {
// Start, Ende, whatever
  public GetSeconds()  Oder sowas
public TimeSpan IntersectWith(TimeSpan t)
}

var givenTime = new TimeSpan(ostern, weihnachten)
int sum = 0;
foreach (day in getDays(givenTime))
{
   sum += Evaluate(givenTime, day);
}

function Evaluate(TimeSpan t, Day d)
{
  switch (getWeekDayFor(d))
  {
    case MO: return t.IntersectWith(new TimeSpan(day, 816)).GetSeconds();
    case D1: return t.IntersectWith(new TimeSpan(day, 818)).GetSeconds();
    case MI: return t.IntersectWith(new TimeSpan(day, 816)).GetSeconds();
    case DO: return t.IntersectWith(new TimeSpan(day, 818)).GetSeconds();
    case FR: return t.IntersectWith(new TimeSpan(day, 816)).GetSeconds();
  }  
  return 0;
}

function IEnumerable<TimeSpan> getDays(TimeSpan) liefert nacheinander alle Tage (komplett, als TimeSpan), die sich mit der Zeitspanne überschneiden.


Ich vermute, du wirst um ein switch-case nicht drum herum kommen. (Oder du baust dir eine Zeit-Gewichtungsregel-Auswerte-Software, die du mit beliebigen Wichtungsregeln füttern kannst. Also z.B. Karfreitag wird mit 0 gewertet... aber das muss man wollen)
Das wichtige (was ich sehe) ist, dass dein switch-case nicht unerwartet cases dazu bekommt. Solange die Wochentage die Fälle sind, sehe ich da kein Problem.

Falls es mehrere relevante pro Tag geben kann (Mittagspause?), würde ich es so machen:

C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
function Evaluate(TimeSpan t, Day d)
{
  const Dictionary<Weekday, TimeSpan[]> rules = new ....;

  rules[Monday] = { new TimeSpan(day, 811.5), new TimeSpan(day, 12.516) };
  rules[Tuesday] = { new TimeSpan(day, 811.5), new TimeSpan(day, 12.518) };
// ...

  var weekday = getWeekDayFor(d);
  if rules.HasKey(weekday )
  {
    return rules[weekday].Select(ts => t.IntersectWith(ts).GetSeconds()).Sum();
  }

  return 0;
}


Aber ich glaube, das ist erst mal Overkill. Erst mal was machen, was funktioniert. Dann wird man sehen, welchen Erweiterungsbedarf man hat.


Narses - Do 09.03.17 00:18

Moin!

Herzlichen Dank allen Ideen-Spendern. :beer:

Ich habe auch in Boldars Richtung gedacht und deshalb folgenden Ansatz gewählt: (PHP-Pseudocode)

PHP-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
function getWorkTime(DateTime $start, DateTime $stop) {
  $sow = clone $start;
  assertNextWorkDay($sow); // sicherstellen, dass das übergebene DateTime-Objekt auf einen Werktag zeigt; wenn nicht, tageweise in die Zukunft gehen
  setStartOfWork($sow); // Uhrzeit im übergebenen DateTime-Objekt auf den Arbeitsbeginn des Tages setzen
  if ($sow < $start// liegt der Arbeitsbeginn vor dem Start-Zeitpunkt
    $sow = clone $start// dann den Startzeitpunkt nehmen
  $eow = clone $stop;
  assertPrevWorkDay($eow); // sicherstellen, dass das übergebene DateTime-Objekt auf einen Werktag zeigt; wenn nicht, tageweise in die Vergangenheit gehen
  setEndOfWork($eow); // Uhrzeit im übergebenen DateTime-Objekt auf das Arbeitsende des Tages setzen
  if ($eow > $stop// liegt das Arbeitsende hinter dem Start-Zeitpunkt
    $eow = clone $stop// dann den Endezeitpunkt nehmen
  $delta = 0// ermittelte Differenz zwischen $start und $stop
  while ($sow < $eow) {
    if (isSameDay($sow$eow)) {
      $delta += secondsBetween($sow$eow);
    }
    else {
      $this_eow = clone $sow;
      setEndOfWork($this_eow);
      if ($sow < $this_eow)
        $delta += secondsBetween($sow$this_eow);
    }
    nextWorkDay($sow); // das übergebene DateTime-Objekt auf den nächsten Werktag stellen, nicht-Arbeitstage überspringen
    setStartOfWork($sow);
  }
  return $delta;
Dieser Ansatz funktioniert unter der Annahme, dass es pro Werktag immer nur eine "gewertete" Zeitspanne gibt. :idea: (das reicht mir allerdings schon).

cu
Narses