Finding the second lowest frequent element from an array of integers? - javascript

I'm looking to answer a coding challenge in JavaScript that I'm stuck on, here's the question:
Write a function which accepts an array of integers and returns an element of that array.
The function should determine the frequency of each element (how many times the element appears in the array) and whenever possible should return the element with the second-lowest frequency. Otherwise it should return the integer with the lowest frequency.
If there is more than one element satisfying the requirements then the second smallest one (according to value) should be returned.
Example outputs:
secondLowest( [4, 3, 1, 1, 2] ) === 1
secondLowest( [4, 3, 1, 1, 2, 2] ) === 2
secondLowest( [4, 3, 1, 2] ) === 2
This is what I've got so far although don't know how best to go about answering it after this:
function mode(array) {
if (array.length == 0) return null;
let modeMap = {};
let maxEl = array[0],
maxCount = 1;
for (let i = 0; i < array.length; i++) {
var el = array[i];
if (modeMap[el] == null) modeMap[el] = 1;
else modeMap[el]++;
if (modeMap[el] > maxCount) {
maxEl = el;
maxCount = modeMap[el];
}
}
return maxEl;
}

I was determined to give a generic, parameterized function, where no number is hardcoded.
Your example involved two hardcoded values:
The second least frequency should be selected
In cases of ties, the second least value should be selected
The following code works like this:
Get the frequency of each input value
Group together all values with the same frequency.
Sort these grouped pairs by frequency and select the nth-lowest (in your case, n=2)
If the nth-lowest frequency has multiple pairs, sort these pairs by value, and select the mth-lowest pair (in your case, m=2)
Return the value of this final pair
The m and n parameters I refer to here are called freqInd and valInd in the code. Note that in order to select the second-lowest frequency, freqInd should be 1, not 2 (since 0 would select the lowest, and therefore 1 selects the second-lowest).
let lowestFreqVal = (freqInd, valInd, values) => {
// Calculate frequencies in a map
let f = new Map();
for (let v of values) f.set(v, (f.get(v) || 0) + 1);
// Group together all val/freq pairs with the same frequency
let ff = new Map();
for (let [ val, freq ] of f) ff.set(freq, (ff.get(freq) || []).concat([ val ]));
// Sort these groups by frequency
let byFreq = [ ...ff ].sort(([ freq1 ], [ freq2 ]) => freq1 - freq2);
// Here are all the items of the `freqInd`-th lowest frequency, sorted by value
// Note that `[1]` returns an array of integers at the frequency, whereas `[0]` would return the frequency itself
let lowestItems = byFreq[ Math.min(byFreq.length - 1, freqInd) ][1]
.sort((v1, v2) => v1 - v2);
// Return the `valInd`-th lowest value
return lowestItems[ Math.min(lowestItems.length - 1, valInd) ];
};
console.log('Some random examples:');
for (let i = 0; i < 10; i++) {
// An array of random length, full of random integers
let arr = [ ...new Array(3 + Math.floor(Math.random() * 5)) ]
.map(v => Math.floor(Math.random() * 4));
// Show the result of `lowestFreqVal` on this random Array
console.log(`lowestFreqVal(1, 1, ${JSON.stringify(arr)}) = ${lowestFreqVal(1, 1, arr)}`);
}
This is not an optimal solution, since it resorts to using sort. It's known that the problem of finding some nth-maximal value in a list can be implemented to have a better runtime than sort (and a significantly better runtime when n is a small value - we can see this intuitively because if n=0, a single pass (O(n)) does the trick).

Related

How can I decrease the time spent checking each value in this leetcode?

So on LeetCode, I need to return the sum of two numbers that equal the target number. This is a leetcode "easy" type. I've never done leetcode before so I decided to give it a try. Right off the bat, I was able to solve the problem but my solution was nonsenical because it checks each number in the array against eachother. So if the input is a million digits then it will check it a million times for each number.
It's worth noting that although my program works, it can be submitted due to time limit exceeding.
I'm not sure what the mathematical solution would be to opitmize this. I'm currently going to Maths again learning what I am weak at.
Code:
var twoSum = function(nums, target) {
let total = [];
let inc = 1;
let intVal = 0;
let startingPos = nums[intVal];
let nextPos = nums[inc];
for(let x = 0; x < nums.length; x++){
// Do not check the value of position 1 with position 2
if(nums.indexOf(startingPos) === nums.lastIndexOf(nextPos)){
nextPos++;
}
if(startingPos + nextPos === target){
console.log(`First Value ${startingPos}`)
console.log(`Second Value ${nextPos}`)
console.log(`Target ${target}`)
// A match has been found
return [nums.indexOf(startingPos), nums.lastIndexOf(nextPos)];
} else{
// Move to next number if index 1 is not eql
// nextPos++;
let nextPosIndex = nums[inc];
nextPos = nums[inc];
console.log(`Values [${startingPos}], [${nextPos}]`)
console.log("No Matches");
// Increment the next value to check
inc++;
// Reset loop if no match is found from 2nd position
if(x == (nums.length - 1)){
// Increment the initial value in first pos
intVal++;
startingPos = nums[intVal];
// Reset values to check new numbers
x = 0;
inc = 1;
}
// check if we exhausted all options
if(startingPos === undefined){
return "No Matches.";
}
}
}
};
twoSum([5, 2, 5, 5, 1, 3, 6, 8, 4, 3, 2, 7], 14)
--
Before proceeding to any more problems, I am afraid I will be in this loop of choosing the most illogical way of solving the problems.
What can I do to modify this problem to quickly check if two values equals the target.
Here is a live compiler example: https://replit.com/#FPpl/SafeHeartfeltArchitect#index.js
When iterating over a number, you can put the value that, if it would match to sum to the target, into a collection (with O(1) lookup). For example, if you iterate over a number 5, and the target is 20, put 15 into the collection.
During an iteration, if the number being iterated over already exists in the collection, you have a match from one you found previously, and you can return both indicies.
const twoSum = function(nums, target) {
// For this Map, the key is the number which, if found again, is a match
// eg, if target is 20, and the number 5 is iterated over
// the key will be 15
// The value is the index of the prior number found - eg, index of 5
const valuesAlreadyFound = new Map();
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
if (valuesAlreadyFound.has(num)) {
// We have a match, get both indicies:
console.log('Match for values', target - num, num);
return [valuesAlreadyFound.get(num), i];
}
// This wasn't a match. Identify the number which, when paired, is a match
const matchNeeded = target - num;
valuesAlreadyFound.set(matchNeeded, i);
}
return 'No match';
};
console.log('Indicies found:', twoSum([5, 2, 5, 5, 1, 3, 6, 8, 4, 3, 2, 7], 14));

Split array into arrays of numbers where the sum is equal to a specific target

I need to create a function that take as parameter an array and a target. It should return an array of arrays where the sum of these numbers equals to the target
sumPairs(array, target) {
}
For example:
sumPairs([1, 2, 3, 4, 5], 7) // output : [[2, 5], [3, 4]]
I know I have to use map(), and probably reduce(), set(), or filter() maybe (I read their documentation in MDN but still cant find out). I tried some ways but I can't get it.
If you guys could help me to find out how to dynamically create arrays and push them into a new array..
I read there some solutions (Split array into arrays of matching values) but I hate to just use created functions without knowing what they really do or how they work.
Some very basic code for achieving it, Just run all over combinations and conditionally add the items you want.
function sumPairs(array, target) {
var res = [];
for(var i = 0; i < array.length; i++){
for(var j = 0; j < array.length; j++){
if(i!=j && array[i]+array[j]==target &&
res.filter((x)=> x[0] == array[j] && x[1] == array[i]).length == 0 )
res.push([array[i], array[j]]);
}
}
return res;
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
Option 2 - see this answer for more options (like using reduce)
function sumPairs(array, target) {
return array.flatMap(
(v, i) => array.slice(i+1).filter(w => (v!=w && v+w==target)).map(w=> [w,v])
);
}
var result = sumPairs([1, 2, 3, 4, 5], 7);
console.log(result);
"The exercise says that it sould be arrays of pairs that sum the
target value so I think only 2 items"
If you need a pair that matches a sum and you pick any number from the list, you are left with
the following equation to solve num + x = sum where we want to find x. E.g. if you picked 7 and the target sum is 10 then you know you are looking for a 3.
Therefore, we can first construct a counting map of the numbers available in our list linear (O(n)) time and then search for matches in linear time as well rather than brute forcing with a quadratic algorithm.
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
return nums.reduce((pairs, num) => {
countByNum[num]--;
const target = sum - num;
if (countByNum[target] > 0) {
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[num]++;
}
return pairs;
}, []);
}
function countGroupByNum(nums) {
return nums.reduce((acc, n) => (acc[n] = (acc[n] || 0) + 1, acc), {});
}
Here's another implementation with more standard paradigms (e.g. no reduce):
const nums = [1, 2, 3, 4, 5];
console.log(findSumPairs(nums, 7));
function findSumPairs(nums, sum) {
const countByNum = countGroupByNum(nums);
const pairs = [];
for (const num of nums) {
const target = sum - num; //Calculate the target to make the sum
countByNum[num]--; //Make sure we dont pick the same num instance
if (countByNum[target] > 0) { //If we found the target
countByNum[target]--;
pairs.push([num, target]);
} else {
countByNum[target]++; //Didin't find a match, return the deducted num
}
}
return pairs;
}
function countGroupByNum(nums) {
const countByNum = {};
for (const num of nums) {
countByNum[num] = (countByNum[num] || 0) + 1;
}
return countByNum;
}
You can also sort your array and find all the pairs with given sum by using two pointer method. Place the first pointer to the start of the array and the second pointer to the end.
if the sum of the values at the two places is :
More than target: Decrement your second pointer by 1
Less than target: Increment your first pointer by 1
Equal to target: This is one possible answer, push them to your answer array and increment your first pointer by 1 and decrement your second pointer by 1.
This is more performant solution with complexity O(n*log(n))

Performance of Array.reduce

I was trying out the Codility MaxCounter question:
You are given N counters, initially set to 0, and you have two possible operations on them:
increase(X) − counter X is increased by 1,
max_counter − all counters are set to the maximum value of any counter.
A non-empty zero-indexed array A of M integers is given. This array represents consecutive operations:
if A[K] = X, such that 1 ≤ X ≤ N, then operation K is increase(X),
if A[K] = N + 1 then operation K is max_counter.
For example, given integer N = 5 and array A such that:
A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4
the values of the counters after each consecutive operation will be:
(0, 0, 1, 0, 0)
(0, 0, 1, 1, 0)
(0, 0, 1, 2, 0)
(2, 2, 2, 2, 2)
(3, 2, 2, 2, 2)
(3, 2, 2, 3, 2)
(3, 2, 2, 4, 2)
The goal is to calculate the value of every counter after all operations.
For example, given:
A[0] = 3
A[1] = 4
A[2] = 4
A[3] = 6
A[4] = 1
A[5] = 4
A[6] = 4
the function should return [3, 2, 2, 4, 2].
Assume that:
N and M are integers within the range [1..100,000];
each element of array A is an integer within the range [1..N + 1].
Complexity:
expected worst-case time complexity is O(N+M);
expected worst-case space complexity is O(N), beyond input storage (not counting the storage required for input arguments).
Elements of input arrays can be modified.
Here is my solution, for which i used the reduce method. It scores 40% on performance.
Can anyone see where the performance issue is here?
I'm assuming perhaps it is the reduce speed itself thats the problem and that in order to increase this score i would need to convert this to for loops, but this just feels like a really ugly way to use modern javascript in this context.
Hopefully one of you will point out something non JS related about this solution that will not indicate that reduce is the issue and instead indicate that im an idiot (I will deal with the ramifications of this over a cold beer)
function maxCounter(N, A) {
let maxCounter = 0
const NArray = new Array(N).fill(0)
const results = A.reduce((acc, current) => {
if (current === N + 1) return new Array(N).fill(maxCounter)
const out = acc.map((element, index) => {
if (index + 1 === current){
const newValue = element + 1
if (newValue > maxCounter) maxCounter = newValue
return newValue
} else {
return element
}
})
return out
}
, NArray)
return results
}
const results = maxCounter(5, [1,4,2,5,2,6,2])
console.log({results})
You could introduce a min value, which is set if all values have to be set to the max value, but this does happen only if the value is incremented, then the min value is used for update or at the end to give all items at least the min value.
function maxCounter(n, a) {
var min = 0,
max = 0,
result = [],
i;
for (i of a) {
if (--i === n) {
min = max;
continue;
}
if (!result[i] || result[i] < min) result[i] = min;
if (++result[i] > max) max = result[i];
}
for (i = 0; i < n; i++) {
if (!result[i] || result[i] < min) result[i] = min;
}
return result;
}
console.log(...maxCounter(5, [3, 4, 4, 6, 1, 4, 4]));
console.log(...maxCounter(5, [1, 4, 2, 5, 2, 6, 2]));
The time complexity of your solution is O(NM), which exceeds the required complexity of O(N + M). The reason is that your solution builds a new array of length N for each of the M queries.
This isn't because you used reduce, but rather because you used reduce on an array of length M with a reduction operation which takes O(N) time. If your reduction operation took O(1) time instead, then you would be fine.
It is possible to achieve O(1) time per query by updating the counters array in-place; the hard part is answering the "set-all" query in O(1) time, i.e. without updating all N elements of the counter array. One solution is to record a kind of "timestamp" for the last time a counter was modified, and another "timestamp" for the last time "set-all" was done; this allows you to test if a counter's value is older than the most-recent "set-all" query, and get the correct counter value either way. Here's a class which answers both queries in O(1) using this technique:
class Counters {
constructor(n) {
this.setallVal = 0;
this.setallTimestamp = 0;
this.maxCounter = 0;
this.counts = new Array(n).fill(0);
this.timestamps = new Array(n).fill(0);
}
getCounter(i) {
if (this.timestamps[i] >= this.setallTimestamp) {
return this.counts[i];
} else {
return this.setallVal;
}
}
incCounter(i) {
let c = this.getCounter(i) + 1;
this.counts[i] = c;
this.timestamps[i] = this.setallTimestamp;
if (c > this.maxCounter) {
this.maxCounter = c;
}
}
setAllToMax() {
this.setallVal = this.maxCounter;
this.setallTimestamp++;
}
}
#kaya3 Thanks!
I tried this too which gets 60% but i assume falls over with the same issue of setting the max across all elements
function solution(N, A) {
let maxCounter = 0
const NArray = new Array(N).fill(0)
for (i=0; i<A.length; i++){
if (A[i]>N){
NArray.fill(maxCounter)
} else {
const currentVal = NArray[A[i] -1] + 1
if (currentVal > maxCounter) maxCounter = currentVal
NArray[A[i] -1] = currentVal
}
}
return NArray
}

Finding a non-consecutive number pair in an array

Given an array with a minimum length of 3 and a maximum length of 5, which always contains uniquely occurring integers from 0 to 4 in ascending order, I need to pick out two non-consecutive numbers from it. Non-consecutive refers to their numeric value, not their position in the array.
To clarify, here are examples of valid arrays:
[ 1, 2, 3 ]
[ 0, 1, 2, 4 ]
[ 0, 3, 4 ]
For the arrays above, valid answers could be, respectively:
[ 1, 3 ]
[ 0, 2 ], [ 0, 4 ] or [ 1, 4 ]
[ 0, 3 ] or [ 0, 4 ]
Furthermore, in those cases where there is more than one valid answer, I need it to be selected at random, if at all possible (for instance I don't want to favor sequences that begin with the lowest number, which is what would occur if I always began checking from left to right and stopped checking as soon as I found one valid solution).
What would be the most efficient way of tackling this problem in Javascript?
You could use two nested iterations and build an new array for choosing as random result.
function getNonConsecutives(array) {
return array.reduce((r, a, i, aa) => r.concat(aa.slice(i + 2).map(b => [a, b])), []);
}
console.log(getNonConsecutives([ 0, 1, 2, 4 ]));
.as-console-wrapper { max-height: 100% !important; top: 0; }
According to Bee157's answer, you could use a random choice with a constraint, like length for the first index and add the needed space for the second index.
The problem is, due to the nature of choosing the first number first, the distribution of the result is not equal.
function getNonConsecutives(array) {
var i = Math.floor(Math.random() * (array.length - 2));
return [
array[i],
array[Math.floor(Math.random() * (array.length - 2 - i)) + 2 + i]
];
}
console.log(getNonConsecutives([ 0, 1, 2, 4 ]));
demoFn(array) {
var i,j, y =[];
for (i=0; i<=array.length;i++) {
for (j = i + 1; j <= array.length; j++) {
if (array[j] && array[i]) {
if (array[j] !== array[i] + 1) {
y.push([array[i], array[j]]);
}
}
}
}
}
Take a random array and check it.
You can create a function using recursion that will pick random number in each iteration and loop all other elements and if condition is met add to array.
function findN(data) {
data = data.slice();
var r = []
function repeat(data) {
if (data.length < 2) return r;
var n = parseInt(Math.random() * data.length);
data.forEach(function(e, i) {
if (i != n) {
var a = data[n];
if (Math.abs(a - e) != 1 && r.length < 2) r.push(n < i ? [a, e] : [e, a])
}
})
data.splice(n, 1);
repeat(data)
return r;
}
return repeat(data)
}
console.log(findN([1, 2, 3]))
console.log(findN([0, 1, 2, 4]))
console.log(findN([0, 3, 4]))
Something like this should do it:
const pick = nums => {
// Pick a random number
const val = nums[Math.floor(Math.random() * nums.length) + 0];
// Filter out any numbers that are numerically consecutive
const pool = nums.filter(n => Math.abs(n - val) > 1);
// Pick another random number from the remainer
const other = pool[Math.floor(Math.random() * pool.length) + 0];
// Sort + return them
return [val, other].sort();
};
console.log(pick([0, 1, 2, 4]));
since you state that the array ellemnts are all unique, and that they are sorted.
It should suffice to take an random element
var index1=Math.floor(Math.random()*arr.length)
now any other element (except maybe the elemnts on position (index1 +/- 1) are not consecutive
So a new random element can be chosen excluding the first index.
var index2=Math.floor(Math.random()*arr.length);
if(index2==index1){
index2+=((index2<arr.length-1)?1:-1);
}
if(Math.abs(arr[index1]-arr[index2])<=1){
if(index2==0 && arr.length<4){
//set index2 to arr.length-1 and do check again, if not ok=> no result
if(!(arr[index1]-arr[arr.length-1]>=-1)){
return [arr[arr.length-1],arr[index1]];
}
}
else if(index2==arr.length-1 && arr.length<4){
//set index2 to 0 and do check again, if not ok=> no result
if(!(arr[index1]-arr[0]<=1)){
return [arr[0],arr[index1]];
}
}
else{
//if index2>index1 index2++
//else index2--
//always OK so no further check needed
index2+=(index2>index1?1:-1);
return [arr[index1],arr[index2]];
}
}
else{
//ok
return [arr[index1,arr[index2]];
}
return false;
if speed is not important, you can use a filter on the array to calculate a new array with all elements differing more then 1 unit of arr[index1]. and randomly select a new number from this new array.
Other attempt
function getNonConsecutive(arr){
var index1,index2,arr2;
index1=Math.floor(Math.random()*arr.length);
arr2=[].concat(arr);
arr2.splice((index1!==0?index1-1:index1),(index!==0?3:2));
if(arr2.length){
index2=Math.floor(Math.random()*arr2.length);
return [arr[index1],arr2[index2]];
}
else{
//original array has length 3 or less
arr2=[].concat(arr);
arr2.splice(index1),1);
for (var j=0,len=arr.length;j<len;j++){
if(Math.abs(arr1[index1]-arr2[j])>1){
return [arr[index1],arr2[j]];
}
}
}
return false
}

JavaScript Arrays : Keep only values that are present an odd number of times, including once

Hey guys so for example I have an array:
myArray[5,4,1,2,1,4,5,6,7,8,9,10,1,2,1,5,3,2]
I'm sorting that array:
[1, 1, 1, 1, 10, 2, 2, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8, 9]
And I want to delete only the two duplicates so the array I want will be
[10,2,3,5,6,7,8,9]
So i'm using splice:
for (var index = 0; index < myArray.length +1; ++myArray) {
if(myArray[index+1]== myArray[index]) {
myArray.splice(index);
myArray.splice[index+1];
}
}
But when I'm pushing more of the of the numbers, the results seem unpredictable
How to do this properly?
To clarify: The purpose is to eliminate the numbers which repeat an even number of times.
Here's another method, which checks for an odd number of elements by subtracting the indexOf the key from the lastIndexOf the key after sorting:
var myArray = [5,4,1,2,1,4,5,6,7,8,9,10,1,2,1,5,3,2];
var result = myArray.sort().filter(function(key, idx) {
return myArray.indexOf(key) === idx && //handle first instance only
(myArray.lastIndexOf(key) - myArray.indexOf(key)) % 2 === 0;
});
console.log(result);
Here is an ECMAScript2015 solution:
var myArray = [5,4,1,2,1,4,5,6,7,8,9,10,1,2,1,5,3,2];
var count = myArray.reduce((count, num) =>
(count[num] = (count[num] || 0) + 1, count), {});
myArray = Object.keys(count).filter(num => count[num] % 2).map(Number);
console.log(myArray);
The count variable is an object of which the properties are the numbers in the original array. The value for each of these properties is the number of occurrences of that number in the original array.
The keys are then iterated to get only those into the final array that have a value (i.e. occurrences) that is odd. As object properties are iterated in numerical order (when numerical), the result is automatically sorted numerically.
About your code:
The for loop you have, has some issues:
for (var index = 0; index < myArray.length +1; ++myArray) {
if(myArray[index+1]== myArray[index]) {
myArray.splice(index);
myArray.splice[index+1];
}
}
Certainly you don't want to increment myArray, but index.
The boundary condition should not be length+1 but length-1 as in the body you have myArray[index+1] and don't want to go out of bounds there.
But more importantly, doing splice in your for loop will make the elements shift position, and as you then still increment index, you will skip elements.
In short, you should not use splice in such a loop. You can solve this by going in the reverse direction, and start at the end of the array working towards the beginning.
But the above proposed code does not have this problem and also saves you the step of sorting.
You can do this with two reduce and Object.keys(). First add values to object and then check each value with % 2 and add to array.
var myArray = [5,4,1,2,1,4,5,6,7,8,9,10,1,2,1,5,3,2];
var obj = myArray.reduce(function(o, e) {
o[e] = (o[e] || 0)+1;
return o;
}, {})
var result = Object.keys(obj).reduce(function(r, e) {
if(obj[e] % 2) r.push(Number(e));
return r;
}, []);
console.log(result)

Categories

Resources