hll/hll_4update.go (159 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 hll
import (
"fmt"
)
// internalHll4Update is the internal update method for Hll4Array.
func internalHll4Update(h *hll4ArrayImpl, slotNo int, newValue int) error {
var (
actualOldValue int
shiftedNewValue int //value - curMin
curMin = h.curMin
rawStoredOldNibble = h.getNibble(slotNo) // could be 0
lb0nOldValue = rawStoredOldNibble + h.curMin // provable lower bound, could be 0
err error
)
if newValue <= lb0nOldValue {
return nil
}
// Based on whether we have an AUX_TOKEN and whether the shiftedNewValue is greater than
// AUX_TOKEN, we have four cases for how to actually modify the data structure:
// 1. (shiftedNewValue >= AUX_TOKEN) && (rawStoredOldNibble = AUX_TOKEN) //881:
// The byte array already contains aux token
// This is the case where old and new values are both exceptions.
// Therefore, the 4-bit array already is AUX_TOKEN. Only need to update auxMap
// 2. (shiftedNewValue < AUX_TOKEN) && (rawStoredOldNibble = AUX_TOKEN) //885
// This is the (hypothetical) case where old value is an exception and the new one is not,
// which is impossible given that curMin has not changed here and the newValue > oldValue.
// 3. (shiftedNewValue >= AUX_TOKEN) && (rawStoredOldNibble < AUX_TOKEN) //892
// This is the case where the old value is not an exception and the new value is.
// Therefore, the AUX_TOKEN must be stored in the 4-bit array and the new value
// added to the exception table.
// 4. (shiftedNewValue < AUX_TOKEN) && (rawStoredOldNibble < AUX_TOKEN) //897
// This is the case where neither the old value nor the new value is an exception.
// Therefore, we just overwrite the 4-bit array with the shifted new value.
if rawStoredOldNibble == auxToken { //846 Note: This is rare and really hard to test!
if h.auxHashMap == nil {
return fmt.Errorf("auxHashMap must already exist")
}
actualOldValue, err = h.auxHashMap.mustFindValueFor(slotNo)
if newValue <= actualOldValue || err != nil {
return err
}
// We know that the array will be changed, but we haven't actually updated yet.
err := h.hipAndKxQIncrementalUpdate(actualOldValue, newValue)
if err != nil {
return err
}
shiftedNewValue = newValue - curMin
if shiftedNewValue < 0 {
return fmt.Errorf("shifedNewValue < 0")
}
if shiftedNewValue >= auxToken { //CASE 1:
err := h.auxHashMap.mustReplace(slotNo, newValue)
if err != nil {
return err
}
} //else //CASE 2: impossible
} else { //rawStoredOldNibble < AUX_TOKEN
actualOldValue = lb0nOldValue
// We know that the array will be changed, but we haven't actually updated yet.
err := h.hipAndKxQIncrementalUpdate(actualOldValue, newValue)
if err != nil {
return err
}
shiftedNewValue = newValue - curMin
if shiftedNewValue < 0 {
return fmt.Errorf("shifedNewValue < 0")
}
if shiftedNewValue >= auxToken { //CASE 3: //892
h.putNibble(slotNo, auxToken)
if h.auxHashMap == nil {
h.auxHashMap = h.getNewAuxHashMap()
}
err := h.auxHashMap.mustAdd(slotNo, newValue)
if err != nil {
return err
}
} else { // CASE 4: //897
h.putNibble(slotNo, byte(shiftedNewValue))
}
}
// We just changed the HLL array, so it might be time to change curMin.
if actualOldValue == curMin {
if h.numAtCurMin < 1 {
return fmt.Errorf("h.numAtCurMin < 1")
}
h.numAtCurMin--
for h.numAtCurMin == 0 {
// Increases curMin by 1, and builds a new aux table,
// shifts values in 4-bit table, and recounts curMin.
err := shiftToBiggerCurMin(h)
if err != nil {
return err
}
}
}
return nil
}
// This scheme only works with two double registers (2 kxq values).
// HipAccum, kxq0 and kxq1 remain untouched.
// This changes curMin, numAtCurMin, hllByteArr and auxMap.
//
// Entering this routine assumes that all slots have valid nibbles > 0 and <= 15.
// An auxHashMap must exist if any values in the current hllByteArray are already 15.
func shiftToBiggerCurMin(h *hll4ArrayImpl) error {
var (
oldCurMin = h.curMin
newCurMin = oldCurMin + 1
lgConfigK = h.lgConfigK
configK = 1 << lgConfigK
configKmask = configK - 1
numAtNewCurMin = 0
numAuxTokens = 0
)
// Walk through the slots of 4-bit array decrementing stored values by one unless it
// equals AUX_TOKEN, where it is left alone but counted to be checked later.
// If oldStoredValue is 0 it is an error.
// If the decremented value is 0, we increment numAtNewCurMin.
// Because getNibble is masked to 4 bits oldStoredValue can never be > 15 or negative
for i := 0; i < configK; i++ { //724
oldStoredNibble := uint64(h.getNibble(i))
if oldStoredNibble == 0 {
return fmt.Errorf("array slots cannot be 0 at this point")
}
if oldStoredNibble < auxToken {
oldStoredNibble--
h.putNibble(i, byte(oldStoredNibble))
if oldStoredNibble == 0 {
numAtNewCurMin++
}
} else { //oldStoredNibble == AUX_TOKEN
numAuxTokens++
if h.auxHashMap == nil {
return fmt.Errorf("auxHashMap cannot be nil at this point")
}
}
}
// If old auxHashMap exists, walk through it updating some slots and build a new auxHashMap
// if needed.
var (
newAuxMap *auxHashMap
oldAuxMap = h.auxHashMap
)
if oldAuxMap != nil {
var (
slotNum int
oldActualVal int
newShiftedVal int
err error
)
itr := oldAuxMap.iterator()
for itr.nextValid() {
slotNum = itr.getKey() & configKmask
oldActualVal, err = itr.getValue()
if err != nil {
return err
}
newShiftedVal = oldActualVal - newCurMin
if newShiftedVal < 0 {
return fmt.Errorf("newShiftedVal < 0")
}
if h.getNibble(slotNum) != auxToken {
return fmt.Errorf(fmt.Sprintf("Array slot != AUX_TOKEN %d", h.getNibble(slotNum)))
}
if newShiftedVal < auxToken {
if newShiftedVal != 14 {
return fmt.Errorf("newShiftedVal != 14")
}
// The former exception value isn't one anymore, so it stays out of new auxHashMap.
// Correct the AUX_TOKEN value in the HLL array to the newShiftedVal (14).
h.putNibble(slotNum, byte(newShiftedVal))
numAuxTokens--
} else { // newShiftedVal >= AUX_TOKEN
// the former exception remains an exception, so must be added to the newAuxMap
if newAuxMap == nil {
newAuxMap = h.getNewAuxHashMap()
}
err := newAuxMap.mustAdd(slotNum, oldActualVal)
if err != nil {
return err
}
}
}
} else {
if numAuxTokens != 0 {
return fmt.Errorf("numAuxTokens != 0")
}
}
if newAuxMap != nil {
if newAuxMap.getAuxCount() != numAuxTokens {
return fmt.Errorf("newAuxMap.getAuxCount() != numAuxTokens")
}
}
h.auxHashMap = newAuxMap
h.curMin = newCurMin
h.numAtCurMin = numAtNewCurMin
return nil
}