Related
Hard to put into words but I am looking for a function that takes an array and reduces it down to a given size. The use case is specifying ticks for graph in d3.
const availableDatetimes: Date[] = [...]
const numberOfXTicks = chartWidth / 80
const tickValues = reduceArrSize(availableDatetimes, numberOfXTicks)
the result should have evenly distributed dates
I called it shrinkArray(), because reducing an array is the name of a different well-defined operation. Here it is:
const shrinkArray = (array, size) => {
const step = array.length / size
return array.filter((v, i) => Math.floor(i % step) == 0)
}
Let's shrink an array of size 100 to size 33:
const originalArray = (new Array(100).fill(0)).map((v, i) => i)
const shrunkenArray = shrinkArray(originalArray, 33)
console.log(shrunkenArray);
console.log(shrunkenArray.length == 33);
So:
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23,
24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71,
72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
96, 97, 98, 99
]
is shrunken to:
[
0, 4, 7, 10, 13, 16, 19, 22, 25,
28, 31, 34, 37, 40, 43, 46, 49, 53,
56, 59, 62, 65, 68, 71, 74, 77, 80,
83, 86, 89, 92, 95, 98
]
that is of size 33.
In this example the array is integers from 0 to 99, but obviously the function works for any data type.
I need help with the distribution of routes by day of the month.
The situation is as follows. There are, for example, 3 zones, each zone has routes (not always the same number).
The task is to distribute routes by weekdays in a month, but there is a condition: routes from two NOT zones can be assigned for one day, zones must not overlap.
Example:
Routes of 1 zone: *1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21 , 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43; *
Routes 2 zones: 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64 , 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79;
Routes 3 zones: 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100 , 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120;
It turns out 120 routes (for example) per month. Let's say there are 20 days in a month, respectively, we divide 120 by 20, we get 6 routes per day.
As a result, we should get the distribution of routes by working days of the week in the following form:
1-6,7-12,13-18,19-24,25-30,31-36,37-43,44-49,50-55,56-61,62-67,68-73,74-79,80-85,86-91,92-97,98-103,104-109,110-115,116-120
As is clear from the number of "number-number" blocks (from which route by which), this is distributed over 20 days.
You can also see that the routes do not intersect zones, i.e. in 1 day there is no such thing:
"43-49", because route 43 is from the 1st zone, and the rest from the 2nd.
As a result, I began to write an algorithm to distribute everything and I could not think of anything better (so far) how to create a two-dimensional array, i.e. an array in which there are arrays with zones, these arrays have route numbers.
Then I wrote a function that distributes these routes without mixing them between zones by recursion and creating a THREE-DIMENSIONAL array.
Then I print the first and last element of each array and get, for example, a string:
'1-6,7-12,13-18,19-24,25-30,31-36,37-43,44-49,50-55,56-61,62-67,68-73,74-79,80-85,86-91,92-97,98-103,104-109,110-115,116-120';
P.S. I had to fix it a little, because it was the case that depending on the number of working days in a week, there could be 1 routes per day, which is bad, so I added a feature that iterates over an array of arrays and if the length of the nested is less than or equal to three (the number of routes is less than or equal to 3m), then we put it in the previous day (as it were)
In theory, everything works, but there is a huge BUT. It does not always work adequately due to the different number of working days in a month. For example: In a month there are 22 working days, and the algorithm divides everything in total into 19 blocks, which corresponds to 19 days, or if 17 working days, then divides into 21 blocks, which is equal to 21 days (BUT THIS IS ONLY AN EXAMPLE, real the numbers can be seen when testing my code).
Can you please tell me how I can correct the code so that the number of allocated blocks is strictly equal to the number of working days (does not depend on the number of working days)?
// Array a - routs
const a = [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
[44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120]
];
const days = 20;
const countRoutsInDay = Math.floor(a.flat().length / days);
const newArr = [];
const resultArr = [];
let str = '';
// Divide into arrays by day
const splitterRouts = (count) => {
let arr = [];
let currentElemOfMainArray = count;
for (let i = 0; i < Math.ceil(a[count].length / countRoutsInDay); i++) {
arr[i] = a[count].slice((i * countRoutsInDay), (i * countRoutsInDay) + countRoutsInDay);
if (i === Math.ceil(a[count].length / countRoutsInDay) - 1) {
currentElemOfMainArray += 1;
newArr.push(arr);
if (currentElemOfMainArray < a.length) {
splitterRouts(currentElemOfMainArray);
}
}
}
}
splitterRouts(0);
// If there are less than or equal to 3 routes, then connect on the previous day
newArr.forEach((el) => {
el.forEach((el2) => {
if (el2.length <= 3) {
const summRoute = el[el.length - 2].concat(el[el.length - 1]);
el.splice(el.indexOf(el[el.length - 2]), el.indexOf(el[el.length - 1]), summRoute);
}
})
});
newArr.forEach((el) => {
el.forEach((el2) => {
str += `${el2[0]}-${el2[el2.length - 1]},`
resultArr.push(str.split(','));
})
})
console.log(str);
console.log(resultArr.length - 1); // this result must be equal to the number of days given above
The answer depends a bit on one decision: what should happen to zone 3 (41 routs) if there are 22 days? Should it be split into 6 days of 5 routs and one day of 11 routs? Or should the routs be evenly distributed with six days of 7 routs and one day of 6 routs? If it's the former, then you'll need to change the bit of code regarding "less than or equal to 3" to append any excess routs to the last day for that zone. Assuming you want the days more distributed more evenly, this solution accomplishes the second case:
First, days are distributed across all the zones (with the biggest zones getting any remainder). Next, within each zone, routs are distributed across days (with the later days getting more routs):
// Array a - routs
const a = [
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43],
[44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120]
];
const days =20;
//evenly distributing the days among the zones, leaving any remaining days on the longest zone(s)
const countRoutsInDay2 = a.map((aInnerArr,i)=>[aInnerArr.length,i]).sort()/*.map(a=>a[1])*/.reverse();
const countRoutsInDay3 = [];
for (let i = 0; i < a.length; i++){
countRoutsInDay3[i] = Math.floor(days/a.length);
if (i >= (a.length - (days % (Math.floor(days/a.length))) )) {countRoutsInDay3[i]++};
}
countRoutsInDay3.reverse();
let countDaysForZone = [];
for (let i = 0; i < a.length; i++){
countDaysForZone[countRoutsInDay2[i][1]] = countRoutsInDay3[i];
}
const newArr = [];
const resultArr = [];
let str = '';
let countRoutsInDay = 5;
// similarly, dividing each zone into days as evenly as possible, with the longer days later
const splitterRouts = (count) => {
let arr = [];
let currentElemOfMainArray = count;
let routsOnThisDay = [];
let daysForThisArray = countDaysForZone[count];
for (let i = 0; i < daysForThisArray; i++){
routsOnThisDay[i] = Math.floor(a[count].length/daysForThisArray);
if (i >= (daysForThisArray - (a[count].length % daysForThisArray) )) {routsOnThisDay[i]++};
}
for (let i = 0; i < countDaysForZone[count]; i++) {
let routsSoFar = 0;
if (i==1){routsSoFar = routsOnThisDay[0];}
if (i>1){
routsSoFar = routsOnThisDay.slice(0,i).reduce((a,b)=>a+b);
}
arr[i] = a[count].slice(routsSoFar, routsSoFar + routsOnThisDay[i]);
if (i === countDaysForZone[count] - 1) {
currentElemOfMainArray += 1;
newArr.push(arr);
if (currentElemOfMainArray < a.length) {
splitterRouts(currentElemOfMainArray);
}
}
}
}
splitterRouts(0);
// If there are less than or equal to 3 routes, then connect on the previous day
newArr.forEach((el) => {
el.forEach((el2) => {
if (el2.length <= 3) {
const summRoute = el[el.length - 2].concat(el[el.length - 1]);
el.splice(el.indexOf(el[el.length - 2]), el.indexOf(el[el.length - 1]), summRoute);
}
})
});
newArr.forEach((el) => {
el.forEach((el2) => {
str += `${el2[0]}-${el2[el2.length - 1]},`
resultArr.push(str.split(','));
})
})
console.log(str);
console.log(resultArr.length - 1);
So this the function code i came up with I do not know what is wrong with it.
It keeps giving 0 as my code.
function minValue(array) {
let min = 0;
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array[i].length; j++) {
if (array[j][i] == "string") {
min = Math.min(min, parseInt(array[j][i]));
} else {
min = Math.min(min, array[j][i]);
}
}
}
return min;
}
let matrix = [
[57, 71, 37, 99, 62, '83', '95', '54', 38, 25],
[8, 64, 100, '51', 43, 21, 21, 33, 11, 43],
[67, 25, 45, 67, 88, 72, 74, 77, 53, 38]
];
console.log(minValue(matrix));
I keep on getting 0 as my answer.
Your function doesn't work because you use 0 as the initial value, and it's smaller than all other values. Use Infinity as the initial value.
Note: you've switch between i and j here - array[j][i]. I would also remove the condition, and always convert to number (I've used the + operator here).
function minValue(array) {
let min = Infinity;
for (let i = 0; i < array.length; i++) {
for (let j = 0; j < array[i].length; j++) {
min = Math.min(min, +array[i][j]);
}
}
return min;
}
const matrix = [
[57, 71, 37, 99, 62, '83', '95', '54', 38, 25],
[8, 64, 100, '51', 43, 21, 21, 33, 11, 43],
[67, 25, 45, 67, 88, 72, 74, 77, 53, 38]
];
console.log(minValue(matrix));
Another options is to flatten matrix to a single array, map all items to numbers, and spread into Math.min():
const minValue = array => Math.min(...array.flat().map(Number));
const matrix = [
[57, 71, 37, 99, 62, '83', '95', '54', 38, 25],
[8, 64, 100, '51', 43, 21, 21, 33, 11, 43],
[67, 25, 45, 67, 88, 72, 74, 77, 53, 38]
];
console.log(minValue(matrix));
I want to iterate through an Array to find the index of the highest numbers on it, then write those index numbers on another new Array.
I came up with this code:
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
let highestScore = Math.max(...scores);
let topScores = [];
for (score of scores) {
if (score == highestScore) {
topScores.push(scores.indexOf(score));
}
}
console.log(topScores);
Then the result that the console shows is:
topScores = [11, 11]
...when I expected:
topScores = [11, 18]
as those are the positions where the highest numbers (both 69) are on the scores array.
Can someone explain to me what's happening? I searched but I can't come up with the issue. Thank you very much.
As mentioned by Fritz Array.indexOf(x) always returns the first position of x in the array. The first 69 is at index 11.
You can use Array.forEach() instead of for...of:
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
let highestScore = Math.max(...scores);
let topScores = [];
scores.forEach((score, index) => {
if (score == highestScore) {
topScores.push(index);
}
})
console.log(topScores);
This is beacause indexOf always return the first index at which element exist in array. You can use reduce()
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
let highestScore = Math.max(...scores);
let res = scores.reduce((ac,a,i) => (a === highestScore && ac.push(i),ac),[])
console.log(res)
As others have already mentioned, both indexOf and findIndex always return the index of the first match. But there is no need for using either because you can access the current index in for..of like this:
for (const [index, score] of scores.entries())
which allows you to simply do
topScores.push(index);
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
let highestScore = Math.max(...scores);
let topScores = [];
for (const [index, score] of scores.entries()) {
if (score == highestScore) {
topScores.push(index);
}
}
console.log(topScores);
the .indexOf() returns only the index of the first occurrence of the particular ignore the other indexes so in this case, indexOf won't work. Just loop through and push the index of the value equal to the maximum number to a new array
let scores = [60, 50, 60, 58, 54, 54, 58, 50, 52, 54, 48, 69, 34, 55, 51, 52, 44, 51, 69, 64, 66, 55, 52, 61, 46, 31, 57, 52, 44, 18, 41, 53, 55, 61, 51, 44];
let noReplica = Math.max(...scores)
let ret = [];
for (let i = 0, length = scores.length; i < length; i++) {
if (scores[i] === noReplica) {
ret.push(i)
}
}
console.log(ret)
scores.indexOf(score) always returns the first position of score in the scores.
Use following code if you need the index.
for (let i = 0; i < scores.length; i ++) {
let score = scores[i];
if (score == highestScore) {
topScores.push(i);
}
}
console.log(topScores);
I have the following code in a test case on jsPerf:
var arr = [0, 45, 96, 8, 69, 62, 80, 91, 89, 24, 6, 23, 49, 88, 26, 40, 87, 61, 83, 2, 60, 53, 43, 82, 67, 3, 65, 37, 42, 77, 73, 38, 9, 46, 75, 10, 63, 15, 47, 28, 79, 55, 59, 95, 11, 93, 70, 98, 25, 48, 30, 5, 72, 12, 84, 1, 29, 13, 50, 33, 19, 7, 31, 57, 32, 44, 74, 51, 35, 90, 86, 54, 4, 64, 92, 71, 22, 41, 16, 17, 27, 76, 39, 18, 99, 94, 36, 66, 85, 20, 21, 56, 34, 81, 14, 78, 68, 58, 97, 52];
Array.prototype.remove = function(from, to) {
var rest = this.slice((to || from) + 1 || this.length);
this.length = from < 0 ? this.length + from : from;
return this.push.apply(this, rest);
};
function quicksort( arr ) {
if ( arr.length <= 1 )
return arr;
var i = 0,
len = arr.length,
less = [],
greater = [],
random = Math.floor( Math.random() * len ),
pivot = arr[ random ];
arr.remove( random );
for ( ; i < len - 1; i++ ){
if ( arr[ i ] <= pivot )
less.push( arr[ i ] );
else
greater.push( arr[ i ] );
}
return quicksort( less ).concat( pivot, quicksort( greater ) );
};
If you copy that into your console and run quicksort( arr ), you'll see that it correctly returns a sorted array.
But for some reason, in this test case on jsPerf, my quicksort function seems to be returning only a single number ( as can be seen in 'Perparation Code Output' ). It also seems to be running way faster than it probably should.
Anybody ideas into what's going on would be greatly appreciated.
I think the problem is that you're calling that .remove() function on the original array, so it quickly strips it down to nothing. In other words, each initial call to the quicksort function removes an element.
When I make it create a copy of the array first, then it seems to work.