| /* |
| * 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 hivemall.tools.list; |
| |
| import hivemall.utils.collections.BoundedPriorityQueue; |
| import hivemall.utils.hadoop.HiveUtils; |
| import hivemall.utils.lang.CommandLineUtils; |
| import hivemall.utils.lang.NaturalComparator; |
| import hivemall.utils.lang.Preconditions; |
| import hivemall.utils.struct.Pair; |
| |
| import java.io.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.PriorityQueue; |
| |
| import javax.annotation.CheckForNull; |
| import javax.annotation.Nonnegative; |
| import javax.annotation.Nonnull; |
| import javax.annotation.Nullable; |
| |
| import org.apache.commons.cli.CommandLine; |
| import org.apache.commons.cli.HelpFormatter; |
| import org.apache.commons.cli.Options; |
| import org.apache.hadoop.hive.ql.exec.Description; |
| import org.apache.hadoop.hive.ql.exec.UDFArgumentException; |
| import org.apache.hadoop.hive.ql.exec.UDFArgumentTypeException; |
| import org.apache.hadoop.hive.ql.metadata.HiveException; |
| import org.apache.hadoop.hive.ql.parse.SemanticException; |
| import org.apache.hadoop.hive.ql.udf.generic.AbstractGenericUDAFResolver; |
| import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFEvaluator; |
| import org.apache.hadoop.hive.ql.udf.generic.GenericUDAFParameterInfo; |
| import org.apache.hadoop.hive.serde2.objectinspector.ListObjectInspector; |
| import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspector; |
| import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorFactory; |
| import org.apache.hadoop.hive.serde2.objectinspector.ObjectInspectorUtils; |
| import org.apache.hadoop.hive.serde2.objectinspector.PrimitiveObjectInspector; |
| import org.apache.hadoop.hive.serde2.objectinspector.StandardListObjectInspector; |
| import org.apache.hadoop.hive.serde2.objectinspector.StructField; |
| import org.apache.hadoop.hive.serde2.objectinspector.StructObjectInspector; |
| import org.apache.hadoop.hive.serde2.objectinspector.primitive.PrimitiveObjectInspectorFactory; |
| import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo; |
| import org.apache.hadoop.io.BooleanWritable; |
| import org.apache.hadoop.io.IntWritable; |
| |
| /** |
| * Return list of values sorted by value itself or specific key. |
| */ |
| //@formatter:off |
| @Description(name = "to_ordered_list", |
| value = "_FUNC_(PRIMITIVE value [, PRIMITIVE key, const string options])" |
| + " - Return list of values sorted by value itself or specific key", |
| extended = "WITH data as (\n" + |
| " SELECT 5 as key, 'apple' as value\n" + |
| " UNION ALL\n" + |
| " SELECT 3 as key, 'banana' as value\n" + |
| " UNION ALL\n" + |
| " SELECT 4 as key, 'candy' as value\n" + |
| " UNION ALL\n" + |
| " SELECT 1 as key, 'donut' as value\n" + |
| " UNION ALL\n" + |
| " SELECT 2 as key, 'egg' as value \n" + |
| " UNION ALL\n" + |
| " SELECT 4 as key, 'candy' as value -- both key and value duplicates\n" + |
| ")\n" + |
| "SELECT -- expected output\n" + |
| " to_ordered_list(value, key, '-reverse'), -- [apple, candy, candy, (banana, egg | egg, banana), donut] (reverse order)\n" + |
| " to_ordered_list(value, key, '-k 2'), -- [apple, candy] (top-k)\n" + |
| " to_ordered_list(value, key, '-k 100'), -- [apple, candy, candy, (banana, egg | egg, banana), dunut]\n" + |
| " to_ordered_list(value, key, '-k 2 -reverse'), -- [donut, (banana | egg)] (reverse top-k = tail-k)\n" + |
| " to_ordered_list(value, key), -- [donut, (banana, egg | egg, banana), candy, candy, apple] (natural order)\n" + |
| " to_ordered_list(value, key, '-k -2'), -- [donut, (banana | egg)] (tail-k)\n" + |
| " to_ordered_list(value, key, '-k -100'), -- [donut, (banana, egg | egg, banana), candy, candy, apple]\n" + |
| " to_ordered_list(value, key, '-k -2 -reverse'), -- [apple, candy] (reverse tail-k = top-k)\n" + |
| " to_ordered_list(value, '-k 2'), -- [egg, donut] (alphabetically)\n" + |
| " to_ordered_list(key, '-k -2 -reverse'), -- [5, 4] (top-2 keys)\n" + |
| " to_ordered_list(key), -- [1, 2, 3, 4, 4, 5] (natural ordered keys)\n" + |
| " to_ordered_list(value, key, '-k 2 -kv_map'), -- {5:\"apple\",4:\"candy\"}\n" + |
| " to_ordered_list(value, key, '-k 2 -vk_map'), -- {\"apple\":5,\"candy\":4}\n" + |
| " to_ordered_list(value, key, '-k -2 -kv_map'), -- {1:\"donut\",2:\"egg\"}\n" + |
| " to_ordered_list(value, key, '-k -2 -vk_map'), -- {\"donut\":1,\"egg\":2}\n" + |
| " to_ordered_list(value, key, '-k 4 -dedup -vk_map'), -- {\"apple\":5,\"candy\":4,\"banana\":3,\"egg\":2}\n" + |
| " to_ordered_list(value, key, '-k 4 -vk_map'), -- {\"apple\":5,\"candy\":4,\"banana\":3}\n" + |
| " to_ordered_list(value, key, '-k 4 -dedup'), -- [\"apple\",\"candy\",\"banana\",\"egg\"]\n" + |
| " to_ordered_list(value, key, '-k 4') -- [\"apple\",\"candy\",\"candy\",\"banana\"]\n" + |
| "FROM\n" + |
| " data") |
| //@formatter:on |
| public final class UDAFToOrderedList extends AbstractGenericUDAFResolver { |
| |
| @Override |
| public GenericUDAFEvaluator getEvaluator(GenericUDAFParameterInfo info) |
| throws SemanticException { |
| @SuppressWarnings("deprecation") |
| TypeInfo[] typeInfo = info.getParameters(); |
| ObjectInspector[] argOIs = info.getParameterObjectInspectors(); |
| if ((typeInfo.length == 1) |
| || (typeInfo.length == 2 && HiveUtils.isConstString(argOIs[1]))) { |
| // sort values by value itself w/o key |
| if (typeInfo[0].getCategory() != ObjectInspector.Category.PRIMITIVE) { |
| throw new UDFArgumentTypeException(0, |
| "Only primitive type arguments are accepted for value but " |
| + typeInfo[0].getTypeName() + " was passed as the first parameter."); |
| } |
| } else if ((typeInfo.length == 2) |
| || (typeInfo.length == 3 && HiveUtils.isConstString(argOIs[2]))) { |
| // sort values by key |
| if (typeInfo[1].getCategory() != ObjectInspector.Category.PRIMITIVE) { |
| throw new UDFArgumentTypeException(1, |
| "Only primitive type arguments are accepted for key but " |
| + typeInfo[1].getTypeName() + " was passed as the second parameter."); |
| } |
| } else { |
| throw new UDFArgumentTypeException(typeInfo.length - 1, |
| "Number of arguments must be in [1, 3] including constant string for options: " |
| + typeInfo.length); |
| } |
| return new UDAFToOrderedListEvaluator(); |
| } |
| |
| public static class UDAFToOrderedListEvaluator extends GenericUDAFEvaluator { |
| |
| private ObjectInspector valueOI; |
| private PrimitiveObjectInspector keyOI; |
| |
| private ListObjectInspector valueListOI; |
| private ListObjectInspector keyListOI; |
| |
| private StructObjectInspector internalMergeOI; |
| |
| private StructField valueListField; |
| private StructField keyListField; |
| private StructField sizeField; |
| private StructField reverseOrderField; |
| private StructField outKVField, outVKField; |
| |
| @Nonnegative |
| private int size; |
| private boolean reverseOrder; |
| private boolean dedup; |
| private boolean sortByKey; |
| private boolean outKV, outVK; |
| |
| protected Options getOptions() { |
| Options opts = new Options(); |
| opts.addOption("k", true, "To top-k (positive) or tail-k (negative) ordered queue"); |
| opts.addOption("reverse", "reverse_order", false, |
| "Sort values by key in a reverse (e.g., descending) order [default: false]"); |
| opts.addOption("kv", "kv_map", false, |
| "Return Map<K, V> for the result of to_ordered_list(V, K)"); |
| opts.addOption("vk", "vk_map", false, |
| "Return Map<V, K> for the result of to_ordered_list(V, K)"); |
| opts.addOption("dedup", false, "Eliminate/ignore duplications"); |
| return opts; |
| } |
| |
| @Nonnull |
| protected final CommandLine parseOptions(String optionValue) throws UDFArgumentException { |
| String[] args = optionValue.split("\\s+"); |
| Options opts = getOptions(); |
| opts.addOption("help", false, "Show function help"); |
| CommandLine cl = CommandLineUtils.parseOptions(args, opts); |
| |
| if (cl.hasOption("help")) { |
| Description funcDesc = getClass().getAnnotation(Description.class); |
| final String cmdLineSyntax; |
| if (funcDesc == null) { |
| cmdLineSyntax = getClass().getSimpleName(); |
| } else { |
| String funcName = funcDesc.name(); |
| cmdLineSyntax = funcName == null ? getClass().getSimpleName() |
| : funcDesc.value().replace("_FUNC_", funcDesc.name()); |
| } |
| StringWriter sw = new StringWriter(); |
| sw.write('\n'); |
| PrintWriter pw = new PrintWriter(sw); |
| HelpFormatter formatter = new HelpFormatter(); |
| formatter.printHelp(pw, HelpFormatter.DEFAULT_WIDTH, cmdLineSyntax, null, opts, |
| HelpFormatter.DEFAULT_LEFT_PAD, HelpFormatter.DEFAULT_DESC_PAD, null, true); |
| pw.flush(); |
| String helpMsg = sw.toString(); |
| throw new UDFArgumentException(helpMsg); |
| } |
| |
| return cl; |
| } |
| |
| protected CommandLine processOptions(ObjectInspector[] argOIs) throws UDFArgumentException { |
| CommandLine cl = null; |
| |
| int optionIndex = 1; |
| if (sortByKey) { |
| optionIndex = 2; |
| } |
| |
| int k = 0; |
| boolean reverseOrder = false, dedup = false; |
| boolean outKV = false, outVK = false; |
| if (argOIs.length >= optionIndex + 1) { |
| String rawArgs = HiveUtils.getConstString(argOIs[optionIndex]); |
| cl = parseOptions(rawArgs); |
| |
| reverseOrder = cl.hasOption("reverse_order"); |
| dedup = cl.hasOption("dedup"); |
| |
| if (cl.hasOption("k")) { |
| k = Integer.parseInt(cl.getOptionValue("k")); |
| if (k == 0) { |
| throw new UDFArgumentException("`k` must be non-zero value: " + k); |
| } |
| } |
| |
| outKV = cl.hasOption("kv_map"); |
| outVK = cl.hasOption("vk_map"); |
| if (outKV && outVK) { |
| throw new UDFArgumentException( |
| "Both `-kv_map` and `-vk_map` option are unexpectedly specified"); |
| } else if (outKV && sortByKey == false) { |
| throw new UDFArgumentException( |
| "`-kv_map` option can only be applied when both key and value are provided"); |
| } else if (outVK && sortByKey == false) { |
| throw new UDFArgumentException( |
| "`-vk_map` option can only be applied when both key and value are provided"); |
| } |
| } |
| this.size = Math.abs(k); |
| this.outKV = outKV; |
| this.outVK = outVK; |
| this.dedup = dedup; |
| |
| if ((k > 0 && reverseOrder) || (k < 0 && reverseOrder == false) |
| || (k == 0 && reverseOrder == false)) { |
| // top-k on reverse order = tail-k on natural order (so, top-k on descending) |
| this.reverseOrder = true; |
| } else { // (k > 0 && reverseOrder == false) || (k < 0 && reverseOrder) || (k == 0 && reverseOrder) |
| // top-k on natural order = tail-k on reverse order (so, top-k on ascending) |
| this.reverseOrder = false; |
| } |
| |
| return cl; |
| } |
| |
| @Override |
| public ObjectInspector init(Mode mode, ObjectInspector[] argOIs) throws HiveException { |
| super.init(mode, argOIs); |
| |
| // initialize input |
| if (mode == Mode.PARTIAL1 || mode == Mode.COMPLETE) {// from original data |
| // this flag will be used in `processOptions` and `iterate` (= when Mode.PARTIAL1 or Mode.COMPLETE) |
| this.sortByKey = (argOIs.length == 2 && !HiveUtils.isConstString(argOIs[1])) |
| || (argOIs.length == 3 && HiveUtils.isConstString(argOIs[2])); |
| |
| if (sortByKey) { |
| this.valueOI = argOIs[0]; |
| this.keyOI = HiveUtils.asPrimitiveObjectInspector(argOIs[1]); |
| } else { |
| // sort values by value itself |
| this.valueOI = HiveUtils.asPrimitiveObjectInspector(argOIs[0]); |
| this.keyOI = HiveUtils.asPrimitiveObjectInspector(argOIs[0]); |
| } |
| |
| processOptions(argOIs); |
| } else {// from partial aggregation |
| StructObjectInspector soi = (StructObjectInspector) argOIs[0]; |
| this.internalMergeOI = soi; |
| |
| // re-extract input value OI |
| this.valueListField = soi.getStructFieldRef("valueList"); |
| StandardListObjectInspector valueListOI = |
| (StandardListObjectInspector) valueListField.getFieldObjectInspector(); |
| this.valueOI = valueListOI.getListElementObjectInspector(); |
| this.valueListOI = ObjectInspectorFactory.getStandardListObjectInspector(valueOI); |
| |
| // re-extract input key OI |
| this.keyListField = soi.getStructFieldRef("keyList"); |
| StandardListObjectInspector keyListOI = |
| (StandardListObjectInspector) keyListField.getFieldObjectInspector(); |
| this.keyOI = HiveUtils.asPrimitiveObjectInspector( |
| keyListOI.getListElementObjectInspector()); |
| this.keyListOI = ObjectInspectorFactory.getStandardListObjectInspector(keyOI); |
| |
| this.sizeField = soi.getStructFieldRef("size"); |
| this.reverseOrderField = soi.getStructFieldRef("reverseOrder"); |
| |
| List<? extends StructField> fieldRefs = soi.getAllStructFieldRefs(); |
| |
| |
| this.outKVField = HiveUtils.getStructFieldRef("outKV", fieldRefs); |
| if (outKVField != null) { |
| this.outKV = true; |
| } |
| this.outVKField = HiveUtils.getStructFieldRef("outVK", fieldRefs); |
| if (outVKField != null) { |
| this.outVK = true; |
| } |
| } |
| |
| // initialize output |
| final ObjectInspector outputOI; |
| if (mode == Mode.PARTIAL1 || mode == Mode.PARTIAL2) {// terminatePartial |
| outputOI = internalMergeOI(valueOI, keyOI, outKV, outVK); |
| } else {// terminate |
| if (outKV) { |
| outputOI = ObjectInspectorFactory.getStandardMapObjectInspector( |
| ObjectInspectorUtils.getStandardObjectInspector(keyOI), |
| ObjectInspectorUtils.getStandardObjectInspector(valueOI)); |
| } else if (outVK) { |
| outputOI = ObjectInspectorFactory.getStandardMapObjectInspector( |
| ObjectInspectorUtils.getStandardObjectInspector(valueOI), |
| ObjectInspectorUtils.getStandardObjectInspector(keyOI)); |
| } else { |
| outputOI = ObjectInspectorFactory.getStandardListObjectInspector( |
| ObjectInspectorUtils.getStandardObjectInspector(valueOI)); |
| } |
| } |
| |
| return outputOI; |
| } |
| |
| @Nonnull |
| private StructObjectInspector internalMergeOI(@Nonnull ObjectInspector valueOI, |
| @Nonnull PrimitiveObjectInspector keyOI, boolean outKV, boolean outVK) { |
| List<String> fieldNames = new ArrayList<String>(); |
| List<ObjectInspector> fieldOIs = new ArrayList<ObjectInspector>(); |
| |
| fieldNames.add("valueList"); |
| fieldOIs.add(ObjectInspectorFactory.getStandardListObjectInspector( |
| ObjectInspectorUtils.getStandardObjectInspector(valueOI))); |
| fieldNames.add("keyList"); |
| fieldOIs.add(ObjectInspectorFactory.getStandardListObjectInspector( |
| ObjectInspectorUtils.getStandardObjectInspector(keyOI))); |
| fieldNames.add("size"); |
| fieldOIs.add(PrimitiveObjectInspectorFactory.writableIntObjectInspector); |
| fieldNames.add("reverseOrder"); |
| fieldOIs.add(PrimitiveObjectInspectorFactory.writableBooleanObjectInspector); |
| if (outKV) { |
| fieldNames.add("outKV"); |
| fieldOIs.add(PrimitiveObjectInspectorFactory.writableBooleanObjectInspector); |
| } else if (outVK) { |
| fieldNames.add("outVK"); |
| fieldOIs.add(PrimitiveObjectInspectorFactory.writableBooleanObjectInspector); |
| } |
| |
| return ObjectInspectorFactory.getStandardStructObjectInspector(fieldNames, fieldOIs); |
| } |
| |
| @SuppressWarnings("deprecation") |
| @Override |
| public AggregationBuffer getNewAggregationBuffer() throws HiveException { |
| QueueAggregationBuffer myagg = new QueueAggregationBuffer(); |
| reset(myagg); |
| return myagg; |
| } |
| |
| @Override |
| public void reset(@SuppressWarnings("deprecation") AggregationBuffer agg) |
| throws HiveException { |
| QueueAggregationBuffer myagg = (QueueAggregationBuffer) agg; |
| myagg.reset(size, reverseOrder, dedup, outKV, outVK); |
| } |
| |
| @Override |
| public void iterate(@SuppressWarnings("deprecation") AggregationBuffer agg, |
| Object[] parameters) throws HiveException { |
| if (parameters[0] == null) { |
| return; |
| } |
| Object value = ObjectInspectorUtils.copyToStandardObject(parameters[0], valueOI); |
| |
| final Object key; |
| if (sortByKey) { |
| if (parameters[1] == null) { |
| return; |
| } |
| key = ObjectInspectorUtils.copyToStandardObject(parameters[1], keyOI); |
| } else { |
| // set value to key |
| key = ObjectInspectorUtils.copyToStandardObject(parameters[0], valueOI); |
| } |
| |
| TupleWithKey tuple = new TupleWithKey(key, value); |
| QueueAggregationBuffer myagg = (QueueAggregationBuffer) agg; |
| |
| myagg.iterate(tuple); |
| } |
| |
| @Override |
| public Object terminatePartial(@SuppressWarnings("deprecation") AggregationBuffer agg) |
| throws HiveException { |
| QueueAggregationBuffer myagg = (QueueAggregationBuffer) agg; |
| |
| Pair<List<Object>, List<Object>> tuples = myagg.drainQueue(); |
| if (tuples == null) { |
| return null; |
| } |
| List<Object> keyList = tuples.getKey(); |
| List<Object> valueList = tuples.getValue(); |
| |
| Object[] partialResult = new Object[outKV || outVK ? 5 : 4]; |
| partialResult[0] = valueList; |
| partialResult[1] = keyList; |
| partialResult[2] = new IntWritable(myagg.size); |
| partialResult[3] = new BooleanWritable(myagg.reverseOrder); |
| if (myagg.outKV) { |
| partialResult[4] = new BooleanWritable(true); |
| } else if (myagg.outVK) { |
| partialResult[4] = new BooleanWritable(true); |
| } |
| return partialResult; |
| } |
| |
| @Override |
| public void merge(@SuppressWarnings("deprecation") AggregationBuffer agg, Object partial) |
| throws HiveException { |
| if (partial == null) { |
| return; |
| } |
| |
| Object valueListObj = internalMergeOI.getStructFieldData(partial, valueListField); |
| final List<?> valueListRaw = |
| valueListOI.getList(HiveUtils.castLazyBinaryObject(valueListObj)); |
| final List<Object> valueList = new ArrayList<Object>(); |
| for (Object v : valueListRaw) { |
| valueList.add(ObjectInspectorUtils.copyToStandardObject(v, valueOI)); |
| } |
| |
| Object keyListObj = internalMergeOI.getStructFieldData(partial, keyListField); |
| final List<?> keyListRaw = |
| keyListOI.getList(HiveUtils.castLazyBinaryObject(keyListObj)); |
| final List<Object> keyList = new ArrayList<Object>(); |
| for (Object k : keyListRaw) { |
| keyList.add(ObjectInspectorUtils.copyToStandardObject(k, keyOI)); |
| } |
| |
| Object sizeObj = internalMergeOI.getStructFieldData(partial, sizeField); |
| int size = PrimitiveObjectInspectorFactory.writableIntObjectInspector.get(sizeObj); |
| |
| Object reverseOrderObj = internalMergeOI.getStructFieldData(partial, reverseOrderField); |
| boolean reverseOrder = |
| PrimitiveObjectInspectorFactory.writableBooleanObjectInspector.get( |
| reverseOrderObj); |
| |
| QueueAggregationBuffer myagg = (QueueAggregationBuffer) agg; |
| myagg.setOptions(size, reverseOrder, dedup, outKV, outVK); |
| myagg.merge(keyList, valueList); |
| } |
| |
| @Override |
| public Object terminate(@SuppressWarnings("deprecation") AggregationBuffer agg) |
| throws HiveException { |
| QueueAggregationBuffer myagg = (QueueAggregationBuffer) agg; |
| if (myagg.outKV) { |
| return myagg.drainMapKV(); |
| } else if (myagg.outVK) { |
| return myagg.drainMapVK(); |
| } else { |
| return myagg.drainValues(); |
| } |
| } |
| |
| static class QueueAggregationBuffer extends AbstractAggregationBuffer { |
| |
| private transient AbstractQueueHandler queueHandler; |
| |
| @Nonnegative |
| private int size; |
| private boolean reverseOrder, dedup; |
| private boolean outKV, outVK; |
| |
| QueueAggregationBuffer() { |
| super(); |
| } |
| |
| void reset(@Nonnegative int size, boolean reverseOrder, boolean dedup, boolean outKV, |
| boolean outVK) { |
| setOptions(size, reverseOrder, dedup, outKV, outVK); |
| this.queueHandler = null; |
| } |
| |
| void setOptions(@Nonnegative int size, boolean reverseOrder, boolean dedup, |
| boolean outKV, boolean outVK) { |
| this.size = size; |
| this.reverseOrder = reverseOrder; |
| this.dedup = dedup; |
| this.outKV = outKV; |
| this.outVK = outVK; |
| } |
| |
| void iterate(@Nonnull TupleWithKey tuple) { |
| if (queueHandler == null) { |
| initQueueHandler(); |
| } |
| if (dedup && queueHandler.contains(tuple)) { |
| return; |
| } |
| queueHandler.offer(tuple); |
| } |
| |
| void merge(@Nonnull List<Object> keys, @Nonnull List<Object> values) { |
| if (queueHandler == null) { |
| initQueueHandler(); |
| } |
| if (dedup) { |
| for (int i = 0, n = keys.size(); i < n; i++) { |
| TupleWithKey tuple = new TupleWithKey(keys.get(i), values.get(i)); |
| if (!queueHandler.contains(tuple)) { |
| queueHandler.offer(tuple); |
| } |
| } |
| } else { |
| for (int i = 0, n = keys.size(); i < n; i++) { |
| queueHandler.offer(new TupleWithKey(keys.get(i), values.get(i))); |
| } |
| } |
| |
| } |
| |
| @Deprecated |
| @Nullable |
| Pair<List<Object>, List<Object>> drainQueue() { |
| if (queueHandler == null) { |
| return null; |
| } |
| |
| final int n = queueHandler.size(); |
| final Object[] keys = new Object[n]; |
| final Object[] values = new Object[n]; |
| for (int i = n - 1; i >= 0; i--) { // head element in queue should be stored to tail of array |
| TupleWithKey tuple = queueHandler.poll(); |
| keys[i] = tuple.getKey(); |
| values[i] = tuple.getValue(); |
| } |
| queueHandler.clear(); |
| |
| return Pair.of(Arrays.asList(keys), Arrays.asList(values)); |
| } |
| |
| @Nullable |
| List<Object> drainValues() { |
| if (queueHandler == null) { |
| return null; |
| } |
| |
| final int n = queueHandler.size(); |
| final Object[] values = new Object[n]; |
| for (int i = n - 1; i >= 0; i--) { // head element in queue should be stored to tail of array |
| TupleWithKey tuple = queueHandler.poll(); |
| values[i] = tuple.getValue(); |
| } |
| queueHandler.clear(); |
| |
| return Arrays.asList(values); |
| } |
| |
| @Nullable |
| Map<Object, Object> drainMapKV() { |
| if (queueHandler == null) { |
| return null; |
| } |
| |
| final int n = queueHandler.size(); |
| final Object[] keys = new Object[n]; |
| final Object[] values = new Object[n]; |
| for (int i = n - 1; i >= 0; i--) { // head element in queue should be stored to tail of array |
| TupleWithKey tuple = queueHandler.poll(); |
| keys[i] = tuple.getKey(); |
| values[i] = tuple.getValue(); |
| } |
| |
| final Map<Object, Object> map = new LinkedHashMap<>(n * 2); |
| for (int i = 0; i < n; i++) { |
| final Object k = keys[i]; |
| if (map.containsKey(k)) { |
| continue; |
| } |
| map.put(k, values[i]); |
| } |
| queueHandler.clear(); |
| |
| return map; |
| } |
| |
| @Nullable |
| Map<Object, Object> drainMapVK() { |
| if (queueHandler == null) { |
| return null; |
| } |
| |
| final int n = queueHandler.size(); |
| final Object[] keys = new Object[n]; |
| final Object[] values = new Object[n]; |
| for (int i = n - 1; i >= 0; i--) { // head element in queue should be stored to tail of array |
| TupleWithKey tuple = queueHandler.poll(); |
| keys[i] = tuple.getValue(); |
| values[i] = tuple.getKey(); |
| } |
| |
| final Map<Object, Object> map = new LinkedHashMap<>(n * 2); |
| for (int i = 0; i < n; i++) { |
| final Object k = keys[i]; |
| if (map.containsKey(k)) { |
| continue; // avoid duplicate |
| } |
| map.put(k, values[i]); |
| } |
| queueHandler.clear(); |
| |
| return map; |
| } |
| |
| private void initQueueHandler() { |
| final Comparator<TupleWithKey> comparator; |
| if (reverseOrder) { |
| comparator = Collections.reverseOrder(); |
| } else { |
| comparator = NaturalComparator.getInstance(); |
| } |
| |
| if (size > 0) { |
| this.queueHandler = new BoundedQueueHandler(size, comparator); |
| } else { |
| this.queueHandler = new QueueHandler(comparator); |
| } |
| } |
| |
| } |
| |
| /** |
| * Since BoundedPriorityQueue does not directly inherit PriorityQueue, we provide handler |
| * class which wraps each of PriorityQueue and BoundedPriorityQueue. |
| */ |
| private static abstract class AbstractQueueHandler { |
| |
| abstract boolean contains(@Nonnull TupleWithKey tuple); |
| |
| abstract void offer(@Nonnull TupleWithKey tuple); |
| |
| abstract int size(); |
| |
| @Nullable |
| abstract TupleWithKey poll(); |
| |
| abstract void clear(); |
| |
| } |
| |
| private static final class QueueHandler extends AbstractQueueHandler { |
| |
| private static final int DEFAULT_INITIAL_CAPACITY = 11; // same as PriorityQueue |
| |
| @Nonnull |
| private final PriorityQueue<TupleWithKey> queue; |
| |
| QueueHandler(@Nonnull Comparator<TupleWithKey> comparator) { |
| this.queue = new PriorityQueue<TupleWithKey>(DEFAULT_INITIAL_CAPACITY, comparator); |
| } |
| |
| @Override |
| boolean contains(@Nonnull TupleWithKey tuple) { |
| return queue.contains(tuple); |
| } |
| |
| @Override |
| void offer(TupleWithKey tuple) { |
| queue.offer(tuple); |
| } |
| |
| @Override |
| int size() { |
| return queue.size(); |
| } |
| |
| @Override |
| TupleWithKey poll() { |
| return queue.poll(); |
| } |
| |
| @Override |
| void clear() { |
| queue.clear(); |
| } |
| |
| } |
| |
| private static final class BoundedQueueHandler extends AbstractQueueHandler { |
| |
| @Nonnull |
| private final BoundedPriorityQueue<TupleWithKey> queue; |
| |
| BoundedQueueHandler(int size, @Nonnull Comparator<TupleWithKey> comparator) { |
| this.queue = new BoundedPriorityQueue<TupleWithKey>(size, comparator); |
| } |
| |
| @Override |
| boolean contains(@Nonnull TupleWithKey tuple) { |
| return queue.contains(tuple); |
| } |
| |
| @Override |
| void offer(TupleWithKey tuple) { |
| queue.offer(tuple); |
| } |
| |
| @Override |
| int size() { |
| return queue.size(); |
| } |
| |
| @Override |
| TupleWithKey poll() { |
| return queue.poll(); |
| } |
| |
| @Override |
| void clear() { |
| queue.clear(); |
| } |
| |
| } |
| |
| private static final class TupleWithKey implements Comparable<TupleWithKey> { |
| @Nonnull |
| private final Object key; |
| @Nonnull |
| private final Object value; |
| |
| TupleWithKey(@CheckForNull Object key, @CheckForNull Object value) { |
| this.key = Preconditions.checkNotNull(key); |
| this.value = Preconditions.checkNotNull(value); |
| } |
| |
| @Nonnull |
| Object getKey() { |
| return key; |
| } |
| |
| @Nonnull |
| Object getValue() { |
| return value; |
| } |
| |
| @Override |
| public int compareTo(TupleWithKey o) { |
| @SuppressWarnings("unchecked") |
| Comparable<? super Object> k = (Comparable<? super Object>) key; |
| return k.compareTo(o.getKey()); |
| } |
| |
| @Override |
| public int hashCode() { |
| final int prime = 31; |
| int result = 1; |
| result = prime * result + ((key == null) ? 0 : key.hashCode()); |
| result = prime * result + ((value == null) ? 0 : value.hashCode()); |
| return result; |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) |
| return true; |
| if (obj == null) |
| return false; |
| if (getClass() != obj.getClass()) |
| return false; |
| TupleWithKey other = (TupleWithKey) obj; |
| if (key == null) { |
| if (other.key != null) |
| return false; |
| } else if (!key.equals(other.key)) |
| return false; |
| if (value == null) { |
| if (other.value != null) |
| return false; |
| } else if (!value.equals(other.value)) |
| return false; |
| return true; |
| } |
| } |
| } |
| } |