Related
I'm looking to move all occurrences of undefined to the end of an array.
So for example if my array looks like: [undefined,"bbc", "cnn"] the function will create: ["bbc", "cnn", undefined]
I have built a script that can do that:
arr = [undefined,"bbc", "cnn"]
var loopNo = 0
for (var f = 0; f < arr.length; f++) {
loopNo += 1
var keyNo = loopNo - 1
if (arr[f] === undefined){
arr.push(arr.splice(keyNo, 1)[0]);
}
}
console.log(arr)
However if undefined occurs more than once it doesn't move it - i.e [undefined , undefined, "cnn"]
How do I make it so undefined is shifted to the end of the array every-time?
Thanks.
Better way [Big O(N)]:
const shiftUndefined = (arr) => {
let duplicate = [];
for (let i = 0, len = arr.length, j = len - 1, k = 0; k <= j; i++) {
const item = arr[i];
if (item === undefined) {
duplicate[j--] = undefined;
} else {
duplicate[k++] = item;
}
}
return duplicate;
};
const arr = [undefined, "bbc", "cnn"];
const arr2 = [undefined, undefined, "cnn"];
console.log(shiftUndefined(arr)); // [ 'bbc', 'cnn', undefined ]
console.log(shiftUndefined(arr2)); // [ 'cnn', undefined, undefined ]
Keep Same ref: Big O(N)
function sameRefMove(array) {
const filtered = array.filter(Boolean);
let index = 0;
while (index < filtered.length) {
array[index] = filtered[index++];
}
while (index < array.length) {
array[index++] = undefined;
}
return array;
}
var array = [undefined, "bbc", "cnn"];
console.log(sameRefMove(array));
console.log(sameRefMove([undefined, undefined, "cnn"]));
Using reduce:
const move = (arr) => {
const [filtered, noValue] = arr.reduce(
([filtered, noValue], item) => {
if (item === undefined) noValue.push(undefined);
else filtered.push(item);
return [filtered, noValue];
},
[[], []]
);
return filtered.concat(noValue);
};
let arr = [undefined, "bbc", "cnn"];
arr = move(arr);
console.log(arr);
arr = [undefined, undefined, "cnn"];
arr = move(arr);
console.log(arr);
Just use sort if performance is not concern.
const arr = [undefined,"bbc", "cnn"]
console.log(arr.sort()) // [ 'bbc', 'cnn', undefined ]
Assuming you:
only want to move undefined to the end of an array without affecting others,
are okay with creating intermediate arrays,
here's a simple way to accomplish this, using Array.prototype.filter():
let arr = [undefined, undefined,"bbc", "cnn", "abc"];
const arrDefined = arr.filter(el => el !== undefined);
const arrUndefined = arr.filter(el => el === undefined);
arr = [...arrDefined, ...arrUndefined];
console.log('arr:', arr);
// [ "bbc", "cnn", "abc", undefined, undefined ]
You could iterate from the end and splice and push undefined values.
function move(value, array) {
var i = array.length - 1;
while (i--) {
if (array[i] === value) {
array.push(array.splice(i, 1)[0]);
}
}
return array;
}
var array = [undefined, "bbc", "cnn"];
console.log(move(undefined, array));
I'm trying to write a function that would return all permutations of a given array of numbers, as in the examples below:
a = [1, 1, 2]
permutator(a) = [
[1,1,2],
[1,2,1],
[2,1,1]
]
b = [2, 3, 4]
permutator(b) = [
[2,3,4],
[2,4,3],
[3,2,4],
[3,4,2],
[4,2,3],
[4,3,2]
]
order of the results isn't accurate
Can you explain why the code below doesn't work? I tried debugging and I think the variables temp and result aren't being kept in memory as arrays but I'm not sure if that is the case nor how to solve it.
permutator = (array) => {
const result = [];
array.forEach((num, i) => {
array.forEach((num2, j) => {
if (i !== j) {
let temp = array;
temp[i] = temp[j];
temp[j] = num;
console.log(`temp: ${temp}`);
console.log(`result: ${result}`);
if (!result.includes(temp)) result.push(temp);
console.log(`result: ${result}`);
}
});
});
return result;
}
as you can see I tried console.logging everything to no avail...
This works for three digits, but I wouldn't call it elegant:
let indexAtEachPosition = function (ind, arr) {
let arrayGroup = [];
let arrLength = arr.length;
let movedArray = [...arr];
for (let indx = 0; indx < arrLength; indx++) {
let firstItem = movedArray[0];
let otherItems = movedArray.slice(1);
otherItems[arrLength - 1] = firstItem
movedArray = [...otherItems];
arrayGroup.push(movedArray.join(" "));
}
return arrayGroup;
}
let permutator = function permutator(values) {
let returnValue = new Set();
let digitCount = values.length;
returnValue.add(values.join(" "));
values.forEach(digit => {
indexAtEachPosition(digit, values).forEach(variation => {
returnValue.add(variation);
});
});
[...values.reverse()].forEach(digit => {
indexAtEachPosition(digit, values).forEach(variation => {
returnValue.add(variation);
});
});
return [...returnValue].map(eachArr => {
return eachArr.split(" ");
});
};
// console.log( permutator([0,0,0]) );
// console.log( permutator([1,0,0]) );
// console.log( permutator([1,2,3]) );
console.log( permutator([1,1,2]) );
console.log( permutator([2,3,4]) );
I'm not sure what the goal of this is, but it intrigued me like some kind of code test so I tried to solve it.
This answer is the most elegant solution I found.
const permutator = (inputArr) => {
let result = [];
const permute = (arr, m = []) => {
if (arr.length === 0) {
result.push(m)
} else {
for (let i = 0; i < arr.length; i++) {
let curr = arr.slice();
let next = curr.splice(i, 1);
permute(curr.slice(), m.concat(next))
}
}
}
permute(inputArr)
return result;
}
console.log(permutator([1,2,3]));
I am trying to find values that commonly appear next to each other in an array.
E.G. given the array:
["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"]
it should return something similar to:
[[["dog","cat"],4],[["cat","pig"],2],[["dog","cat","pig"],2]]
Here is some better data: https://pastebin.com/UG4iswrZ
Help would be greatly appreciated. Here is my current failed attempt at doing something similar:
function findAssociations(words){
var temp = [],tempStore = [],store = [],found = false;
//loop through the words counting occurrances of words together with a window of 5
for(var i = 0;i<words.length-1;i++){
if(i % 5 == 0){
//on every fith element, loop through store attempting to add combinations of words stored in tempStore
for(var j = 0;j<5;j++){
temp = []
//create the current combination
for(var k = 0;k<j;k++){
temp.push(tempStore[k]);
}
//find if element is already stored, if it is, increment the occurrence counter
for(var k = 0;k<store.length;k++){
if(store[k][0]===temp){
found = true;
store[k][1] = store[k][1]+1;
}
}
//if it isn't add it
if(found == false){
store.push([temp,1]);
}
found == false;
}
tempStore = [];
} else {
//add word to tempStore if it i isnt a multiple of 5
tempStore.push(words[i]);
}
}
}
This script is doesn't remove combinations that appear once,it doesn't sort the output by occurrences, nor does it work. It is just an outline of how a possible solution might work (as suggested by benvc).
Here is a generic solution working with multiple group sizes.
You specify a range of group sizes, for example [2,4] for groups of 2 to 4 elements and a minimum number of occurrences.
The function then generates all groups of neighbours of the given sizes, sorts each group and counts the duplicates. The sorting step can be removed is the order in the groups matters.
The duplicates are counted by creating a dictionary whose keys are the group elements sorted and jointed with a special marker. The values in the dictionary are the counts.
It then returns the groups sorted by occurences and then by group size.
const data = ["dog","cat","goat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
function findSimilarNeighbors(groupSizeRange, minOccurences, data) {
const getNeighbors = (size, arr) => arr.reduce((acc, x) => {
acc.push([]);
for (let i = 0; i < size; ++ i) {
const idx = acc.length - i - 1;
(acc[idx] || []).push(x);
}
return acc;
}, []).filter(x => x.length === size);
const groups = [];
for (let groupSize = groupSizeRange[0]; groupSize <= groupSizeRange[1]; ++groupSize) {
groups.push(...getNeighbors(groupSize, data));
}
const groupName = group => group.sort().join('###'); // use a separator that won't occur in the strings
const groupsInfo = groups.reduce((acc, group) => {
const name = groupName(group);
acc[name] = acc[name] || {};
acc[name] = { group, count: (acc[name].count || 0) + 1 };
return acc;
}, {});
return Object.values(groupsInfo)
.filter(group => group.count >= minOccurences)
.sort((a, b) => {
const countDiff = b.count - a.count;
return countDiff ? countDiff : b.group.length - a.group.length;
})
.map(({ group, count }) => [group, count]);
};
console.log(findSimilarNeighbors([2, 4], 2, data));
console.log(findSimilarNeighbors([4, 4], 2, data));
Here is what I came up with. It only finds pairs, but you could modify it to find sets of 3, 4, etc, based on what you % by
const animals = ['dog','cat','goat','dog','cat','elephant','dog','cat','pig','seal','dog','cat','pig','monkey'];
let pairs = ',';
animals.forEach((animal, i) => {
let separator = ',';
if (i % 2 === 0) {
separator = ';'
}
pairs += animal + separator;
});
const evenPairs = pairs.split(',');
const oddPairs = pairs.split(';');
const allPairs = evenPairs.concat(oddPairs).map(pair => pair.replace(/[;,]/, ' '));
let result = {}
allPairs.forEach(pair => {
if (pair.length) {
if (result[pair] === undefined) {
result[pair] = 1;
} else {
result[pair]++;
}
}
});
results in:
dog: 1
cat elephant: 1
cat goat: 1
cat pig: 2
dog cat: 4
elephant dog: 1
goat dog: 1
monkey : 1
pig monkey: 1
pig seal: 1
seal dog: 1
https://stackblitz.com/edit/typescript-wvuvnr
You need to be clear what you mean by close and how close. Just looking at first neighbours you could try:
const findAssociations = words => {
const associations = {}
for (let i = 0; i < words.length - 1; i++) {
const word = words[i]
const wordRight = words[i+1]
const wordOne = word < wordRight ? word : wordRight;
const wordTwo = word < wordRight ? wordRight : word;
const keys = Object.keys(associations)
const key = `${wordOne}:${wordTwo}`
if (keys.indexOf(key) >= 0) {
associations[key]++
} else {
associations[key] = 1
}
}
const keys = Object.keys(associations)
const values = Object.values(associations)
const zipped = keys.map((key, index) => [key, values[index]])
zipped.sort((a, b) => a[1] < b[1] ? 1 : -1);
return zipped;
}
https://stackblitz.com/edit/js-3ppdit
You can use this function inside another function and add every time an element to ["dog", "cat"]
const arr = ["dog", "cat", "goat", "dog", "cat", "dog", "cat", "elephant", "dog", "cat", "pig", "seal", "dog", "cat", "pig", "monkey"]
const findArrayInArray = (arr1, arr2) => {
let count = 0,
arrString1 = arr1.join(""),
arrString2 = arr2.join("");
while (arrString2.indexOf(arrString1) > -1) {
count += 1;
arrString2 = arrString2.replace(arrString1, '');
}
return count;
}
console.log(`["dog", "cat"] exist ${findArrayInArray(["dog", "cat"], arr)} times`)
Assuming each item in the list is a delimiter of a set, and each set counts once for each item (i.e. ["dog", "cat", "goat"] counts as ["dog", "cat"] and ["dog", "cat", "goat"], and assuming you don't want any single occurrences, then here's one way:
const full_list = ["dog","cat","goat","dog","cat","dog","cat","elephant","dog","cat","pig","seal","dog","cat","pig","monkey"];
// create list of unique items
const distinct = (value, index, self) => {
return self.indexOf(value) ===index;
}
const unique_items = full_list.filter(distinct);
// get all patterns
var pre_report = {};
for (var i in unique_items) {
item = unique_items[i];
var pattern = [item];
var appending = false;
for (var j = full_list.indexOf(item) + 1; j < full_list.length; ++j) {
const related_item = full_list[j];
if (item == related_item) {
pattern = [item]
continue;
}
pattern.push(related_item);
if (pattern in pre_report) {
++pre_report[pattern];
} else {
pre_report[pattern] = 1;
}
}
}
// filter out only single occurring patterns
var report = {};
for (key in pre_report) {
if (pre_report[key] > 1) {
report[key] = pre_report[key];
}
}
console.log(report);
produces:
{ 'dog,cat': 5, 'dog,cat,pig': 2, 'cat,pig': 2 }
Yesterday, I faced this interview question. Initially, it seemed pretty easy, at least logically.
But somehow, I could not make it work in JavaScript.
Here is a 2d array of student scores, student names might be repeated multiple times. If this is the case, sum up all the scores and divide by the number of occurrences to find the average, do the Math.floor if necessary.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]]
So, Charles average score would be Math.floor((100+22+37)/3) = 53
And, for Eric it would be Math.floor((65+49)/2) = 57.
So highest average would be ["Bobby","87"].
So far what I have tried fruitlessly..
var op_arr = [];
arr.each(function(item) {
var sum = 0;
var itemCount = 1;
var checkFlag = isItemInArray(arr,item);
if(checkFlag) {
itemCount++;
sum += item[1];
}
});
function isItemInArray(array,item) {
for(let i = 0;i < array.length; i++) {
if(array[i][0] === item[0]) {
return array[i];
}
}
return false;
}
But this does not work.
Please help me and explain the logic.
I would use some code to convert the list into a hash map based on the fact that there can be multiple of the same student.
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]
];
var scores = {};
for (var i = 0; i < arr.length; i++) {
var student = arr[i];
if (!scores.hasOwnProperty(student[0]))
scores[student[0]] = []
scores[student[0]].push(student[1])
}
The result should be:
{
"Bobby": ["87"],
"Charles": ["100", "22", "37"],
"Eric": ["65", "49"]
}
And now you can do a second pass over the object to calculate the average
for (var key in scores) {
if (!scores.hasOwnProperty(key)) continue;
var total = scores[key].reduce(function(next, cur) {
return next + parseInt(cur);
}, 0);
scores[key] = Math.floor(total / scores[key].length);
}
console.log(scores);
I'm sure you can make it a lot more elegant using ES6 features, but this should give you an idea of one solution.
You could first get all average values of each person and then get the highest average of the temporary result.
If more than one person have the same score, all persons are included.
var array = [["Bobby", "87"], ["Charles", "100"], ["Eric", "65"], ["Charles", "22"], ["Charles", "37"], ["Eric", "49"]],
highest = array
.reduce(function (r, a) {
var i = r.findIndex(b => a[0] === b[0]);
if (i !== -1) {
r[i][1] = (r[i][1] * r[i][2] + +a[1]) / ++r[i][2];
} else {
r.push(a.concat(1));
}
return r;
}, [])
.reduce(function (r, a, i) {
if (!i || r[0][1] < a[1]) {
return [a.slice(0, 2)];
}
if (r[0][1] === a[1]) {
r.push(a.slice(0, 2));
}
return r;
}, []);
console.log(highest);
You can following this approach
Collate all the scores for a name in an array of scores using reduce
Iterate the result using map and compute the average of scores array using reduce again.
Demo
var fnSumAvgArray = (arr) => arr.reduce( ( a, c ) => a + c, 0 )/arr.length;
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var output = Object.values( arr.reduce( (a,c) => (
a[c[0]] = (a[c[0]] || { name : c[0], scores : [] }), //check if accumulator already has been initialized for this name or else intialize
a[ c[ 0 ] ].scores.push ( +c[1] ), //push the score into the name based score array
a ) , {}) ) //return the accumulator
.map( s => (
s.avg = fnSumAvgArray(s.scores), s
));
console.log( output );
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","65"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
var x, students=[], counter=[], scoreSums=[], name, i=0;
for(x in arr) {
name = arr[x][0];
j = students.indexOf(name);
if(j < 0) {
students[i] = name;
counter[i] = 1;
scoreSums[i] = parseInt(arr[x][1]);
i++;
} else {
scoreSums[j] += parseInt(arr[x][1]);
counter[j]++;
}
}
var maxMean = 0, mean;
for(i=0; i<students.length; i++) {
mean = scoreSums[i] / counter[i];
if(mean > maxMean) {
name = students[i];
maxMean = mean;
}
}
document.getElementById('maxMean').innerHTML='Highest average is '+ name+' at '+maxMean;
console.log(name, maxMean);
<div id="maxMean"></div>
Here is a 'round the houses' approach. It is a slightly longer way to write it out but I think it's human readable which should help with the concept.
I've commented the code below.
var arr = [
["Bobby", "87"],
["Charles", "100"],
["Eric", "65"],
["Charles", "22"],
["Charles", "37"],
["Eric", "49"]
];
let objects = [];
let names = [];
for (let item of arr) {
// Get unique names
if (names.indexOf(item[0]) <= 0) {
names.push(item[0]);
}
// Create object rather than array
let score = {
name: item[0],
score: parseInt(item[1])
}
objects.push(score);
}
// Work out average based on name
function countScore(name) {
let count = 0;
let total = 0;
for (let object of objects) {
if (object.name === name) {
count += 1;
total += object.score
}
}
let avgScore = total / count;
console.log(name + ': ' + avgScore)
}
// Run function for each unique name
for (let name of names) {
countScore(name);
}
I hope you find this helpful.
let findMaxAvg = (arr) => {
let studmap = new Map();
arr.forEach((item,ind) => {
let [name,score] = [item[0],item[1]];
!studmap.has(name) ? studmap.set(name,[score,1]) : studmap.set(name,
[studmap.get(name)[0]+score,studmap.get(name)[1]+1])
});
return Math.max(...[...studmap.values()].map(sdata =>
Math.round(sdata[0]/sdata[1])));
}
var arr = [
["Bobby","87"],
["Charles","100"],
["Eric","200"],
["Charles","22"],
["Charles","37"],
["Eric","49"]];
console.log(findMaxAvg(arr));
There's a lot of ways you can solve this as per the solutions provided above. I've also encountered this question before and I used
Map()
to collect all students and their scores and return array with [name, average].
const scores = [['brian', 80], ['brian', 90], ['brian', 65], ['robhy', 85], ['robhy', 75], ['ryan', 75], ['murphy', 85]];
const calculateHigestAvg = (scores)=>{
let studentObj = new Map();
let score = [];
for (var i = 0; i < scores.length; i++) {
if (studentObj.has(scores[i][0])) {
const studentScoreArr = studentObj.get(scores[i][0])
studentScoreArr.push(scores[i][1]);
studentObj.set(scores[i][0], studentScoreArr);
} else {
studentObj.set(scores[i][0], [scores[i][1]]);
}
}
return Array.from(studentObj.entries())
.map((item, index)=>{
return [item[0], Math.floor(item[1].reduce((a, b) => a + b) / item[1].length)]
}
, 0)
}
calculateHigestAvg(scores);
// results
[["brian", 78],["robhy", 80],["ryan", 75],["murphy", 85]]
So I have a series of arrays, each of which are 2500 long, and I need to serialize and store all them in very limited space.
Since I have many duplicates, I wanted to cut them down to something like below.
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
// to
[0x4,2,7,3x2,0x9]
I wrote a couple one-liners (utilising Lodash' _.repeat) to convert to and from this pattern, however converting to doesn't seem to work in most/all cases.
let serialized = array.toString().replace(/((?:(\d)+,?)((?:\2+,?){2,}))/g, (m, p1, p2) => p2 + 'x' + m.replace(/,/g, '').length);
let parsed = serialized.replace(/(\d+)x(\d+),?/g, (z, p1, p2) => _.repeat(p1 + ',', +p2)).split(',');
I don't know why it doesn't work. It may be due to some of the numbers in the array. Eye-balling, the largest one is 4294967295, however well over 90% is just 0.
What am I missing in my RegEx that's preventing it from working correctly? Is there a simpler way that I'm too blind to see?
I'm fairly confident with converting it back from the serialized state, just need a hand getting it to the state.
Straight forward and simple serialization:
let serialize = arr => {
const elements = [];
const counts = []
let last = undefined;
[0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0].forEach((el,i,arr)=>{
if (el!==last) {
elements.push(el);
counts.push(1);
} else {
counts[counts.length-1]++;
}
last = el;
})
return elements.map((a,i)=>counts[i]>1?`${a}x${counts[i]}`:a).join(",");
};
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
UPDATE
Pure functional serialize one:
let serialize = arr => arr
.reduce((memo, element, i) => {
if (element !== arr[i - 1]) {
memo.push({count: 1, element});
} else {
memo[memo.length - 1].count++;
}
return memo;
},[])
.map(({count, element}) => count > 1 ? `${count}x${element}` : element)
.join(",");
console.log(serialize([0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]));
Pure functional deserialize:
const deserialize = str => str
.split(",")
.map(c => c.split("x").reverse())
.reduce((memo, [el, count = 1]) => memo.concat(Array(+count).fill(+el)), []);
console.log(deserialize("4x0,2,7,2x3,9x0"))
In order to avoid using .reverse() in this logic, I'd recommend to change serialization from 4x0 to 0x4
Try this
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0];
var finalArray = []; //array into which count of values will go
var currentValue = ""; //current value for comparison
var tmpArr = []; //temporary array to hold values
arr.forEach( function( val, index ){
if ( val != currentValue && currentValue !== "" )
{
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
tmpArr = [];
}
tmpArr.push(val);
currentValue = val;
});
finalArray.push( tmpArr.length + "x" + tmpArr[0] );
console.log(finalArray);
Another version without temporary array
var arr = [0, 0, 0, 0, 2, 7, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var finalArray = []; //array into which count of values will go
var tmpCount = 0; //temporary variable to hold count
arr.forEach(function(val, index) {
if ( (val != arr[ index - 1 ] && index !== 0 ) )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
tmpCount = 0;
}
tmpCount++;
if ( index == arr.length - 1 )
{
finalArray.push(tmpCount + "x" + arr[ index - 1 ] );
}
});
console.log(finalArray);
Do not use RegEx. Just use regular logic. I recommend array.reduce for this job.
const arr1 = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0]
const arr2 = ['0x4','2','7','3x2','0x9'];
const compact = arr => {
const info = arr.reduce((c, v) =>{
if(c.prevValue !== v){
c.order.push(v);
c.count[v] = 1;
c.prevCount = 1;
c.prevValue = v;
} else {
c.prevCount = c.prevCount + 1;
c.count[v] = c.count[v] + 1;
};
return c;
},{
prevValue: null,
prevCount: 0,
count: {},
order: []
});
return info.order.map(v => info.count[v] > 1 ? `${v}x${info.count[v]}` : `${v}`);
}
const expand = arr => {
return arr.reduce((c, v) => {
const split = v.split('x');
const value = +split[0];
const count = +split[1] || 1;
Array.prototype.push.apply(c, Array(count).fill(value));
return c;
}, []);
}
console.log(compact(arr1));
console.log(expand(arr2));
This is a typical reducing job. Here is your compress function done in just O(n) time..
var arr = [0,0,0,0,2,7,3,3,0,0,0,0,0,0,0,0,0],
compress = a => a.reduce((r,e,i,a) => e === a[i-1] ? (r[r.length-1][1]++,r) : (r.push([e,1]) ,r),[]);
console.log(JSON.stringify(compress(arr)));
since the motivation here is to reduce the size of the stored arrays, consider using something like gzip-js to compress your data.