function getUniqueInfo(values, uniqueValues) {
    // Initialize uniqueValues if not specified
    if(uniqueValues === undefined || uniqueValues === null) {
        uniqueValues = [];
    } else {
        // Shallow copy so append below doesn't alter input array
        uniqueValues = uniqueValues.map(function(e) {return e;});
    }

    // Initialize Variables
    var uniqueValueCounts = {};
    var uniqueValueInds = {};
    var inds = [];

    // Initialize uniqueValueCounts and
    uniqueValues.forEach(function(uniqueVal, valInd) {
        uniqueValueCounts[uniqueVal] = 0;
        uniqueValueInds[uniqueVal] = valInd;
    });

    // Compute the necessary unique info in a single pass
    for(var i = 0; i < values.length; i++) {
        var item = values[i];
        var itemInd;

        if(uniqueValueCounts[item] === undefined) {
            // This item has a previously unseen value
            uniqueValueCounts[item] = 1;
            itemInd = uniqueValues.push(item) - 1;
            uniqueValueInds[item] = itemInd;
        } else {
            // Increment count for this item
            uniqueValueCounts[item]++;
            itemInd = uniqueValueInds[item];
        }
        inds.push(itemInd);
    }

    // Build UniqueInfo
    var uniqueCounts = uniqueValues.map(function(v) { return uniqueValueCounts[v]; });

    return {
        uniqueValues: uniqueValues,
        uniqueCounts: uniqueCounts,
        inds: inds
    };
}
Compute unique value information for an array

IMPORTANT: Note that values are considered unique
if their string representations are unique.

@param {Array} values
@param {Array|undefined} uniqueValues
 Array of expected unique values. The uniqueValues property of the resulting UniqueInfo object will begin with
 these entries. Entries are included even if there are zero occurrences in the values array. Entries found in
 the values array that are not present in uniqueValues will be included at the end of the array in the
 UniqueInfo object.
@return {UniqueInfo}