Reorder Data in Log Files - Javascript - javascript

I'm trying to solve the Reorder Data in Log Files algorithm.
You have an array of logs. Each log is a space delimited string of words.
For each log, the first word in each log is an alphanumeric identifier. Then, either:
Each word after the identifier will consist only of lowercase letters, or;
Each word after the identifier will consist only of digits.
We will call these two varieties of logs letter-logs and digit-logs. It is guaranteed that each log has at least one word after its identifier.
Reorder the logs so that all of the letter-logs come before any digit-log. The letter-logs are ordered lexicographically ignoring identifier, with the identifier used in case of ties. The digit-logs should be put in their original order.
Return the final order of the logs.
Example:
Input: logs = ["dig1 8 1 5 1","let1 art can","dig2 3 6","let2 own kit dig","let3 art zero"]
Output: ["let1 art can","let3 art zero","let2 own kit dig","dig1 8 1 5 1","dig2 3 6"]
My idea is having a map for the digits and one for the letters. I have done it. Then, I would need to sort the digits and letters and add all the sorted letters to my answer array and all the sorted digits to my answer array.
var reorderLogFiles = function(logs) {
if(!logs || logs.length === 0)
return [];
let numbers = {
'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6,
'7': 7, '8': 8, '9': 9
};
let digits = new Map();
let letters = new Map();
for(let i=0; i<logs.length; i++) {
const log = logs[i].split(" ");
if(numbers[log[1]] !== undefined)
digits.set(log[0], log.splice(1, log.length));
else
letters.set(log[0], log.splice(1, log.length));
}
// How can I sort letter and digits?
let ans = [];
for(const [key, value] of sortedLetters) {
const temp = key + " " + value.join(" ");
ans.push(temp);
}
for(const [key, value] of sortedDigits) {
const temp = key + " " + value.join(" ");
ans.push(temp);
}
return ans;
};

I think you can simplify your code somewhat. First, create the digits and letters groups by filtering the original logs; this can be made easier by first splitting all the values in logs. Next, sort the letters based on the second value in the array and add the digits to the end of the sorted array. Finally, join the strings back together:
const reorderLogFiles = logs => {
// split values on first space
logs = logs.map(v => v.split(/\s+(.*)/).filter(Boolean));
// filter into digits and letters
let digits = logs.filter(v => v[1].match(/^[\s\d]+$/));
let letters = logs.filter(v => v[1].match(/^[a-z\s]+$/));
// sort the letters
letters.sort((a, b) => (c = a[1].localeCompare(b[1])) ? c : a[0].localeCompare(b[0]));
// reassemble the list
result = letters.concat(digits);
// and convert back to strings
result = result.map(a => a.join(' '));
return result;
}
let logs = ["dig1 8 1 5 1", "let1 art can", "dig2 3 6", "let2 own kit dig", "let3 art zero"];
console.log(reorderLogFiles(logs));
logs = ["a1 9 2 3 1", "g1 act car", "zo4 4 7", "ab1 off key dog", "a8 act zoo", "a2 act car"];
console.log(reorderLogFiles(logs));
Note this code can be written more compactly by chaining operations but I've written it out more fully to make it easier to follow.
If you don't want to use regex, you can test the first character of each substring to see if it's a digit or letter. For example:
let digits = logs.filter(v => v[1][0] >= '0' && v[1][0] <= '9');
let letters = logs.filter(v => v[1][0] >= 'a' && v[1][0] <= 'z');

Related

Find the longest anagram with array javascript

I try to find the longest anagram in Javascript. For this, I have an array with 10 letters and a dictionary that contains every words.
I would like that the program test every combination possible.
We started from 10 (the array length of letters) and we check if it's an anagram
If not, we remove the char at the very end, and we check, if not, we shift the removed char by one to the left... When the entire combinations with 9 letters is tested, we test for 8, 7, 6, 5, 4, 3, 2 letters.
var wordFound = '' // The longest word found
var copyArr = [] // I don't manipulate the lettersChosen array, so I save a copy in copyArr
var savedWord = [] // A copy of copyArr but i'm not sure about this
var lengthLetters = 0 // The length of the numbers left
var lettersChosen = ['A', 'S', 'V', 'T', 'S', 'E', 'A', 'M', 'N'] //This the the array of letters
function isAnagram(stringA, stringB) {
stringA = stringA.toLowerCase().replace(/[\W_]+/g, "");
stringB = stringB.toLowerCase().replace(/[\W_]+/g, "");
const stringASorted = stringA.split("").sort().join("");
const stringBSorted = stringB.split("").sort().join("");
return stringASorted === stringBSorted;
}
function checkForEachWord(arr) {
strLetters = ''
for (i in arr)
strLetters = strLetters + arr[i]
for (var i in file)
if (isAnagram(strLetters, file[i])) {
wordFound = file[i]
return true
}
return false
}
function getOneOfTheLongestWord() {
lettersChosen.forEach(letter => {
copyArr.push(letter) // I copy the array
})
var index = 1 // The index of the letter to remove
var countLetter = 1 // How much letters I have to remove
var position = copyArr.length - index // The actual position to remove
var savedArray = [] // The copy of CopyArr but i'm not sure about that
var iteration = 0 // The total of combination possible
var test = checkForEachWord(copyArr) // I try with 10 letters
if (test == true)
return true // I found the longest word
while (test == false) {
copyArr.splice(position, 1) // I remove the char at current position
index++ // Change letter to remove
if (index > copyArr.length + 1) { // If I hit the first character, then restart from the end
index = 1
countLetter++ // Remove one more letter
}
console.log(copyArr + ' | ' + position)
position = copyArr.length - index // Get the position based on the actual size of the array letters
test = checkForEachWord(copyArr) // Test the anagram
copyArr = [] // Reset array
lettersChosen.forEach(letter => { // Recreate the array
copyArr.push(letter)
})
}
return true // Word found
}
getOneOfTheLongestWord()
My code is not optimal there is so many way to improve it.
Actually my output is good with 9 letters.
copyArr | position
A,S,V,T,S,E,A,M | 8
A,S,V,T,S,E,M,N | 6
A,S,V,T,S,A,M,N | 5
A,S,V,T,E,A,M,N | 4
A,S,V,S,E,A,M,N | 3
A,S,T,S,E,A,M,N | 2
A,V,T,S,E,A,M,N | 1
S,V,T,S,E,A,M,N | 0
But not with 8 letters, I don't see how I can use my countLetter to test all combinations...
Thank you very much.
Short answer, put the sorted versions of dictionary words into a trie, then do an A* search.
Longer answer because you probably haven't encountered those things.
A trie is a data structure which at each point gives you a lookup by character of the next level of the trie. You can just use a blank object as a trie. Here is some simple code to add a word to one.
function add_to_trie (trie, word) {
let letters = word.split('').sort();
for (let i in letters) {
let letter = letters[i];
if (! trie[letter]) {
trie[letter] = {};
}
trie = trie[letter];
}
trie['final'] = word;
}
An A* search simply means that we have a priority queue that gives us the best option to look at next. Rather than implement my own priority queue I will simply use an existing one at flatqueue. It returns the lowest priority possible. So I'll use as a priority one that puts the longest possible word first, and if there is a tie then goes with whatever word we are farthest along on. Here is an implementation.
import FlatQueue from "flatqueue";
function longest_word_from (trie, letters) {
let sorted_letters = letters.sort();
let queue = new FlatQueue();
// Entries will be [position, current_length, this_trie]
// We prioritize the longest word length first, then the
// number of characters. Since we get the minimum first,
// we make priorities negative numbers.
queue.push([0, 0, trie], - (letters.length ** 2));
while (0 < queue.length) {
let entry = queue.pop();
let position = entry[0];
let word_length = entry[1];
let this_trie = entry[2];
if (position == letters.length) {
if ('final' in this_trie) {
return this_trie['final'];
}
}
else {
if (letters[position] in this_trie) {
queue.push(
[
position + 1, // Advance the position
word_length + 1, // We added a letter
this_trie[letters[position]] // And the sub-trie after that letter
],
- letters.length * (
letters.length + position - word_length
) - word_length - 1
);
}
queue.push(
[
position + 1, // Advance the position
word_length, // We didn't add a a letter
this_trie // And stayed at the same position.
],
- letters.length * (
letters.length + position - word_length - 1
) - word_length
);
}
}
return null;
}
If the import doesn't work for you, you can simply replace that line with the code from index.js. Simply remove the leading export default and the rest will work.
And with that, here is sample code that demonstrates it in action.
let file = ['foo', 'bar', 'baz', 'floop'];
let letters = 'fleaopo'.split('')
let this_trie = {};
for (var i in file) {
add_to_trie(this_trie, file[i]);
}
console.log(longest_word_from(this_trie, letters));
If you have a long dictionary, loading the dictionary into the trie is most of your time. But once you've done that you can call it over and over again with different letters, and get answers quite quickly.

Is there a way to avoid number to string conversion & nested loops for performance?

I just took a coding test online and this one question really bothered me. My solution was correct but was rejected for being unoptimized. The question is as following:
Write a function combineTheGivenNumber taking two arguments:
numArray: number[]
num: a number
The function should check all the concatenation pairs that can result in making a number equal to num and return their count.
E.g. if numArray = [1, 212, 12, 12] & num = 1212 then we will have return value of 3 from combineTheGivenNumber
The pairs are as following:
numArray[0]+numArray[1]
numArray[2]+numArray[3]
numArray[3]+numArray[2]
The function I wrote for this purpose is as following:
function combineTheGivenNumber(numArray, num) {
//convert all numbers to strings for easy concatenation
numArray = numArray.map(e => e+'');
//also convert the `hay` to string for easy comparison
num = num+'';
let pairCounts = 0;
// itereate over the array to get pairs
numArray.forEach((e,i) => {
numArray.forEach((f,j) => {
if(i!==j && num === (e+f)) {
pairCounts++;
}
});
});
return pairCounts;
}
console.log('Test 1: ', combineTheGivenNumber([1,212,12,12],1212));
console.log('Test 2: ', combineTheGivenNumber([4,21,42,1],421));
From my experience, I know conversion of number to string is slow in JS, but I am not sure whether my approach is wrong/lack of knowledge or does the tester is ignorant of this fact. Can anyone suggest further optimization of the code snipped?
Elimination of string to number to string will be a significant speed boost but I am not sure how to check for concatenated numbers otherwise.
Elimination of string to number to string will be a significant speed boost
No, it won't.
Firstly, you're not converting strings to numbers anywhere, but more importantly the exercise asks for concatenation so working with strings is exactly what you should do. No idea why they're even passing numbers. You're doing fine already by doing the conversion only once for each number input, not every time your form a pair. And last but not least, avoiding the conversion will not be a significant improvement.
To get a significant improvement, you should use a better algorithm. #derpirscher is correct in his comment: "[It's] the nested loop checking every possible combination which hits the time limit. For instance for your example, when the outer loop points at 212 you don't need to do any checks, because regardless, whatever you concatenate to 212, it can never result in 1212".
So use
let pairCounts = 0;
numArray.forEach((e,i) => {
if (num.startsWith(e)) {
//^^^^^^^^^^^^^^^^^^^^^^
numArray.forEach((f,j) => {
if (i !== j && num === e+f) {
pairCounts++;
}
});
}
});
You might do the same with suffixes, but it becomes more complicated to rule out concatenation to oneself there.
Optimising further, you can even achieve a linear complexity solution by putting the strings in a lookup structure, then when finding a viable prefix just checking whether the missing part is an available suffix:
function combineTheGivenNumber(numArray, num) {
const strings = new Map();
for (const num of numArray) {
const str = String(num);
strings.set(str, 1 + (strings.get(str) ?? 0));
}
const whole = String(num);
let pairCounts = 0;
for (const [prefix, pCount] of strings) {
if (!whole.startsWith(prefix))
continue;
const suffix = whole.slice(prefix.length);
if (strings.has(suffix)) {
let sCount = strings.get(suffix);
if (suffix == prefix) sCount--; // no self-concatenation
pairCounts += pCount*sCount;
}
}
return pairCounts;
}
(the proper handling of duplicates is a bit difficile)
I like your approach of going to strings early. I can suggest a couple of simple optimizations.
You only need the numbers that are valid "first parts" and those that are valid "second parts"
You can use the javascript .startsWith and .endsWith to test for those conditions. All other strings can be thrown away.
The lengths of the strings must add up to the length of the desired answer
Suppose your target string is 8 digits long. If you have 2 valid 3-digit "first parts", then you only need to know how many valid 5-digit "second parts" you have. Suppose you have 9 of them. Those first parts can only combine with those second parts, and give you 2 * 9 = 18 valid pairs.
You don't actually need to keep the strings!
It struck me that if you know you have 2 valid 3-digit "first parts", you don't need to keep those actual strings. Knowing that they are valid 2-digit first parts is all you need to know.
So let's build an array containing:
How many valid 1-digit first parts do we have?,
How many valid 2-digit first parts do we have?,
How many valid 3-digit first parts do we have?,
etc.
And similarly an array containing the number of valid 1-digit second parts, etc.
X first parts and Y second parts can be combined in X * Y ways
Except if the parts are the same length, in which case we are reusing the same list, and so it is just X * (Y-1).
So not only do we not need to keep the strings, but we only need to do the multiplication of the appropriate elements of the arrays.
5 1-char first parts & 7 3-char second parts = 5 * 7 = 35 pairs
6 2-char first part & 4 2-char second parts = 6 * (4-1) = 18 pairs
etc
So this becomes extremely easy. One pass over the strings, tallying the "first part" and "second part" matches of each length. This can be done with an if and a ++ of the relevant array element.
Then one pass over the lengths, which will be very quick as the array of lengths will be very much shorter than the array of actual strings.
function combineTheGivenNumber(numArray, num) {
const sElements = numArray.map(e => "" + e);
const sTarget = "" + num;
const targetLength = sTarget.length
const startsByLen = (new Array(targetLength)).fill(0);
const endsByLen = (new Array(targetLength)).fill(0);
sElements.forEach(sElement => {
if (sTarget.startsWith(sElement)) {
startsByLen[sElement.length]++
}
if (sTarget.endsWith(sElement)) {
endsByLen[sElement.length]++
}
})
// We can now throw away the strings. We have two separate arrays:
// startsByLen[1] is the count of strings (without attempting to remove duplicates) which are the first character of the required answer
// startsByLen[2] similarly the count of strings which are the first 2 characters of the required answer
// etc.
// and endsByLen[1] is the count of strings which are the last character ...
// and endsByLen[2] is the count of strings which are the last 2 characters, etc.
let pairCounts = 0;
for (let firstElementLength = 1; firstElementLength < targetLength; firstElementLength++) {
const secondElementLength = targetLength - firstElementLength;
if (firstElementLength === secondElementLength) {
pairCounts += startsByLen[firstElementLength] * (endsByLen[secondElementLength] - 1)
} else {
pairCounts += startsByLen[firstElementLength] * endsByLen[secondElementLength]
}
}
return pairCounts;
}
console.log('Test 1: ', combineTheGivenNumber([1, 212, 12, 12], 1212));
console.log('Test 2: ', combineTheGivenNumber([4, 21, 42, 1], 421));
Depending on a setup, the integer slicing can be marginally faster
Although in the end it falls short
Also, when tested on higher N values, the previous answer exploded in jsfiddle. Possibly a memory error.
As far as I have tested with both random and hand-crafted values, my solution holds. It is based on an observation, that if X, Y concantenated == Z, then following must be true:
Z - Y == X * 10^(floor(log10(Y)) + 1)
an example of this:
1212 - 12 = 1200
12 * 10^(floor((log10(12)) + 1) = 12 * 10^(1+1) = 12 * 100 = 1200
Now in theory, this should be faster then manipulating strings. And in many other languages it most likely would be. However in Javascript as I just learned, the situation is a bit more complicated. Javascript does some weird things with casting that I haven't figured out yet. In short - when I tried storing the numbers(and their counts) in a map, the code got significantly slower making any possible gains from this logarithm shenanigans evaporate. Furthermore, storing them in a custom-crafted data structure isn't guaranteed to be faster since you have to build it etc. Also it would be quite a lot of work.
As it stands this log comparison is ~ 8 times faster in a case without(or with just a few) matches since the quadratic factor is yet to kick in. As long as the possible postfix count isn't too high, it will outperform the linear solution. Unfortunately it is still quadratic in nature with the breaking point depending on a total number of strings as well as their length.
So if you are searching for a needle in a haystack - for example you are looking for a few pairs in a huge heap of numbers, this can help. In the other case of searching for many matches, this won't help. Similarly, if the input array was sorted, you could use binary search to push the breaking point further up.
In the end, unless you manage to figure out how to store ints in a map(or some custom implementation of it) in a way that doesn't completely kill the performance, the linear solution of the previous answer will be faster. It can still be useful even with the performance hit if your computation is going to be memory heavy. Storing numbers takes less space then storing strings.
var log10 = Math.log(10)
function log10floored(num) {
return Math.floor(Math.log(num) / log10)
}
function combineTheGivenNumber(numArray, num) {
count = 0
for (var i=0; i!=numArray.length; i++) {
let portion = num - numArray[i]
let removedPart = Math.pow(10, log10floored(numArray[i]))
if (portion % (removedPart * 10) == 0) {
for (var j=0; j!=numArray.length; j++) {
if (j != i && portion / (removedPart * 10) == numArray[j] ) {
count += 1
}
}
}
}
return count
}
//The previous solution, that I used for timing, comparison and check purposes
function combineTheGivenNumber2(numArray, num) {
const strings = new Map();
for (const num of numArray) {
const str = String(num);
strings.set(str, 1 + (strings.get(str) ?? 0));
}
const whole = String(num);
let pairCounts = 0;
for (const [prefix, pCount] of strings) {
if (!whole.startsWith(prefix))
continue;
const suffix = whole.slice(prefix.length);
if (strings.has(suffix)) {
let sCount = strings.get(suffix);
if (suffix == prefix) sCount--; // no self-concatenation
pairCounts += pCount*sCount;
}
}
return pairCounts;
}
var myArray = []
for (let i =0; i!= 10000000; i++) {
myArray.push(Math.floor(Math.random() * 1000000))
}
var a = new Date()
t1 = a.getTime()
console.log('Test 1: ', combineTheGivenNumber(myArray,15285656));
var b = new Date()
t2 = b.getTime()
console.log('Test 2: ', combineTheGivenNumber2(myArray,15285656));
var c = new Date()
t3 = c.getTime()
console.log('Test1 time: ', t2 - t1)
console.log('test2 time: ', t3 - t2)
Small update
As long as you are willing to take a performance hit with the setup and settle for the ~2 times performance, using a simple "hashing" table can help.(Hashing tables are nice and tidy, this is a simple modulo lookup table. The principle is similar though.)
Technically this isn't linear, practicaly it is enough for the most cases - unless you are extremely unlucky and all your numbers fall in the same bucket.
function combineTheGivenNumber(numArray, num) {
count = 0
let size = 1000000
numTable = new Array(size)
for (var i=0; i!=numArray.length; i++) {
let idx = numArray[i] % size
if (numTable[idx] == undefined) {
numTable[idx] = [numArray[i]]
} else {
numTable[idx].push(numArray[i])
}
}
for (var i=0; i!=numArray.length; i++) {
let portion = num - numArray[i]
let removedPart = Math.pow(10, log10floored(numArray[i]))
if (portion % (removedPart * 10) == 0) {
if (numTable[portion / (removedPart * 10) % size] != undefined) {
let a = numTable[portion / (removedPart * 10) % size]
for (var j=0; j!=a.length; j++) {
if (j != i && portion / (removedPart * 10) == a[j] ) {
count += 1
}
}
}
}
}
return count
}
Here's a simplified, and partially optimised approach with 2 loops:
// let's optimise 'combineTheGivenNumber', where
// a=array of numbers AND n=number to match
const ctgn = (a, n) => {
// convert our given number to a string using `toString` for clarity
// this isn't entirely necessary but means we can use strict equality later
const ns = n.toString();
// reduce is an efficient mechanism to return a value based on an array, giving us
// _=[accumulator], na=[array number] and i=[index]
return a.reduce((_, na, i) => {
// convert our 'array number' to an 'array number string' for later concatenation
const nas = na.toString();
// iterate back over our array of numbers ... we're using an optimised/reverse loop
for (let ii = a.length - 1; ii >= 0; ii--) {
// skip the current array number
if (i === ii) continue;
// string + number === string, which lets us strictly compare our 'number to match'
// if there's a match we increment the accumulator
if (a[ii] + nas === ns) ++_;
}
// we're done
return _;
}, 0);
}

converting a string with commas to a array with commas (or even just a number with commas

I'm using JS inside MaxMSP
I would like to take a string output (of separated by comma single fibonacci numbers) and convert it to either an array or number, keeping the space and commas but getting ride of the "double quotes"
I've tried a bunch of stuff here but unfortunately I'm still banging my head against the wall.
autowatch = 1;
inlets = 2;
outlets = 2;
function msg_int(num){
var a = 1;
var b = 0, temp;
while (num >= 0){
temp = a;
a = a + b;
b = temp;
num--;
outlet(0, b);
var n = temp;
digits = n.toString().replace(/(\d{1})/g,'$1, ')
outlet(1, digits);
}
return b;
}
The Input number is 14
The first outlet(0) number is 610
The second outlet(1) is "6, 1, 0, "
I would like the second outlet to be 6, 1, 0,
var result = '"6, 1, 0,"'.replace(/['"]+/g, '')
console.info(result);
It looks like you want outlet(1) to output a Max “list” as opposed to a symbol. outlet will handle that for you, but you need to hand it an array to achieve this. Here’s what it says in the docs:
If the argument to outlet is an array, it is unrolled (to one level only) and passed as a Max message or list (depending on whether the first element of the array is a number or string).
Knowing this, you need to convert digits into an array before passing it to outlet:
var n = temp;
var digits = n.toString(); // convert 610 to "610" (for example)
digits.split(""); // split "610" into ["6", "1", "0"]
outlet(1, digits);
Whether this a Max list or message depends on the type of the first element, so if you need a list of numbers (integers in your case), you could do something like this before passing it to outlet:
// map ["6", "1", "0"] to [6, 1, 0]
digits = digits.map(function (i) { return parseInt(i) });

How to optimize this function generating numbers III?

I am writing a function for the codewars problem below:
"We want to generate all the numbers of three digits that:
the value of adding their corresponding ones(digits) is equal to 10.
their digits are in increasing order (the numbers may have two or more equal contiguous digits)
The numbers that fulfill the two above constraints are: 118, 127, 136, 145, 226, 235, 244, 334
Make a function that receives two arguments:
the sum of digits value
the amount of desired digits for the numbers
The function should output an array with three values: [1,2,3]
1 - the total amount of all these possible numbers
2 - the minimum number
3 - the maximum numberwith
The example given above should be:
findAll(10, 3) ---> [8, "118", "334"]
If we have only one possible number as a solution, it should output a result like the one below:
findAll(27, 3) ---> [1, "999", "999"]
If there are no possible numbers, the function should output the empty array.
findAll(84, 4) ---> []
The number of solutions climbs up when the number of digits increases.
findAll(35, 6) ---> [123, '116999', '566666']
"
My code returns the result for each of the 4 test but it seems that it is not optimized enough. It takes too much time, so could anyone please let me know what I could do to improve it?
function findAll(n, k) {
let finalArray = [];
let startingNum = 1 + Array(k).join(0)
let finishNum = startingNum * 10;
for (let i = startingNum; i<finishNum; i++){
if (i.toString().split("").reduce((a,b) => Number(a) + Number(b)) === n){
let arr = i.toString().split('')
let newArr = arr.filter((element, index) => {
if (index !== arr.length) {
return element <= arr[index+1] || index === arr.length-1;
}})
if (newArr.length === arr.length) {
if (!finalArray[0]){
finalArray[0]=1
}
else finalArray[0]++
if (!finalArray[1]){
finalArray.push(i)
}
if (!finalArray[2]) {
finalArray.push(i)
} else {
finalArray.pop();
finalArray.push(i)
}}
}}
if (finalArray[1] && finalArray[2]){
finalArray[1] = finalArray[1].toString();
finalArray[2] = finalArray[2].toString();
}
return finalArray;
}
findAll(35, 6)

Why does this recursive function skip numbers?

I'm trying to find the various possibilities to equal 100 with digits 1-9. This function produces the desired results, but also others which I had not intended. The other results add up to 100, but without some of these digits, like leaving out 3 or 6. Why are these other results included?
var nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
var signs = ["+", "-", "N"];
var results = [];
find100("1");
function find100(expr) {
if (eval(expr.replace(/N/g, "")) === 100) {
results.push(expr);
} else {
for (var i = eval(expr.substring(expr.length - 1, expr.length)) + 1; i <=
nums.length; i++) {
signs.forEach(function(sign) {
var expr2 = expr;
find100(expr2 += sign + i);
});
}
}
}
Desired output:
1+2+3-4+5+6+78+9,
1+2+34-5+67-8+9,
1+23-4+5+6+78-9,
1+23-4+56+7+8+9,
12+3+4+5-6-7+89,
12+3-4+5+67+8+9,
12-3-4+5-6+7+89,
123+4-5+67-89,
123+45-67+8-9,
123-4-5-6-7+8-9,
123-45-67+89
It's adding undesired results because your first loop iterates through each of the remaining numbers and adds ANY results that evaluate to 100, even if it has skipped a number to do so. If the method finds a solution for a number it adds the solution to results - which is correct, however if it doesn't find a solution it moves onto the next number anyway. This is the source of the skipped numbers. If there was no solution for a number it should have not continued to the next number.
As to how to fix it, that's a different question (but why not ...)
The difference here is that you can ONLY get a result if for any number there exists an expression that uses all remaining numbers.
var results = [];
var operations = [ "+", "-", "" ];
var expected = 100;
var limit = 10;
function findExpression(expr, next) {
if (next === limit) {
eval(expr) === expected && results.push(expr);
} else {
operations.forEach(function(operation) {
findExpression(expr + operation + next, next + 1);
});
}
}
$(document).ready(function() {
findExpression("1", 2);
for(var i=0; i<results.length; i++) {
$("#foo").append(results[i]+"<br />");
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<body>
<div id="foo"></div>
</body>
The reason that some digits are skipped is in this loop:
for (var i = eval(expr.substring(expr.length - 1, expr.length)) + 1; i <=
nums.length; i++) {
On the second iteration it will increment that last digit in the expression, which will therefore create a gap in the continued recursion. In short, that loop should not be there.
I would suggest a solution without using eval, not because it would be somehow dangerous, but because it is responsible for a major performance hit.
Instead you could keep a numerical variable updated to what the expression represents. In fact, I suggest to use two such variables, one for the sum of the previous terms, and another for the last term, because that one might need to still be extended with more digits.
To facilitate the different way the signs influence the expression, I have defined a function per sign: it takes the above mentioned numerical values, and also the last digit, and returns the updated values.
Here is a working snippet (ES6 syntax) using that idea, and you'll notice the dramatic performance improvement:
function find100(digits, signs) {
const loop = (expr, i, [sum, value]) =>
// Not yet all digits used?
i < digits.length ?
// Apply each of the signs in turn:
Object.keys(signs).reduce( (results, sign) =>
// Recurse, passing on the modified expression, the sum of the
// preceding terms, and the value of the last term. As '+' is
// not any different than '' before the first digit, skip '+':
sign != '+' || i ?
results.concat(loop(expr+sign+digits[i], i+1,
signs[sign](sum, value, digits[i]))) :
results,
[] ) :
// All digits were used. Did it match?
sum+value == 100 ? [expr] : [];
// Start recursion
return loop('', 0, [0, 0]);
}
var nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
// define how each sign should modify the expression value:
var signs = {
'+': (sum, value, digit) => [sum+value, digit],
'-': (sum, value, digit) => [sum+value, -digit],
'' : (sum, value, digit) => [sum, value*10 + (value<0 ? -digit : digit)]
};
var results = find100(nums, signs);
console.log(results);
Note that this also outputs the following expression:
-1+2-3+4+5+6+78+9
This is because the code also tries the signs before the first digit. I thought it would be relevant to have this also included in the output.

Categories

Resources