Related
I'm creating a polyfill of Array.flat() method, however, I'm facing issues while calling the function within itself after checking that the looped element is an array and thats need to be flattened further. When a write a code that is not prototypal, the flattening is proper, however when I try to create a prototype function, I'm unable to get the flattened array. I'm pretty sure that the issue is related with the 'this' keyword. Please have a look at my code.
Here is the code
let arrayFlat = [1, 2, 3, [4, 5, 6, [7, 8, [9]], 10, [11, 12]], [13, [14, 15]]];
const flatArray = (array) => {
let output = [];
const flatten = (array) => {
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i]);
} else {
output.push(array[i]);
}
}
return output;
};
return flatten(array);
};
Array.prototype.myFlat = function () {
let output = [];
for (let i = 0; i < this.length; i++) {
if (Array.isArray(this[i])) {
console.log(this[i]);
this[i].myFlat();
} else {
output.push(this[i]);
}
}
return output;
};
In your first piece of code, you create a single output array. When you recursively call flatten, the code is always pushing to the exact same output array, which is in the closure of flatten. Then once everything is done, you return that array.
In the second code, you create a new array every time you recurse. Each recursion will create an array, flatten itself, and then return that new array. But the return value is ignored, so these values don't go anywhere.
You have a few options
Make the code basically identical to your first one, with an internal function for doing the recursion, and a closure variable used by all:
Array.prototype.myFlat = function () {
let output = [];
const flatten = (array) => {
for (let i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i]);
} else {
output.push(array[i]);
}
}
return output;
};
return flatten(this);
}
Pass the output array as a parameter when you recurse:
// VVVVVV--- added parameter
Array.prototype.myFlat = function (output = []) {
for (let i = 0; i < this.length; i++) {
if (Array.isArray(this[i])) {
this[i].myFlat(output); // <---- forward the array along
} else {
output.push(this[i]);
}
}
return output;
};
Continue having separate arrays, but then merge them together as the stack unwinds:
Array.prototype.myFlat = function () {
let output = [];
for (let i = 0; i < this.length; i++) {
if (Array.isArray(this[i])) {
output.push(...this[i].myFlat()); // <---- added output.push
} else {
output.push(this[i]);
}
}
return output;
};
I am a strong proponent of keeping classes as thin as possible, wrapping functional interfaces wherever possible -
function myFlat(t) {
return Array.isArray(t)
? t.reduce((r, v) => r.concat(myFlat(v)), [])
: [t]
}
Array.prototype.myFlat = function() { return myFlat(this) }
console.log([1,[2,[3],4],[[5]],6,[[[7]]]].myFlat())
// [ 1, 2, 3, 4, 5, 6, 7 ]
I am a Javascript beginner, I have a personal project to use program to find all the possible & no repeat combinations form specific arrays
I suppose have 3 sets of products and 10 style in each set, like this array
[1,2,3,4..10,1,2,4...8,9,10]
①②③④⑤⑥⑦⑧⑨⑩
①②③④⑤⑥⑦⑧⑨⑩
①②③④⑤⑥⑦⑧⑨⑩
totaly array length = 30
I plan to randomly separate it into 5 children, but they can't repeat the same products style
OK result:
①②③④⑤⑥ ✔
②③④⑤⑥⑦ ✔
①②③⑧⑨⑩ ✔
④⑥⑦⑧⑨⑩ ✔
①⑤⑦⑧⑨⑩ ✔
Everyone can evenly assign products that are not duplicated
NG:
①②③④⑤⑥ ✔
①②③④⑤⑦ ✔
①②⑥⑧⑨⑩ ✔
③④⑤⑦⑧⑨ ✔
⑥⑦⑧⑨⑩⑩ ✘ (because number 10 repeated)
My solution is randomly to assign 5 sets of arrays, then use "new Set(myArray[i]).size;" check the value sum is 30 or not, Use [do..white], while sum is not 30 then repeat to do the random assign function until the result is not duplicated.
like this:
function splitArray() {
do {
var productArray = [1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10,1,2,3,4,5,6,7,8,9,10]; //Just for sample, actualy this array will be more then 30
var productPerChildArray = [];
for (var i = 0; i < 5; i++) {
GroupNum = [];
for (var v = 0; v < 6; v++) {
var selectNum = productArray[Math.floor(Math.random() * productArray.length)];
GroupNum.push(selectNum);
productArray = removeItemOnce(productArray, selectNum);
}
productPerChildArray.push(GroupNum);
}
} while (checkIfArrayIsUnique(productPerChildArray));
return productPerChildArray;
}
//---------check repeat or not----------
function checkIfArrayIsUnique(myArray) {
var countRight = 0;
for (var i = 0; i < myArray.length; i++) {
countRight += new Set(myArray[i]).size;
}
return (countRight != 5*6);
}
//----------update productArray status----------
function removeItemOnce(arr, value) {
var index = arr.indexOf(value);
if (index > -1) {
arr.splice(index, 1);
}
return arr;
}
console.log(splitArray());
Seems to solve the problem, but actualy productArray is not must 30, this solution will spend to much time to try the no repeat combination. Low efficiency
I believe they have a other solution to solve the problem better than my idea
Any help would be greatly appreciated.
My approach would be: just place the next number in an array that is selected randomly - of course, filter out those that already contain the next number.
// the beginning dataset
const data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
// the size of the groups to be formed (must divide the
// size of beginning dataset without a remainder)
const ARR_LENGTH = 6
// creating the array that will be filled randomly
const getBaseArray = ({ data, arrLength }) => {
const num = parseInt(data.length / arrLength, 10)
return Array.from(Array(num), () => [])
}
// filter arrays that satisfy conditions
const conditions = ({ subArr, val }) => {
if (subArr.includes(val)) return false
if (ARR_LENGTH <= subArr.length) return false
return true
}
const getArraysFiltered = ({ val, arr }) => {
return arr
.map((e, i) => ({
origIdx: i,
subArr: e,
}))
.filter(({ subArr }) => conditions({ subArr, val }))
}
// select a random array from a list of arrays
const getRandomArrIdx = ({ arr }) => {
return Math.floor(Math.random() * arr.length)
}
// get the original array index from the filtered values
const getArrIdx = ({ val, arr }) => {
const filtered = getArraysFiltered({ val, arr })
if (!filtered.length) return -1
const randomArrIdx = getRandomArrIdx({ arr: filtered })
return filtered[randomArrIdx].origIdx
}
const getFinalArr = ({ data }) => {
// short circuit: if the data cannot be placed in
// arrays evenly, then it's a mistake (based on
// the current ruleset)
if (data.length % ARR_LENGTH) return [false]
// creating the array that will hold the numbers
const arr = getBaseArray({ data, arrLength: ARR_LENGTH })
let i = 0;
for (i; i < data.length; i++) {
const idx = getArrIdx({
val: data[i],
arr,
})
// if there's no place that the next number could be
// placed, then break (prematurely), so the algorithm
// can be restarted as early as possible
if (idx === -1) break;
arr[idx].push(data[i])
}
if (i < data.length) {
// restart algorithm if we couldn't place
// all the numbers in the dataset
return getFinalArr({ data })
} else {
return arr
}
}
// constructing the final array of arrays & logging them:
console.log('res', getFinalArr({ data }).join(' '))
console.log('res', getFinalArr({ data }).join(' '))
console.log('res', getFinalArr({ data }).join(' '))
console.log('res', getFinalArr({ data }).join(' '))
I don't know if it's more efficient, but I found this question interesting.
Now, the algorithm is
broken down into small logical steps that are
easy to follow
simple to tweak to needs
works with all sizes of data (if the groups to be made can be filled equally)
This is what I've tried and it seems like I'm on the right path but I've been trying to tweak this algorithm for a while and I can't figure out what I'm doing wrong. Here is my code so far:
const getThem = async () => {
const format2 = 'YYYY/MM/DD';
const obj = {};
for (let i = 0; i < data.length; i++) {
for (let j = 0; j < data[i].tests.length; j++) {
for (let z = 0; z < channel.length; z++) {
if (data[i].tests[j] === channel[z].id) {
const dateTime = moment(channel[z].start).format(format2);
const dateTime2 = moment(channel[z].end).format(format2);
const dateList = getDateArray(new Date(dateTime), new Date(dateTime2));
if (Date.parse(dateTime) > Date.parse('2019-11-15') || Date.parse(dateTime) < Date.parse('2019-11-04')) {
break;
}
if (!channel[z].end) {
// eslint-disable-next-line operator-assignment
obj[dateTime] += (1 / data.length);
} else {
for (let k = 0; k < dateList.length; k++) {
if (!obj.hasOwnProperty(dateList[k])) {
obj[dateList[k]] = 1 / data.length;
} else {
obj[dateList[k]] += (1 / data.length);
}
}
}
}
}
}
}
setDates(Array.sort(Object.keys(obj)));
setValues(Object.values(obj));
};
Check my answer. The approach is simple. First we loop through all the testsResponse data and get the minimum and maximum dates then we iterate through each day from min to max date. For each current day we check the test ids in testsResponse data and then we filter the channels data from the tests array in channelsResponse data and then finally we take the % by the filtered channels data and total channels response data and push it in the results array. The only catch here is that it will push all the dates between minimum test.start and maximum test.end dates both inclusive. If you want to exclude the dates for which testIds is empty then just before the results.push() check if testIds.length is not 0. Here is the getThem():
// given channelsResponse & testsResponse
const getThem = () => {
// get min and max dates
let max='';
let min=new Date().toISOString();
testsResponse.forEach(test => {
min=test.start && min.localeCompare(test.start)>0?test.start:min;
max=test.end && max.localeCompare(test.end)<0?test.end:max;
});
min=new Date(min.substr(0,10)+'T00:00:00.000Z');
max=new Date(max.substr(0,10)+'T00:00:00.000Z');
let results = [];
// loop from min date to max date
for(var i=min; i<=max; i.setDate(i.getDate()+1)) {
let p=0;
let d=i.toISOString().substr(0,10);
// get test ids within the current date
const testIds = testsResponse.filter(t => d.localeCompare((t.start+'').substr(0,10))>=0 && d.localeCompare((t.end+'').substr(0,10))<=0).map(t => t.id);
// get channels where above test ids were found
const channels = channelsResponse.filter(c => c.tests.some(t => testIds.includes(t)));
// calculate %
p=Math.round(channels.length*100.0/channelsResponse.length);
// push data for current date to results
results.push({date: i.toISOString(), utilizationPercentage: p})
}
return results;
};
console.log(getThem());
// [{"date":"2019-11-04T00:00:00.000Z","utilizationPercentage":14},{"date":"2019-11-05T00:00:00.000Z","utilizationPercentage":86},{"date":"2019-11-06T00:00:00.000Z","utilizationPercentage":71},{"date":"2019-11-07T00:00:00.000Z","utilizationPercentage":43},{"date":"2019-11-08T00:00:00.000Z","utilizationPercentage":57},{"date":"2019-11-09T00:00:00.000Z","utilizationPercentage":29},{"date":"2019-11-10T00:00:00.000Z","utilizationPercentage":29},{"date":"2019-11-11T00:00:00.000Z","utilizationPercentage":57},{"date":"2019-11-12T00:00:00.000Z","utilizationPercentage":57},{"date":"2019-11-13T00:00:00.000Z","utilizationPercentage":57},{"date":"2019-11-14T00:00:00.000Z","utilizationPercentage":43},{"date":"2019-11-15T00:00:00.000Z","utilizationPercentage":43}]
Check the demo code here: https://ideone.com/L2rbVe
To avoid iteration over your inputs several times, getting a bad time complexity, I would suggest using Maps and Sets, so you can find related information efficiently.
I kept your function getDateArray. As you didn't provide its source, I reproduced it. But you may want to use your own:
const channelsResponse = [{id: 372,name: 'Channel 01',lab: 'Fullerton',tests: [3, 4, 7, 8],},{id: 373,name: 'Channel 02',lab: 'Fullerton',tests: [1, 2, 5, 6],},{id: 374,name: 'Beta Channel',lab: 'Fullerton',tests: [],},{id: 472,name: 'Channel 01',lab: 'Queens',tests: [9, 11, 12, 13],},{id: 473,name: 'Channel 02',lab: 'Queens',tests: [15, 17, 19],},{id: 474,name: 'Channel 03',lab: 'Queens',tests: [21, 22, 24, 25],},{id: 475,name: 'Channel 04',lab: 'Queens',tests: [26, 27, 28, 29, 30],},];
const testsResponse = [{id: 1,start: '2019-11-05T11:05:00Z',end: '2019-11-05T13:05:00Z',},{id: 2,start: '2019-11-06T11:05:00Z',end: '2019-11-06T13:05:00Z',},{id: 3,start: '2019-11-04T11:05:00Z',end: '2019-11-04T13:09:00Z',},{id: 4,start: '2019-11-04T17:00:00Z',end: '2019-11-05T09:32:00Z',},{id: 5,start: '2019-11-11T11:05:00Z',end: '2019-11-12T13:05:00Z',},{id: 6,start: '2019-11-12T14:05:00Z',end: '2019-11-15T13:05:00Z',},{id: 7,start: '2019-11-07T11:05:00Z',end: '2019-11-08T13:05:00Z',},{id: 8,start: '2019-11-08T15:05:00Z',end: '2019-11-08T15:35:00Z',},{id: 9,start: '2019-11-05T09:05:00Z',end: '2019-11-08T12:05:00Z',},{id: 11,start: '2019-11-08T12:35:00Z',end: '2019-11-08T13:35:00Z',},{id: 12,start: '2019-11-08T17:00:00Z',end: '2019-11-11T10:00:00Z',},{id: 13,start: '2019-11-11T12:00:00Z',end: null,},{id: 15},{id: 17,start: '2019-11-05T17:00:00Z',end: '2019-11-06T10:00:00Z',},{id: 19,start: '2019-11-06T12:00:00Z',end: '2019-11-06T13:22:00Z',},{id: 21,start: '2019-11-05T09:05:00Z',end: '2019-11-06T12:05:00Z',},{id: 22,start: '2019-11-08T12:35:00Z',end: '2019-11-08T13:35:00Z',},{id: 24,start: '2019-11-11T17:00:00Z',end: '2019-11-15T10:00:00Z',},{id: 25,start: '2019-11-15T12:00:00Z',},{id: 26,start: '2019-11-05T09:05:00Z',end: '2019-11-06T12:05:00Z',},{id: 27,start: '2019-11-07T12:35:00Z',end: '2019-11-07T13:35:00Z',},{id: 28,start: '2019-11-08T17:00:00Z',end: '2019-11-11T10:00:00Z',},{id: 29,start: '2019-11-12T12:00:00Z',end: '2019-11-12T14:00:00Z',},{id: 30,start: '2019-11-13T12:00:00Z',end: '2019-11-13T14:00:00Z',},];
function getDateArray(start, end) {
if (!start || !end) return [];
let date = new Date(start.slice(0,11) + "00:00:00Z");
let last = Date.parse(end.slice(0,11) + "00:00:00Z");
let dates = [date.toJSON()];
while (+date != last) {
date.setDate(date.getDate()+1);
dates.push(date.toJSON());
}
return dates; // array of date-strings
}
// Create two maps for faster retrieval
let mapTestToDates = new Map(testsResponse.map(( { id, start, end }) => [id, getDateArray(start, end)]));
let allDates = [...new Set([...mapTestToDates.values()].flat())].sort();
let mapDateToChannels = new Map(allDates.map(date => [date, new Set]));
// Main data collection loop
for (let channel of channelsResponse) {
for (let test of channel.tests) {
for (let date of mapTestToDates.get(test)) {
mapDateToChannels.get(date).add(channel);
}
}
}
// Finally calculate the percentages
let result = Array.from(mapDateToChannels.entries(), ([date, channels]) =>
({ date, utilizationPercentage: Math.round(channels.size * 100 / channelsResponse.length) })
);
console.log(result);
I'm trying to create an algorithm to find duplicate values in a list and return their respective indexes, but the script only returns the correct value, when I have 2 equal elements:
array = [1,2,0,5,0]
result -> (2) [2,4]
Like the example below:
array = [0,0,2,7,0];
result -> (6) [0, 1, 0, 1, 0, 4]
The expected result would be [0,1,4]
Current code:
const numbers = [1,2,0,5,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(numbers.indexOf(avg),numbers.indexOf(avg,n_loop))
};
};
};
return tie;
}
console.log(checkATie(numbers));
if possible I would like to know some way to make this code more concise and simple
Use a Set
return [...new Set(tie)]
const numbers1 = [1,2,0,5,0];
const numbers2 = [0,0,2,7,0];
const checkATie = avgList => {
let averages, tie, n_loop, currentAverage;
averages = [... avgList];
tie = [];
n_loop = 0;
for(let n = 0; n <= averages.length; n++) {
currentAverage = parseInt(averages.shift());
n_loop++
for(let avg of averages) {
if(avg === currentAverage) {
tie.push(avgList.indexOf(avg),avgList.indexOf(avg,n_loop))
};
};
};
return [...new Set(tie)]
}
console.log(checkATie(numbers1));
console.log(checkATie(numbers2));
I hope this help you.you can use foreach function to check each item of array
var array = [0,0,2,7,0];
var result = [] ;
array.forEach((item , index)=>{
if(array.findIndex((el , i )=> item === el && index !== i ) > -1 ){
result.push(index)
}
})
console.log(result);
//duplicate entries as an object
checkDuplicateEntries = (array) => {
const duplicates = {};
for (let i = 0; i < array.length; i++) {
if (duplicates.hasOwnProperty(array[i])) {
duplicates[array[i]].push(i);
} else if (array.lastIndexOf(array[i]) !== i) {
duplicates[array[i]] = [i];
}
}
console.log(duplicates);
}
checkDuplicateEntries([1,2,0,5,0]);
// hope this will help
Create a lookup object with value and their indexes and then filter all the values which occurred more than once and then merge all indexes and generate a new array.
const array = [1, 2, 0, 5, 0, 1, 0, 2],
result = Object.values(array.reduce((r, v, i) => {
r[v] = r[v] || [];
r[v].push(i);
return r;
}, {}))
.filter((indexes) => indexes.length > 1)
.flatMap(x => x);
console.log(result);
I am programming a physics simulation, and the current version is on my website. I don't know if I can post the link, so I won't. The program is a multi-link inverted pendulum, and I am trying to write functions for running the physics. To do this, I have to set a lot of arrays for the different values of each inverted pendulum, such as an array for all of the moment-of-intertias, the masses, the thetas, and more. This is how I am doing it right now:
function fillArray(begin, end, alg) {
let arr = [];
for (let i = begin; i < end; i++) {
arr[i] = alg();
}
return arr;
}
let Ls = fillArray(0, numPoles, () => 2 * this.ls[i]);
When I output the Ls array, it says that every element within is Nan (not a number). What am I doing wrong? How can I make this work?
the alg() function is passed outside the loop where contains a variable i.
this.ls[i] is undefined where i is undefined
so 2 * undefined return NaN
this.ls is not in the function scope so the arrow function should use global ls or you have to pass it into the function.
Try the following code
function fillArray(begin, end, alg) {
let arr = [];
for (let i = begin; i < end; i++) {
arr[i] = alg(i);
}
return arr;
}
let ls = [1,3,11,22];
let numPoles = 3;
let Ls = fillArray(0, numPoles, (i) => 2 * ls[i]);
console.log(Ls);
It would probably be a lot easier to use Array.from inline, rather than calling another standalone function:
const obj = {
ls: [5, 6, 7, 8, 9],
makeLs: function() {
const numPoles = 5;
const Ls = Array.from({ length: numPoles }, (_, i) => 2 * this.ls[i]);
console.log(Ls);
},
};
obj.makeLs();
Closer to your original code, you could modify it by having alg accept a parameter, as i:
const obj = {
ls: [5, 6, 7, 8, 9],
makeLs: function() {
function fillArray(begin, end, alg) {
let arr = [];
for (let i = begin; i < end; i++) {
arr[i] = alg(i);
}
return arr;
}
const numPoles = 5;
let Ls = fillArray(0, numPoles, (i) => 2 * this.ls[i]);
console.log(Ls);
},
};
obj.makeLs();