• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

apache / datasketches-java / #306

30 Apr 2024 10:01PM UTC coverage: 97.645% (-0.5%) from 98.139%
#306

push

web-flow
Merge pull request #555 from apache/fix_pom_xml_header

Fix pom xml header

26865 of 27513 relevant lines covered (97.64%)

0.98 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.44
/src/main/java/org/apache/datasketches/hllmap/CouponTraverseMap.java
1
/*
2
 * Licensed to the Apache Software Foundation (ASF) under one
3
 * or more contributor license agreements.  See the NOTICE file
4
 * distributed with this work for additional information
5
 * regarding copyright ownership.  The ASF licenses this file
6
 * to you under the Apache License, Version 2.0 (the
7
 * "License"); you may not use this file except in compliance
8
 * with the License.  You may obtain a copy of the License at
9
 *
10
 *   http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing,
13
 * software distributed under the License is distributed on an
14
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15
 * KIND, either express or implied.  See the License for the
16
 * specific language governing permissions and limitations
17
 * under the License.
18
 */
19

20
package org.apache.datasketches.hllmap;
21

22
import java.util.Arrays;
23

24
import org.apache.datasketches.common.SketchesArgumentException;
25
import org.apache.datasketches.hash.MurmurHash3;
26

27
/**
28
 * Implements a key-value map where the value is a simple array of coupons. Search operations are a
29
 * simple traverse of the consecutive coupons. Because of this, the maximum practical size of the
30
 * coupon array is about 8 coupons.
31
 *
32
 * <p>The map is implemented as a prime-sized, Open Address, Double Hash, with deletes and a 1-bit
33
 * state array. The size of this map can grow or shrink.
34
 *
35
 * @author Lee Rhodes
36
 * @author Alexander Saydakov
37
 * @author Kevin Lang
38
 */
39
final class CouponTraverseMap extends Map {
40
  private static final double RSE = 0.408 / Math.sqrt(1024);
1✔
41
  //private static int
42
  private final int maxCouponsPerKey_;
43

44
  private int tableEntries_;
45
  private int capacityEntries_;
46
  private int numActiveKeys_;
47
  private int numDeletedKeys_;
48
  private double entrySizeBytes_;
49

50
  //Arrays
51
  private byte[] keysArr_;
52
  private short[] couponsArr_;
53

54
  /**
55
   * <ul><li>State: 0: Empty always, don't need to look at 1st coupon. Coupons could be dirty.</li>
56
   * <li>State: 1: Valid entry or dirty. During rebuild, look at the first coupon to determine.
57
   * If first coupon != 0 means valid entry; first coupon == 0: dirty (we set to 0 when deleted)</li>
58
   * </ul>
59
   */
60
  private byte[] stateArr_;
61

62
  private CouponTraverseMap(final int keySizeBytes, final int maxCouponsPerKey) {
63
    super(keySizeBytes);
1✔
64
    maxCouponsPerKey_ = maxCouponsPerKey;
1✔
65
  }
1✔
66

67
  static CouponTraverseMap getInstance(final int keySizeBytes, final int maxCouponsPerKey) {
68
    final CouponTraverseMap map = new CouponTraverseMap(keySizeBytes, maxCouponsPerKey);
1✔
69
    map.tableEntries_ = COUPON_MAP_MIN_NUM_ENTRIES;
1✔
70
    map.capacityEntries_ = (int)(map.tableEntries_ * COUPON_MAP_GROW_TRIGGER_FACTOR);
1✔
71
    map.numActiveKeys_ = 0;
1✔
72
    map.numDeletedKeys_ = 0;
1✔
73
    map.entrySizeBytes_ = updateEntrySizeBytes(map.tableEntries_, keySizeBytes, maxCouponsPerKey);
1✔
74

75
    map.keysArr_ = new byte[COUPON_MAP_MIN_NUM_ENTRIES * keySizeBytes];
1✔
76
    map.couponsArr_ = new short[COUPON_MAP_MIN_NUM_ENTRIES * maxCouponsPerKey];
1✔
77
    map.stateArr_ = new byte[COUPON_MAP_MIN_NUM_ENTRIES_ARR_SIZE];
1✔
78
    return map;
1✔
79
  }
80

81
  @Override //used for test
82
  double update(final byte[] key, final short coupon) {
83
    final int entryIndex = findOrInsertKey(key);
1✔
84
    return update(entryIndex, coupon);
1✔
85
  }
86

87
  @Override
88
  double update(final int entryIndex, final short value) {
89
    final int offset = entryIndex * maxCouponsPerKey_;
1✔
90
    boolean wasFound = false;
1✔
91
    for (int i = 0; i < maxCouponsPerKey_; i++) {
1✔
92
      if (couponsArr_[offset + i] == 0) {
1✔
93
        if (wasFound) { return i; }
1✔
94
        couponsArr_[offset + i] = value;
1✔
95
        return i + 1;
1✔
96
      }
97
      if (couponsArr_[offset + i] == value) {
1✔
98
        wasFound = true;
×
99
      }
100
    }
101
    if (wasFound) { return maxCouponsPerKey_; }
1✔
102
    return -maxCouponsPerKey_; //signal to promote
1✔
103
  }
104

105
  @Override
106
  double getEstimate(final byte[] key) {
107
    final int entryIndex = findKey(key);
1✔
108
    if (entryIndex < 0) { return 0; }
1✔
109
    return getCouponCount(entryIndex);
1✔
110
  }
111

112
  @Override
113
  double getUpperBound(final byte[] key) {
114
    return getEstimate(key) * (1 + RSE);
1✔
115
  }
116

117
  @Override
118
  double getLowerBound(final byte[] key) {
119
    return getEstimate(key) * (1 - RSE);
1✔
120
  }
121

122
  /**
123
   * Returns entryIndex if the given key is found. If not found, returns one's complement entryIndex
124
   * of an empty slot for insertion, which may be over a deleted key.
125
   * @param key the given key
126
   * @return the entryIndex
127
   */
128
  @Override
129
  int findKey(final byte[] key) {
130
    final long[] hash = MurmurHash3.hash(key, SEED);
1✔
131
    int entryIndex = getIndex(hash[0], tableEntries_);
1✔
132
    int firstDeletedIndex = -1;
1✔
133
    final int loopIndex = entryIndex;
1✔
134
    do {
135
      if (isBitClear(stateArr_, entryIndex)) {
1✔
136
        return firstDeletedIndex == -1 ? ~entryIndex : ~firstDeletedIndex; // found empty or deleted
1✔
137
      }
138
      if (couponsArr_[entryIndex * maxCouponsPerKey_] == 0) { //found deleted
1✔
139
        if (firstDeletedIndex == -1) { firstDeletedIndex = entryIndex; }
1✔
140
      } else if (Map.arraysEqual(keysArr_, entryIndex * keySizeBytes_, key, 0, keySizeBytes_)) {
1✔
141
        return entryIndex; // found key
1✔
142
      }
143
      entryIndex = (entryIndex + getStride(hash[1], tableEntries_)) % tableEntries_;
1✔
144
    } while (entryIndex != loopIndex);
1✔
145
    throw new SketchesArgumentException("Key not found and no empty slots!");
×
146
  }
147

148
  @Override
149
  int findOrInsertKey(final byte[] key) {
150
    int entryIndex = findKey(key);
1✔
151
    if (entryIndex < 0) {
1✔
152
      entryIndex = ~entryIndex;
1✔
153
      if (isBitSet(stateArr_, entryIndex)) { // reusing slot from a deleted key
1✔
154
        clearCouponArea(entryIndex);
1✔
155
        numDeletedKeys_--;
1✔
156
      }
157
      if ((numActiveKeys_ + numDeletedKeys_ + 1) > capacityEntries_) {
1✔
158
        resize();
1✔
159
        entryIndex = ~findKey(key);
1✔
160
        assert entryIndex >= 0;
1✔
161
      }
162
      System.arraycopy(key, 0, keysArr_, entryIndex * keySizeBytes_, keySizeBytes_);
1✔
163
      setBit(stateArr_, entryIndex);
1✔
164
      numActiveKeys_++;
1✔
165
    }
166
    return entryIndex;
1✔
167
  }
168

169
  @Override
170
  void deleteKey(final int entryIndex) {
171
    couponsArr_[entryIndex * maxCouponsPerKey_] = 0;
1✔
172
    numActiveKeys_--;
1✔
173
    numDeletedKeys_++;
1✔
174
    if ((numActiveKeys_ > COUPON_MAP_MIN_NUM_ENTRIES)
1✔
175
        && (numActiveKeys_ < (tableEntries_ * COUPON_MAP_SHRINK_TRIGGER_FACTOR))) {
176
      resize();
1✔
177
    }
178
  }
1✔
179

180
  private int getCouponCount(final int entryIndex) {
181
    final int offset = entryIndex * maxCouponsPerKey_;
1✔
182
    for (int i = 0; i < maxCouponsPerKey_; i++) {
1✔
183
      if (couponsArr_[offset + i] == 0) {
1✔
184
        return i;
1✔
185
      }
186
    }
187
    return maxCouponsPerKey_;
1✔
188
  }
189

190
  @Override
191
  CouponsIterator getCouponsIterator(final int entryIndex) {
192
    return new CouponsIterator(couponsArr_, entryIndex * maxCouponsPerKey_, maxCouponsPerKey_);
1✔
193
  }
194

195
  @Override
196
  double getEntrySizeBytes() {
197
    return entrySizeBytes_;
1✔
198
  }
199

200
  @Override
201
  int getTableEntries() {
202
    return tableEntries_;
1✔
203
  }
204

205
  @Override
206
  int getCapacityEntries() {
207
    return capacityEntries_;
1✔
208
  }
209

210
  @Override
211
  int getCurrentCountEntries() {
212
    return numActiveKeys_ + numDeletedKeys_;
1✔
213
  }
214

215
  @Override
216
  long getMemoryUsageBytes() {
217
    return keysArr_.length
1✔
218
        + ((long)couponsArr_.length * Short.BYTES)
219
        + stateArr_.length + (4L * Integer.BYTES);
220
  }
221

222
  @Override
223
  int getActiveEntries() {
224
    return numActiveKeys_;
1✔
225
  }
226

227
  @Override
228
  int getDeletedEntries() {
229
    return numDeletedKeys_;
1✔
230
  }
231

232
  @Override
233
  int getMaxCouponsPerEntry() {
234
    return maxCouponsPerKey_;
1✔
235
  }
236

237
  @Override
238
  int getCapacityCouponsPerEntry() {
239
    return maxCouponsPerKey_;
1✔
240
  }
241

242
  private void resize() { //can grow or shrink
243
    final byte[] oldKeysArr = keysArr_;
1✔
244
    final short[] oldCouponsArr = couponsArr_;
1✔
245
    final byte[] oldStateArr = stateArr_;
1✔
246
    final int oldSizeKeys = tableEntries_;
1✔
247
    tableEntries_ = Math.max(
1✔
248
      nextPrime((int) (numActiveKeys_ / COUPON_MAP_TARGET_FILL_FACTOR)),
1✔
249
      COUPON_MAP_MIN_NUM_ENTRIES
250
    );
251
    capacityEntries_ = (int)(tableEntries_ * COUPON_MAP_GROW_TRIGGER_FACTOR);
1✔
252
    numActiveKeys_ = 0;
1✔
253
    numDeletedKeys_ = 0;
1✔
254
    entrySizeBytes_ = updateEntrySizeBytes(tableEntries_, keySizeBytes_, maxCouponsPerKey_);
1✔
255

256
    keysArr_ = new byte[tableEntries_ * keySizeBytes_];
1✔
257
    couponsArr_ = new short[tableEntries_ * maxCouponsPerKey_];
1✔
258
    stateArr_ = new byte[(int) Math.ceil(tableEntries_ / 8.0)];
1✔
259

260
    //move data
261
    for (int i = 0; i < oldSizeKeys; i++) {
1✔
262
      if (isBitSet(oldStateArr, i) && (oldCouponsArr[i * maxCouponsPerKey_] != 0)) {
1✔
263
        final byte[] key =
1✔
264
            Arrays.copyOfRange(oldKeysArr, i * keySizeBytes_, (i * keySizeBytes_) + keySizeBytes_);
1✔
265
        final int index = insertKey(key);
1✔
266
        System.arraycopy(oldCouponsArr, i * maxCouponsPerKey_, couponsArr_,
1✔
267
            index * maxCouponsPerKey_, maxCouponsPerKey_);
268
      }
269
    }
270
  }
1✔
271

272
  // for internal use during resize, so no resize check here
273
  private int insertKey(final byte[] key) {
274
    final long[] hash = MurmurHash3.hash(key, SEED);
1✔
275
    int entryIndex = getIndex(hash[0], tableEntries_);
1✔
276
    final int loopIndex = entryIndex;
1✔
277
    do {
278
      if (isBitClear(stateArr_, entryIndex)) {
1✔
279
        System.arraycopy(key, 0, keysArr_, entryIndex * keySizeBytes_, keySizeBytes_);
1✔
280
        setBit(stateArr_, entryIndex);
1✔
281
        numActiveKeys_++;
1✔
282
        return entryIndex;
1✔
283
      }
284
      entryIndex = (entryIndex + getStride(hash[1], tableEntries_)) % tableEntries_;
1✔
285
    } while (entryIndex != loopIndex);
1✔
286
    throw new SketchesArgumentException("Key not found and no empty slots!");
×
287
  }
288

289
  private void clearCouponArea(final int entryIndex) {
290
    final int couponAreaIndex = entryIndex * maxCouponsPerKey_;
1✔
291
    for (int i = 0; i < maxCouponsPerKey_; i++) {
1✔
292
      couponsArr_[couponAreaIndex + i] = 0;
1✔
293
    }
294
  }
1✔
295

296
  private static final double updateEntrySizeBytes(final int tableEntries, final int keySizeBytes,
297
      final int maxCouponsPerKey) {
298
    final double byteFraction = Math.ceil(tableEntries / 8.0) / tableEntries;
1✔
299
    return keySizeBytes + ((double) maxCouponsPerKey * Short.BYTES) + byteFraction;
1✔
300
  }
301

302
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc