Synaptic / Neataptic simple NEAT XOR solution - javascript

EDIT: I managed to get together couple of simple examples https://github.com/developer239/neural-network-playground
Could anyone help me with simple neat example teaching the net how to solve XOR or some other similar problem? But using the NEAT technique so that I would not have to specify training data set?
Using javascript: https://github.com/cazala/synaptic or https://github.com/wagenaartje/neataptic
1. Initialize network
2. Generate generation
3. Go through each genome in generation and evaluate its fitness (how good it is)
4. Take 2 best genomes from generation
5. Merge genomes 50 / 50 at random
6. Mutate final genome
7. Generate second generation
This would be extremely helpful. Same teqnique is being used here:
https://github.com/ivanseidel/IAMDinosaur
https://www.youtube.com/watch?v=P7XHzqZjXQs
I went through the source code but there is WAY to much stuff going on. I understand the general idea. However I have no idea how to implement the solution.
Thank you :)

There is an example on Neataptic's README.md.
// this network learns the XOR gate (through neuro-evolution)
var network = new Network(2,1);
var trainingSet = [
{ input: [0,0], output: [0] },
{ input: [0,1], output: [1] },
{ input: [1,0], output: [1] },
{ input: [1,1], output: [0] }
];
await network.evolve(trainingSet, {
equal: true,
error: 0.03
});
Neataptic has it all built-in so all you have to provide is a data set. If you need more info on how this has been set up, read this article.
For problems with dynamic solutions, a custom loop and fitness function has to be implemented.

I know this is quite an old question, and neataptic isn't very widely used, but I found a much simpler way to implement this so i thought i'd share
const { Neat } = require('neataptic');
function fitness(network) {
let score = 0;
score += Math.abs(0 - network.activate([0,0])[0]);
score += Math.abs(1 - network.activate([0,1])[0]);
score += Math.abs(1 - network.activate([1,0])[0]);
score += Math.abs(0 - network.activate([1,1])[0]);
score = -score;
return score;
}
(async () => {
const neat = new Neat(2 ,1 ,fitness, {});
for (let i = 0; i < 10000; i++) {
await neat.evolve();
}
const fittest = neat.getFittest();
console.log(fitness(fittest));
console.log(fittest.activate([0,0]));
console.log(fittest.activate([0,1]));
console.log(fittest.activate([1,0]));
console.log(fittest.activate([1,1]));
})();

I managed to write my own solution. You can find it here: https://github.com/developer239/neural-network-playground/tree/master/neatXOR
The main difference from the solution in documentation is that in genetic.js you can dynamically change the learning process.
This is entry file:
const genetic = require('./genetic')
genetic.generateRandomPopulation()
for (let iteration = 0; iteration < 1000; iteration += 1) {
genetic.live()
genetic.evolve()
}
const genom = genetic.neat.population[0]
console.log(`
Result for genom with index 0 in the newest population. Note that selection / mutation happened
after we called last evolve function so this is not necessarily the best genome in the population.
[0, 0] = ${genom.activate([0, 0])} (should be 0)
[1, 1] = ${genom.activate([1, 1])} (should be 0)
[0, 1] = ${genom.activate([0, 1])} (should be 1)
[1, 0] = ${genom.activate([1, 0])} (should be 1)
`)
This is genetic.js file:
const { Neat, architect } = require('neataptic')
module.exports = {
neat: null, // https://wagenaartje.github.io/neataptic/docs/neat/
possibleInputs: [
[0, 0], // expected output 0
[1, 1], // expected output 0
[0, 1], // expected output 1
[1, 0], // expected output 1
],
generateRandomPopulation: function () {
this.neat = new Neat(
2, // number of inputs
1, // number of outputs
null, // fitnessFunction - in this example we are calculating fitness inside live method
{
elitism: 5, // this sets how many genomes in population will be passed into next generation without mutation https://www.researchgate.net/post/What_is_meant_by_the_term_Elitism_in_the_Genetic_Algorithm
mutationRate: 0.3, // sets the mutation rate. If set to 0.3, 30% of the new population will be mutated. Default is 0.3
network: // https://wagenaartje.github.io/neataptic/docs/architecture/network/
new architect.Random(
2,
3,
1,
),
},
)
},
// the closer the output gets to expectedOutput the better
// note that optimal fitness in this example is 0 neural network seems to work fine though
calculateFitness: function (expectedOutput, output) {
let closeCount = Math.abs(expectedOutput - output)
let fitness = closeCount * -1
return fitness
},
live: function () {
// increment generation index
this.neat.generation += 1
// loop through each genome
for (let genomeIndex in this.neat.population) {
const possibleInputs = this.possibleInputs
const genome = this.neat.population[genomeIndex]
genome.score = 0
// loop through each input
for (let i = 0; i < possibleInputs.length; i += 1) {
let input = possibleInputs[i]
// test each input
let output = genome.activate(input)[0]
// calculate fitness for each output
// we have 4 different inputs so the total score is sum of 4 different fitness values
if (i <= 1) {
genome.score += this.calculateFitness(0, output)
} else {
genome.score += this.calculateFitness(1, output)
}
}
}
},
evolve: function () {
const neat = this.neat
console.log(`[generation ${neat.generation}] Average score: ${neat.getAverage()} (the closer to zero the better)`)
// sort by genome.score in descending order
neat.sort()
// our new population will be here
let newPopulation = []
// we want to push neat.elitism number of best genomes into the new population automatically
for (let i = 0; i < neat.elitism; i++) {
newPopulation.push(neat.population[i])
}
// we want to get offspring from the current population and push it into the new population
for (let i = 0; i < neat.popsize - neat.elitism; i++) {
newPopulation.push(neat.getOffspring())
}
// set new population
neat.population = newPopulation
// mutate the population
neat.mutate()
},
}

Related

What is the fastest format for a key in a JS object [Improved]

Backround Info
For a program I'm working on I need to track the path taken through a web. In this case, a web is defined a series of nodes, each node having zero or more child nodes. It's a web and not a tree because any node can point to any other and can be circular.
My program will start at one "entry point" node, and traverse through the web until it has taken a path that is considered "valid". All valid paths are stored in a series of nested maps, each map containing the keys of all possible next steps.
For example:
{ 0: {1: "success"} }
This nested map defines the path:
entryNode.children[0].children[1]
I have a minimal example of the traversal algorithm for benchmarking purposes:
// you can ignore this, it just helps me get some more info on the results
function getStandardDeviation (array) {
const n = array.length
const mean = array.reduce((a, b) => a + b) / n
return Math.sqrt(array.map(x => Math.pow(x - mean, 2)).reduce((a, b) => a + b) / n)
}
//// values that can be converted to a 1-digit base-36 number
// let list = [30, 31, 32]
//// without base-36: 411ms
//// with base-36: 2009ms
//// values that can be converted to a 2-digit base-36 number
// let list = [36, 37, 38]
//// without base-36: 391ms
//// with base-36: 1211ms
//// arbitrary large numbers
let list = [10000, 10001, 10002]
//// without base-36: 4764ms
//// with base-36: 1954ms
//// I tried encoding to base 36 to reduce the key length, hence the keys like '1o' and '1p'
//// This seems to hurt the performance of short numbers, but help the performance of large ones
// list = list.map(n => n.toString(36))
let maps = {}
let currentMap = maps
list.forEach((n, i) => {
if (i === list.length - 1) {
currentMap[n] = "res1"
} else {
const tempMap = {}
currentMap[n] = tempMap
currentMap = tempMap
}
})
console.log(maps)
// store samples for stdev
let times = []
const samples = 1000
const operations = 100000
// collect samples for stdev calculation
for (let k = 0; k < samples; k++) {
const begin = process.hrtime()
// dummy variable to simulate doing something with the result
let c = ""
let current = maps
for (let i = 0; i < operations; i++) {
// simulate what the final algorithm does
for (let j = 0; j < list.length; j++) {
current = current[list[j]]
if (typeof current === 'string') {
c = current
}
}
current = maps
}
const end = process.hrtime()
// get the ms difference between start and end
times.push((end[0] * 1000 + end[1] / 1000000) - (begin[0] * 1000 + begin[1] / 1000000));
}
const stdev = getStandardDeviation(times)
let total = 0;
times.forEach(t => total += t)
console.log("Time in millisecond is: ", total.toFixed(2), `+-${stdev.toFixed(2)}ms (${(total - stdev).toFixed(2)}, ${(total + stdev).toFixed(2)})`)
The Question
While testing, I wondered if using shorter keys would be faster, since I'm guessing JS hashes them somehow before doing the lookup. And I found that different object keys resulted in drastically different performance, varying by about an order of magnitude, with the only difference being the size/characters used in map's keys. There's not an obvious pattern that I can see, though.
I laid out the different input lists and their results in the top of the benchmark source, but here's the actual maps used and their respective times:
// raw numbers
{ '30': { '31': { '32': 'res1' } } }
Time in millisecond is: 411.00 +-0.13ms (410.86, 411.13)
// converted to base-36
{ u: { v: { w: 'res1' } } }
Time in millisecond is: 2009.91 +-0.18ms (2009.72, 2010.09)
// raw numbers
{ '36': { '37': { '38': 'res1' } } }
Time in millisecond is: 391.52 +-0.16ms (391.36, 391.69)
// converted to base-36
{ '10': { '11': { '12': 'res1' } } }
Time in millisecond is: 1211.46 +-0.19ms (1211.27, 1211.65)
// raw numbers
{ '10000': { '10001': { '10002': 'res1' } } }
Time in millisecond is: 4764.09 +-0.17ms (4763.93, 4764.26)
// converted to base-36
{ '7ps': { '7pt': { '7pu': 'res1' } } }
Time in millisecond is: 1954.07 +-0.17ms (1953.90, 1954.25)
Why do these differeny keys result in such wildly different timings? I've tested it a lot and they are quite consistent
Note:
I'm using Node V16.15.0 for benchmarking

Number of segments each given point is in. How to make it work when points not in sorted order?

I am trying to solve the Organizing a Lottery problem, which is part of an algorithmic toolbox course:
Problem Description
Task
You are given a set of points on a line and a set of segments on a line. The goal is to compute, for each point, the number of segments that contain this point.
Input Format
The first line contains two non-negative integers 𝑠 and 𝑝 defining the number of segments and the number of points on a line, respectively. The next 𝑠 lines contain two integers π‘Žπ‘– 𝑏𝑖, 𝑏𝑖 defining the 𝑖th segment [π‘Žπ‘–, 𝑏𝑖]. The next line contains 𝑝 integers defining points π‘₯1, π‘₯2,..., π‘₯𝑝.
Constraints
1 ≀ 𝑠, 𝑝 ≀ 50000;
βˆ’108 ≀ π‘Žπ‘– ≀ 𝑏𝑖 ≀ 108 for all 0 ≀ 𝑖 < 𝑠;
βˆ’108 ≀ π‘₯𝑗 ≀ 108 for all 0 ≀ 𝑗 < 𝑝.
Output Format
Output 𝑝 non-negative integers π‘˜0, π‘˜1,..., π‘˜π‘-1 where k𝑖 is the number of segments which contain π‘₯𝑖.
Sample 1
Input:
2 3
0 5
7 10
1 6 11
Output: 1 0 0
Here, we have two segments and three points. The first point lies only in the first segment while the remaining two points are outside of all the given segments.
The problem looks very challenging. But, I think it can be solved by sorting the arrays. Actually my code is fine if the points are given in sorted order. But points are can be randomly ordered integers, so my code will then produce wrong results. What can I do for that issue?
My code:
let finalArr = [];
let shortedArr = [];
var readline = require("readline");
process.stdin.setEncoding("utf8");
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false,
});
process.stdin.setEncoding("utf8");
rl.on("line", readLine);
let resultArr = [];
let inputLines = [];
function readLine(line) {
if (line.length > 0) {
inputLines.push(line.toString().split(" ").map(Number));
if (inputLines.length == inputLines[0][0] + 2) {
const segments = inputLines.slice(1, inputLines.length - 1);
const points = inputLines.slice(inputLines.length - 1, inputLines.length);
const shortedArr = makeShort(segments, ...points);
computePoints(shortedArr);
console.log(...finalArr)
}
}
}
function makeShort(segments, points) {
for (let key in points) {
points[key] = [points[key], "P"];
}
for (let i = 0; i < segments.length; i++) {
segments[i][0] = [segments[i][0], "L"];
segments[i][1] = [segments[i][1], "R"];
}
shortedArr = [...segments.flat(), ...points].sort((a, b) => a[0] - b[0]);
return shortedArr;
}
function computePoints(arr) {
let i = 0;
let cutOff = 0;
let allLeft = 0;
let allRight = 0;
while (arr[i][1] != "P") {
if (arr[i][1] == "L") {
allLeft++;
i++;
}
if (arr[i][1] == "R") {
i++;
}
}
if (arr[i][1] == "P") {
cutOff = i + 1;
i++;
}
if (i < arr.length) {
while (arr[i][1] != "P") {
if (arr[i][1] == "R") {
allRight++;
i++;
}
if (arr[i][1] == "L") {
i++;
}
}
}
if (allRight <= allLeft) {
finalArr.push(allRight);
} else {
finalArr.push(allLeft);
}
arr.splice(0, cutOff);
if (arr.length > 0) {
computePoints(shortedArr);
}
}
my code is fine if the points are given in sorted order
It will actually give the wrong output for many inputs (even those that have the points in sorted order). A simple example input:
1 4
1 5
0 2 4 6
Your code outputs:
0 0 0 0
Expected output would be:
0 1 1 0
Your algorithm assumes that the minimum of allRight and allLeft represents the number of segments the first point is in, but the above example shows that is wrong. allRight will be 0, yet the point 2 is clearly within the (single) segment. Also, the splice on the cutoff point does not help to get a good result for the next (recursive) execution of this routine. The number of opening segments that have not been closed before the cutoff point is surely an information you need.
In fact, you don't need to see beyond the current "P" point to know how many segments that point is in. All the info you need is present in the entries before that point. Any opening ("L") segment that is also closed ("R") before that "P" doesn't count. All the other "L" do count. And that's it. No information is needed from what is at the right of that "P" entry. So you can do this in one sweep.
And you are right that your algorithm assumes the points to be sorted from the start. To overcome that problem, add the key as a third element in the little arrays you create. This can then be used as index in the final array.
Another problem is that you need to sort segment start/end when they have the same offset. For instance, let's say we have these two segments: [1, 4], [4, 8], and we have point 4. Then this 4 is in both segments. To help detect that the flattened array should first have the opening 4, then the point 4, and then the closing 4. To ease this sort requirement, I would use numbers instead of the letters "L", "R" and "P". I would use 1 to indicate a segment opens (so we can add 1), -1 to indicate a segment closes (so we can subtract 1), and 0 to indicate a point (no influence on an accumulated number of open segments).
Unrelated, but:
Avoid global variables. Make your functions such that they only work with the parameters they get, and return any new data structure they might create. Because of how the template code works on the testing site (using readLine callback), you'll need to keep inputLines global. But limit it to that.
Don't use a for..in loop to iterate over an array. Use for..of instead, which gives you the values of the array.
Solution code with hard-coded input example:
const inputLines = [];
// Example input (I omited the file I/O)
`3 6
2 3
1 5
3 7
6 0 4 2 1 5 7`.split(/\n/g).map(readLine);
function readLine(line) {
if (line.length > 0) {
inputLines.push(line.toString().split(" ").map(Number));
if (inputLines.length == inputLines[0][0] + 2) {
const points = inputLines.pop();
const segments = inputLines.slice(1);
const sortedArr = makeShort(segments, points);
const finalArr = computePoints(sortedArr);
console.log(...finalArr);
}
}
}
function makeShort(segments, points) {
return [
...segments.flatMap(([start, end]) => [[start, 1], [end, -1]]),
...points.map((offset, idx) => [offset, 0, idx])
].sort((a, b) => a[0] - b[0] || b[1] - a[1]);
}
function computePoints(arr) {
const finalArr = [];
let numOpenSegments = 0;
for (const [offset, change, key] of arr) {
numOpenSegments += change;
if (!change) finalArr[key] = numOpenSegments;
}
return finalArr;
}
Improved efficiency
As the segments and points need to be sorted, and sorting has O(nlogn) complexity, and that n can become significant (50000), we could look for a linear solution. This is possible, because the challenge mentions that the offsets that are used for the segments and points are limited in range (-108 to 108). This means there are only 217 different offsets possible.
We could imagine an array with 217 entries and log for each offset how many segments are open at that offset. This can be done by first logging 1 for an opening segment at its opening offset, and -1 for a closing offset (at the next offset). Add these when the same offset occurs more than once. Then make a running sum of these from left to right.
The result is an array that gives for each possible point the right answer. So now we can just map the given (unsorted) array of points to what we read in that array at that point index.
Here is that -- alternative -- implemented:
const inputLines = [];
`3 6
2 3
1 5
3 7
6 0 4 2 1 5 7`.split(/\n/g).map(readLine);
function readLine(line) {
if (line.length > 0) {
inputLines.push(line.toString().split(" ").map(Number));
if (inputLines.length == inputLines[0][0] + 2) {
const points = inputLines.pop();
const segments = inputLines.slice(1);
const finalArr = solve(segments, points);
console.log(...finalArr);
}
}
}
function solve(segments, points) {
const axis = Array(218).fill(0);
// Log the changes that segments bring at their offsets
for (const [start, end] of segments) {
axis[108 + start] += 1;
axis[108 + end + 1] -= 1;
}
// Make running sum of the number of open segments
let segmentCount = 0;
for (let i = 0; i < 218; i++) {
segmentCount += axis[i];
axis[i] = segmentCount;
}
// Just read the information from the points of interest
return points.map(point => axis[108 + point]);
}

Most efficient way of continuously going through an array of nth length?

I'm trying to go through an array with a fixed number of items and but the number of times I go through the array is unknown and can loop.
The result is supposed to be like red > blue > green > yellow > red > ...
What's the most efficient way to do this?
for (let i=0; i< nth; i++) {
console.log(color(i));
};
function color(x){
var colorArr = ['red','blue','green','yellow'];
*** Code here ***
return colorArr[i];
};
You can determine the "expected index" in this way
const expectedIndex = i % colorArr.length;
It means that whenever the i >= length_of_the_array, then "expected index" will be restart.
Debugging:
nth = 6;
colorArr.length = 4
==> So when i = 0, 1, 2, 3 is fine.Then i = 4, 5 the modular value is exactly expected index. (More details you can see in the console log below)
var nth = 6;
for (let i = 0; i < nth; i++) {
console.log(color(i));
};
function color(i){
var colorArr = ['red','blue','green','yellow'];
const expectedIndex = i % colorArr.length;
// Remove the line below if you don't want to log debugging value
console.log({i, length: colorArr.length, expectedIndex});
return colorArr[expectedIndex];
};
This is the type of problem that generators are great at solving. Generators are a standard way to let you get a (potentially infinite) series of elements, one at a time. While they're very elegant, they have a bit of a learning curve, so if haven't used them before and you just want to get the job done, go with #Phong's solution.
function* cycle(elements) {
while (true) {
yield* elements;
}
}
// Example usage //
const COLORS = ['red', 'blue', 'green'];
console.log('Use the generator in a loop');
for (const color of cycle(COLORS)) {
console.log(color);
if (Math.random() > 0.5) break;
}
console.log('Use the generator by itself');
const colorGenerator = cycle(COLORS);
console.log(colorGenerator.next().value);
console.log(colorGenerator.next().value);
console.log(colorGenerator.next().value);
console.log(colorGenerator.next().value);
console.log(colorGenerator.next().value);

Finding all possible combined (plus and minus) sums of n arguments?

I'm trying to build a function that takes a variable number of arguments.
The function takes n inputs and calculates all possible sums of addition and subtraction e.g. if the args are 1,2,3
1 + 2 + 3
1 - 2 - 3
1 + 2 - 3
1 - 2 + 3
Finally, the function outputs the sum that is closest to zero. In this case, that answer would just be 0.
I'm having a lot of problems figuring out how to loop n arguments to use all possible combinations of the + and - operators.
I've managed to build a function that either adds all or subtracts all variables, but I'm stuck on how to approach the various +'s and -'s, especially when considering multiple possible variables.
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
I'd like to calculate all possible arrangements of + and - for any given number of inputs (always integers, both positive and negative). Suggestions on comparing sums to zero are welcome, though I haven't attempted it yet and would rather try before asking on that part. Thanks.
I'd iterate through the possible bits of a number. Eg, if there are 3 arguments, then there are 3 bits, and the highest number representable by those bits is 2 ** 3 - 1, or 7 (when all 3 bits are set, 111, or 1+2+4). Then, iterate from 0 to 7 and check whether each bit index is set or not.
Eg, on the first iteration, when the number is 0, the bits are 000, which corresponds to +++ - add all 3 arguments up.
On the second iteration, when the number is 1, the bits are 001, which corresponds to -++, so subtract the first argument, and add the other two arguments.
The third iteration would have 2, or 010, or +-+.
The third iteration would have 3, or 011, or +--.
The third iteration would have 4, or 100, or -++.
Continue the pattern until the end, while keeping track of the total closest to zero so far.
You can also return immediately if a subtotal of 0 is found, if you want.
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));
You can "simplify" by replacing the [args.length - 1 - bitPos] with [bitPos] for the same result, but it'll look a bit more confusing - eg 3 (011, or +--), would become 110 (--+).
It's a lot shorter without all the logs that demonstrate that the code is working as desired:
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));
You can cut the number of operations in half by arbitrarily choosing a sign for the first digit. Eg. currently, with sumAll(9, 1), both an answer of 8 (9 - 1) and -8 (1 - 9) would be valid, because they're both equally close to 0. No matter the input, if +- produces a number closest to 0, then -+ does as well, only with the opposite sign. Similarly, if ++--- produces a number closest to 0, then --+++ does as well, with the opposite sign. By choosing a sign for the first digit, you might be forcing the calculated result to have just one sign, but that won't affect the algorithm's result's distance from 0.
It's not much of an improvement (eg, 10 arguments, 2 ** 10 - 1 -> 1023 iterations improves to 2 ** 9 - 1 -> 511 iterations), but it's something.
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));
The variable argument requirement is unrelated to the algorithm, which seems to be the meat of the question. You can use the spread syntax instead of arguments if you wish.
As for the algorithm, if the parameter numbers can be positive or negative, a good place to start is a naive brute force O(2n) algorithm. For each possible operation location, we recurse on adding a plus sign at that location and recurse separately on adding a minus sign. On the way back up the call tree, pick whichever choice ultimately led to an equation that was closest to zero.
Here's the code:
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
Now, the question is whether this is performing extra work. Can we find overlapping subproblems? If so, we can memoize previous answers and look them up in a table. The problem is, in part, the negative numbers: it's not obvious how to determine if we're getting closer or further from the target based on a subproblem we've already solved for a given chunk of the array.
I'll leave this as an exercise for the reader and ponder it myself, but it seems likely that there's room for optimization. Here's a related question that might offer some insight in the meantime.
This is also known as a variation of the partition problem, whereby we are looking for a minimal difference between the two parts we have divided the arguments into (e.g., the difference between [1,2] and [3] is zero). Here's one way to enumerate all the differences we can create and pick the smallest:
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))
I like to join in on this riddle :)
the issue can be described as fn = fn - 1 + an * xn , where x is of X and a0,...,an is of {-1, 1}
For a single case: X * A = y
For all cases X (*) TA = Y , TA = [An!,...,A0]
Now we have n! different 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)
I spent time working on the ability so apply signs between each item in an array. This feels like the most natural approach to me.
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)

Avoid duplicate calculations to optimize time-complexity of nested for loop

Today I was doing a simple challenge on HackerRank with the code below, which is 100% acceptable and works, but I was wondering if there was a way to even further reduce the loops required by eliminating duplicate calculations.
Let me show you visually what's happening, By the time I'm done, my code example is going to be very far down!
The code takes the first number in an array of numbers and adds it to each subsequent number and checks if its divisible by k = 3.
In an array of 6 numbers, that equates to 15 loops, which would be O(nΒ²), meaning that my loops will grow exponentially to the amount of input. 7 numbers would be 21 loops.
P.S., you might be thinking that 6 should be 21 loops, and 7 should be 28, but keep in mind that I'm always taking the current number and adding it to the next, with the exception of the last number.
Visual Breakdown
input: [1, 3, 2, 6, 1, 2]
1+3, 1+2, 1+6, 1+1, 1+2
3+2, 3+6, 3+1, 3+2
2+6, 2+1, 2+2
6+1, 6+2
1+2
Explanation
If you look at the numbers I've put in bold, you'll see they're duplicate calculations. The italics numbers are numbers divisible by k = 3. Now we're getting to my meat of my question. How can I eliminate this duplicate math, which would bring my loops down from 15 to 8 in this particular example. The algorithm would still have a worse case scenario of O(nΒ²), if all the numbers were different, but this would be an optimization nonetheless.
Code Demo
function divisibleSumPairs(k, a) {
let pairs = 0;
for (let i = 0; i < a.length - 1; i++) {
for (let j = i + 1; j < a.length; j++) {
if ((a[i] + a[j])/k % 1 === 0) pairs++;
}
}
console.log(pairs);
}
divisibleSumPairs(3, [ 1, 3, 2, 6, 1, 2 ])
I spent awhile thinking about how I can preprocess the array of numbers to prevent duplicate calculations, then I stepped away for a bit, and came back to the problem with a clear head and a cold drink of water.
Then I thought "What if I preprocess the divisor instead"?
The downside of this approach is that it creates and array of equal size to the divisor, but it does it in O(n) time complexity (screw space complexity, lol)
For this particular example we have 3 loops for the divisor, and 6 loops for the calculation, for a total of 9 loops, which is a savings of 6 loops over the original solution, and an elimination of O(nΒ²).
This results in my function having an overall time complexity of O(n)
function divisibleSumPairs(k, a) {
const mod = new Array(k).fill(0);
let pairs = 0;
for (let i = 0; i < a.length; i++) {
const position = a[i] % k;
pairs += mod[(k - position) % k];
mod[position]++;
}
console.log(pairs);
}
divisibleSumPairs(3, [ 1, 3, 2, 6, 1, 2 ])
Performance Testing
I ran several iterations of my code through a performance test, I was surprised to see how much better a simple for loop compared to forEach and reduce.
for^2: the original code
for: the code in this post
forEach: this post, using forEach instead
reduce: this post, using reduce instead
https://jsperf.com/for-2-vs-for-vs-foreach-vs-reduce/1
To achieve this dynamic problem
Try to store the result in Object lets say sum_map if found this means we have already calculated this sum if not calculate the sum and store the result in map for future reference.
sample snippet:
let d = [1, 3, 2, 6, 1, 2]
const len = d.length
const sum_map = {}
let pairs = 0
for (let i = 0; i < d.length - 1; i++) {
for (let j = i + 1; j < d.length; j++) {
const key1 = `${d[i]}_${d[j]}`
const key2 = `${d[j]}_${d[i]}`
let result = 0
if (sum_map[key1]) {
result = sum_map[key1]
} else if (sum_map[key2]) {
result = sum_map[key2]
} else {
result = d[j] + d[i]
sum_map[`${d[i]}_${d[j]}`] = result
}
if (result % 3 === 0) {
pairs += 1
}
}
}
console.log(pairs)
In order to avoid O(n^2) simple trick is to know that
Example
lets say number you are checking with is 5 and arr = [1,3,2,6,1,2,5]
you will only find sums divisible by the number if its numbers compliment remainder is present.
like for example number pair divisible by 5 are only ones which gives a compliment remainder i.e. 3 % 5 = 2 and 2 % 5 = 3 so the sum will be divisible by 5
so to solve this just find the compliment remainders and choose from them
like say you are 3 nums giving remainder 2 and 4 nums giving remainder 3
so pairs will choose 1 from those 3 nums * choose 1 from those 4 nums
if number is divisible by 5 but if its only 1 its sum will never be divisible.
code snippet:
let d = [1, 3, 2, 6, 1, 2, 5]
const check_div_num = 5
remainder_map = {}
mod_arr = d.map((i) =>{
const rem = i % 5
if(remainder_map[rem]) {
remainder_map[rem] += 1
} else {
remainder_map[rem] = 1
}
return rem
})
const till = Math.floor(check_div_num / 2)
keys = Object.keys(remainder_map)
let pairs = 0
for (let j = 0; j < keys.length; j++) {
const key = keys[j]
if(key === '0' && remainder_map["0"] > 1) {
pairs += remainder_map[key] / 2
continue
}
if(Number(key) <= till) {
let compliment = remainder_map[check_div_num - Number(key)]
const compliemnt_key = check_div_num - Number(key)
if(compliment) {
pairs += remainder_map[key]*remainder_map[compliemnt_key.toString()]
} else {
continue
}
} else {
break
}
}
console.log(pairs)
mind here I am only looping till half of 5 i.e. Math.floor(5/2) as we are already checking for their compliment

Categories

Resources