Main MRPT website > C++ reference for MRPT 1.9.9
CSparseMatrixTemplate.h
Go to the documentation of this file.
1 /* +------------------------------------------------------------------------+
2  | Mobile Robot Programming Toolkit (MRPT) |
3  | http://www.mrpt.org/ |
4  | |
5  | Copyright (c) 2005-2018, Individual contributors, see AUTHORS file |
6  | See: http://www.mrpt.org/Authors - All rights reserved. |
7  | Released under BSD License. See details in http://www.mrpt.org/License |
8  +------------------------------------------------------------------------+ */
9 #pragma once
10 
11 #include <map>
12 #include <vector>
13 
14 namespace mrpt
15 {
16 namespace math
17 {
18 /** A sparse matrix container (with cells of any type), with iterators.
19  * This class stores only those elements created by assigning them a value,
20  * for example: "M(2,3)=8;".
21  *
22  * This class doesn't implement math operations since it's a generic sparse
23  * container, but it can be
24  * used to initialize the contents of a CSparse library-based matrix of type
25  * mrpt::math::CSparseMatrix.
26  *
27  * Note that reading non-existing cell elements will return the default value
28  * (0 for numbers)
29  * and that cell will remain non-created in the matrix.
30  *
31  * There is an additional method "exists(i,j)" to check whether a given
32  * element exists in the matrix.
33  *
34  * \sa mrpt::math::MatrixBlockSparseCols, mrpt::math::CSparseMatrix,
35  * CSparseSymmetricalMatrix
36  * \note Methods marked as "Doesn't check bounds" mean that if an access to an
37  * element out of the matrix size is tried, an empty element will be assumed,
38  * but this will not raise any invalid memory access.
39  * \ingroup mrpt_math_grp
40  */
41 template <class T>
43 {
44  // Public typedefs
45  public:
46  /**
47  * Internal map type, used to store the actual matrix.
48  */
49  using SparseMatrixMap = typename std::map<std::pair<size_t, size_t>, T>;
50  /**
51  * Const iterator to move through the matrix.
52  * \sa CSparseMatrixTemplate::const_reverse_iterator
53  */
55  /**
56  * Const reverse iterator to move through the matrix.
57  * \sa CSparseMatrixTemplate::const_iterator
58  */
60  typename SparseMatrixMap::const_reverse_iterator;
61 
62  protected:
63  /**
64  * Size of the matrix.
65  */
66  size_t mRows{0}, mColumns{0};
67  /**
68  * Actual matrix.
69  */
71 
72  public:
73  /**
74  * Basic constructor with no data. Size is set to (0,0).
75  */
77  /**
78  * Constructor with default size.
79  */
80  CSparseMatrixTemplate(size_t nR, size_t nC) : mRows(nR), mColumns(nC) {}
81  /**
82  * Element access operator. Doesn't check bounds.
83  */
84  inline T operator()(size_t r, size_t c) const
85  {
86  const_iterator it = objectList.find(std::make_pair(r, c));
87  if (it == objectList.end())
88  return T();
89  else
90  return it->second;
91  }
92 
93  /** Element access operator. Checks bounds.
94  */
95  inline bool exists(size_t r, size_t c) const
96  {
97 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
98  if (r >= mRows || c >= mColumns) throw std::logic_error("Out of range");
99 #endif
100  return (objectList.find(std::make_pair(r, c)) != objectList.end());
101  }
102 
103  /**
104  * Reference access operator. Checks for bounds.
105  */
106  inline T& operator()(size_t r, size_t c)
107  { //-V659
108 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
109  if (r >= mRows || c >= mColumns) throw std::logic_error("Out of range");
110 #endif
111  return objectList[std::make_pair(r, c)];
112  }
113  /**
114  * Returns the amount of rows in this matrix.
115  * \sa getColCount,getRow
116  */
117  inline size_t rows() const { return mRows; }
118  /**
119  * Returns the amount of columns in this matrix.
120  * \sa rows()
121  */
122  inline size_t cols() const { return mColumns; }
123  /**
124  * Extracts a full row from the matrix.
125  * \sa rows(),getColumn,setRow
126  * \throw std::logic_error on out of range.
127  */
128  template <typename VECTOR>
129  void getRow(size_t nRow, VECTOR& vec) const
130  {
131 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
132  if (nRow >= mRows) throw std::logic_error("Out of range");
133 #endif
134  vec.resize(mColumns);
135  size_t nextIndex = 0;
136  for (typename SparseMatrixMap::const_iterator it = objectList.begin();
137  it != objectList.end(); ++it)
138  {
139  const std::pair<size_t, size_t>& index = it->first;
140  if (index.first < nRow)
141  continue;
142  else if (index.first == nRow)
143  {
144  for (size_t i = nextIndex; i < index.second; i++) vec[i] = T();
145  vec[index.second] = it->second;
146  nextIndex = index.second + 1;
147  }
148  else
149  {
150  for (size_t i = nextIndex; i < mColumns; i++) vec[i] = T();
151  break;
152  }
153  }
154  }
155  /**
156  * Extracts a full column from the matrix.
157  * \sa getColCount,getRow,setColumn
158  * \throw std::logic_error on out of range.
159  */
160  template <typename VECTOR>
161  void getColumn(size_t nCol, VECTOR& vec) const
162  {
163 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
164  if (nCol >= mColumns) throw std::logic_error("Out of range");
165 #endif
166  vec.resize(mRows);
167  size_t nextIndex = 0;
168  for (typename SparseMatrixMap::const_iterator it = objectList.begin();
169  it != objectList.end(); ++it)
170  {
171  const std::pair<size_t, size_t>& index = it->first;
172  if (index.second == nCol)
173  {
174  for (size_t i = nextIndex; i < index.first; i++) vec[i] = T();
175  vec[index.first] = it->second;
176  nextIndex = index.first + 1;
177  }
178  }
179  for (size_t i = nextIndex; i < mRows; i++) vec[i] = T();
180  }
181  /**
182  * Inserts an element into the matrix.
183  * \sa operator()(size_t,size_t)
184  */
185  inline void insert(size_t row, size_t column, const T& obj)
186  {
187  operator()(row, column) = obj;
188  }
189 
190  /** Inserts submatrix at a given location */
191  template <class MATRIX_LIKE>
192  inline void insertMatrix(size_t row, size_t column, const MATRIX_LIKE& mat)
193  {
194  for (size_t nr = 0; nr < mat.rows(); nr++)
195  for (size_t nc = 0; nc < mat.cols(); nc++)
196  operator()(row + nr, column + nc) = mat(nr, nc);
197  }
198 
199  // Public interface only supports const iterators. This way, no user of this
200  // class will be able to freely modify it contents.
201  /**
202  * Returns an iterator which points to the starting point of the matrix.
203  * It's a const_iterator, so that the usar isn't able to modify the matrix
204  * content into an invalid state.
205  * \sa end,rbegin,rend
206  */
207  inline const_iterator begin() const { return objectList.begin(); }
208  /**
209  * Returns an iterator which points to the end of the matrix. It's a
210  * const_iterator, so that the usar isn't able to modify the matrix content
211  * into an invalid state.
212  * \sa begin,rbegin,rend
213  */
214  inline const_iterator end() const { return objectList.end(); }
215  /**
216  * Returns an iterator which points to the end of the matrix, and can be
217  * used to move backwards. It's a const_reverse_iterator, so that the usar
218  * isn't able to modify the matrix content into an invalid state.
219  * \sa begin,end,rend
220  */
221  inline const_reverse_iterator rbegin() const { return objectList.rbegin(); }
222  /**
223  * Returns an iterator which points to the starting point of the matrix,
224  * although it's the upper limit of the matrix since it's a reverse
225  * iterator. Also, it's a const_reverse_iterator, so that the usar isn't
226  * able to modify the matrix content into an invalid state.
227  * \sa begin,end,rbegin
228  */
229  inline const_reverse_iterator rend() const { return objectList.rend(); }
230  /**
231  * Inserts a full row into the matrix. The third argument is used to
232  * specify a null object (which won't be inserted, since the matrix is
233  * sparse).
234  * \sa getRow
235  * \throw std::logic_error on out of range or wrong sized vector.
236  */
237  template <typename VECTOR>
238  void setRow(size_t nRow, const VECTOR& vec, const T& nullObject = T())
239  {
240 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
241  if (nRow >= mRows) throw std::logic_error("Out of range");
242 #endif
243  size_t N = vec.size();
244  if (N != mColumns) throw std::logic_error("Wrong-sized vector");
245  for (size_t i = 0; i < N; i++)
246  {
247  const T& obj = vec[i];
248  std::pair<size_t, size_t> index = std::make_pair(nRow, i);
249  if (obj == nullObject)
250  objectList.erase(index);
251  else
252  objectList[index] = obj;
253  }
254  }
255  /**
256  * Inserts a full column into the matrix. The third argument is used to
257  * specify a null object (which won't be inserted, since the matrix is
258  * sparse).
259  * \sa getColumn
260  * \throw std::logic_error on out of range or wrong sized vector.
261  */
262  template <typename VECTOR>
263  void setColumn(size_t nCol, const VECTOR& vec, const T& nullObject = T())
264  {
265 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
266  if (nCol >= mColumns) throw std::logic_error("Out of range");
267 #endif
268  size_t N = vec.size();
269  if (N != mRows) throw std::logic_error("Wrong-sized vector");
270  for (size_t i = 0; i < N; i++)
271  {
272  const T& obj = vec[i];
273  std::pair<size_t, size_t> index = std::make_pair(i, nCol);
274  if (obj == nullObject)
275  objectList.erase(index);
276  else
277  objectList[index] = obj;
278  }
279  }
280  /**
281  * Changes the size of the matrix.
282  */
283  void resize(size_t nRows, size_t nCols)
284  {
285  // if (mRows<0||mColumns<0) throw std::logic_error("Invalid range"); //
286  // This case never happens!
287  if (mRows == nRows && mColumns == nCols) return;
288  mRows = nRows;
289  mColumns = nCols;
290  std::vector<std::pair<size_t, size_t>> toErase;
291  for (const_iterator it = objectList.begin(); it != objectList.end();
292  ++it)
293  {
294  const std::pair<size_t, size_t>& i = it->first;
295  if (i.first >= nRows || i.second >= nCols)
296  toErase.push_back(it->first);
297  }
298  for (std::vector<std::pair<size_t, size_t>>::const_iterator it =
299  toErase.begin();
300  it != toErase.end(); ++it)
301  objectList.erase(*it);
302  }
303  /**
304  * Extracts a submatrix form the matrix.
305  * \sa operator()(size_t,size_t)
306  * \throw std::logic_error on invalid bounds.
307  */
309  size_t firstRow, size_t lastRow, size_t firstColumn,
310  size_t lastColumn) const
311  {
312 #if defined(_DEBUG) || (MRPT_ALWAYS_CHECKS_DEBUG_MATRICES)
313  if (lastRow >= mRows || lastColumn >= mColumns)
314  throw std::logic_error("Out of range");
315  if (firstRow > lastRow || firstColumn > lastColumn)
316  throw std::logic_error("Invalid size");
317 #endif
319  lastRow + 1 - firstRow, lastColumn + 1 - firstColumn);
320  for (typename SparseMatrixMap::const_iterator it = begin(); it != end();
321  ++it)
322  {
323  const std::pair<size_t, size_t>& i = it->first;
324  if (i.first >= firstRow && i.first <= lastRow &&
325  i.second >= firstColumn && i.second <= lastColumn)
326  res(i.first - firstRow, i.second - firstColumn) = it->second;
327  }
328  return res;
329  }
330  /**
331  * Gets a vector containing all the elements of the matrix, ignoring their
332  * position.
333  */
334  template <typename VECTOR>
335  void getAsVector(VECTOR& vec) const
336  {
337  size_t N = objectList.size();
338  vec.resize(0);
339  vec.reserve(N);
340  for (const_iterator it = objectList.begin(); it != objectList.end();
341  ++it)
342  vec.push_back(it->second);
343  }
344  /**
345  * Gets the amount of non-null elements inside the matrix.
346  * \sa getNullElements,isNull,isNotNull
347  */
348  inline size_t getNonNullElements() const { return objectList.size(); }
349  /** Are there no elements set to !=0 ?
350  * \sa getNullElements,isNull,isNotNull
351  */
352  inline bool empty() const { return objectList.empty(); }
353  /**
354  * Gets the amount of null elements inside the matrix.
355  * \sa getNonNullElements,isNull,isNotNull
356  */
357  inline size_t getNullElements() const
358  {
359  return mRows * mColumns - getNonNullElements();
360  }
361  /**
362  * Checks whether an element of the matrix is the default object.
363  * \sa getNonNullElements,getNullElements,isNotNull
364  * \throw std::logic_error on out of range
365  */
366  inline bool isNull(size_t nRow, size_t nCol) const
367  {
368  if (nRow >= mRows || nCol >= mColumns)
369  throw std::logic_error("Out of range");
370  return objectList.count(std::make_pair(nRow, nCol)) == 0;
371  }
372  /**
373  * Checks whether an element of the matrix is not the default object.
374  * \sa getNonNullElements,getNullElements,isNull
375  */
376  inline bool isNotNull(size_t nRow, size_t nCol) const
377  {
378  if (nRow >= mRows || nCol >= mColumns)
379  throw std::logic_error("Out of range");
380  return objectList.count(std::make_pair(nRow, nCol)) > 0;
381  }
382  /**
383  * Completely removes all elements, although maintaining the matrix's size.
384  */
385  inline void clear() { objectList.clear(); }
386  /**
387  * Checks each non-null elements against the basic objects, erasing
388  * unnecesary references to it.
389  */
390  void purge(T nullObject = T())
391  {
392  std::vector<std::pair<size_t, size_t>> nulls;
393  for (const_iterator it = begin(); it != end(); ++it)
394  if (it->second == nullObject) nulls.push_back(it->first);
395  for (std::vector<std::pair<size_t, size_t>>::const_iterator it =
396  nulls.begin();
397  it != nulls.end(); ++it)
398  objectList.erase(*it);
399  }
400 }; // end of sparse matrix
401 
402 /** A sparse matrix container for square symmetrical content around the main
403  * diagonal.
404  * This class saves half of the space with respect to CSparseMatrixTemplate
405  * since only those entries (c,r) such as c>=r are really stored,
406  * but both (c,r) and (r,c) can be retrieved or set and both redirect to the
407  * same internal cell container.
408  * \sa CSparseMatrixTemplate
409  */
410 template <class T>
412 {
413  public:
416  : CSparseMatrixTemplate<T>(o)
417  {
418  }
420  : CSparseMatrixTemplate<T>(o)
421  {
422  }
424  void resize(size_t matrixSize)
425  {
426  CSparseMatrixTemplate<T>::resize(matrixSize, matrixSize);
427  }
428 
429  inline T operator()(size_t r, size_t c) const
430  {
431  if (c < r) std::swap(r, c); // Symmetrical matrix
433  CSparseMatrixTemplate<T>::objectList.find(std::make_pair(r, c));
435  return T();
436  else
437  return it->second;
438  }
439  inline T& operator()(size_t r, size_t c)
440  { //-V659
441  if (c < r) std::swap(r, c); // Symmetrical matrix
444  throw std::logic_error("Out of range");
445  return CSparseMatrixTemplate<T>::objectList[std::make_pair(r, c)];
446  }
447 
448 }; // end of CSparseSymmetricalMatrix
449 } // namespace math
450 } // namespace mrpt
mrpt::math::CSparseMatrixTemplate::empty
bool empty() const
Are there no elements set to !=0 ?
Definition: CSparseMatrixTemplate.h:352
mrpt::math::CSparseMatrixTemplate::const_reverse_iterator
typename SparseMatrixMap::const_reverse_iterator const_reverse_iterator
Const reverse iterator to move through the matrix.
Definition: CSparseMatrixTemplate.h:60
mrpt::math::CSparseMatrixTemplate::getAsVector
void getAsVector(VECTOR &vec) const
Gets a vector containing all the elements of the matrix, ignoring their position.
Definition: CSparseMatrixTemplate.h:335
mrpt::math::CSparseMatrixTemplate::CSparseMatrixTemplate
CSparseMatrixTemplate()
Basic constructor with no data.
Definition: CSparseMatrixTemplate.h:76
const_iterator
const Scalar * const_iterator
Definition: eigen_plugins.h:27
mrpt::math::CSparseMatrixTemplate::isNull
bool isNull(size_t nRow, size_t nCol) const
Checks whether an element of the matrix is the default object.
Definition: CSparseMatrixTemplate.h:366
mrpt::math::CSparseSymmetricalMatrix::resize
void resize(size_t matrixSize)
Definition: CSparseMatrixTemplate.h:424
mrpt::math::CSparseMatrixTemplate::mColumns
size_t mColumns
Definition: CSparseMatrixTemplate.h:66
mrpt::math::CSparseSymmetricalMatrix::~CSparseSymmetricalMatrix
virtual ~CSparseSymmetricalMatrix()
Definition: CSparseMatrixTemplate.h:423
c
const GLubyte * c
Definition: glext.h:6313
mrpt::math::CSparseMatrixTemplate::operator()
T operator()(size_t r, size_t c) const
Element access operator.
Definition: CSparseMatrixTemplate.h:84
column
GLenum GLenum GLvoid GLvoid * column
Definition: glext.h:3576
obj
GLsizei GLsizei GLuint * obj
Definition: glext.h:4070
mrpt::math::CSparseMatrixTemplate::const_iterator
typename SparseMatrixMap::const_iterator const_iterator
Const iterator to move through the matrix.
Definition: CSparseMatrixTemplate.h:54
mrpt::math::CSparseMatrixTemplate::objectList
SparseMatrixMap objectList
Actual matrix.
Definition: CSparseMatrixTemplate.h:70
mrpt::math::CSparseSymmetricalMatrix::operator()
T operator()(size_t r, size_t c) const
Definition: CSparseMatrixTemplate.h:429
mrpt
This is the global namespace for all Mobile Robot Programming Toolkit (MRPT) libraries.
Definition: CKalmanFilterCapable.h:30
mrpt::math::CSparseMatrixTemplate::rbegin
const_reverse_iterator rbegin() const
Returns an iterator which points to the end of the matrix, and can be used to move backwards.
Definition: CSparseMatrixTemplate.h:221
mrpt::math::CSparseMatrixTemplate::insertMatrix
void insertMatrix(size_t row, size_t column, const MATRIX_LIKE &mat)
Inserts submatrix at a given location.
Definition: CSparseMatrixTemplate.h:192
mrpt::math::CSparseMatrixTemplate::rend
const_reverse_iterator rend() const
Returns an iterator which points to the starting point of the matrix, although it's the upper limit o...
Definition: CSparseMatrixTemplate.h:229
r
GLdouble GLdouble GLdouble r
Definition: glext.h:3705
mrpt::math::CSparseSymmetricalMatrix::CSparseSymmetricalMatrix
CSparseSymmetricalMatrix(const CSparseMatrixTemplate< T > &o)
Definition: CSparseMatrixTemplate.h:419
mrpt::math::CSparseMatrixTemplate::insert
void insert(size_t row, size_t column, const T &obj)
Inserts an element into the matrix.
Definition: CSparseMatrixTemplate.h:185
mrpt::math::CSparseMatrixTemplate::rows
size_t rows() const
Returns the amount of rows in this matrix.
Definition: CSparseMatrixTemplate.h:117
mrpt::math::CSparseMatrixTemplate::getNonNullElements
size_t getNonNullElements() const
Gets the amount of non-null elements inside the matrix.
Definition: CSparseMatrixTemplate.h:348
mrpt::math::CSparseMatrixTemplate::operator()
T & operator()(size_t r, size_t c)
Reference access operator.
Definition: CSparseMatrixTemplate.h:106
mrpt::math::CSparseMatrixTemplate::setRow
void setRow(size_t nRow, const VECTOR &vec, const T &nullObject=T())
Inserts a full row into the matrix.
Definition: CSparseMatrixTemplate.h:238
mrpt::math::CSparseMatrixTemplate::getColumn
void getColumn(size_t nCol, VECTOR &vec) const
Extracts a full column from the matrix.
Definition: CSparseMatrixTemplate.h:161
mrpt::math::CSparseMatrixTemplate::isNotNull
bool isNotNull(size_t nRow, size_t nCol) const
Checks whether an element of the matrix is not the default object.
Definition: CSparseMatrixTemplate.h:376
res
GLuint res
Definition: glext.h:7268
index
GLuint index
Definition: glext.h:4054
mrpt::math::CSparseMatrixTemplate::begin
const_iterator begin() const
Returns an iterator which points to the starting point of the matrix.
Definition: CSparseMatrixTemplate.h:207
mrpt::math::CSparseMatrixTemplate::purge
void purge(T nullObject=T())
Checks each non-null elements against the basic objects, erasing unnecesary references to it.
Definition: CSparseMatrixTemplate.h:390
mrpt::math::CSparseMatrixTemplate::CSparseMatrixTemplate
CSparseMatrixTemplate(size_t nR, size_t nC)
Constructor with default size.
Definition: CSparseMatrixTemplate.h:80
mrpt::math::CSparseSymmetricalMatrix::CSparseSymmetricalMatrix
CSparseSymmetricalMatrix()
Definition: CSparseMatrixTemplate.h:414
mrpt::math::CSparseMatrixTemplate::operator()
CSparseMatrixTemplate< T > operator()(size_t firstRow, size_t lastRow, size_t firstColumn, size_t lastColumn) const
Extracts a submatrix form the matrix.
Definition: CSparseMatrixTemplate.h:308
mrpt::math::CSparseMatrixTemplate
A sparse matrix container (with cells of any type), with iterators.
Definition: CSparseMatrixTemplate.h:42
mrpt::math::CSparseMatrixTemplate::resize
void resize(size_t nRows, size_t nCols)
Changes the size of the matrix.
Definition: CSparseMatrixTemplate.h:283
mrpt::math::CSparseMatrixTemplate::end
const_iterator end() const
Returns an iterator which points to the end of the matrix.
Definition: CSparseMatrixTemplate.h:214
mrpt::math::CSparseSymmetricalMatrix::CSparseSymmetricalMatrix
CSparseSymmetricalMatrix(const CSparseSymmetricalMatrix &o)
Definition: CSparseMatrixTemplate.h:415
mrpt::math::CSparseMatrixTemplate::setColumn
void setColumn(size_t nCol, const VECTOR &vec, const T &nullObject=T())
Inserts a full column into the matrix.
Definition: CSparseMatrixTemplate.h:263
mrpt::math::CSparseMatrixTemplate::exists
bool exists(size_t r, size_t c) const
Element access operator.
Definition: CSparseMatrixTemplate.h:95
mrpt::math::CSparseMatrixTemplate::cols
size_t cols() const
Returns the amount of columns in this matrix.
Definition: CSparseMatrixTemplate.h:122
mrpt::math::CSparseMatrixTemplate::clear
void clear()
Completely removes all elements, although maintaining the matrix's size.
Definition: CSparseMatrixTemplate.h:385
row
GLenum GLenum GLvoid * row
Definition: glext.h:3576
mrpt::math::CSparseMatrixTemplate::getRow
void getRow(size_t nRow, VECTOR &vec) const
Extracts a full row from the matrix.
Definition: CSparseMatrixTemplate.h:129
mrpt::math::CSparseMatrixTemplate::SparseMatrixMap
typename std::map< std::pair< size_t, size_t >, T > SparseMatrixMap
Internal map type, used to store the actual matrix.
Definition: CSparseMatrixTemplate.h:49
mrpt::math::CSparseMatrixTemplate::getNullElements
size_t getNullElements() const
Gets the amount of null elements inside the matrix.
Definition: CSparseMatrixTemplate.h:357
mrpt::math::CSparseSymmetricalMatrix::operator()
T & operator()(size_t r, size_t c)
Definition: CSparseMatrixTemplate.h:439
mrpt::math::CSparseSymmetricalMatrix
A sparse matrix container for square symmetrical content around the main diagonal.
Definition: CSparseMatrixTemplate.h:411
mrpt::math::CSparseMatrixTemplate::mRows
size_t mRows
Size of the matrix.
Definition: CSparseMatrixTemplate.h:66



Page generated by Doxygen 1.8.17 for MRPT 1.9.9 Git: ad3a9d8ae Tue May 1 23:10:22 2018 -0700 at miƩ 12 jul 2023 10:03:34 CEST