最普遍的填充就是没有规律的时候复制对应位置的内容,可以标记为:
NORMAL_RULE = ({ value }) => {return value;};
一、数字类型
数字类型的填充可以归结为一点:最小二乘拟合。因此你需要知道什么是最小二乘拟合(https://baike.baidu.com/item/%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%B3%95/2522346?fromtitle=%E6%9C%80%E5%B0%8F%E4%BA%8C%E4%B9%98%E6%8B%9F%E5%90%88&fromid=6260453&fr=aladdin).
/*
numberList: 找规律的数据,是一个数组
return: 返回的是一个填充的规律
规律接受参数为填充的第几项
*/
getLeastSquares(numberList) {
let slope, intercept, xAverage, yAverage;
let xSum = 0, ySum = 0, xSquareSum = 0, xySum = 0;
let validCellsLen = 0;
let emptyCellPositions = [];
numberList.forEach((v, i) => {
if (v !== undefined && v !== null && v !== '') {
validCellsLen++;
xSum += i;
ySum += v;
xySum += (v * i);
xSquareSum += Math.pow(i, 2);
} else {
emptyCellPositions.push(i);
}
});
if (validCellsLen < 2) { // 长度小于2的,直接认为是复制对应位置的内容
return NORMAL_RULE;
}
xAverage = xSum / validCellsLen;
yAverage = ySum / validCellsLen;
slope = (xySum - validCellsLen * xAverage * yAverage) / (xSquareSum - validCellsLen * Math.pow(xAverage, 2));
intercept = yAverage - slope * xAverage;
return ({ n }) => {
if (emptyCellPositions.length && emptyCellPositions.includes(n % numberList.length)) {
return '';
}
let y = n * slope + intercept; // 拟合公式:y = k * x + b
return Number(parseFloat(y).toFixed(8));
};
}
二、文本类型
文本类型需要注意的是当遇见数字的时候,遇见数字时,如果仅是数字部分不同,要按照数字部分的规律进行填充
const REG_STRING_NUMBER_PARTS = /\d+|\D+/g; // 将字符串进行切分
const REG_NUMBER_DIGIT = /\d/; // 匹配数字
// 是不是顺序数列
function isArithmeticSequence(numberList) {
let number0 = numberList[0];
let tolerance = numberList[1] - number0;
let func = (v, n) => {
return v === n * (tolerance) + number0;
};
return numberList.every(func);
}
function getTextItemStructureInfo(textItem) {
let validTextItem = textItem || '';
let lastNumberPosition = -1, lastNumber = validTextItem;
let valueList = validTextItem.match(REG_STRING_NUMBER_PARTS); // 是否存在数字部分
for (let i = valueList.length - 1; i > -1; i--) {
let valueItem = valueList[i];
if (REG_NUMBER_DIGIT.test(valueItem)) { // 寻找最末尾的数字部分下标
lastNumberPosition = i;
lastNumber = valueItem;
break;
}
}
if (lastNumberPosition !== -1) {
valueList[lastNumberPosition] = '-|*|-|*|-|*|-'; // 标识无特殊意义
}
return {lastNumberPosition, lastNumber, structure: valueList.join('')};
}
function getTextFillNumberRule(valueList, lastNumber, lastNumberPosition, fillFunc) {
let isStartWith0 = lastNumber.startsWith('0');
return ({ n }) => {
let fillValue = fillFunc ? fillFunc({lastNumber, n}) : '';
if (isStartWith0 && fillValue.length < lastNumber.length) {
fillValue = '0'.repeat(lastNumber.length - fillValue.length) + fillValue;
}
valueList[lastNumberPosition] = fillValue;
return valueList.join('');
};
}
function getTextRule(textList) {
let isAllNotIncludeNumber = textList.every(item => !REG_NUMBER_DIGIT.test(item || ''));
if (isAllNotIncludeNumber) {
return NORMAL_RULE;
}
if (textList.length === 1) {
let valueList = textList[0].match(REG_STRING_NUMBER_PARTS);
let { lastNumberPosition, lastNumber } = getTextItemStructureInfo(textList[0]);
return getTextFillNumberRule(valueList, lastNumber, lastNumberPosition, ({lastNumber, n}) => {
let lastNumberValue = parseInt(lastNumber, 10);
return (lastNumberValue + n) + '';
});
}
// isStructureConsistent: the last number part is not equal, other is equal
let structureList = textList.map((text) => getTextItemStructureInfo(text));
let firstStructure = structureList[0];
let isStructureConsistent = structureList.every(structure => structure['lastNumberPosition'] === firstStructure['lastNumberPosition'] && structure['structure'] === firstStructure['structure']);
if (isStructureConsistent) {
let numberList = structureList.map(structure => parseInt(structure.lastNumber, 10));
if (isArithmeticSequence(numberList)) {
let valueList = textList[0].match(REG_STRING_NUMBER_PARTS);
let secondStructure = structureList[1];
let secondStructureLastNumberValue = parseInt(secondStructure['lastNumber'], 10);
return getTextFillNumberRule(valueList, firstStructure['lastNumber'], firstStructure['lastNumberPosition'], ({lastNumber, n}) => {
let lastNumberValue = parseInt(lastNumber, 10);
return (n * (secondStructureLastNumberValue - lastNumberValue) + lastNumberValue) + '';
});
}
return NORMAL_RULE;
}
return ({value, n}) => {
if (REG_NUMBER_DIGIT.test(value || '')) {
let valueList = value.match(REG_STRING_NUMBER_PARTS);
let { lastNumberPosition, lastNumber } = getTextItemStructureInfo(value);
let isStartWith0 = lastNumber.startsWith('0');
let lastNumberValue = parseInt(lastNumber, 10);
let fillValue = (lastNumberValue + Math.floor(n / textList.length)) + '';
if (isStartWith0 && fillValue.length < lastNumber.length) {
fillValue = '0'.repeat(lastNumber.length - fillValue.length) + fillValue;
}
valueList[lastNumberPosition] = fillValue;
return valueList.join('');
}
return value;
};
}