blob: 57112fedd8f2f422608655a98ccd24df0516f512 [file]
/*
* 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 org.apache.datasketches.theta;
import static java.lang.foreign.ValueLayout.JAVA_LONG_UNALIGNED;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.datasketches.common.ByteArrayUtil.putLongLE;
import static org.apache.datasketches.hash.MurmurHash3.hash;
import static org.apache.datasketches.theta.PreambleUtil.SINGLEITEM_FLAG_MASK;
import static org.apache.datasketches.theta.PreambleUtil.checkSegPreambleCap;
import static org.apache.datasketches.theta.PreambleUtil.extractFamilyID;
import static org.apache.datasketches.theta.PreambleUtil.extractFlags;
import static org.apache.datasketches.theta.PreambleUtil.extractSeedHash;
import static org.apache.datasketches.theta.PreambleUtil.extractSerVer;
import java.lang.foreign.MemorySegment;
import org.apache.datasketches.common.Family;
import org.apache.datasketches.common.SketchesArgumentException;
import org.apache.datasketches.common.Util;
/**
* A CompactThetaSketch that holds only one item hash.
*
* @author Lee Rhodes
*/
final class SingleItemSketch extends CompactThetaSketch {
private static final long DEFAULT_SEED_HASH = Util.computeSeedHash(Util.DEFAULT_UPDATE_SEED) & 0xFFFFL;
// For backward compatibility, a candidate pre0_ long must have:
// Flags (byte 5): Ordered, Compact, NOT Empty, Read Only, NOT BigEndian = 11010 = 0x1A. (without SI flag)
// Flags mask will be 0x1F.
// SingleItem flag may not be set due to a historical bug, so we can't depend on it for now.
// However, if the above flags are correct, preLongs == 1, SerVer >= 3, FamilyID == 3,
// and the hash seed matches, it is virtually guaranteed that we have a SingleItemThetaSketch.
private static final long PRE0_LO6_SI = 0X00_00_3A_00_00_03_03_01L; //low 6 bytes, with SI flag
private long pre0_ = 0;
private long hash_ = 0;
//Internal Constructor. All checking & hashing has been done, assumes default seed
private SingleItemSketch(final long hash) {
pre0_ = (DEFAULT_SEED_HASH << 48) | PRE0_LO6_SI;
hash_ = hash;
}
//All checking & hashing has been done, given the relevant seed
SingleItemSketch(final long hash, final long seed) {
final long seedHash = Util.computeSeedHash(seed) & 0xFFFFL;
pre0_ = (seedHash << 48) | PRE0_LO6_SI;
hash_ = hash;
}
//All checking & hashing has been done, given the relevant seedHash
SingleItemSketch(final long hash, final short seedHash) {
final long seedH = seedHash & 0xFFFFL;
pre0_ = (seedH << 48) | PRE0_LO6_SI;
hash_ = hash;
}
/**
* Creates a SingleItemSketch on the heap given a SingleItemThetaSketch MemorySegment image and a seedHash.
* Checks the seed hash of the given MemorySegment against the given seedHash.
* @param srcSeg the MemorySegment to be heapified.
* @param expectedSeedHash the given seedHash to be checked against the srcSeg seedHash
* @return a SingleItemThetaSketch
*/ //does not override ThetaSketch
static SingleItemSketch heapify(final MemorySegment srcSeg, final short expectedSeedHash) {
Util.checkSeedHashes((short) extractSeedHash(srcSeg), expectedSeedHash);
final boolean singleItem = checkForSingleItem(srcSeg);
if (singleItem) { return new SingleItemSketch(srcSeg.get(JAVA_LONG_UNALIGNED, 8), expectedSeedHash); }
throw new SketchesArgumentException("Input MemorySegment is not a SingleItemSketch.");
}
@Override
public CompactThetaSketch compact(final boolean dstOrdered, final MemorySegment dstSeg) {
if (dstSeg == null) { return this; }
else {
dstSeg.set(JAVA_LONG_UNALIGNED, 0, pre0_);
dstSeg.set(JAVA_LONG_UNALIGNED, 8, hash_);
return new DirectCompactSketch(dstSeg);
}
}
//Create methods using the default seed
/**
* Create this sketch with a long.
*
* @param datum The given long datum.
* @return a SingleItemThetaSketch
*/
static SingleItemSketch create(final long datum) {
final long[] data = { datum };
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
/**
* Create this sketch with the given double (or float) datum.
* The double will be converted to a long using Double.doubleToLongBits(datum),
* which normalizes all NaN values to a single NaN representation.
* Plus and minus zero will be normalized to plus zero.
* The special floating-point values NaN and +/- Infinity are treated as distinct.
*
* @param datum The given double datum.
* @return a SingleItemThetaSketch
*/
static SingleItemSketch create(final double datum) {
final double d = (datum == 0.0) ? 0.0 : datum; // canonicalize -0.0, 0.0
final long[] data = { Double.doubleToLongBits(d) };// canonicalize all NaN forms
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
/**
* Create this sketch with the given String.
* The string is converted to a byte array using UTF8 encoding.
* If the string is null or empty no create attempt is made and the method returns null.
*
* <p>Note: this will not produce the same hash values as the {@link #create(char[])}
* method and will generally be a little slower depending on the complexity of the UTF8 encoding.
* </p>
*
* @param datum The given String.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final String datum) {
if ((datum == null) || datum.isEmpty()) { return null; }
final byte[] data = datum.getBytes(UTF_8);
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
/**
* Create this sketch with the given byte array.
* If the byte array is null or empty no create attempt is made and the method returns null.
*
* @param data The given byte array.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final byte[] data) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
/**
* Create this sketch with the given char array.
* If the char array is null or empty no create attempt is made and the method returns null.
*
* <p>Note: this will not produce the same output hash values as the {@link #create(String)}
* method but will be a little faster as it avoids the complexity of the UTF8 encoding.</p>
*
* @param data The given char array.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final char[] data) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
/**
* Create this sketch with the given integer array.
* If the integer array is null or empty no create attempt is made and the method returns null.
*
* @param data The given int array.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final int[] data) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
/**
* Create this sketch with the given long array.
* If the long array is null or empty no create attempt is made and the method returns null.
*
* @param data The given long array.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final long[] data) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, Util.DEFAULT_UPDATE_SEED)[0] >>> 1);
}
//Updates with a user specified seed
/**
* Create this sketch with a long and a seed.
*
* @param datum The given long datum.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch
*/
static SingleItemSketch create(final long datum, final long seed) {
final long[] data = { datum };
return new SingleItemSketch(hash(data, seed)[0] >>> 1);
}
/**
* Create this sketch with the given double (or float) datum and a seed.
* The double will be converted to a long using Double.doubleToLongBits(datum),
* which normalizes all NaN values to a single NaN representation.
* Plus and minus zero will be normalized to plus zero.
* The special floating-point values NaN and +/- Infinity are treated as distinct.
*
* @param datum The given double datum.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch
*/
static SingleItemSketch create(final double datum, final long seed) {
final double d = (datum == 0.0) ? 0.0 : datum; // canonicalize -0.0, 0.0
final long[] data = { Double.doubleToLongBits(d) };// canonicalize all NaN forms
return new SingleItemSketch(hash(data, seed)[0] >>> 1, seed);
}
/**
* Create this sketch with the given String and a seed.
* The string is converted to a byte array using UTF8 encoding.
* If the string is null or empty no create attempt is made and the method returns null.
*
* <p>Note: this will not produce the same output hash values as the {@link #create(char[])}
* method and will generally be a little slower depending on the complexity of the UTF8 encoding.
* </p>
*
* @param datum The given String.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final String datum, final long seed) {
if ((datum == null) || datum.isEmpty()) { return null; }
final byte[] data = datum.getBytes(UTF_8);
return new SingleItemSketch(hash(data, seed)[0] >>> 1, seed);
}
/**
* Create this sketch with the given byte array and a seed.
* If the byte array is null or empty no create attempt is made and the method returns null.
*
* @param data The given byte array.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final byte[] data, final long seed) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, seed)[0] >>> 1, seed);
}
/**
* Create this sketch with the given char array and a seed.
* If the char array is null or empty no create attempt is made and the method returns null.
*
* <p>Note: this will not produce the same output hash values as the {@link #create(String)}
* method but will be a little faster as it avoids the complexity of the UTF8 encoding.</p>
*
* @param data The given char array.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final char[] data, final long seed) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, seed)[0] >>> 1, seed);
}
/**
* Create this sketch with the given integer array and a seed.
* If the integer array is null or empty no create attempt is made and the method returns null.
*
* @param data The given int array.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final int[] data, final long seed) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, seed)[0] >>> 1, seed);
}
/**
* Create this sketch with the given long array (as an item) and a seed.
* If the long array is null or empty no create attempt is made and the method returns null.
*
* @param data The given long array.
* @param seed used to hash the given value.
* @return a SingleItemThetaSketch or null
*/
static SingleItemSketch create(final long[] data, final long seed) {
if ((data == null) || (data.length == 0)) { return null; }
return new SingleItemSketch(hash(data, seed)[0] >>> 1, seed);
}
//ThetaSketch
@Override //much faster
public int getCountLessThanThetaLong(final long thetaLong) {
return (hash_ < thetaLong) ? 1 : 0;
}
@Override
public int getCurrentBytes() {
return 16;
}
@Override
public double getEstimate() {
return 1.0;
}
@Override
public HashIterator iterator() {
return new HeapCompactHashIterator(new long[] { hash_ });
}
@Override
public double getLowerBound(final int numStdDev) {
return 1.0;
}
@Override
public int getRetainedEntries(final boolean valid) { //valid is only relevant for the AlphaSketch
return 1;
}
@Override
public long getThetaLong() {
return Long.MAX_VALUE;
}
@Override
public double getUpperBound(final int numStdDev) {
return 1.0;
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean isOrdered() {
return true;
}
@Override
public byte[] toByteArray() {
final byte[] out = new byte[16];
putLongLE(out, 0, pre0_);
putLongLE(out, 8, hash_);
return out;
}
//restricted methods
@Override
long[] getCache() {
return new long[] { hash_ };
}
@Override
int getCompactPreambleLongs() {
return 1;
}
@Override
int getCurrentPreambleLongs() {
return 1;
}
@Override
short getSeedHash() {
return (short) (pre0_ >>> 48);
}
static boolean checkForSingleItem(final MemorySegment seg) {
final int preLongs = checkSegPreambleCap(seg);
return checkForSingleItem(preLongs, extractSerVer(seg), extractFamilyID(seg), extractFlags(seg) );
}
static boolean checkForSingleItem(
final int preLongs,
final int serVer,
final int famId,
final int flags) {
// Flags byte: SI=X, Ordered=T, Compact=T, Empty=F, ReadOnly=T, Reserved(BE)=F = X11010 = 0x1A.
// Flags mask will be 0x1F.
// SingleItem flag may not be set due to a historical bug, so we can't depend on it for now.
// However, if the above flags are correct, preLongs == 1, SerVer >= 3, FamilyID == 3,
// and the hash seed matches (not done here), it is virtually guaranteed that we have a
// SingleItemThetaSketch.
final boolean preLongsOK = preLongs == 1;
final boolean serVerOK = serVer >= 3;
final boolean famIdOK = famId == Family.COMPACT.getID();
final boolean flagsOK = (flags & 0x1F) == 0x1A; //no SI, yet
final boolean singleFlagOK = (flags & SINGLEITEM_FLAG_MASK) > 0;
return (preLongsOK && serVerOK && famIdOK && flagsOK) || singleFlagOK;
}
}