View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF 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.apache.commons.math4.legacy.linear;
18  
19  import java.io.Serializable;
20  
21  import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
22  import org.apache.commons.math4.legacy.exception.MathArithmeticException;
23  import org.apache.commons.math4.legacy.exception.NotPositiveException;
24  import org.apache.commons.math4.legacy.exception.OutOfRangeException;
25  import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
26  import org.apache.commons.math4.core.jdkmath.JdkMath;
27  import org.apache.commons.math4.legacy.linear.OpenIntToDoubleHashMap.Iterator;
28  
29  /**
30   * This class implements the {@link RealVector} interface with a
31   * {@link OpenIntToDoubleHashMap} backing store.
32   * <p>
33   *  Caveat: This implementation assumes that, for any {@code x},
34   *  the equality {@code x * 0d == 0d} holds. But it is is not true for
35   *  {@code NaN}. Moreover, zero entries will lose their sign.
36   *  Some operations (that involve {@code NaN} and/or infinities) may
37   *  thus give incorrect results, like multiplications, divisions or
38   *  functions mapping.
39   * </p>
40   * @since 2.0
41   */
42  public class OpenMapRealVector extends SparseRealVector
43      implements Serializable {
44      /** Default Tolerance for having a value considered zero. */
45      public static final double DEFAULT_ZERO_TOLERANCE = 1.0e-12;
46      /** Serializable version identifier. */
47      private static final long serialVersionUID = 8772222695580707260L;
48      /** Entries of the vector. */
49      private final OpenIntToDoubleHashMap entries;
50      /** Dimension of the vector. */
51      private final int virtualSize;
52      /** Tolerance for having a value considered zero. */
53      private final double epsilon;
54  
55      /**
56       * Build a 0-length vector.
57       * Zero-length vectors may be used to initialized construction of vectors
58       * by data gathering. We start with zero-length and use either the {@link
59       * #OpenMapRealVector(OpenMapRealVector, int)} constructor
60       * or one of the {@code append} method ({@link #append(double)},
61       * {@link #append(RealVector)}) to gather data into this vector.
62       */
63      public OpenMapRealVector() {
64          this(0, DEFAULT_ZERO_TOLERANCE);
65      }
66  
67      /**
68       * Construct a vector of zeroes.
69       *
70       * @param dimension Size of the vector.
71       */
72      public OpenMapRealVector(int dimension) {
73          this(dimension, DEFAULT_ZERO_TOLERANCE);
74      }
75  
76      /**
77       * Construct a vector of zeroes, specifying zero tolerance.
78       *
79       * @param dimension Size of the vector.
80       * @param epsilon Tolerance below which a value considered zero.
81       */
82      public OpenMapRealVector(int dimension, double epsilon) {
83          virtualSize = dimension;
84          entries = new OpenIntToDoubleHashMap(0.0);
85          this.epsilon = epsilon;
86      }
87  
88      /**
89       * Build a resized vector, for use with append.
90       *
91       * @param v Original vector.
92       * @param resize Amount to add.
93       */
94      protected OpenMapRealVector(OpenMapRealVector v, int resize) {
95          virtualSize = v.getDimension() + resize;
96          entries = new OpenIntToDoubleHashMap(v.entries);
97          epsilon = v.epsilon;
98      }
99  
100     /**
101      * Build a vector with known the sparseness (for advanced use only).
102      *
103      * @param dimension Size of the vector.
104      * @param expectedSize The expected number of non-zero entries.
105      */
106     public OpenMapRealVector(int dimension, int expectedSize) {
107         this(dimension, expectedSize, DEFAULT_ZERO_TOLERANCE);
108     }
109 
110     /**
111      * Build a vector with known the sparseness and zero tolerance
112      * setting (for advanced use only).
113      *
114      * @param dimension Size of the vector.
115      * @param expectedSize Expected number of non-zero entries.
116      * @param epsilon Tolerance below which a value is considered zero.
117      */
118     public OpenMapRealVector(int dimension, int expectedSize, double epsilon) {
119         virtualSize = dimension;
120         entries = new OpenIntToDoubleHashMap(expectedSize, 0.0);
121         this.epsilon = epsilon;
122     }
123 
124     /**
125      * Create from an array.
126      * Only non-zero entries will be stored.
127      *
128      * @param values Set of values to create from.
129      */
130     public OpenMapRealVector(double[] values) {
131         this(values, DEFAULT_ZERO_TOLERANCE);
132     }
133 
134     /**
135      * Create from an array, specifying zero tolerance.
136      * Only non-zero entries will be stored.
137      *
138      * @param values Set of values to create from.
139      * @param epsilon Tolerance below which a value is considered zero.
140      */
141     public OpenMapRealVector(double[] values, double epsilon) {
142         virtualSize = values.length;
143         entries = new OpenIntToDoubleHashMap(0.0);
144         this.epsilon = epsilon;
145         for (int key = 0; key < values.length; key++) {
146             double value = values[key];
147             if (!isDefaultValue(value)) {
148                 entries.put(key, value);
149             }
150         }
151     }
152 
153     /**
154      * Create from an array.
155      * Only non-zero entries will be stored.
156      *
157      * @param values The set of values to create from
158      */
159     public OpenMapRealVector(Double[] values) {
160         this(values, DEFAULT_ZERO_TOLERANCE);
161     }
162 
163     /**
164      * Create from an array.
165      * Only non-zero entries will be stored.
166      *
167      * @param values Set of values to create from.
168      * @param epsilon Tolerance below which a value is considered zero.
169      */
170     public OpenMapRealVector(Double[] values, double epsilon) {
171         virtualSize = values.length;
172         entries = new OpenIntToDoubleHashMap(0.0);
173         this.epsilon = epsilon;
174         for (int key = 0; key < values.length; key++) {
175             double value = values[key].doubleValue();
176             if (!isDefaultValue(value)) {
177                 entries.put(key, value);
178             }
179         }
180     }
181 
182     /**
183      * Copy constructor.
184      *
185      * @param v Instance to copy from.
186      */
187     public OpenMapRealVector(OpenMapRealVector v) {
188         virtualSize = v.getDimension();
189         entries = new OpenIntToDoubleHashMap(v.getEntries());
190         epsilon = v.epsilon;
191     }
192 
193     /**
194      * Generic copy constructor.
195      *
196      * @param v Instance to copy from.
197      */
198     public OpenMapRealVector(RealVector v) {
199         virtualSize = v.getDimension();
200         entries = new OpenIntToDoubleHashMap(0.0);
201         epsilon = DEFAULT_ZERO_TOLERANCE;
202         for (int key = 0; key < virtualSize; key++) {
203             double value = v.getEntry(key);
204             if (!isDefaultValue(value)) {
205                 entries.put(key, value);
206             }
207         }
208     }
209 
210     /**
211      * Get the entries of this instance.
212      *
213      * @return the entries of this instance.
214      */
215     private OpenIntToDoubleHashMap getEntries() {
216         return entries;
217     }
218 
219     /**
220      * Determine if this value is within epsilon of zero.
221      *
222      * @param value Value to test
223      * @return {@code true} if this value is within epsilon to zero,
224      * {@code false} otherwise.
225      * @since 2.1
226      */
227     protected boolean isDefaultValue(double value) {
228         return JdkMath.abs(value) < epsilon;
229     }
230 
231     /** {@inheritDoc} */
232     @Override
233     public RealVector add(RealVector v)
234         throws DimensionMismatchException {
235         checkVectorDimensions(v.getDimension());
236         if (v instanceof OpenMapRealVector) {
237             return add((OpenMapRealVector) v);
238         } else {
239             return super.add(v);
240         }
241     }
242 
243     /**
244      * Optimized method to add two OpenMapRealVectors.
245      * It copies the larger vector, then iterates over the smaller.
246      *
247      * @param v Vector to add.
248      * @return the sum of {@code this} and {@code v}.
249      * @throws DimensionMismatchException if the dimensions do not match.
250      */
251     public OpenMapRealVector add(OpenMapRealVector v)
252         throws DimensionMismatchException {
253         checkVectorDimensions(v.getDimension());
254         boolean copyThis = entries.size() > v.entries.size();
255         OpenMapRealVector res = copyThis ? this.copy() : v.copy();
256         Iterator iter = copyThis ? v.entries.iterator() : entries.iterator();
257         OpenIntToDoubleHashMap randomAccess = copyThis ? entries : v.entries;
258         while (iter.hasNext()) {
259             iter.advance();
260             int key = iter.key();
261             if (randomAccess.containsKey(key)) {
262                 res.setEntry(key, randomAccess.get(key) + iter.value());
263             } else {
264                 res.setEntry(key, iter.value());
265             }
266         }
267         return res;
268     }
269 
270     /**
271      * Optimized method to append a OpenMapRealVector.
272      * @param v vector to append
273      * @return The result of appending {@code v} to self
274      */
275     public OpenMapRealVector append(OpenMapRealVector v) {
276         OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension());
277         Iterator iter = v.entries.iterator();
278         while (iter.hasNext()) {
279             iter.advance();
280             res.setEntry(iter.key() + virtualSize, iter.value());
281         }
282         return res;
283     }
284 
285     /** {@inheritDoc} */
286     @Override
287     public OpenMapRealVector append(RealVector v) {
288         if (v instanceof OpenMapRealVector) {
289             return append((OpenMapRealVector) v);
290         } else {
291             final OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension());
292             for (int i = 0; i < v.getDimension(); i++) {
293                 res.setEntry(i + virtualSize, v.getEntry(i));
294             }
295             return res;
296         }
297     }
298 
299     /** {@inheritDoc} */
300     @Override
301     public OpenMapRealVector append(double d) {
302         OpenMapRealVector res = new OpenMapRealVector(this, 1);
303         res.setEntry(virtualSize, d);
304         return res;
305     }
306 
307     /**
308      * {@inheritDoc}
309      * @since 2.1
310      */
311     @Override
312     public OpenMapRealVector copy() {
313         return new OpenMapRealVector(this);
314     }
315 
316     /** {@inheritDoc} */
317     @Override
318     public OpenMapRealVector ebeDivide(RealVector v)
319         throws DimensionMismatchException {
320         checkVectorDimensions(v.getDimension());
321         OpenMapRealVector res = new OpenMapRealVector(this);
322         /*
323          * MATH-803: it is not sufficient to loop through non zero entries of
324          * this only. Indeed, if this[i] = 0d and v[i] = 0d, then
325          * this[i] / v[i] = NaN, and not 0d.
326          */
327         final int n = getDimension();
328         for (int i = 0; i < n; i++) {
329             res.setEntry(i, this.getEntry(i) / v.getEntry(i));
330         }
331         return res;
332     }
333 
334     /** {@inheritDoc} */
335     @Override
336     public OpenMapRealVector ebeMultiply(RealVector v)
337         throws DimensionMismatchException {
338         checkVectorDimensions(v.getDimension());
339         OpenMapRealVector res = new OpenMapRealVector(this);
340         Iterator iter = entries.iterator();
341         while (iter.hasNext()) {
342             iter.advance();
343             res.setEntry(iter.key(), iter.value() * v.getEntry(iter.key()));
344         }
345         return res;
346     }
347 
348     /** {@inheritDoc} */
349     @Override
350     public OpenMapRealVector getSubVector(int index, int n)
351         throws NotPositiveException, OutOfRangeException {
352         checkIndex(index);
353         if (n < 0) {
354             throw new NotPositiveException(LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n);
355         }
356         checkIndex(index + n - 1);
357         OpenMapRealVector res = new OpenMapRealVector(n);
358         int end = index + n;
359         Iterator iter = entries.iterator();
360         while (iter.hasNext()) {
361             iter.advance();
362             int key = iter.key();
363             if (key >= index && key < end) {
364                 res.setEntry(key - index, iter.value());
365             }
366         }
367         return res;
368     }
369 
370     /** {@inheritDoc} */
371     @Override
372     public int getDimension() {
373         return virtualSize;
374     }
375 
376     /**
377      * Optimized method to compute distance.
378      *
379      * @param v Vector to compute distance to.
380      * @return the distance from {@code this} and {@code v}.
381      * @throws DimensionMismatchException if the dimensions do not match.
382      */
383     public double getDistance(OpenMapRealVector v)
384         throws DimensionMismatchException {
385         checkVectorDimensions(v.getDimension());
386         Iterator iter = entries.iterator();
387         double res = 0;
388         while (iter.hasNext()) {
389             iter.advance();
390             int key = iter.key();
391             double delta;
392             delta = iter.value() - v.getEntry(key);
393             res += delta * delta;
394         }
395         iter = v.getEntries().iterator();
396         while (iter.hasNext()) {
397             iter.advance();
398             int key = iter.key();
399             if (!entries.containsKey(key)) {
400                 final double value = iter.value();
401                 res += value * value;
402             }
403         }
404         return JdkMath.sqrt(res);
405     }
406 
407     /** {@inheritDoc} */
408     @Override
409     public double getDistance(RealVector v) throws DimensionMismatchException {
410         checkVectorDimensions(v.getDimension());
411         if (v instanceof OpenMapRealVector) {
412             return getDistance((OpenMapRealVector) v);
413         } else {
414             return super.getDistance(v);
415         }
416     }
417 
418     /** {@inheritDoc} */
419     @Override
420     public double getEntry(int index) throws OutOfRangeException {
421         checkIndex(index);
422         return entries.get(index);
423     }
424 
425     /**
426      * Distance between two vectors.
427      * This method computes the distance consistent with
428      * L<sub>1</sub> norm, i.e. the sum of the absolute values of
429      * elements differences.
430      *
431      * @param v Vector to which distance is requested.
432      * @return distance between this vector and {@code v}.
433      * @throws DimensionMismatchException if the dimensions do not match.
434      */
435     public double getL1Distance(OpenMapRealVector v)
436         throws DimensionMismatchException {
437         checkVectorDimensions(v.getDimension());
438         double max = 0;
439         Iterator iter = entries.iterator();
440         while (iter.hasNext()) {
441             iter.advance();
442             double delta = JdkMath.abs(iter.value() - v.getEntry(iter.key()));
443             max += delta;
444         }
445         iter = v.getEntries().iterator();
446         while (iter.hasNext()) {
447             iter.advance();
448             int key = iter.key();
449             if (!entries.containsKey(key)) {
450                 double delta = JdkMath.abs(iter.value());
451                 max +=  JdkMath.abs(delta);
452             }
453         }
454         return max;
455     }
456 
457     /** {@inheritDoc} */
458     @Override
459     public double getL1Distance(RealVector v)
460         throws DimensionMismatchException {
461         checkVectorDimensions(v.getDimension());
462         if (v instanceof OpenMapRealVector) {
463             return getL1Distance((OpenMapRealVector) v);
464         } else {
465             return super.getL1Distance(v);
466         }
467     }
468 
469     /**
470      * Optimized method to compute LInfDistance.
471      *
472      * @param v Vector to compute distance from.
473      * @return the LInfDistance.
474      * @throws DimensionMismatchException if the dimensions do not match.
475      */
476     private double getLInfDistance(OpenMapRealVector v)
477         throws DimensionMismatchException {
478         checkVectorDimensions(v.getDimension());
479         double max = 0;
480         Iterator iter = entries.iterator();
481         while (iter.hasNext()) {
482             iter.advance();
483             double delta = JdkMath.abs(iter.value() - v.getEntry(iter.key()));
484             if (delta > max) {
485                 max = delta;
486             }
487         }
488         iter = v.getEntries().iterator();
489         while (iter.hasNext()) {
490             iter.advance();
491             int key = iter.key();
492             if (!entries.containsKey(key) && iter.value() > max) {
493                 max = iter.value();
494             }
495         }
496         return max;
497     }
498 
499     /** {@inheritDoc} */
500     @Override
501     public double getLInfDistance(RealVector v)
502         throws DimensionMismatchException {
503         checkVectorDimensions(v.getDimension());
504         if (v instanceof OpenMapRealVector) {
505             return getLInfDistance((OpenMapRealVector) v);
506         } else {
507             return super.getLInfDistance(v);
508         }
509     }
510 
511     /** {@inheritDoc} */
512     @Override
513     public boolean isInfinite() {
514         boolean infiniteFound = false;
515         Iterator iter = entries.iterator();
516         while (iter.hasNext()) {
517             iter.advance();
518             final double value = iter.value();
519             if (Double.isNaN(value)) {
520                 return false;
521             }
522             if (Double.isInfinite(value)) {
523                 infiniteFound = true;
524             }
525         }
526         return infiniteFound;
527     }
528 
529     /** {@inheritDoc} */
530     @Override
531     public boolean isNaN() {
532         Iterator iter = entries.iterator();
533         while (iter.hasNext()) {
534             iter.advance();
535             if (Double.isNaN(iter.value())) {
536                 return true;
537             }
538         }
539         return false;
540     }
541 
542     /** {@inheritDoc} */
543     @Override
544     public OpenMapRealVector mapAdd(double d) {
545         return copy().mapAddToSelf(d);
546     }
547 
548     /** {@inheritDoc} */
549     @Override
550     public OpenMapRealVector mapAddToSelf(double d) {
551         for (int i = 0; i < virtualSize; i++) {
552             setEntry(i, getEntry(i) + d);
553         }
554         return this;
555     }
556 
557     /** {@inheritDoc} */
558     @Override
559     public void setEntry(int index, double value)
560         throws OutOfRangeException {
561         checkIndex(index);
562         if (!isDefaultValue(value)) {
563             entries.put(index, value);
564         } else if (entries.containsKey(index)) {
565             entries.remove(index);
566         }
567     }
568 
569     /** {@inheritDoc} */
570     @Override
571     public void setSubVector(int index, RealVector v)
572         throws OutOfRangeException {
573         checkIndex(index);
574         checkIndex(index + v.getDimension() - 1);
575         for (int i = 0; i < v.getDimension(); i++) {
576             setEntry(i + index, v.getEntry(i));
577         }
578     }
579 
580     /** {@inheritDoc} */
581     @Override
582     public void set(double value) {
583         for (int i = 0; i < virtualSize; i++) {
584             setEntry(i, value);
585         }
586     }
587 
588     /**
589      * Optimized method to subtract OpenMapRealVectors.
590      *
591      * @param v Vector to subtract from {@code this}.
592      * @return the difference of {@code this} and {@code v}.
593      * @throws DimensionMismatchException if the dimensions do not match.
594      */
595     public OpenMapRealVector subtract(OpenMapRealVector v)
596         throws DimensionMismatchException {
597         checkVectorDimensions(v.getDimension());
598         OpenMapRealVector res = copy();
599         Iterator iter = v.getEntries().iterator();
600         while (iter.hasNext()) {
601             iter.advance();
602             int key = iter.key();
603             if (entries.containsKey(key)) {
604                 res.setEntry(key, entries.get(key) - iter.value());
605             } else {
606                 res.setEntry(key, -iter.value());
607             }
608         }
609         return res;
610     }
611 
612     /** {@inheritDoc} */
613     @Override
614     public RealVector subtract(RealVector v)
615         throws DimensionMismatchException {
616         checkVectorDimensions(v.getDimension());
617         if (v instanceof OpenMapRealVector) {
618             return subtract((OpenMapRealVector) v);
619         } else {
620             return super.subtract(v);
621         }
622     }
623 
624     /** {@inheritDoc} */
625     @Override
626     public OpenMapRealVector unitVector() throws MathArithmeticException {
627         OpenMapRealVector res = copy();
628         res.unitize();
629         return res;
630     }
631 
632     /** {@inheritDoc} */
633     @Override
634     public void unitize() throws MathArithmeticException {
635         double norm = getNorm();
636         if (isDefaultValue(norm)) {
637             throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
638         }
639         Iterator iter = entries.iterator();
640         while (iter.hasNext()) {
641             iter.advance();
642             entries.put(iter.key(), iter.value() / norm);
643         }
644     }
645 
646     /** {@inheritDoc} */
647     @Override
648     public double[] toArray() {
649         double[] res = new double[virtualSize];
650         Iterator iter = entries.iterator();
651         while (iter.hasNext()) {
652             iter.advance();
653             res[iter.key()] = iter.value();
654         }
655         return res;
656     }
657 
658     /**
659      * {@inheritDoc}
660      * Implementation Note: This works on exact values, and as a result
661      * it is possible for {@code a.subtract(b)} to be the zero vector, while
662      * {@code a.hashCode() != b.hashCode()}.
663      */
664     @Override
665     public int hashCode() {
666         final int prime = 31;
667         int result = 1;
668         long temp;
669         temp = Double.doubleToLongBits(epsilon);
670         result = prime * result + (int) (temp ^ (temp >>> 32));
671         result = prime * result + virtualSize;
672         Iterator iter = entries.iterator();
673         while (iter.hasNext()) {
674             iter.advance();
675             temp = Double.doubleToLongBits(iter.value());
676             result = prime * result + (int) (temp ^ (temp >>32));
677         }
678         return result;
679     }
680 
681     /**
682      * {@inheritDoc}
683      * Implementation Note: This performs an exact comparison, and as a result
684      * it is possible for {@code a.subtract(b}} to be the zero vector, while
685      * {@code  a.equals(b) == false}.
686      */
687     @Override
688     public boolean equals(Object obj) {
689         if (this == obj) {
690             return true;
691         }
692         if (!(obj instanceof OpenMapRealVector)) {
693             return false;
694         }
695         OpenMapRealVector other = (OpenMapRealVector) obj;
696         if (virtualSize != other.virtualSize) {
697             return false;
698         }
699         if (Double.doubleToLongBits(epsilon) !=
700             Double.doubleToLongBits(other.epsilon)) {
701             return false;
702         }
703         Iterator iter = entries.iterator();
704         while (iter.hasNext()) {
705             iter.advance();
706             double test = other.getEntry(iter.key());
707             if (Double.doubleToLongBits(test) != Double.doubleToLongBits(iter.value())) {
708                 return false;
709             }
710         }
711         iter = other.getEntries().iterator();
712         while (iter.hasNext()) {
713             iter.advance();
714             double test = iter.value();
715             if (Double.doubleToLongBits(test) != Double.doubleToLongBits(getEntry(iter.key()))) {
716                 return false;
717             }
718         }
719         return true;
720     }
721 
722     /**
723      *
724      * @return the percentage of none zero elements as a decimal percent.
725      * @since 2.2
726      */
727     public double getSparsity() {
728         return (double)entries.size()/(double)getDimension();
729     }
730 
731     /** {@inheritDoc} */
732     @Override
733     public java.util.Iterator<Entry> sparseIterator() {
734         return new OpenMapSparseIterator();
735     }
736 
737     /**
738      * Implementation of {@code Entry} optimized for OpenMap.
739      * This implementation does not allow arbitrary calls to {@code setIndex}
740      * since the order in which entries are returned is undefined.
741      */
742     protected class OpenMapEntry extends Entry {
743         /** Iterator pointing to the entry. */
744         private final Iterator iter;
745 
746         /**
747          * Build an entry from an iterator point to an element.
748          *
749          * @param iter Iterator pointing to the entry.
750          */
751         protected OpenMapEntry(Iterator iter) {
752             this.iter = iter;
753         }
754 
755         /** {@inheritDoc} */
756         @Override
757         public double getValue() {
758             return iter.value();
759         }
760 
761         /** {@inheritDoc} */
762         @Override
763         public void setValue(double value) {
764             entries.put(iter.key(), value);
765         }
766 
767         /** {@inheritDoc} */
768         @Override
769         public int getIndex() {
770             return iter.key();
771         }
772     }
773 
774     /**
775      * Iterator class to do iteration over just the non-zero elements.
776      * This implementation is fail-fast, so cannot be used to modify
777      * any zero element.
778      */
779     protected class OpenMapSparseIterator implements java.util.Iterator<Entry> {
780         /** Underlying iterator. */
781         private final Iterator iter;
782         /** Current entry. */
783         private final Entry current;
784 
785         /** Simple constructor. */
786         protected OpenMapSparseIterator() {
787             iter = entries.iterator();
788             current = new OpenMapEntry(iter);
789         }
790 
791         /** {@inheritDoc} */
792         @Override
793         public boolean hasNext() {
794             return iter.hasNext();
795         }
796 
797         /** {@inheritDoc} */
798         @Override
799         public Entry next() {
800             iter.advance();
801             return current;
802         }
803 
804         /** {@inheritDoc} */
805         @Override
806         public void remove() {
807             throw new UnsupportedOperationException("Not supported");
808         }
809     }
810 }