1   /* Copyright 2002-2025 CS GROUP
2    * Licensed to CS GROUP (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.utils;
18  
19  import java.io.Serializable;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.HashMap;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Map;
27  
28  /** String → double[] mapping, for small number of keys.
29   * <p>
30   * This class is a low overhead for a very small number of keys.
31   * It is based on simple array and string comparison. It plays
32   * the same role a {@code Map<String, double[]>} but with reduced
33   * features and not intended for large number of keys. For such
34   * needs the regular {@code Map<String, double[]>} should be preferred.
35   * </p>
36   *
37   * @see DataDictionary
38   * @since 11.1
39   */
40  public class DoubleArrayDictionary implements Serializable {
41  
42      /** Serializable UID. */
43      private static final long serialVersionUID = 20211121L;
44  
45      /** Default capacity. */
46      private static final int DEFAULT_INITIAL_CAPACITY = 4;
47  
48      /** Data container. */
49      private final List<Entry> data;
50  
51      /** Constructor with {@link #DEFAULT_INITIAL_CAPACITY default initial capacity}.
52       */
53      public DoubleArrayDictionary() {
54          this(DEFAULT_INITIAL_CAPACITY);
55      }
56  
57      /** Constructor with specified capacity.
58       * @param initialCapacity initial capacity
59       */
60      public DoubleArrayDictionary(final int initialCapacity) {
61          data = new ArrayList<>(initialCapacity);
62      }
63  
64      /** Constructor from another dictionary.
65       * @param dictionary dictionary to use for initializing entries
66       */
67      public DoubleArrayDictionary(final DoubleArrayDictionary dictionary) {
68          // take care to call dictionary.getData() and not use dictionary.data,
69          // otherwise we get an empty dictionary when using a DoubleArrayDictionary.view
70          this(DEFAULT_INITIAL_CAPACITY + dictionary.getData().size());
71          for (final Entry entry : dictionary.getData()) {
72              // we don't call put(key, value) to avoid the overhead of the unneeded call to remove(key)
73              data.add(new Entry(entry.getKey(), entry.getValue()));
74          }
75      }
76  
77      /** Constructor from a map.
78       * @param map map to use for initializing entries
79       */
80      public DoubleArrayDictionary(final Map<String, double[]> map) {
81          this(map.size());
82          for (final Map.Entry<String, double[]> entry : map.entrySet()) {
83              // we don't call put(key, value) to avoid the overhead of the unneeded call to remove(key)
84              data.add(new Entry(entry.getKey(), entry.getValue()));
85          }
86      }
87  
88      /** Get an unmodifiable view of the dictionary entries.
89       * @return unmodifiable view of the dictionary entries
90       */
91      public List<Entry> getData() {
92          return Collections.unmodifiableList(data);
93      }
94  
95      /** Get the number of dictionary entries.
96       * @return number of dictionary entries
97       */
98      public int size() {
99          return data.size();
100     }
101 
102     /** Create a map from the instance.
103      * <p>
104      * The map contains a copy of the instance data
105      * </p>
106      * @return copy of the dictionary, as an independent map
107      */
108     public Map<String, double[]> toMap() {
109         final Map<String, double[]> map = new HashMap<>(data.size());
110         for (final Entry entry : data) {
111             map.put(entry.getKey(), entry.getValue());
112         }
113         return map;
114     }
115 
116     /** Remove all entries.
117      */
118     public void clear() {
119         data.clear();
120     }
121 
122     /** Add an entry.
123      * <p>
124      * If an entry with the same key already exists, it will be removed first.
125      * </p>
126      * <p>
127      * The new entry is always put at the end.
128      * </p>
129      * @param key entry key
130      * @param value entry value
131      */
132     public void put(final String key, final double[] value) {
133         remove(key);
134         data.add(new Entry(key, value));
135     }
136 
137     /** Put all the entries from the map in the dictionary.
138      * @param map map to copy into the instance
139      */
140     public void putAll(final Map<String, double[]> map) {
141         for (final Map.Entry<String, double[]> entry : map.entrySet()) {
142             put(entry.getKey(), entry.getValue());
143         }
144     }
145 
146     /** Put all the entries from another dictionary.
147      * @param dictionary dictionary to copy into the instance
148      */
149     public void putAll(final DoubleArrayDictionary dictionary) {
150         for (final Entry entry : dictionary.data) {
151             put(entry.getKey(), entry.getValue());
152         }
153     }
154 
155     /** Get the value corresponding to a key.
156      * @param key entry key
157      * @return copy of the value corresponding to the key or null if key not present
158      */
159     public double[] get(final String key) {
160         final Entry entry = getEntry(key);
161         return entry == null ? null : entry.getValue();
162     }
163 
164     /** Get a complete entry.
165      * @param key entry key
166      * @return entry with key if it exists, null otherwise
167      */
168     public Entry getEntry(final String key) {
169         for (final Entry entry : data) {
170             if (entry.getKey().equals(key)) {
171                 return entry;
172             }
173         }
174         return null;
175     }
176 
177     /** Remove an entry.
178      * @param key key of the entry to remove
179      * @return true if an entry has been removed, false if the key was not present
180      */
181     public boolean remove(final String key) {
182         final Iterator<Entry> iterator = data.iterator();
183         while (iterator.hasNext()) {
184             if (iterator.next().getKey().equals(key)) {
185                 iterator.remove();
186                 return true;
187             }
188         }
189         return false;
190     }
191 
192 
193     /** Get a string representation of the dictionary.
194      * <p>
195      * This string representation is intended for improving displays in debuggers only.
196      * </p>
197      * @return string representation of the dictionary
198      */
199     @Override
200     public String toString() {
201         final StringBuilder builder = new StringBuilder();
202         builder.append('{');
203         for (int i = 0; i < data.size(); ++i) {
204             if (i > 0) {
205                 builder.append(", ");
206             }
207             builder.append(data.get(i).getKey());
208             builder.append('[');
209             builder.append(data.get(i).getValue().length);
210             builder.append(']');
211         }
212         builder.append('}');
213         return builder.toString();
214     }
215 
216     /** Entry in a dictionary. */
217     public static class Entry implements Serializable {
218 
219         /** Serializable UID. */
220         private static final long serialVersionUID = 20211121L;
221 
222         /** Key. */
223         private final String key;
224 
225         /** Value. */
226         private final double[] value;
227 
228         /** Simple constructor.
229          * @param key key
230          * @param value value
231          */
232         Entry(final String key, final double[] value) {
233             this.key   = key;
234             this.value = value.clone();
235         }
236 
237         /** Get the entry key.
238          * @return entry key
239          */
240         public String getKey() {
241             return key;
242         }
243 
244         /** Get the value.
245          * @return a copy of the value (independent from internal array)
246          */
247         public double[] getValue() {
248             return value.clone();
249         }
250 
251         /** Get the size of the value array.
252          * @return size of the value array
253          */
254         public int size() {
255             return value.length;
256         }
257 
258         /** Increment the value.
259          * <p>
260          * For the sake of performance, no checks are done on argument.
261          * </p>
262          * @param increment increment to apply to the entry value
263          */
264         public void increment(final double[] increment) {
265             for (int i = 0; i < increment.length; ++i) {
266                 value[i] += increment[i];
267             }
268         }
269 
270         /** Increment the value with another scaled entry.
271          * <p>
272          * Each component {@code value[i]} will be replaced by {@code value[i] + factor * raw.value[i]}.
273          * </p>
274          * <p>
275          * For the sake of performance, no checks are done on arguments.
276          * </p>
277          * @param factor multiplicative factor for increment
278          * @param raw raw increment to be multiplied by {@code factor} and then added
279          * @since 11.1.1
280          */
281         public void scaledIncrement(final double factor, final Entry raw) {
282             for (int i = 0; i < raw.value.length; ++i) {
283                 value[i] += factor * raw.value[i];
284             }
285         }
286 
287         /** Reset the value to zero.
288          */
289         public void zero() {
290             Arrays.fill(value, 0.0);
291         }
292 
293     }
294 }