I worked on a problem where you are given an array of numbers and a target sum, and it's your job to find a pair of numbers that sum up to the target number. Here was my solution using simple nested for loops:
function findPairForSum(integers, target) {
var output = [];
for (var i = 0; i < integers.length; i++) {
for (var j = 0; j < integers.length; j++) {
if (i !== j && integers[i] + integers[j] === target) {
output.push(integers[i], integers[j]);
return output;
}
}
}
return 'not possible';
}
findPairForSum([3, 34, 4, 12, 5, 2], 9); // --> [4, 5]
My question is, is there a cleaner way to write this solution using higher order functions (perhaps forEach?)
Here was my attempt to use forEach:
function findPairForSum(integers, target) {
var output = [];
integers.forEach(function(firstNum) {
integers.forEach(function(secondNum) {
if (firstNum + secondNum === target) {
output.push(firstNum, secondNum);
}
})
})
if (output === []) {
return 'not possible';
}
return output;
}
findPairForSum([3, 34, 4, 12, 5, 2], 9); // --> [ 4, 5, 5, 4 ]
I tried putting a return after the two pushes, but it did not return anything. So instead, I put the return at the very end.
Why won't it return after the initial two pushes? I want it to stop right there, and only push the two numbers. Instead, by putting the return at the end, it pushed 4 numbers. It should be [4,5] but I got something like [4,5,5,4].
Any advice and help would be much appreciated!
Assume we have the following set of numbers, and we must find a subset of 2 numbers whose sum is 9:
Numbers: 4, 5, 6
Your current code iterates both with i and j from 0 to length. This means that the following iterations match the condition:
Indices: 0, 1, 2
Numbers: 4, 5, 6 // (i) (j)
---------------- // ↓ ↓
i j // Numbers[0] + Numbers[1] === 9
j i // Numbers[1] + Numbers[0] === 9
As you can see, the numbers 4 and 5 are matched twice, in 2 iterations:
i === 0 && j === 1
i === 1 && j === 0
You can avoid this by making sure one simple condition is met:
j must at all times be greater than i
This condition can be met met by initializing j with i + 1 in the inner for loop:
for (var i = 0; i < integers.length; i++) {
for (var j = i + 1; j < integers.length; j++) {
// ...
}
}
This way, j can never be 0 when i is 1, because the inner for-loop will run to completion before i is ever incremented once more. Once that happens, a brand new inner for-loop is created, in which j is again set to i + 1. The following diagram is the result:
Indices: 0, 1, 2
Numbers: 4, 5, 6
----------------
i j
X i // ← j can never be 0 if (i === 1),
// so the same set is never evaluated twice.
In other words, only the following combinations for i and j are checked at most:
Indices: 0, 1, 2
----------------
i j
i j
i j
is there a cleaner way to write this solution using higher order functions (perhaps forEach?)
A for loop is actually a fine solution for your use-case. They allow you to break early - after the first time you find a valid pair of numbers. forEach or other array iterator functions on the other hand will always continue until all set indices are visited.
You are actually breaking early in your first example with the statement return output;
When you use forEach on a set of numbers with multiple valid sets, you'll always get back all numbers involved:
Indices: 0, 1, 2, 3
Numbers: 4, 5, 6, 3 // (i) (j)
------------------- // ↓ ↓
i j // Numbers[0] + Numbers[1] === 4 + 5 === 9
i j // Numbers[2] + Numbers[3] === 6 + 3 === 9
forEach, map, reduce and the like do not allow you to break early. The following snippet demonstrates this issue of the diagram above:
function findPairForSum(integers, target) {
var output = [];
integers.forEach(function(firstNum, i) {
// slice(i + 1) has the same effect as for (var j = i + 1; ...)
integers.slice(i + 1).forEach(function(secondNum, j) {
if (firstNum + secondNum === target) {
// There is no way here to stop the iteration of either
// forEach call... T_T
output.push(firstNum, secondNum);
}
});
})
if (output.length) {
return output;
}
return 'not possible';
}
console.log(findPairForSum([4, 5, 6, 3], 9)); // --> [4, 5, 6, 3]
This is why I highly recommend sticking with the for loops for this specific use case. With for loop you can simply return as you already did as soon as you encounter a valid set of numbers:
function findPairForSum(integers, target) {
for (var i = 0; i < integers.length; i++) {
for (var j = i + 1; j < integers.length; j++) {
if (integers[i] + integers[j] === target) {
return [integers[i], integers[j]];
}
}
}
return 'not possible';
}
console.log(findPairForSum([4, 5, 6, 3], 9)); // --> [4, 5]
This could be your solution:
function findPairForSum(arr, sum) {
var pairs = [];
arr.forEach(n1 => {
var n2 = arr.find(n2 => n1 + n2 == sum)
if (n2) pairs.push([n1, n2]);
});
return pairs;
}
var sums = findPairForSum([3, 34, 4, 12, 6, 2], 9);
console.log(sums)
The problem is, you iterate from the start of the array for the inner loop. You could use a copy which starts at the index of the outer loop plus one and exit early on a found value.
But this does not solves the problem with multiple pairs. The result is simply wrong.
function findPairForSum(integers, target) {
var output = [];
integers.forEach(function(firstNum, i) {
integers.slice(i + 1).some(function(secondNum) {
if (firstNum + secondNum === target) {
output.push(firstNum, secondNum);
return true;
}
});
});
return output.length && output || 'not possible';
}
// console.log(findPairForSum([3, 34, 4, 12, 5, 2], 9));
console.log(findPairForSum([3, 34, 4, 4, 12, 5, 2, 4, 5], 9));
For a solution, you need to remember which pairs are used. This approach works with only one loop and a hash table for counting missing values.
If a pair is found, the counter is decremented and the two values are pushed to the result set.
function findPairForSum(integers, target) {
var hash = Object.create(null),
output = [];
integers.forEach(function(value) {
if (hash[value]) {
output.push(target - value, value);
hash[value]--;
return;
}
hash[target - value] = (hash[target - value] || 0) + 1;
});
return output.length && output || 'not possible';
}
console.log(findPairForSum([3, 34, 4, 4, 12, 5, 2, 4, 5], 9));
This is expected, since you didn't compare the indexes.
This inner array should only loop through the indexes which larger than the outer index.
You can achieve this by using the 2nd parameter, index, in forEach's callback function:
const ints = [3, 34, 4, 12, 5, 6, 2];
function findPairForSum(integers, target) {
let result;
integers.forEach((val1, idx1) => {
integers.forEach((val2, idx2) => {
if (idx1 < idx2 && val1 + val2 === target) {
result = [val1, val2];
}
})
})
return result;
}
console.log(findPairForSum(ints, 9));
Use can reduce your array into another which has sum equals target value:
const ints = [3, 34, 4, 12, 6, 2];
const value = 9;
const resp = ints.reduce((acc, ele, idx, self) => {
let found = self.find(x => x + ele == value)
return found ? [found, ele] : acc;
}, []);
console.log(resp); // [3, 6]
You can use Array.prototype.some which will stop execution as soon as the condition becomes true. See below code.
function findPairForSum(arr, sum) {
var pairs = [];
arr.some(n1 => {
var n2 = arr.find(n2 => n1 + n2 == sum)
if (n2) {
pairs.push(n1, n2); return true;
};
return false;
});
return pairs.length > 0 ? pairs : "not possible";
}
console.log(findPairForSum([3, 34, 4, 12, 7, 2], 9));
I'm trying to solve a basic javascript algorithm and i'm kinda stuck, here is the question:
Given an array of integers, find the pair of adjacent elements that
has the largest product and return that product.
Example
For inputArray = [3, 6, -2, -5, 7, 3], the output should be
adjacentElementsProduct(inputArray) = 21.
7 and 3 produce the largest product.
Here is my code, i can't find the problem but in the tests it says that it returns null:
function adjacentElementsProduct(inputArray) {
var cb;
for(var i=0;i<inputArray.length;i++){
if(inputArray[i] !== inputArray[inputArray.length-1]){
if(inputArray[i]*inputArray[i+1] > cb){
cb = inputArray[i]*inputArray[i+1];
}
}
}
return cb;
}
What is my problem and what i need to change?
What you ultimately want to do is iterate through the array excluding the last member, and compare a product of each current item with its next adjacent member to that of the largest one found thus far.
You can use .slice() and .reduce() for this.
function adjacentElementsProduct(arr) {
return arr.slice(0, -1)
.reduce((max, n, i) => Math.max(max, n * arr[i + 1]), -Infinity)
}
console.log(adjacentElementsProduct([3, 6, -2, -5, 7, 3]));
An alternate solution would be to create an array of products using .map() after doing the same .slice(), and pass that array's members to Math.max.
function adjacentElementsProduct(arr) {
return Math.max(...arr.slice(0, -1).map((n, i) => n * arr[i + 1]))
}
console.log(adjacentElementsProduct([3, 6, -2, -5, 7, 3]));
The problem with your code is you never initialized cb. So the comparison with cb is always invalid. Here is the correct version of your code -
function adjacentElementsProduct(inputArray) {
var cb = Number.NEGATIVE_INFINITY;
for(var i=0;i<inputArray.length-1;i++){
if(inputArray[i]*inputArray[i+1] > cb){
cb = inputArray[i]*inputArray[i+1];
}
}
return cb;
}
console.log(adjacentElementsProduct([3, 6, -2, -5, 7, 7]))
Since you need to use pairs of elements - you can just run through it and keep store maximum product value, something like this:
function adjacentElementsProduct(items) {
var product = 0;
for (var i = 0; i < items.length - 1; i++) {
product = Math.max(product, items[i] * items[i + 1]);
}
return product;
}
console.log(adjacentElementsProduct([3, 6, -2, -5, 7, 3]));
Using reduce we can simplify the amount of code.
var a = [3, 6, -2, -2, 7, 3].reduce(function(a, b, i, arr){
if (b*arr[i-1] > a)
{
a = b*arr[i-1];
}
return a;
})
console.log(a)
First of all, you could iterate from index 1 to the end and use i - 1 and i for adjacent elements. Then you could check if you are in iteration one and take the value or if the multiplication is greater then the old value, then assing the greater product.
function adjacentElementsProduct(inputArray) {
var cb, i;
for (i = 1; i < inputArray.length; i++) {
if (i === 1 || inputArray[i - 1] * inputArray[i] > cb) {
cb = inputArray[i - 1] * inputArray[i];
}
}
return cb;
}
console.log(adjacentElementsProduct([3, 6, -2, -5, 7, 3]));
More generic solution:
This will work for every array with negatives and non negatives numbers.
int adjacentElementsProduct(int[] inputArray) {
int length = inputArray.Length;
int max = Number.NEGATIVE_INFINITY;
for (int i = 0; i < length - 1; i++ ) {
max = Math.Max(max, inputArray[i] * inputArray[i+1]);
}
return max;
}
int adjacentElementsProduct(std::vector<int> inputArray) {
int n=inputArray.size();
int p=1;
int maxp=inputArray[0]*inputArray[1];
for(int i=0;i<n-1;i++){
p=inputArray[i]*inputArray[i+1];
if(p>maxp){
maxp=p;
}
}
return maxp;
}
Here is how you will do that in Python:
def adjacentElementsProduct(inputArray):
max = inputArray[0]* inputArray[1]
for i in range(0, len(inputArray)-1):
if inputArray[i] * inputArray[i+1] > max :
max = inputArray[i] * inputArray[i+1]
i+1
else:
i+1
return max
Or you can do it in one line like:
def adjacentElementsProduct(inputArray):
return max([inputArray[i] * inputArray[i+1] for i in range(len(inputArray)-1)])
function largetProduct(inputArray) {
let pairs=[];
for(var i = 0;i<inputArray.length ;i+=2){
pairs.push(inputArray.slice(i,i+2))
}
let products = pairs.map(v=> v[0]*v[1] || 1);
return (Math.max.apply(0,products));
}
Using JavaScript, I'm trying to find a way to find the longest occurrence of the same number (in this case, 1) in an array.
For instance, here's a sample array:
[2,5,3,1,1,1,3,7,9,6,4,1,1,1,1,1,4,7,2,3,1,1,4,3]
I'd like to write a function that would return "5", since the number 1 occurs 5 times in a row. (It also occurs 3 and 2 times in a row, but I'm after the longest occurrence).
So far, I have written:
function streak(arr) {
var i,
temp,
streak,
length = arr.length;
for(i=0; i<length; i++) {
if (arr[i] === 1) {
streak += 1;
} else {
temp = streak;
break;
}
}
}
I know I need some way of knowing where I left off if I find an occurrence, but I'm feeling kind of stuck.
Any pointers?
I've modified your function slightly. You need to store the highest streak as a separate variable from the current streak, and overwrite that where necessary in your loop - finally returning that variable at the end of your function.
function streak(arr) {
var i,
temp,
streak,
length = arr.length,
highestStreak = 0;
for(i = 0; i < length; i++) {
// check the value of the current entry against the last
if(temp != '' && temp == arr[i]) {
// it's a match
streak++;
} else {
// it's not a match, start streak from 1
streak = 1;
}
// set current letter for next time
temp = arr[i];
// set the master streak var
if(streak > highestStreak) {
highestStreak = streak;
}
}
return highestStreak;
}
var array = [2,5,3,1,1,1,3,7,9,6,4,1,1,1,1,1,4,7,2,3,1,1,4,3];
console.log(streak(array)); // 5
And if you want to also track what the value of the highest streak was, define another variable at the start of your function, save the value of it when you save the highest streak, and return it as an array:
// set the master streak var
if(streak > highestStreak) {
highestStreakValue = temp;
highestStreak = streak;
}
}
return [highestStreak, highestStreakValue];
var array = [2,5,3,1,1,1,3,7,9,6,4,'a','a','a','a','a',4,7,2,3,1,1,4,3];
console.log(streak(array)); // [5, "a"]
Demo returning both
An alternative approach. I'm converting the array to a string. The regular expression has a backrefence, which ensures that only sequences of the same character are matched. Also when exec is used with the g flag, repeated executions will continue from the end of last match, and not from the beginning.
var arr = [2,5,3,1,1,1,3,7,9,6,4,1,1,1,1,1,4,7,2,3,1,1,4,3];
var str = arr.join('');
var regex = /(.)\1*/g;
var match;
var largest = '';
while (match = regex.exec(str)) {
largest = match[0].length > largest.length ? match[0] : largest;
}
console.log(largest.length);
Your problems:
You don't store current streak
You don't specify when streak is more then older streak
Use this:
function streak(arr) {
var i,
temp,
streak = 1,
maxStreak = 0,
prevNumber,
length = arr.length;
for(i=1; i<length; i++) {
prevNumber = arr[i-1];
if (arr[i] == prevNumber) {
streak += 1;
} else {
if(streak > maxStreak) {
maxStreak = streak;
streak = 1;
}
}
}
return maxStreak;
}
Demo
You will need another two arrays here.
Store the distinct numbers from your source array using a loop
Make a second set of array which is equal to the length of the first set of array which has the distinct numbers.
Make a loop equal to the length of the first set of array and then push the values to the second set of array according to its index.
Make a loop again using the second set of array and there you will find the most occurence using the index of the second array
Finally, get from the first set of array the number using the index you got from step 4.
I did not make the code for you to try it yourself first since you are asking only for some pointers
Alternative: use regexp and converting the array to a string.
var arr = [2,5,3,1,1,1,3,7,9,6,4,1,1,1,1,1,4,7,2,3,1,1,4,3];
var str = arr.join('').match(/1+/g);
console.log(process ? process.sort().pop() : "No ocurrences");
You could take Array#reduce and return the start index of the actual same item sequence. Then check and update the counter if the item is not equal.
var array = [2, 5, 3, 1, 1, 1, 3, 7, 9, 6, 4, 1, 1, 1, 1, 1, 4, 7, 2, 3, 1, 1, 4, 3],
maxCount = 0,
maxValues;
array.reduce(function (j, a, i, aa) {
if (aa[j] === a) {
return j;
}
if (i - j === maxCount){
maxValues.push(aa[j]);
}
if (i - j > maxCount) {
maxCount = i - j;
maxValues = [aa[j]];
}
return i;
}, -1);
console.log(maxCount);
console.log(maxValues);
My proposal:
function getLongestRow(inputArray) {
// Initialize dummy variables
var start = inputArray[0], curRowLen = 0, maxRowLen = 0, maxRowEle = 0;
// Run through the array
for(var i = 0;i < inputArray.length;i++) {
// If current Element does not belong to current row
if(inputArray[i] != start) {
// If current row is longer than previous rows, save as new longest row
if(curRowLen > maxRowLen) {
maxRowLen = curRowLen;
maxRowEle = start;
curRowLen = 1;
}
// Start new row
start = inputArray[i];
} else {
// Current element does belongt to current row, increase length
curRowLen++;
}
}
// Check whether last row was longer than previous rows
if(curRowLen > maxRowLen) {
maxRowLen = curRowLen;
maxRowEle = start;
}
// Return longest row & element longest row consits of
console.log('The longest row in your array consists of '+maxRowLen+' elements of '+maxRowEle+'.');
}
JsFiddle: http://jsfiddle.net/hdwp5/
Here's a way to do it:
var values = function(obj) {
var res = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
res.push(obj[i]);
}
}
return res;
};
var countStreak = function(xs) {
var res = xs.reduce(function(acc, x, i) {
if (x === xs[i+1]) {
acc[x] = acc[x]+1 || 2;
} else {
acc[x] = acc[x]-1 || 0;
}
return acc;
},{})
return Math.max.apply(0, values(res));
};
var ns = [2,5,3,1,1,1,3,7,9,6,4,1,1,1,1,1,4,7,2,3,1,1,4,3]
countStreak(ns) //=> 5
You can use fewer iterations by looking ahead at all matches from a given index,
and jumping ahead to the next non-matching item's index.
You can also quit when there are less items left than the maximum you have found.
function maxRepeats(arr){
var L= arr.length, i= 0,
max= 1, count= 0;
while(L-i > max){
while(arr[i+count]=== arr[i])++count;
if(count > max) max= count;
i+= count;
count= 0;
}
return max;
}
var A= [2, 5, 3, 1, 1, 1, 3, 7, 9, 6, 4, 1,
1, 1, 1, 1, 4, 7, 2, 3, 1, 1, 4, 3];
maxRepeats(A); returns 5
Finding multiple items that repeat the max number of times is not so easy,
since you have to find the max number before you can list them.
If you really only need the max number, ignore this:
function mostRepeats(arr, maximum){
var i= 0, max= maximum || 1,
L= arr.length-max,
count= 0, index= [];
while(i<L){
while(arr[i+count]=== arr[i])++count;
if(count=== maximum) index.push(arr[i]+' starting at #'+i);
else if(count > max) max= count;
i+= count;
count= 0;
}
if(max===1) return 'No repeats';
return maximum? max+' repeats of: '+index.join(', '): mostRepeats(arr, max);
}
var A= [2, 5, 3, 1, 1, 1, 3, 7, 9, 6, 4, 1, 1, 1,
1, 1, 4, 7, 2, 3, 3, 3, 3, 3, 1, 1, 4, 3];
mostRepeats(A);returns:
5 repeats of: 1 starting at #11, 3 starting at #19
Unfortunately I can't comment yet due to lack of reputation so I will post this as an answer. For my task Robbie Averill's solution was perfect, but it contains a little bug. I had array that consisted of 2 values - 0 & 1.5, but above-mentioned code was counting only "1.5" values although I had "0" repeating in a higher streak. Problem was that value wasn't doing strict comparison here:
if(temp != '' && temp == arr[i]) {
and the fix was simple: if(temp !== '' && temp == arr[i]) {
I've updated Robbie's jsfiddler with this fix: http://jsfiddle.net/d5X2k/5/
Unfortunatly, a question has been marked as duplicate, but it was not the same as this one. So I must put my answer here, sorry…
let tab = [0,0,0,1,1,1,0,0,0,0,1,0,1,1,1,1,1]
, arr = []
, n = 0
, res = null ;
for(let i of tab)
{
if ( i ) { ++ n }
else if ( n ) { arr.push(n) ; n = 0 }
}
arr.push(n) ;
res = Math.max(...arr);
console.log("Streak with 1 is ", Math.max(...arr));
It's a better solution than with reduce, slower, as you can see:
let tab = [0,0,0,1,1,1,0,0,0,0,1,0,1,1,1,1,1];
let arr = [];
let n = 0;
let res = null;
let loop = 0;
let start = new Date().getTime();
while (loop < 1000000){
++ loop;
arr = [];
for(let i of tab)
{
if ( i ) { ++ n }
else if ( n ) { arr.push(n) ; n = 0 }
}
arr.push(n);
res = Math.max(...arr);
}
let end = new Date().getTime();
console.log("laps old fashion = ", end - start);
loop = 0;
let streaks = null;
start = new Date().getTime();
while (loop < 1000000){
++ loop;
streaks = tab.reduce((res, n) =>
(n ? res[res.length-1]++ : res.push(0), res)
, [0]);
res = Math.max(...streaks);
}
end = new Date().getTime();
console.log("laps reduce = ", end - start);
console.log("Streak with 1 is ", Math.max(...arr));
Input array:
const seq = [
0, 0, 0,
1, 1, 1,
1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1,
];
Shortest solutions:
console.log(Math.max(...Array.from(seq.join("").matchAll(/(.)\1+/g), m=>m[0].length)))
Alternative with regexp (spoiler: it's ~25%, slower than solution with reduce(). See "Modern approach with reduce()" below):
const longestSeq = (seq) => {
let max = 0;
seq.join("").replace(/(.)\1+/g, m=> max = Math.max(max, m.length));
return max;
};
Straightforward, old-school style, human readable and fastest solution:
let longestSeq = () => {
let maxCount = 0,
curCount = 0,
curItem, prevItem,
l = seq.length+2, // +1+1 to finish last sequence and compare 'undefined' with previous
i = 0;
for (; i < l; ++i) {
curItem = seq[i];
if (curItem === prevItem) ++curCount;
else {
if (curCount > maxCount) maxCount = curCount;
curCount = 1;
prevItem = curItem;
}
}
return maxCount;
}
Modern approach with reduce() (just very little slower than old-school code above):
const longestSeq = (seq) => seq
.reduce(
({count, max}, item) => item === 0
? { count: ++count, max: Math.max(count, max) }
: { count: 0, max: max },
{ count: 0, max: 0} )
.max;
Performance test, Reduce() vs old-school for(): https://jsbench.me/ifkgsin56z/1