@pisell/pisellos
Version:
一个可扩展的前端模块化SDK框架,支持插件系统
423 lines (391 loc) • 16 kB
JavaScript
function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); }
function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); }
function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
/**
* 现金支付推荐算法
*
* 核心原理:
* 1. 每个推荐金额都应该是独立的最优组合
* 2. 不推荐在已有最优解基础上添加额外面额的组合
* 3. 优先推荐使用不同数量币种的组合
* 4. 根据组合判断去重,避免扩展组合
*/
/**
* 常见国家货币面额配置
*/
export var CURRENCY_DENOMINATIONS = {
// 美元
'USD': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01],
// 人民币
'CNY': [100, 50, 20, 10, 5, 1, 0.5, 0.1, 0.05, 0.01],
'RMB': [100, 50, 20, 10, 5, 1, 0.5, 0.1, 0.05, 0.01],
// 欧元
'EUR': [500, 200, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01],
// 日元
'JPY': [10000, 5000, 2000, 1000, 500, 100, 50, 10, 5, 1],
// 英镑
'GBP': [50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.02, 0.01],
// 澳元
'AUD': [100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1],
// 加元
'CAD': [100, 50, 20, 10, 5, 2, 1, 0.25, 0.1, 0.05],
// 港元
'HKD': [1000, 500, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1],
// 新台币
'TWD': [2000, 1000, 500, 200, 100, 50, 10, 5, 1],
// 韩元
'KRW': [50000, 10000, 5000, 1000, 500, 100, 50, 10, 5, 1],
// 新加坡元
'SGD': [10000, 1000, 100, 50, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05, 0.01],
// 瑞士法郎
'CHF': [1000, 200, 100, 50, 20, 10, 5, 2, 1, 0.5, 0.2, 0.1, 0.05],
// 默认通用面额(如果币种不在列表中)
'DEFAULT': [100, 50, 20, 10, 5, 1, 0.5, 0.25, 0.1, 0.05, 0.01]
};
/**
* 支付组合类型定义
*/
/**
* 最优支付金额推荐算法
* 推荐通过不同数量面额组合刚好足够支付的最小金额
*
* @param targetAmount 目标金额
* @param denominations 币种面值数组
* @returns 推荐支付金额数组
*/
export function recommendOptimalPayments(targetAmount, denominations) {
// 参数验证
if (targetAmount <= 0 || !isFinite(targetAmount) || isNaN(targetAmount)) {
return [];
}
if (!denominations || !Array.isArray(denominations) || denominations.length === 0) {
return [Math.ceil(targetAmount)];
}
// 过滤有效面额并限制搜索空间以提高性能
var maxReasonableDenom = targetAmount * 5; // 适度限制搜索空间
var validDenoms = denominations.filter(function (denom) {
return denom > 0 && isFinite(denom) && !isNaN(denom) && denom <= maxReasonableDenom;
}).slice(0, 8); // 限制面额数量以提高性能
if (validDenoms.length === 0) {
// 如果没有合适的面额,返回最接近的整数金额
return [Math.ceil(targetAmount)];
}
try {
// 处理精度问题,将金额转换为整数计算
var precision = 100; // 假设最小单位是0.01
var target = Math.round(targetAmount * precision);
var denoms = validDenoms.map(function (d) {
return Math.round(d * precision);
}).sort(function (a, b) {
return b - a;
});
// 存储不同的支付组合方案
var paymentOptions = [];
// 首先尝试找到精确匹配的组合
var exactCombination = findExactCombination(target, denoms, precision);
if (exactCombination) {
paymentOptions.push(exactCombination);
}
// 为了提高效率,我们将按币种数量来生成组合
// 1币种组合:只用一种面额
for (var i = 0; i < Math.min(denoms.length, 6); i++) {
var denom = denoms[i];
var count = Math.ceil(target / denom);
var sum = count * denom;
if (sum >= target && count <= 12) {
// 限制硬币数量以提高性能
paymentOptions.push({
combination: Array(count).fill(denom / precision),
sum: sum / precision,
coinCount: count,
denomTypes: 1
});
}
}
// 2币种组合:使用两种面额(大幅限制搜索范围以提高性能)
for (var _i = 0; _i < Math.min(denoms.length - 1, 5); _i++) {
for (var j = _i + 1; j < Math.min(denoms.length, 6); j++) {
var denom1 = denoms[_i];
var denom2 = denoms[j];
// 大幅限制搜索范围,基于目标金额动态调整
var maxCount1 = Math.min(Math.ceil(target / denom1) + 1, 8);
var maxCount2 = Math.min(Math.ceil(target / denom2) + 1, 10);
for (var count1 = 1; count1 <= maxCount1; count1++) {
for (var count2 = 1; count2 <= maxCount2; count2++) {
var _sum = count1 * denom1 + count2 * denom2;
if (_sum >= target) {
var combination = [].concat(_toConsumableArray(Array(count1).fill(denom1 / precision)), _toConsumableArray(Array(count2).fill(denom2 / precision)));
paymentOptions.push({
combination: combination,
sum: _sum / precision,
coinCount: count1 + count2,
denomTypes: 2
});
}
}
}
}
}
// 3币种组合:使用三种面额(大幅限制搜索以提高效率)
for (var _i2 = 0; _i2 < Math.min(denoms.length - 2, 3); _i2++) {
for (var _j = _i2 + 1; _j < Math.min(denoms.length - 1, 4); _j++) {
for (var k = _j + 1; k < Math.min(denoms.length, 5); k++) {
var _denom = denoms[_i2];
var _denom2 = denoms[_j];
var denom3 = denoms[k];
// 大幅限制搜索范围
var _maxCount = Math.min(Math.ceil(target / _denom) + 1, 5);
var _maxCount2 = Math.min(Math.ceil(target / _denom2) + 1, 6);
var maxCount3 = Math.min(Math.ceil(target / denom3) + 1, 8);
for (var _count = 1; _count <= _maxCount; _count++) {
for (var _count2 = 1; _count2 <= _maxCount2; _count2++) {
for (var count3 = 1; count3 <= maxCount3; count3++) {
var _sum2 = _count * _denom + _count2 * _denom2 + count3 * denom3;
if (_sum2 >= target) {
var _combination = [].concat(_toConsumableArray(Array(_count).fill(_denom / precision)), _toConsumableArray(Array(_count2).fill(_denom2 / precision)), _toConsumableArray(Array(count3).fill(denom3 / precision)));
paymentOptions.push({
combination: _combination,
sum: _sum2 / precision,
coinCount: _count + _count2 + count3,
denomTypes: 3
});
}
}
}
}
}
}
}
// 4币种组合:使用四种面额(极度限制搜索以提高性能)
for (var _i3 = 0; _i3 < Math.min(denoms.length - 3, 2); _i3++) {
for (var _j2 = _i3 + 1; _j2 < Math.min(denoms.length - 2, 3); _j2++) {
for (var _k = _j2 + 1; _k < Math.min(denoms.length - 1, 4); _k++) {
for (var l = _k + 1; l < Math.min(denoms.length, 5); l++) {
var _denom3 = denoms[_i3];
var _denom4 = denoms[_j2];
var _denom5 = denoms[_k];
var denom4 = denoms[l];
// 极度限制搜索范围
var _maxCount3 = Math.min(Math.ceil(target / _denom3) + 1, 3);
var _maxCount4 = Math.min(Math.ceil(target / _denom4) + 1, 4);
var _maxCount5 = Math.min(Math.ceil(target / _denom5) + 1, 5);
var maxCount4 = Math.min(Math.ceil(target / denom4) + 1, 6);
for (var _count3 = 1; _count3 <= _maxCount3; _count3++) {
for (var _count4 = 1; _count4 <= _maxCount4; _count4++) {
for (var _count5 = 1; _count5 <= _maxCount5; _count5++) {
for (var count4 = 1; count4 <= maxCount4; count4++) {
var _sum3 = _count3 * _denom3 + _count4 * _denom4 + _count5 * _denom5 + count4 * denom4;
if (_sum3 >= target) {
var _combination2 = [].concat(_toConsumableArray(Array(_count3).fill(_denom3 / precision)), _toConsumableArray(Array(_count4).fill(_denom4 / precision)), _toConsumableArray(Array(_count5).fill(_denom5 / precision)), _toConsumableArray(Array(count4).fill(denom4 / precision)));
paymentOptions.push({
combination: _combination2,
sum: _sum3 / precision,
coinCount: _count3 + _count4 + _count5 + count4,
denomTypes: 4
});
}
}
}
}
}
}
}
}
}
// 如果没有找到任何组合,提供默认建议
if (paymentOptions.length === 0) {
// 找到大于等于目标金额的最小面额
var minValidDenom = validDenoms.find(function (denom) {
return denom >= targetAmount;
});
if (minValidDenom) {
return [minValidDenom];
} else {
// 使用最大面额的组合
var maxDenom = Math.max.apply(Math, _toConsumableArray(validDenoms));
var _count6 = Math.ceil(targetAmount / maxDenom);
return [_count6 * maxDenom];
}
}
// 移除重复和扩展组合
var uniqueCombinations = removeDuplicateAndExtendedCombinations(paymentOptions, targetAmount);
// 按币种类型数量排序,然后按总硬币数量排序,最后按金额排序
uniqueCombinations.sort(function (a, b) {
if (a.denomTypes !== b.denomTypes) {
return a.denomTypes - b.denomTypes;
}
if (a.coinCount !== b.coinCount) {
return a.coinCount - b.coinCount;
}
return a.sum - b.sum;
});
// 按金额去重并返回推荐的支付金额
var uniqueAmounts = new Set();
var finalResults = [];
for (var _i4 = 0, _uniqueCombinations = uniqueCombinations; _i4 < _uniqueCombinations.length; _i4++) {
var item = _uniqueCombinations[_i4];
var roundedAmount = Math.round(item.sum * 100) / 100; // 处理浮点数精度
if (!uniqueAmounts.has(roundedAmount) && finalResults.length < 10) {
uniqueAmounts.add(roundedAmount);
finalResults.push(roundedAmount);
}
}
return finalResults.sort(function (a, b) {
return a - b;
});
} catch (error) {
// 发生错误时返回安全的默认值
console.warn('推荐支付金额计算出错:', error);
// 返回最接近的整数金额作为兜底
var safeAmount = Math.ceil(targetAmount);
return [targetAmount, safeAmount];
}
}
/**
* 尝试找到精确匹配目标金额的组合
*/
function findExactCombination(target, denoms, precision) {
// 简单的深度优先搜索,限制搜索深度
function dfs(remaining, denomIndex, currentCombination) {
if (remaining === 0) {
return currentCombination;
}
if (remaining < 0 || denomIndex >= denoms.length || currentCombination.length > 10) {
return null;
}
var denom = denoms[denomIndex];
// 尝试使用当前面额 0 到 maxCount 次
var maxCount = Math.min(Math.floor(remaining / denom), 8);
for (var count = maxCount; count >= 0; count--) {
var newCombination = [].concat(_toConsumableArray(currentCombination), _toConsumableArray(Array(count).fill(denom)));
var result = dfs(remaining - count * denom, denomIndex + 1, newCombination);
if (result) {
return result;
}
}
return null;
}
var exactMatch = dfs(target, 0, []);
if (exactMatch && exactMatch.length > 0) {
var denomTypes = new Set(exactMatch).size;
return {
combination: exactMatch.map(function (d) {
return d / precision;
}),
sum: target / precision,
coinCount: exactMatch.length,
denomTypes: denomTypes
};
}
return null;
}
/**
* 移除重复和扩展组合
* 核心原理:如果组合A包含组合B的所有硬币,并且还有额外硬币,则A是B的扩展,应该被移除
*/
function removeDuplicateAndExtendedCombinations(combinations, targetAmount) {
var result = [];
// 按币种类型数量、硬币数量、金额排序,确保较简单的组合在前面
combinations.sort(function (a, b) {
if (a.denomTypes !== b.denomTypes) {
return a.denomTypes - b.denomTypes;
}
if (a.coinCount !== b.coinCount) {
return a.coinCount - b.coinCount;
}
return a.sum - b.sum;
});
for (var i = 0; i < combinations.length; i++) {
var current = combinations[i];
var shouldSkip = false;
// 检查当前组合是否与已添加的组合重复或者是扩展
for (var j = 0; j < result.length; j++) {
var existing = result[j];
// 如果完全相同,跳过
if (isSameCombination(current.combination, existing.combination)) {
shouldSkip = true;
break;
}
// 如果当前组合是已存在组合的扩展,跳过
if (isExtensionOf(current.combination, existing.combination)) {
shouldSkip = true;
break;
}
}
// 检查是否有其他组合是当前组合的扩展,如果有,移除那些扩展
if (!shouldSkip) {
// 移除所有当前组合的扩展
for (var _j3 = result.length - 1; _j3 >= 0; _j3--) {
if (isExtensionOf(result[_j3].combination, current.combination)) {
result.splice(_j3, 1);
}
}
result.push(current);
}
}
return result;
}
/**
* 检查组合A是否是组合B的扩展(即A包含B的所有硬币,并且还有额外的硬币)
*/
function isExtensionOf(combinationA, combinationB) {
// 如果A的硬币数量少于等于B,A不可能是B的扩展
if (combinationA.length <= combinationB.length) {
return false;
}
// 统计每个面额的数量
var countA = {};
var countB = {};
combinationA.forEach(function (coin) {
countA[coin] = (countA[coin] || 0) + 1;
});
combinationB.forEach(function (coin) {
countB[coin] = (countB[coin] || 0) + 1;
});
// 检查B的所有面额在A中是否都有足够的数量
for (var coin in countB) {
if (!countA[coin] || countA[coin] < countB[coin]) {
return false;
}
}
// 检查A是否有B没有的额外硬币
var hasExtra = false;
for (var _coin in countA) {
if (countA[_coin] > (countB[_coin] || 0)) {
hasExtra = true;
break;
}
}
return hasExtra;
}
/**
* 检查两个组合是否本质相同(相同的面额组合)
*/
function isSameCombination(combinationA, combinationB) {
if (combinationA.length !== combinationB.length) {
return false;
}
var countA = {};
var countB = {};
combinationA.forEach(function (coin) {
countA[coin] = (countA[coin] || 0) + 1;
});
combinationB.forEach(function (coin) {
countB[coin] = (countB[coin] || 0) + 1;
});
// 检查两个组合是否有相同的面额和数量
for (var coin in countA) {
if (countA[coin] !== (countB[coin] || 0)) {
return false;
}
}
for (var _coin2 in countB) {
if (countB[_coin2] !== (countA[_coin2] || 0)) {
return false;
}
}
return true;
}