Find int that appears an odd number of times in an array - javascript

I am working on this task where I need to find a number that happens to appear an odd number of times in an array.
I believe I've almost done it, but if some number appears more than once in a row (like 1 in [1,1,3,1,1]), it will always return that number, no matter if it appears an odd number of times or not.
function findOdd(A) {
var a;
var count = 0;
for (var i = 0; i < A.length; i++) {
a = A[i];
for (var l = i + 1; l < A.length; l++) {
if (a == A[l]) {
count++;
}
}
if (!(count % 2)) {
break;
} else {
count = 0;
}
}
return a;
}
console.log(findOdd([ 1, 1, 2, -2, 5, 2, 4, 4, -1, -2, 5 ]));
I've tried to play with adding 1 to count if [i] = [i+1], but it didn't work.
I'd expect output of findOdd([1, 1, 2, -2, 5, 2, 4, 4, -1, -2, 5]) to be -1, but it is 1. The function always returns first number that happens to be equal to next element of an array.

There's no need to reset count or use a break.
function findOdd(A) {
for (var i = 0; i < A.length; i++){
var count = 0;
for (var l = 0; l < A.length; l++) {
if (A[i] === A[l]) count++;
}
if (count % 2 !== 0) return A[i];
}
}
An important thing to note is that the inner loop is not starting at i+1, its starting at the 0. When A[i] matches A[l], we increment count. A number that appears an odd number of times will result in count becoming odd as well and we can return that number.

The following works but I wonder how the performance compares to simply doing for loops. The complexity seems to be the same.
function findOdd(a) {
let m = {};
a.forEach(e => (m[e] in m) ? m[e] += 1 : m[e] = 1);
for (k in m) {
if (m[k] % 2 != 0) return k;
}
}
console.log(findOdd([1, 1, 3, 1, 1]));
console.log(findOdd([1, 1, 2, -2, 5, 2, 4, 4, -1, -2, 5]));

You could count all values first and then get the value with an odd count.
function getOddCount(array) {
var value,
count = {},
k;
for (value of array) count[value] = (count[value] || 0) + 1;
for (k in count) if (count[k] % 2) return +k;
}
console.log(getOddCount([1, 1, 3, 1, 1]));
console.log(getOddCount([1, 1, 2, -2, 5, 2, 4, 4, -1, -2, 5]));

A naive implementation would simply use an object to store the frequency of each element and then iterate over it at the end to find the element that appeared an odd amount of times.
function findOdd(arr) {
const freq = {};
for(const num of arr){
freq[num] = (freq[num] || 0) + 1;
}
return +Object.keys(freq).find(num => freq[num] % 2 == 1);
}
A more efficient implementation could leverage the properties of the bitwise XOR (^), namely the fact that a ^ a == 0 and that the operation is commutative and associative, leading to the solution of applying XOR on each element of the array to obtain the answer.
function findOdd(arr) {
return arr.reduce((a,c)=>a ^ c, 0);
}

Related

remove duplicates and sort an array with single loop in javascript

I want to write an algorithm to remove duplicates and sort an array with single loop in javascript. I want to do it with algorithms not declarative methods.
I can sort it with single loop but can't remove duplicates. I will be glad if someone help me.
Number of elements in array is 10^6, each element is equal to or greater than 0, less than 300
This is my sorting algorithm:
var a = [2, 7, 5, 1, 3, 2, 7];
for (let i = 0; i < a.length - 1; i++) {
if (a[i] > a[i + 1]) {
let temp = a[i]
a[i] = a[i + 1]
a[i + 1] = temp
i = -1
}
}
console.log(a)
It's honorable that you was curious enough to try to find a solution, even after the interview. A good sign for a programmer.
I would use an object to keep track of any duplicates.
Also loop from the end, mostly because it feels right, not messing up the index.
Change position in a if current < previous
Else add to the object.
Check if object has previous index, or
Check if current is the same as previous (ex. 8, 8 at the end of the array)
then splice the previous index.
Added numberOfLoops just because it's fun to know.
var a = [2, 2, -1, 8, 5, 1, 3, 3, 13, 2, 8, 8];
var temp = {}; // 1
var result = [];
var current, previous;
var numberOfLoops = 0;
for (let i = a.length - 1; i > 0 ; i--) { // 2
current = a[i];
previous = a[i - 1];
numberOfLoops++; // 8
if (temp.hasOwnProperty(previous) || // 5
current == previous) { // 6
a.splice(i - 1, 1); // 7
// i++;
} else if (current < previous) { // 3
a[i - 1] = current;
a[i] = previous;
i += 2;
delete temp[current];
} else { // 4
temp[current] = current;
}
}
console.log({numberOfLoops}); // 23
console.log(a); // [-1, 1, 2, 3, 5, 8, 13]

Finding the integer that appears an odd number of times

Question
Given an array of integers, find the one that appears an odd number of times.
There will always be only one integer that appears an odd number of times.
My Attempt
function findOdd(A) {
let newArr = A.sort();
let altArr = [];
let count = 0;
let firstCharacter = newArr[0];
for (let i = 0; i < newArr.length; i++) {
if (newArr[i] == firstCharacter) {
count++;
} else {
let subArr = newArr.slice(newArr[0], count);
console.log(subArr);
altArr.push(subArr);
count = 0;
firstCharacter = newArr[i];
}
}
for (let i = 0; i < altArr.length; i++) {
if (altArr[i].length % 2 != 0) {
return altArr[i][0];
}
}
}
console.log(findOdd([20, 1, -1, 2, -2, 3, 3, 5, 5, 1, 2, 4, 20, 4, -1, -2, 5]))
Problem
I used console logs which showed me that the slices are returning an empty array Which I think is why this algorithm is broken. But why does it return an empty array? And is there any other mistakes i have missed?
You are making this problem much more complicated than it actually is. A naive implementation would simply use an object to store the frequency of each element and then iterate over it at the end to find the element that appeared an odd amount of times.
function findOdd(arr) {
const freq = {};
for(const num of arr){
freq[num] = (freq[num] || 0) + 1;
}
return +Object.keys(freq).find(num => freq[num] % 2 == 1);
}
A more efficient implementation could leverage the properties of the bitwise XOR (^), namely the fact that a ^ a == 0, a ^ 0 == a, and that the operation is commutative and associative, leading to the solution of applying XOR on each element of the array to obtain the answer.
function findOdd(arr) {
return arr.reduce((a,c)=>a ^ c, 0);
}
newArr.slice(newArr[0], count);
slice takes 2 parameters and they need to be index, not an actual value. So the above code is definitely not correct.
I think you can use dictionary to simplify this algorithm. Here is my approach.
function findOdd(A) {
const appearances = {};
A.forEach(val => {
appearances[val] = (appearances[val] || 0) + 1;
});
const oddNumbers = Object.keys(appearances).filter(key => appearances[key] % 2 != 0);
return oddNumbers.length > 0 ? oddNumbers[0] : NaN;
}
After sorting, you could take a single loop and check the value against the last value (initialize without a value).
function findOdd(array) {
let count = 0;
let last;
array.sort();
for (let i = 0; i < array.length; i++) {
if (array[i] === last) {
count++;
continue;
}
if (count % 2) return last;
last = array[i];
count = 1;
}
return last;
}
console.log(findOdd([20, 1, -1, 2, -2, 3, 3, 5, 5, 1, 2, 4, 20, 4, -1, -2, 5]))
.as-console-wrapper { max-height: 100% !important; top: 0; }

JavaScript: Writing this solution using higher order functions

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));

Javascript: adjacent Elements Product algorithm

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));
}

Find longest occurrence of same number in array

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

Categories

Resources