Skip to content

Commit 5a3e3d0

Browse files
Implementiere Zeitraum.ueberlagere/1 mit Ueberlagerung-Struct
Neue Funktion die eine Liste von Zeiträumen zu einer überschneidungsfreien Partition überlagert. Jedes resultierende Ueberlagerung-Struct enthält den Zeitraum als Intervall und die Liste der ursprünglichen Elemente die diesen Abschnitt überdecken. Das Ueberlagerung-Struct implementiert ZeitraumProtokoll und kann direkt mit differenz/2, ueberschneidung/2 etc. weiterverwendet werden. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent e5c092f commit 5a3e3d0

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

lib/zeitraum.ex

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ defmodule Shared.Zeitraum do
99
"""
1010

1111
alias Shared.ZeitraumProtokoll
12+
alias Shared.Zeitraum.Ueberlagerung
1213

1314
@type t :: ZeitraumProtokoll.t()
1415

@@ -184,6 +185,67 @@ defmodule Shared.Zeitraum do
184185
)
185186
end
186187

188+
@doc """
189+
Überlagert eine Liste von Zeiträumen zu einer überschneidungsfreien Partition.
190+
191+
Nimmt eine Liste von Zeiträumen (beliebige Typen die `t:Shared.ZeitraumProtokoll.t/0`
192+
implementieren) und erzeugt eine Liste von `t:Ueberlagerung.t/0` Structs, in der sich
193+
keine Zeiträume mehr überschneiden. Jede Überlagerung enthält die Liste der ursprünglichen
194+
Elemente, die den jeweiligen Abschnitt überdecken.
195+
196+
Die resultierenden Überlagerungen implementieren selbst das `t:Shared.ZeitraumProtokoll.t/0`
197+
und können direkt mit `differenz/2`, `ueberschneidung/2`, etc. verwendet werden.
198+
199+
## Beispiel
200+
201+
Nicht überlappende Zeiträume bleiben getrennt:
202+
203+
iex> ergebnis = ueberlagere([~D[2025-01-01], ~D[2025-01-03]])
204+
[
205+
%Ueberlagerung{zeitraum: ~Z[2025-01-01 00:00:00/2025-01-02 00:00:00], elemente: [~D[2025-01-01]]},
206+
%Ueberlagerung{zeitraum: ~Z[2025-01-03 00:00:00/2025-01-04 00:00:00], elemente: [~D[2025-01-03]]},
207+
]
208+
iex> Enum.all?(ergebnis, &match?(%{elemente: [_]}, &1))
209+
true
210+
211+
Überlappende Zeiträume werden aufgeteilt. Die Überlappung enthält beide Elemente:
212+
213+
iex> mo_bis_mi = ~Z[2025-01-06/2025-01-09]
214+
iex> di_bis_do = ~Z[2025-01-07/2025-01-10]
215+
iex> ueberlagere([mo_bis_mi, di_bis_do]) |> Enum.sort_by(& &1.zeitraum.from)
216+
[
217+
%Ueberlagerung{zeitraum: ~Z[2025-01-06/2025-01-07], elemente: [~Z[2025-01-06/2025-01-09]]},
218+
%Ueberlagerung{zeitraum: ~Z[2025-01-07/2025-01-09], elemente: [~Z[2025-01-06/2025-01-09], ~Z[2025-01-07/2025-01-10]]},
219+
%Ueberlagerung{zeitraum: ~Z[2025-01-09/2025-01-10], elemente: [~Z[2025-01-07/2025-01-10]]},
220+
]
221+
"""
222+
@spec ueberlagere(list(t())) :: list(Ueberlagerung.t())
223+
def ueberlagere(zeitraeume) do
224+
Enum.reduce(zeitraeume, [], fn neuer_zeitraum, bestehende ->
225+
ueberschneidungen =
226+
for %Ueberlagerung{} = bestehend <- bestehende,
227+
ueberschneidung = ueberschneidung(bestehend, neuer_zeitraum) do
228+
%Ueberlagerung{
229+
zeitraum: als_intervall(ueberschneidung),
230+
elemente: bestehend.elemente ++ [neuer_zeitraum]
231+
}
232+
end
233+
234+
unveraenderte =
235+
for %Ueberlagerung{} = bestehend <- bestehende,
236+
rest <- differenz(bestehend, ueberschneidungen) do
237+
%Ueberlagerung{zeitraum: als_intervall(rest), elemente: bestehend.elemente}
238+
end
239+
240+
neue =
241+
neuer_zeitraum
242+
|> differenz(ueberschneidungen)
243+
|> Enum.map(&%Ueberlagerung{zeitraum: als_intervall(&1), elemente: [neuer_zeitraum]})
244+
245+
unveraenderte ++ ueberschneidungen ++ neue
246+
end)
247+
end
248+
187249
@doc """
188250
Ermittelt die Überschneidung zweier Zeitperioden.
189251

lib/zeitraum/ueberlagerung.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
defmodule Shared.Zeitraum.Ueberlagerung do
2+
@moduledoc """
3+
Ein Zeitraum zusammen mit allen Elementen, die diesen Zeitraum überlagern.
4+
5+
Implementiert `Shared.ZeitraumProtokoll`, sodass Überlagerungen direkt mit
6+
`Zeitraum.differenz/2`, `Zeitraum.ueberschneidung/2`, etc. verwendet werden können.
7+
"""
8+
9+
@type t :: %__MODULE__{zeitraum: Timex.Interval.t(), elemente: [Shared.ZeitraumProtokoll.t()]}
10+
11+
@enforce_keys [:zeitraum, :elemente]
12+
defstruct [:zeitraum, :elemente]
13+
14+
defimpl Shared.ZeitraumProtokoll do
15+
def als_intervall(%{zeitraum: zeitraum}), do: zeitraum
16+
end
17+
end

lib/zeitraum_test.exs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ defmodule Shared.ZeitraumTest do
55
import Shared.Month, only: [sigil_m: 2]
66

77
alias Shared.Zeitraum
8+
alias Shared.Zeitraum.Ueberlagerung
89

910
doctest Zeitraum, import: true
1011

@@ -28,4 +29,117 @@ defmodule Shared.ZeitraumTest do
2829

2930
assert [] == Zeitraum.differenz([~D[2025-01-01], ~D[2025-01-13]], [~v[2025-01], ~v[2025-03]])
3031
end
32+
33+
describe "ueberlagere/1" do
34+
import Shared.Zeitraum, only: [sigil_Z: 2]
35+
36+
test "leere Liste ergibt leere Liste" do
37+
assert [] == Zeitraum.ueberlagere([])
38+
end
39+
40+
test "einzelner Zeitraum" do
41+
assert [
42+
%Ueberlagerung{
43+
zeitraum: ~Z[2025-01-01 00:00:00/2025-01-02 00:00:00],
44+
elemente: [~D[2025-01-01]]
45+
}
46+
] == Zeitraum.ueberlagere([~D[2025-01-01]])
47+
end
48+
49+
test "nicht überlappende Zeiträume bleiben getrennt" do
50+
assert [
51+
%Ueberlagerung{
52+
zeitraum: ~Z[2025-01-01 00:00:00/2025-01-02 00:00:00],
53+
elemente: [~D[2025-01-01]]
54+
},
55+
%Ueberlagerung{
56+
zeitraum: ~Z[2025-01-03 00:00:00/2025-01-04 00:00:00],
57+
elemente: [~D[2025-01-03]]
58+
}
59+
] == Zeitraum.ueberlagere([~D[2025-01-01], ~D[2025-01-03]])
60+
end
61+
62+
test "identische Zeiträume werden vollständig überlagert" do
63+
assert [
64+
%Ueberlagerung{
65+
zeitraum: ~Z[2025-01-01 00:00:00/2025-01-02 00:00:00],
66+
elemente: [~D[2025-01-01], ~D[2025-01-01]]
67+
}
68+
] == Zeitraum.ueberlagere([~D[2025-01-01], ~D[2025-01-01]])
69+
end
70+
71+
test "teilweise überlappende Zeiträume werden in drei Segmente aufgeteilt" do
72+
mo_bis_mi = ~Z[2025-01-06/2025-01-08]
73+
di_bis_do = ~Z[2025-01-07/2025-01-09]
74+
75+
assert [
76+
%Ueberlagerung{zeitraum: ~Z[2025-01-06/2025-01-07], elemente: [mo_bis_mi]},
77+
%Ueberlagerung{
78+
zeitraum: ~Z[2025-01-07/2025-01-08],
79+
elemente: [mo_bis_mi, di_bis_do]
80+
},
81+
%Ueberlagerung{zeitraum: ~Z[2025-01-08/2025-01-09], elemente: [di_bis_do]}
82+
] ==
83+
Zeitraum.ueberlagere([mo_bis_mi, di_bis_do])
84+
|> Enum.sort_by(& &1.zeitraum.from, NaiveDateTime)
85+
end
86+
87+
test "ein Zeitraum vollständig in einem anderen enthalten" do
88+
woche = ~v[2025-01]
89+
mittwoch = ~D[2025-01-01]
90+
91+
assert [
92+
%Ueberlagerung{
93+
zeitraum: ~Z[2024-12-30 00:00:00/2025-01-01 00:00:00],
94+
elemente: [woche]
95+
},
96+
%Ueberlagerung{
97+
zeitraum: ~Z[2025-01-01 00:00:00/2025-01-02 00:00:00],
98+
elemente: [woche, mittwoch]
99+
},
100+
%Ueberlagerung{
101+
zeitraum: ~Z[2025-01-02 00:00:00/2025-01-06 00:00:00],
102+
elemente: [woche]
103+
}
104+
] ==
105+
Zeitraum.ueberlagere([woche, mittwoch])
106+
|> Enum.sort_by(& &1.zeitraum.from, NaiveDateTime)
107+
end
108+
109+
test "drei überlappende Zeiträume" do
110+
a = ~Z[2025-01-06/2025-01-08]
111+
b = ~Z[2025-01-07/2025-01-09]
112+
c = ~Z[2025-01-08/2025-01-10]
113+
114+
assert [
115+
%Ueberlagerung{zeitraum: ~Z[2025-01-06/2025-01-07], elemente: [a]},
116+
%Ueberlagerung{zeitraum: ~Z[2025-01-07/2025-01-08], elemente: [a, b]},
117+
%Ueberlagerung{zeitraum: ~Z[2025-01-08/2025-01-09], elemente: [b, c]},
118+
%Ueberlagerung{zeitraum: ~Z[2025-01-09/2025-01-10], elemente: [c]}
119+
] ==
120+
Zeitraum.ueberlagere([a, b, c])
121+
|> Enum.sort_by(& &1.zeitraum.from, NaiveDateTime)
122+
end
123+
124+
test "Gesamtdauer bleibt erhalten" do
125+
woche = ~v[2025-01]
126+
mittwoch = ~D[2025-01-01]
127+
128+
gesamt_vorher = Zeitraum.dauer(woche, :hours)
129+
130+
gesamt_nachher =
131+
Zeitraum.ueberlagere([woche, mittwoch])
132+
|> Enum.map(&Zeitraum.dauer(&1, :hours))
133+
|> Enum.sum()
134+
135+
assert gesamt_vorher == gesamt_nachher
136+
end
137+
138+
test "Ueberlagerung implementiert ZeitraumProtokoll" do
139+
[ueberlagerung] = Zeitraum.ueberlagere([~D[2025-01-01]])
140+
141+
assert Zeitraum.als_intervall(ueberlagerung) == Zeitraum.als_intervall(~D[2025-01-01])
142+
assert Zeitraum.dauer(ueberlagerung, :hours) == 24
143+
end
144+
end
31145
end

0 commit comments

Comments
 (0)