Skip to content

Instantly share code, notes, and snippets.

@AurelioDeRosa
Created January 17, 2016 17:51
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AurelioDeRosa/abe9cb4e1cb64269cdea to your computer and use it in GitHub Desktop.
Save AurelioDeRosa/abe9cb4e1cb64269cdea to your computer and use it in GitHub Desktop.
Sum stings representing numbers
/*
* Exercise:
*
* Given the string representations of a variable number of integers,
* write a function that returns the string representation of the sum of
* those integers. The function must return the correct result even
* if the sum of the numbers exceeds the maximum number allowed
* (Number.MAX_VALUE). In addition, it must work with negative integers.
*/
/**
* Sums a variable number of stings representing numbers
*
* @param {string[]} [strings] The strings to sum
*
* @return {string}
*/
function sumStrings() {
// The idea of this function is to turn each string
// provided into an array of single-character strings
// representing digits, and sum them. The function
// calculates the final result by summing the strings
// two at a time.
//
// Some special considerations are made for the sum
// of negative numbers and a possible carry from
// the sum of the single digits of the numbers.
/**
* Sums two stings representing numbers
*
* @param {string} a The first string
* @param {string} b The second string
*
* @return {string}
*/
function sum(a, b) {
// Calculate the sign of the numbers and stores
// it as an integer. If the number is positive or
// zero the value stored is 1; otherwise it's -1.
//
// The sign of the numbers is crucial for the sum
// of the individual digits. If the sign of the
// numbers is different, the algorithm has to
// perform a subtraction instead of an addition.
var aSign = +a >= 0 ? 1 : -1;
var bSign = +b >= 0 ? 1 : -1;
// Based on the signs of the numbers, the operation
// will be changed. This is important because if the
// operation to perform is a subtraction, there are
// slightly harder cases to manage.
//
// For example if the first number is negative and
// the second positive with an absolute value greater
// than the first, there are more considerations
// and adjustments to make before proceeding with the
// operation.
if (aSign < 0) {
// If both numbers are negative, the result is equal
// to the opposite (or additive inverse) of the sum
// of the numbers.
//
// If the first number is negative and the second is
// positive, the addition can be transformed into
// a subtraction. A special case of this operation is
// represented by the example described above for which
// a further adjustment is performed.
if (bSign < 0) {
return '-' + sum(a.replace('-', ''), b.replace('-', ''));
} else {
return sum(b, a);
}
}
// Remove the sign from the numbers (if present)
// and convert them into reversed arrays.
// This change makes the sum of the numbers easier
// because they will be aligned starting from
// the last digit.
//
// Examples:
// "145" is turned into ["5", "4", "1"]
// "-39" is turned into ["9", "3"]
var aArr = a.replace('-', '')
.split('')
.reverse();
var bArr = b.replace('-', '')
.split('')
.reverse();
// The variable in which the carry of the individual
// sums is stored. This is needed for a correct
// calculation of the result.
//
// Examples:
// 5 + 9 results in a carry of 1
// 3 + 2 results in a carry of 0
var carry = 0;
// When calculating a sum between a positive and
// a negative number, the operation performed
// is actually a difference. This operation
// requires that a number smaller than the one to
// subtract "borrows" a unit from the next digit,
// if such digit exists.
//
// Examples:
// 35 - 28
// The first calculation is
// 5 - 8
// which is turned into
// 15 - 8
// that gives 7.
// The second and last operation is
// 2 - 2
var decimalCarry = 0;
// Determine when to stop the sums by calculating
// the number with most digits.
var maxLength = Math.max(aArr.length, bArr.length);
// The array in which the results of the individual
// sums are stored.
var result = [];
for (var i = 0; i < maxLength; i++) {
// If a decimal carry was used in the previous
// operation between two digits, reset it to zero
// and decrease by the current digit because it
// "borrowed" a unit.
if (decimalCarry === 10) {
decimalCarry = 0;
aArr[i]--;
}
// Determine if the current digit of the first number
// needs to "land" one unit.
if (aArr[i] < bArr[i] && aArr[i + 1] !== undefined) {
decimalCarry = 10;
}
// Calculate the sum between the two digits taking
// into account any carry from a previous sum and
// the decimal carry.
var digitsSum = decimalCarry + (+aArr[i] || 0) + (bSign * (+bArr[i] || 0)) + carry;
// If the sum is less than zero, the second number
// is negative. Therefore, to avoid complex branching
// of the algorithm, the signs of both the numbers
// are reverted. Because of this operation, the result
// is reverted as well (by prepending the minus sign).
if (digitsSum < 0) {
if (aSign < 0) {
return '-' + sum(b.replace('-', ''), a.replace('-', ''));
} else {
return '-' + sum(b.replace('-', ''), '-' + a);
}
}
// Store in the result array the value of the calculation,
// removing any carry.
result.push(digitsSum % 10);
// Store the carry derived from the sum performed.
carry = Math.floor(digitsSum / 10);
}
// If there is any carry left, add it to the final result.
if (carry !== 0) {
result.push(carry);
}
// The result is stored into an array in reverse order.
// So, the returned value must be reversed. Then, it
// must be turned into a string and any trailing
// (non-significant) zero must be removed.
//
// If the trim operation leaves an empty string, return zero.
return result.reverse()
.join('')
.replace(/^0+/, '') || '0';
}
// If no arguments are provided, default the result to zero.
if (arguments.length === 0) {
return '0';
}
// Calculate the sum of the arguments two at a time.
return [].reduce.call(
arguments,
function(previous, current) {
return sum(previous, current);
},
'0'
);
}
/**
* Strictly compares two values.
*
* Returns <code>true</code> if the values are stricly equal;
* <code>false</code> otherwise.
*
* @param {*} actual The first value
* @param {*} expected The second value
* @param {string} [message] A message to display
*
* @return {boolean}
*/
function strictEqual(actual, expected, message) {
if (actual === expected) {
console.log((message ? message : actual + ' is equal to ' + expected) + ': OK!');
} else {
console.warn((message ? message + ': ' : '') + 'Expected ' + actual + ' to be ' + expected);
}
return actual === expected;
}
function testSumStrings() {
strictEqual(sumStrings(), '0', 'No arguments');
strictEqual(sumStrings('5'), '5', 'One argument');
strictEqual(sumStrings('16', '29'), '45', 'Two arguments with both positive');
strictEqual(sumStrings('-22', '19'), '-3', 'Two arguments with the first negative and greater than the positive');
strictEqual(sumStrings('-12', '19'), '7', 'Two arguments with the first negative and less than the positive');
strictEqual(sumStrings('19', '-22'), '-3', 'Two arguments with the second negative and greater than the positive');
strictEqual(sumStrings('29', '-12'), '17', 'Two arguments with the second negative and less than the positive');
strictEqual(sumStrings('-16', '-29'), '-45', 'Two arguments with both negative');
strictEqual(sumStrings('-122', '19'), '-103', 'Two arguments with digits to carry');
strictEqual(sumStrings('1', '2', '3', '4', '5'), '15', 'Multiple arguments');
strictEqual(sumStrings('15', '-12', '24', '-7', '-9'), '11', 'Multiple arguments with a mix of positive and negative integers');
strictEqual(sumStrings('10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', '10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), '20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', 'Two arguments whose sum exceeds the maximum number');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment