"use strict";
const similarity = (a, b) => {
const l = Math.min(a.length, b.length);
let dist = 0;
for (let i = 0; i < l; i++) {
const ca = a.charCodeAt(i);
const cb = b.charCodeAt(i);
dist += Math.max(0, 10 - Math.abs(ca - cb));
}
return dist;
};
const getName = (a, b) => {
const l = Math.min(a.length, b.length);
let r = "";
for (let i = 0; i < l; i++) {
const ca = a.charAt(i);
const cb = b.charAt(i);
r += ca;
if (ca === cb) {
continue;
}
return r;
}
return a;
};
class Node {
constructor(item, key, size) {
this.item = item;
this.key = key;
this.size = size;
}
}
class Group {
constructor(nodes, similarities) {
this.nodes = nodes;
this.similarities = similarities;
this.size = nodes.reduce((size, node) => size + node.size, 0);
this.key = undefined;
}
}
module.exports = ({ maxSize, minSize, items, getSize, getKey }) => {
const result = [];
const nodes = Array.from(
items,
item => new Node(item, getKey(item), getSize(item))
);
const initialNodes = [];
nodes.sort((a, b) => {
if (a.key < b.key) return -1;
if (a.key > b.key) return 1;
return 0;
});
for (const node of nodes) {
if (node.size >= maxSize) {
result.push(new Group([node], []));
} else {
initialNodes.push(node);
}
}
if (initialNodes.length > 0) {
const similarities = [];
for (let i = 1; i < initialNodes.length; i++) {
const a = initialNodes[i - 1];
const b = initialNodes[i];
similarities.push(similarity(a.key, b.key));
}
const initialGroup = new Group(initialNodes, similarities);
if (initialGroup.size < minSize) {
if (result.length > 0) {
const smallestGroup = result.reduce((min, group) =>
min.size > group.size ? group : min
);
for (const node of initialGroup.nodes) smallestGroup.nodes.push(node);
smallestGroup.nodes.sort((a, b) => {
if (a.key < b.key) return -1;
if (a.key > b.key) return 1;
return 0;
});
} else {
result.push(initialGroup);
}
} else {
const queue = [initialGroup];
while (queue.length) {
const group = queue.pop();
if (group.size < maxSize) {
result.push(group);
continue;
}
let left = 0;
let leftSize = 0;
while (leftSize <= minSize) {
leftSize += group.nodes[left].size;
left++;
}
let right = group.nodes.length - 1;
let rightSize = 0;
while (rightSize <= minSize) {
rightSize += group.nodes[right].size;
right--;
}
if (left - 1 > right) {
result.push(group);
continue;
}
if (left <= right) {
let best = left - 1;
let bestSimilarity = group.similarities[best];
for (let i = left; i <= right; i++) {
const similarity = group.similarities[i];
if (similarity < bestSimilarity) {
best = i;
bestSimilarity = similarity;
}
}
left = best + 1;
right = best;
}
const rightNodes = [group.nodes[right + 1]];
const rightSimilaries = [];
for (let i = right + 2; i < group.nodes.length; i++) {
rightSimilaries.push(group.similarities[i - 1]);
rightNodes.push(group.nodes[i]);
}
queue.push(new Group(rightNodes, rightSimilaries));
const leftNodes = [group.nodes[0]];
const leftSimilaries = [];
for (let i = 1; i < left; i++) {
leftSimilaries.push(group.similarities[i - 1]);
leftNodes.push(group.nodes[i]);
}
queue.push(new Group(leftNodes, leftSimilaries));
}
}
}
result.sort((a, b) => {
if (a.nodes[0].key < b.nodes[0].key) return -1;
if (a.nodes[0].key > b.nodes[0].key) return 1;
return 0;
});
for (let i = 0; i < result.length; i++) {
const group = result[i];
const first = group.nodes[0];
const last = group.nodes[group.nodes.length - 1];
let name = getName(first.key, last.key);
group.key = name;
}
return result.map(group => {
return {
key: group.key,
items: group.nodes.map(node => node.item),
size: group.size
};
});
};