All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.helger.collection.map.IntDoubleMap Maven / Gradle / Ivy

/*
 * Copyright (C) 2014-2024 Philip Helger (www.helger.com)
 * philip[at]helger[dot]com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.helger.collection.map;

import java.util.Arrays;

import javax.annotation.CheckForSigned;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.NotThreadSafe;

import com.helger.commons.ValueEnforcer;
import com.helger.commons.annotation.ReturnsMutableCopy;
import com.helger.commons.lang.IHasSize;

/**
 * Special int-float-primitive map. Based on:
 * https://github.com/mikvor/hashmapTest
 *
 * @author Mikhail Vorontsov
 * @author Philip Helger
 */
@NotThreadSafe
public class IntDoubleMap implements IHasSize
{
  /**
   * Represents a function that accepts an key-type argument and produces a
   * value-typed result.
   * 

* This is a functional interface whose functional method is * {@link #apply(int)}. */ @FunctionalInterface public interface IKeyToValueFunction { /** * Applies this function to the given argument. * * @param value * the function argument * @return the function result */ double apply (int value); } private static final int FREE_KEY = 0; public static final double NO_VALUE = Double.NEGATIVE_INFINITY; /** Keys */ private int [] m_aKeys; /** Values */ private double [] m_aValues; /** Do we have 'free' key in the map? */ private boolean m_bHasFreeKey; /** Value of 'free' key */ private double m_dFreeValue = NO_VALUE; /** Fill factor, must be between (0 and 1) */ private final float m_fFillFactor; /** We will resize a map once it reaches this size */ private int m_nThreshold; /** Current map size */ private int m_nSize; /** Mask to calculate the original position */ private int m_nMask; public IntDoubleMap () { this (16); } public IntDoubleMap (final int nSize) { this (nSize, 0.75f); } public IntDoubleMap (final int nSize, final float fFillFactor) { ValueEnforcer.isBetweenInclusive (fFillFactor, "FillFactor", 0f, 1f); ValueEnforcer.isGT0 (nSize, "Size"); final int nCapacity = MapHelper.arraySize (nSize, fFillFactor); m_nMask = nCapacity - 1; m_fFillFactor = fFillFactor; m_aKeys = new int [nCapacity]; m_aValues = _createValueArray (nCapacity); m_nThreshold = (int) (nCapacity * fFillFactor); } @Nonnull @ReturnsMutableCopy private static double [] _createValueArray (@Nonnegative final int nSize) { final double [] ret = new double [nSize]; Arrays.fill (ret, NO_VALUE); return ret; } public double get (final int key) { return get (key, NO_VALUE); } public double get (final int key, final double fDefault) { if (key == FREE_KEY) return m_bHasFreeKey ? m_dFreeValue : fDefault; final int idx = _getReadIndex (key); return idx != -1 ? m_aValues[idx] : fDefault; } public double computeIfAbsent (final int key, @Nonnull final IKeyToValueFunction aProvider) { double ret = get (key); if (ret == NO_VALUE) { ret = aProvider.apply (key); if (ret != NO_VALUE) put (key, ret); } return ret; } public double put (final int key, final double value) { if (key == FREE_KEY) { final double ret = m_dFreeValue; if (!m_bHasFreeKey) { ++m_nSize; m_bHasFreeKey = true; } m_dFreeValue = value; return ret; } int idx = _getPutIndex (key); if (idx < 0) { // no insertion point? Should not happen... _rehash (m_aKeys.length * 2); idx = _getPutIndex (key); } final double prev = m_aValues[idx]; if (m_aKeys[idx] != key) { m_aKeys[idx] = key; m_aValues[idx] = value; ++m_nSize; if (m_nSize >= m_nThreshold) _rehash (m_aKeys.length * 2); } else { // it means used cell with our key if (m_aKeys[idx] != key) throw new IllegalStateException (); m_aValues[idx] = value; } return prev; } public double remove (final int key) { if (key == FREE_KEY) { if (!m_bHasFreeKey) return NO_VALUE; m_bHasFreeKey = false; final double ret = m_dFreeValue; m_dFreeValue = NO_VALUE; --m_nSize; return ret; } final int idx = _getReadIndex (key); if (idx == -1) return NO_VALUE; final double res = m_aValues[idx]; m_aValues[idx] = NO_VALUE; _shiftKeys (idx); --m_nSize; return res; } @Nonnegative public int size () { return m_nSize; } public boolean isEmpty () { return m_nSize == 0; } private void _rehash (final int nNewCapacity) { m_nThreshold = (int) (nNewCapacity * m_fFillFactor); m_nMask = nNewCapacity - 1; final int nOldCapacity = m_aKeys.length; final int [] aOldKeys = m_aKeys; final double [] aOldValues = m_aValues; m_aKeys = new int [nNewCapacity]; m_aValues = _createValueArray (nNewCapacity); m_nSize = m_bHasFreeKey ? 1 : 0; int i = nOldCapacity; while (i > 0) { i--; if (aOldKeys[i] != FREE_KEY) put (aOldKeys[i], aOldValues[i]); } } private int _shiftKeys (final int nPos) { // Shift entries with the same hash. int pos = nPos; final int [] keys = m_aKeys; while (true) { final int last = pos; pos = _getNextIndex (pos); int k; while (true) { k = keys[pos]; if (k == FREE_KEY) { keys[last] = FREE_KEY; m_aValues[last] = NO_VALUE; return last; } // calculate the starting slot for the current key final int slot = MapHelper.phiMix (k) & m_nMask; if (last <= pos ? last >= slot || slot > pos : last >= slot && slot > pos) break; pos = _getNextIndex (pos); } keys[last] = k; m_aValues[last] = m_aValues[pos]; } } /** * Find key position in the map. * * @param key * Key to look for * @return Key position or -1 if not found */ @CheckForSigned private int _getReadIndex (final int key) { int idx = MapHelper.phiMix (key) & m_nMask; if (m_aKeys[idx] == key) { // we check FREE prior to this call return idx; } if (m_aKeys[idx] == FREE_KEY) { // end of chain already return -1; } final int startIdx = idx; while ((idx = _getNextIndex (idx)) != startIdx) { if (m_aKeys[idx] == FREE_KEY) return -1; if (m_aKeys[idx] == key) return idx; } return -1; } /** * Find an index of a cell which should be updated by 'put' operation. It can * be: 1) a cell with a given key 2) first free cell in the chain * * @param key * Key to look for * @return Index of a cell to be updated by a 'put' operation */ @CheckForSigned private int _getPutIndex (final int key) { final int readIdx = _getReadIndex (key); if (readIdx >= 0) return readIdx; // key not found, find insertion point final int startIdx = MapHelper.phiMix (key) & m_nMask; if (m_aKeys[startIdx] == FREE_KEY) return startIdx; int idx = startIdx; while (m_aKeys[idx] != FREE_KEY) { idx = _getNextIndex (idx); if (idx == startIdx) return -1; } return idx; } private int _getNextIndex (final int currentIndex) { return (currentIndex + 1) & m_nMask; } @FunctionalInterface public interface IConsumer { void accept (int nKey, double dValue); } public void forEach (@Nonnull final IConsumer aConsumer) { if (m_bHasFreeKey) aConsumer.accept (FREE_KEY, m_dFreeValue); final int nLen = m_aKeys.length; for (int i = 0; i < nLen; ++i) { final int nKey = m_aKeys[i]; if (nKey != FREE_KEY) { final double dValue = m_aValues[i]; if (dValue != NO_VALUE) aConsumer.accept (nKey, dValue); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy