我正在尝试构建一个带有可变数量参数的函数。
该函数接受n个输入并计算所有可能的加法和减法之和,例如如果参数是1,2,3
1 + 2 + 3
1-2-3
1 + 2-3-
1-2 + 3
最后,该函数输出最接近零的和。在这种情况下,答案仅为0。
我在解决如何循环n个参数以使用+和-运算符的所有可能组合时遇到很多问题。
我设法构建了一个将所有变量相加或相减的函数,但是我一直坚持如何处理各种+和-,特别是在考虑多个可能的变量时。
var sub = 0;
var add = 0;
function sumAll() {
var i;
for (i = 0; i < arguments.length; i++) {
sub -= arguments[i];
}
for (i = 0; i < arguments.length; i++) {
add += arguments[i];
}
return add;
return sub;
};
console.log(add, sub); // just to test the outputs
对于任何给定数量的输入(总是整数,正负),我想计算+和-的所有可能排列。欢迎提出将总和与零进行比较的建议,尽管我还没有尝试过,但宁愿先询问这一部分。谢谢。
答案 0 :(得分:2)
我会遍历数字的可能位。例如,如果有3个自变量,则有3位,并且这些位可表示的最高数字是2 ** 3 - 1
或7(当所有3位都已设置时,111
或1 + 2 + 4)。然后,从0迭代到7,并检查是否设置了每个位索引。
例如,在第一次迭代中,当数字为0时,位为000
,对应于+++
-将所有3个参数加起来。
在第二次迭代中,当数字为1时,位为001
(对应于-++
),因此减去第一个自变量,再加上其他两个自变量。
第三次迭代将具有2
或010
或+-+
。
第三次迭代将具有3
或011
或+--
。
第三次迭代将具有4
或100
或-++
。
继续模式直到结束,同时跟踪到目前为止到目前为止最接近零的总数。
如果愿意,如果总计为0,也可以立即返回。
const sumAll = (...args) => {
const limit = 2 ** args.length - 1; // eg, 2 ** 3 - 1 = 7
let totalClosestToZeroSoFar = Infinity;
for (let i = 0; i < limit; i++) {
// eg '000', or '001', or '010', or '011', or '100', etc
const bitStr = i.toString(2).padStart(args.length, '0');
let subtotal = 0;
console.log('i:', i, 'bitStr:', bitStr);
args.forEach((arg, bitPos) => {
if (bitStr[args.length - 1 - bitPos] === '0') {
console.log('+', arg);
subtotal += arg;
} else {
console.log('-', arg);
subtotal -= arg;
}
});
console.log('subtotal', subtotal);
if (Math.abs(subtotal) < Math.abs(totalClosestToZeroSoFar)) {
totalClosestToZeroSoFar = subtotal;
}
}
return totalClosestToZeroSoFar;
};
console.log('final', sumAll(1, 2, 3));
您可以通过将[args.length - 1 - bitPos]
替换为[bitPos]
来“简化”以获得相同的结果,但看起来会更加混乱-例如3
(011
,或+--
)将变为110
(--+
)。
它要短很多,而没有所有日志可以证明代码正在按预期运行:
const sumAll = (...args) => {
const limit = 2 ** args.length - 1;
let totalClosestToZeroSoFar = Infinity;
for (let i = 0; i < limit; i++) {
const bitStr = i.toString(2).padStart(args.length, '0');
let subtotal = 0;
args.forEach((arg, bitPos) => {
subtotal += (bitStr[bitPos] === '0' ? -1 : 1) * arg;
});
if (Math.abs(subtotal) < Math.abs(totalClosestToZeroSoFar)) {
totalClosestToZeroSoFar = subtotal;
}
}
return totalClosestToZeroSoFar;
};
console.log('final', sumAll(1, 2, 3));
您可以通过任意选择第一个数字的符号来将操作次数减少一半。例如。目前,对于sumAll(9, 1)
,8
(9 - 1
)和-8
(1 - 9
)的答案都是有效的,因为它们都接近0。无论输入如何,如果+-
产生一个最接近0的数字,那么-+
也会这样做,只是带有相反的符号。同样,如果++---
产生一个最接近0的数字,那么--+++
也会产生一个相反的符号。通过为第一个数字选择一个符号,您可能会强制计算结果仅具有一个 符号,但这不会影响算法的结果与0的距离。
没有太大的改进(例如,10个参数,2 ** 10 - 1
-> 1023迭代改进为2 ** 9 - 1
-> 511迭代),但这是有的。
const sumAll = (...args) => {
let initialDigit = args.shift();
const limit = 2 ** args.length - 1;
let totalClosestToZeroSoFar = Infinity;
for (let i = 0; i < limit; i++) {
const bitStr = i.toString(2).padStart(args.length, '0');
let subtotal = initialDigit;
args.forEach((arg, bitPos) => {
subtotal += (bitStr[bitPos] === '0' ? -1 : 1) * arg;
});
if (Math.abs(subtotal) < Math.abs(totalClosestToZeroSoFar)) {
totalClosestToZeroSoFar = subtotal;
}
}
return totalClosestToZeroSoFar;
};
console.log('final', sumAll(1, 2, 3));
答案 1 :(得分:1)
变量参数要求与算法无关,这似乎是问题的关键。如果需要,可以使用spread syntax代替arguments
。
对于算法,如果参数编号可以为正数或负数,那么一个很好的起点是朴素的蛮力O(2 n )算法。对于每个可能的操作位置,我们在该位置添加一个加号来递归,而在添加负号时分别递归。在备份调用树的过程中,选择最终导致方程最接近零的任何选择。
代码如下:
const closeToZero = (...nums) =>
(function addExpr(nums, total, i=1) {
if (i < nums.length) {
const add = addExpr(nums, total + nums[i], i + 1);
const sub = addExpr(nums, total - nums[i], i + 1);
return Math.abs(add) < Math.abs(sub) ? add : sub;
}
return total;
})(nums, nums[0])
;
console.log(closeToZero(1, 17, 6, 10, 15)); // 1 - 17 - 6 + 10 + 15
现在,问题是这是否正在执行额外的工作。我们可以找到overlapping subproblems吗?如果是这样,我们可以记住以前的答案并在表中查找它们。问题部分在于负数:基于已经针对数组的给定块解决的子问题,如何确定我们是否离目标越来越近并不明显。
我会将其留给读者自己做练习,然后自己考虑一下,但是似乎还有优化的余地。这是related question,在此期间可能会提供一些见识。
答案 2 :(得分:1)
这也称为partition problem的变体,即我们正在寻找将参数划分为两部分之间的最小差异(例如[1,2]和[3之间的差异]为零)。这是一种枚举我们可以创建并选择最小差异的方法:
function f(){
let diffs = new Set([Math.abs(arguments[0])])
for (let i=1; i<arguments.length; i++){
const diffs2 = new Set
for (let d of Array.from(diffs)){
diffs2.add(Math.abs(d + arguments[i]))
diffs2.add(Math.abs(d - arguments[i]))
}
diffs = diffs2
}
return Math.min(...Array.from(diffs))
}
console.log(f(5,3))
console.log(f(1,2,3))
console.log(f(1,2,3,5))
答案 3 :(得分:0)
我花了很多时间在这项技能上,所以在数组中的每个项目之间加上符号。这对我来说似乎是最自然的方法。
const input1 = [1, 2, 3]
const input2 = [1, 2, 3, -4]
const input3 = [-3, 6, 0, -5, 9]
const input4 = [1, 17, 6, 10, 15]
const makeMatrix = (input, row = [{ sign: 1, number: input[0] }]) => {
if(row.length === input.length) return [ row ]
const number = input[row.length]
return [
...makeMatrix(input, row.concat({ sign: 1, number })),
...makeMatrix(input, row.concat({ sign: -1, number }))
]
}
const checkMatrix = matrix => matrix.reduce((best, row) => {
const current = {
calculation: row.map((item, i) => `${i > 0 ? item.sign === -1 ? "-" : "+" : ""}(${item.number})`).join(""),
value: row.reduce((sum, item) => sum += (item.number * item.sign), 0)
}
return best.value === undefined || Math.abs(best.value) > Math.abs(current.value) ? current : best
})
const processNumbers = input => {
console.log("Generating matrix for:", JSON.stringify(input))
const matrix = makeMatrix(input)
console.log("Testing the following matrix:", JSON.stringify(matrix))
const winner = checkMatrix(matrix)
console.log("Closest to zero was:", winner)
}
processNumbers(input1)
processNumbers(input2)
processNumbers(input3)
processNumbers(input4)
答案 4 :(得分:0)
我喜欢加入这个谜语:)
问题可以描述为 f n = f n-1 + a n * x n < / sub> ,其中 x 是 X ,而 a 0 ,...,a n 是 {-1,1}
对于单个情况: X * A = y
对于所有情况 X(*)TA = Y,TA = [A n!,...,A 0 ] < / p>
现在我们有n!不同的A
//consider n < 32
// name mapping TA: SIGN_STATE_GENERATOR, Y: RESULT_VECTOR, X: INPUT
const INPUT = [1,2,3,3,3,1]
const SIGN_STATE_GENERATOR = (function*(n){
if(n >= 32) throw Error("Its working on UInt32 - max length is 32 in this implementation")
let uint32State = -1 >>> 32-n;
while(uint32State){
yield uint32State--;
}
})(INPUT.length)
const RESULT_VECTOR = []
let SIGN_STATE = SIGN_STATE_GENERATOR.next().value
while (SIGN_STATE){
RESULT_VECTOR.push(
INPUT.reduce(
(a,b, index) =>
a + ((SIGN_STATE >> index) & 1 ? 1 : -1) * b,
0
)
)
SIGN_STATE = SIGN_STATE_GENERATOR.next().value
}
console.log(RESULT_VECTOR)