是否可以降低此 Javascript 算法解决方案的复杂性和意大利面条质量?

Is possible to reduce the complexity and spaghetti quality of this Javascript algorithm solution?

问题: 创建一个将两个参数相加的函数。 如果只提供一个参数,那么 return 一个需要一个参数的函数并且 return 是总和。

例如,addTogether(2, 3) 应该 return 5,而 addTogether(2) 应该 return 一个函数。

使用单个参数调用此 returned 函数将 return 求和: var sumTwoAnd = addTogether(2); sumTwoAnd(3) returns 5.

如果任一参数不是有效数字,return未定义。

解决方案应该return:

addTogether(2, 3) 应该 return 5。 addTogether(2)(3) 应该 return 5. addTogether(2, "3") 应该 return 未定义。 addTogether(2)([3]) 应该 return 未定义。

我尽我所能,但唯一有效的,据称是迄今为止最好的解决方案如下:

function addTogether() {
  "use strict";
  // check if argument(s) valid number
  var validateNum = function(num) {
    if(typeof num !== 'number') {
      return undefined;
    } else
      return num;
  };
  // is there is one argument or two
  if(arguments.length > 1) {
    var a = validateNum(arguments[0]);
    var b = validateNum(arguments[1]);
    if(a === undefined || b === undefined) {
      return undefined;
    } else {
      return a + b;
    }
  // if only one argument, return function that expects one argument and returns sum.
  } else {
    var c = arguments[0];
    // start here
    if(validateNum(c)) {
      return function(arg2) {
        if(c === undefined || validateNum(arg2) === undefined) {
          return undefined;
        } else {
          return c + arg2;
        }
      }; // belongs to return function(arg2) {}
    }
  }
}

addTogether(2)(3);

为什么不为函数提供参数。我认为这是最简单的方法:

function add(arg1, arg2) {

    if (isNan(arg1) || isNan(arg2)) return undefined;

    if (!arg2) {
        return functionName(arg1);
    }

    return arg1 + arg2;
}

我想你可以这样做。如果只提供一个参数,它将 return 当前函数,但绑定第一个参数,这样你只需要提供额外的参数

'use strict';

function isNumeric(arg) {
  return typeof arg === 'number' && !isNaN(arg);
}

function sum(a, b) {
  if (arguments.length > 2) {
    return undefined;
  }
  if (isNumeric(a) && isNumeric(b)) {
    return a + b;
  }
  if (typeof b === 'undefined' && arguments.length === 1) {
    if (typeof a === 'undefined') {
      return undefined;
    } else if (isNumeric(a)) {
      return sum.bind(this, a); // returns the sum function with the a argument bound to the current a argument
    }
  }
  return undefined;
}

function test() {
  var args = '',
    result = sum,
    value;
  Array.prototype.forEach.call(arguments, function(argument) {
    value = argument;
    if (!argument) {
      argument = 'undefined';
    }
    args += '(';
    if (Array.isArray(argument)) {
      args += Array.prototype.join.call(argument, ', ');
    } else {
      args += argument.toString();
    }
    args += '}';
    if (typeof result === 'function') {
      if (Array.isArray(value)) {
        result = result.apply({}, value);
      } else {
        result = result.call({}, value);
      }
    }
  });
  console.log(`sum${args} = ${result}`);
}

test(2);
test([2, 3]);
test(2, 3);
test([2, undefined], 3);
test([2, "3"]);
test(2, [
  [3]
]);
test(2, "3");
test(2, [3]);
test([NaN, 2]);

function addTogether(a, b) {
  if (typeof a == "number") {
    if (arguments.length == 1) {
      return b => addTogether(a, b);
    } else if (typeof b == "number") {
      return a + b;
    } 
  }
}

// as per OP's code
// returns 3
console.log("addTogether(1, 2) = " + addTogether(1, 2));
console.log("addTogether(1, 2, 3) = " + addTogether(1, 2, 3));
console.log("addTogether(1)(2) = " + addTogether(1)(2));
console.log("addTogether(1)(2, 3) = " + addTogether(1)(2, 3));
console.log("addTogether(1, 2, '3') = " + addTogether(1, 2, '3'));
console.log("addTogether(1)(2, '3') = " + addTogether(1)(2, '3'));
console.log("addTogether(1, 2, [3]) = " + addTogether(1, 2, [3]));
console.log("addTogether(1)(2, [3]) = " + addTogether(1)(2, [3]));
console.log("addTogether(1, 2, NaN) = " + addTogether(1, 2, NaN));
console.log("addTogether(1)(2, NaN) = " + addTogether(1)(2, NaN));
// returns NaN
console.log("addTogether(1, NaN) = " + addTogether(1, NaN));
console.log("addTogether(1)(NaN) = " + addTogether(1)(NaN));
// returns undefined
console.log("addTogether() = " + addTogether());
console.log("addTogether(1)() = " + addTogether(1)());
console.log("addTogether('1') = " + addTogether('1'));
console.log("addTogether(1, '2') = " + addTogether(1, '2'));
console.log("addTogether(1)('2') = " + addTogether(1)('2'));
console.log("addTogether(1, [2]) = " + addTogether(1, [2]));
console.log("addTogether(1)([2]) = " + addTogether(1)([2]));

建议进行以下改进,但它们会改变 OP 代码的语义:

  • return undefined 如果 abNaN,因为 NaN 不是 'valid number'
  • return undefined 如果提供了两个以上的参数而不是默默地删除它们(感谢@PatrickRoberts)

如果您不介意 return为 e 编写一个函数。 G。 addTogether('x'),使用:

function addTogether(a, b) {
  if (arguments.length == 1) {
    return b => addTogether(a, b);
  } else if (typeof a == "number" && typeof b == "number") {
    return a + b;
  }
}

这样,您将始终 return 一个参数的 function 和两个或更多参数的 Numberundefined = 更健壮的代码。

为了 ES5 兼容性,如果您不介意 addTogether(2)() return 函数,请将 b => addTogether(a, b) 替换为 addTogether.bind(undefined, a)(感谢@PatrickRoberts)。

您可以使用扩展运算符来改进您的函数和一些 Array 函数,例如 somereduce :

这样 addTogether 可以接受多个参数。 如果 addTogether 以一个参数调用,返回的函数也可以用多个参数调用。

let isNotNumber = number=> typeof number != 'number';

let addTogether = function(...numbers){
  if(!numbers.length) return;
  if(numbers.length == 1){
    if(isNotNumber(numbers[0])) return;
    return function(...otherNumbers){
      if(otherNumbers.some(isNotNumber)) return;
      return otherNumbers.reduce((prev, curr)=> prev + curr, numbers[0]);
    }
  } else {
    if(numbers.some(isNotNumber)) return;
    return numbers.reduce((prev, curr)=> prev + curr);
  }
}

// Will return a value
console.log(addTogether(1,2,3));
console.log(addTogether(1)(2,3));

// Will return undefined
console.log(addTogether(1, [2]));
console.log(addTogether(1)('2'));
console.log(addTogether(1)([2]));
console.log(addTogether());

根据 jscomplexity.org, OP's function has a cyclomatic complexity of 8 while the solution below have a cyclomatic complexity of 5 (based on the Babel ES5 transpilation).

此解决方案在功能上等同于 OP 的代码,请参见下面的测试:

'use strict';

function addTogether(...augends) {
  if (augends.slice(0, 2).every(value => typeof value === 'number')) {
    switch (augends.length) {
    case 0:
      return;
    case 1:
      return (addend) => addTogether(augends[0], addend);
    default:
      return augends[0] + augends[1];
    }
  }
}

// should work (returns 3)
console.log("addTogether(1, 2) = " + addTogether(1, 2));
console.log("addTogether(1, 2, 3) = " + addTogether(1, 2, 3));
console.log("addTogether(1)(2) = " + addTogether(1)(2));
console.log("addTogether(1)(2, 3) = " + addTogether(1)(2, 3));
console.log("addTogether(1, 2, '3') = " + addTogether(1, 2, '3'));
console.log("addTogether(1)(2, '3') = " + addTogether(1)(2, '3'));
console.log("addTogether(1, 2, [3]) = " + addTogether(1, 2, [3]));
console.log("addTogether(1)(2, [3]) = " + addTogether(1)(2, [3]));
console.log("addTogether(1, 2, NaN) = " + addTogether(1, 2, NaN));
console.log("addTogether(1)(2, NaN) = " + addTogether(1)(2, NaN));
// should return NaN (not sure if this "works" or not)
console.log("addTogether(1, NaN) = " + addTogether(1, NaN));
console.log("addTogether(1)(NaN) = " + addTogether(1)(NaN));
// should not work (returns undefined)
console.log("addTogether() = " + addTogether());
console.log("addTogether(1)() = " + addTogether(1)());
console.log("addTogether('1') = " + addTogether('1'));
console.log("addTogether(1, '2') = " + addTogether(1, '2'));
console.log("addTogether(1)('2') = " + addTogether(1)('2'));
console.log("addTogether(1, [2]) = " + addTogether(1, [2]));
console.log("addTogether(1)([2]) = " + addTogether(1)([2]));

作为参考,这是我的另一个解决方案,它禁止无关参数并且还针对文字 NaN 值(具有讽刺意味的是 typeof "number")进行测试。 编辑 不幸的是,由于修复了测试用例 console.log("addTogether(1)() = " + addTogether(1)()); 的实现,它现在的圈复杂度为 7:

'use strict';

function addTogether(...augends) {
  if (augends.every(value => typeof value === 'number' && !isNaN(value))) {
    switch (augends.length) {
    case 1:
      return (addend, ...addends) => addTogether(augends[0], addend, ...addends);
    case 2:
      return augends[0] + augends[1];
    }
  }
}

// should work (returns 3)
console.log("addTogether(1, 2) = " + addTogether(1, 2));
console.log("addTogether(1)(2) = " + addTogether(1)(2));
// should not work (returns undefined)
console.log("addTogether() = " + addTogether());
console.log("addTogether(1)() = " + addTogether(1)());
console.log("addTogether('1') = " + addTogether('1'));
console.log("addTogether(1, 2, 3) = " + addTogether(1, 2, 3));
console.log("addTogether(1, '2') = " + addTogether(1, '2'));
console.log("addTogether(1)('2') = " + addTogether(1)('2'));
console.log("addTogether(1, [2]) = " + addTogether(1, [2]));
console.log("addTogether(1)([2]) = " + addTogether(1)([2]));
console.log("addTogether(1)(2, 3) = " + addTogether(1)(2, 3));
console.log("addTogether(1, 2, '3') = " + addTogether(1, 2, '3'));
console.log("addTogether(1)(2, '3') = " + addTogether(1)(2, '3'));
console.log("addTogether(1, 2, [3]) = " + addTogether(1, 2, [3]));
console.log("addTogether(1)(2, [3]) = " + addTogether(1)(2, [3]));
console.log("addTogether(1, 2, NaN) = " + addTogether(1, 2, NaN));
console.log("addTogether(1)(2, NaN) = " + addTogether(1)(2, NaN));
console.log("addTogether(1, NaN) = " + addTogether(1, NaN));
console.log("addTogether(1)(NaN) = " + addTogether(1)(NaN));

您应该阅读有关函数柯里化、绑定、调用和应用的内容。 Currying 适用于 ES6,但绑定、调用和应用 运行 无处不在。

MDN 站点上的详细信息 https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind

简而言之,您正在寻找的解决方案是:

function addTogether(a,b) {
    return a+b;
}
console.log(typeof addTogether); //-->function
console.log(typeof addTogether.bind(null,2)); //-->function
console.log(addTogether.bind(null,2)(3));//5
console.log(addTogether(2,3)); //-->5

是啊,就是这么简单,不是开玩笑!

这不是那么短,但我克服了挑战,我很高兴避免分享我的解决方案:

function addTogether(...args) {
  if (args.length == 0) {
    //there is no args provided to the function .. lets return undefined
    return undefined;
  } else {
    //so there are arguments provided to the function
    if (args.length >= 2) {
      //there are two or more arguments provided to the function, lets consider only the first two arguments
      if (typeof(args[0]) != "number" || typeof(args[1]) != "number") {
        //so at least one of the first two arguments is not a number, lets return undefined
        return undefined;
      } else {
        //so both of the first two arguments are numbers, lets return their sum
        return args[0] + args[1];
      }
    } else { //handling input of one argument
      if (typeof(args[0]) != "number") {
        //the only argument provided is not even a number, lets return undefined
        return undefined;
      } else {
        //lets return a function that will wait for another argument 'y' to be summed with the currently provided argument 'args[0]'
        return function(y) {
          //first lets check if the second argument newly provided is a number or not
          if (typeof(y) != "number") return undefined;
          else return args[0] + y;
        };
      }
    }
  }
}


console.log(addTogether(2, 3)); // should return 5.
console.log(addTogether(2)(3)); // should return 5.
console.log(addTogether(2, "3")); // should return undefined.
console.log(addTogether(2)([3])); // should return undefined.