LotSizingDataModel.Core 2.0.1
Core domain model, shared abstractions and XML-serializable entities.
Loading...
Searching...
No Matches
IntegerTimeSeries.cs
Go to the documentation of this file.
1using System;
2using System.Collections;
3using System.Collections.Generic;
4using System.Linq;
5using System.Xml.Serialization;
6
8
9/// <summary>
10/// Represents a sequence of integer values indexed by planning period.
11///
12/// Period numbers are one-based:
13/// period 1 corresponds to the first value,
14/// period 2 to the second value, and so on.
15/// </summary>
16[Serializable]
17[XmlType(TypeName = "integerTimeSeries")]
18public sealed class IntegerTimeSeries :
20 IEnumerable<int>
21{
22 private int[] _values = Array.Empty<int>();
23
24 /// <summary>
25 /// Initializes an empty time series.
26 ///
27 /// This public parameterless constructor is required
28 /// by <see cref="XmlSerializer"/>.
29 /// </summary>
31 {
32 }
33
34 /// <summary>
35 /// Initializes a time series with the specified number of periods.
36 /// </summary>
37 /// <param name="periodCount">
38 /// Number of periods in the planning horizon.
39 /// </param>
40 /// <param name="defaultValue">
41 /// Initial value assigned to every period.
42 /// </param>
44 int periodCount,
45 int defaultValue = 0)
46 {
47 Resize(periodCount, defaultValue);
48 }
49
50 /// <summary>
51 /// Gets or replaces all values of the time series.
52 ///
53 /// Each XML element corresponds to one planning period.
54 /// The first value corresponds to period 1.
55 /// </summary>
56 [XmlElement("value")]
57 public int[] Values
58 {
59 get => (int[])_values.Clone();
60 set => ReplaceValues(value ?? Array.Empty<int>());
61 }
62
63 /// <summary>
64 /// Gets the number of planning periods represented
65 /// by this time series.
66 /// </summary>
67 [XmlIgnore]
68 public int PeriodCount => _values.Length;
69
70 /// <summary>
71 /// Gets or sets the value associated with a planning period.
72 ///
73 /// Period numbering starts at 1.
74 /// </summary>
75 /// <param name="period">
76 /// Planning period between 1 and <see cref="PeriodCount"/>.
77 /// </param>
78 [XmlIgnore]
79 public int this[int period]
80 {
81 get
82 {
83 int index = ConvertPeriodToIndex(period);
84 return _values[index];
85 }
86 set
87 {
88 int index = ConvertPeriodToIndex(period);
89
90 // Avoid unnecessary notifications if the value does not change
91 if (_values[index] == value)
92 {
93 return;
94 }
95
96 _values[index] = value;
97
99 OnPropertyChanged("Item[]");
100 }
101 }
102
103 /// <summary>
104 /// Resizes the time series.
105 ///
106 /// Existing values are preserved. If the horizon grows,
107 /// newly created periods receive <paramref name="defaultValue"/>.
108 /// If the horizon shrinks, values outside the new horizon
109 /// are discarded.
110 /// </summary>
111 /// <param name="periodCount">
112 /// New number of planning periods.
113 /// </param>
114 /// <param name="defaultValue">
115 /// Value assigned to newly created periods.
116 /// </param>
117 public void Resize(
118 int periodCount,
119 int defaultValue = 0)
120 {
121 if (periodCount < 0)
122 {
123 throw new ArgumentOutOfRangeException(
124 nameof(periodCount),
125 periodCount,
126 "The number of periods cannot be negative.");
127 }
128
129 // No change necessary if the size is the same
130 if (periodCount == _values.Length)
131 {
132 return;
133 }
134
135 int previousPeriodCount = _values.Length;
136 var resizedValues = new int[periodCount];
137
138 // Copy existing values (the minimum between old and new size)
139 int copiedValueCount = Math.Min(
140 previousPeriodCount,
141 periodCount);
142
143 if (copiedValueCount > 0)
144 {
145 Array.Copy(
146 _values,
147 resizedValues,
148 copiedValueCount);
149 }
150
151 // Initialize new periods with the default value when expanding
152 if (periodCount > previousPeriodCount)
153 {
154 Array.Fill(
155 resizedValues,
156 defaultValue,
157 previousPeriodCount,
158 periodCount - previousPeriodCount);
159 }
160
161 _values = resizedValues;
162
163 OnPropertyChanged(nameof(Values));
165 OnPropertyChanged("Item[]");
166 }
167
168 /// <summary>
169 /// Assigns the same value to every planning period.
170 /// </summary>
171 /// <param name="value">
172 /// Value assigned to all periods.
173 /// </param>
174 public void Fill(int value)
175 {
176 // Avoid unnecessary notifications if all values are already equal
177 if (_values.All(currentValue => currentValue == value))
178 {
179 return;
180 }
181
182 Array.Fill(_values, value);
183
184 OnPropertyChanged(nameof(Values));
185 OnPropertyChanged("Item[]");
186 }
187
188 /// <summary>
189 /// Gets the value associated with a planning period.
190 /// </summary>
191 public int GetValue(int period)
192 {
193 return this[period];
194 }
195
196 /// <summary>
197 /// Sets the value associated with a planning period.
198 /// </summary>
199 public void SetValue(int period, int value)
200 {
201 this[period] = value;
202 }
203
204 /// <summary>
205 /// Creates an independent copy of this time series.
206 /// </summary>
208 {
209 return new IntegerTimeSeries
210 {
211 Values = Values
212 };
213 }
214
215 /// <summary>
216 /// Adds a value to the end of the time series.
217 /// </summary>
218 /// <param name="value">
219 /// Value to append.
220 /// </param>
221 /// <remarks>
222 /// This public method is required by
223 /// <see cref="XmlSerializer"/> because this type implements
224 /// <see cref="IEnumerable{T}"/>.
225 /// </remarks>
226 public void Add(
227 int value)
228 {
229 int previousLength =
230 _values.Length;
231
232 Array.Resize(
233 ref _values,
234 previousLength + 1);
235
236 _values[previousLength] =
237 value;
238
239 OnPropertyChanged(nameof(Values));
241 OnPropertyChanged("Item[]");
242 }
243
244 /// <summary>
245 /// Removes all values from the time series.
246 /// </summary>
247 public void Clear()
248 {
249 if (_values.Length == 0)
250 {
251 return;
252 }
253
254 _values =
255 Array.Empty<int>();
256
257 OnPropertyChanged(nameof(Values));
259 OnPropertyChanged("Item[]");
260 }
261
262 /// <summary>
263 /// Returns an enumerator over the period values.
264 /// </summary>
265 public IEnumerator<int> GetEnumerator()
266 {
267 return ((IEnumerable<int>)_values).GetEnumerator();
268 }
269
270 IEnumerator IEnumerable.GetEnumerator()
271 {
272 return GetEnumerator();
273 }
274
275 /// <summary>
276 /// Replaces the internal values of the time series with a new array.
277 /// </summary>
278 /// <param name="values">The new array of values to assign.</param>
279 private void ReplaceValues(int[] values)
280 {
281 // Avoid unnecessary notifications if the values are identical
282 if (_values.SequenceEqual(values))
283 {
284 return;
285 }
286
287 // Detect if the period count changes to notify PropertyChanged
288 bool periodCountChanged =
289 _values.Length != values.Length;
290
291 // Clone the array to ensure encapsulation
292 _values = (int[])values.Clone();
293
294 OnPropertyChanged(nameof(Values));
295 OnPropertyChanged("Item[]");
296
297 if (periodCountChanged)
298 {
300 }
301 }
302
303 /// <summary>
304 /// Converts a period number (1-based) to an array index (0-based).
305 /// </summary>
306 /// <param name="period">The period number (starts at 1).</param>
307 /// <returns>The corresponding index in the array (starts at 0).</returns>
308 /// <exception cref="ArgumentOutOfRangeException">
309 /// If the period is outside the valid range [1, PeriodCount].
310 /// </exception>
311 private int ConvertPeriodToIndex(int period)
312 {
313 if (period < 1 || period > _values.Length)
314 {
315 throw new ArgumentOutOfRangeException(
316 nameof(period),
317 period,
318 $"The period must be between 1 and {_values.Length}.");
319 }
320
321 // Conversion: period 1 -> index 0, period 2 -> index 1, etc.
322 return period - 1;
323 }
324}
void Fill(int value)
Assigns the same value to every planning period.
void Resize(int periodCount, int defaultValue=0)
Resizes the time series.
void Add(int value)
Adds a value to the end of the time series.
int GetValue(int period)
Gets the value associated with a planning period.
IntegerTimeSeries Clone()
Creates an independent copy of this time series.
int PeriodCount
Gets the number of planning periods represented by this time series.
int[] Values
Gets or replaces all values of the time series.
void Clear()
Removes all values from the time series.
IntegerTimeSeries(int periodCount, int defaultValue=0)
Initializes a time series with the specified number of periods.
void SetValue(int period, int value)
Sets the value associated with a planning period.
IEnumerator< int > GetEnumerator()
Returns an enumerator over the period values.
IntegerTimeSeries()
Initializes an empty time series.
Base class for model objects that notify listeners when one of their properties changes.
virtual void OnPropertyChanged([CallerMemberName] string? propertyName=null)
Raises the PropertyChanged event.