JS: Sort a part of an array by order - javascript

I have a task to sort an array of random numbers by a certain order which is given from another array. All the other elements which could not be sorted should land at the end of the result array:
const array = [6,1,2,3,4,5]
const sortOrder = [3,2,1]
const shouldSortTo = [3,2,1,6,4,5]
I've got the following solution :
array.sort((a,b)=> {
if(sortOrder.indexOf(a) === -1 && sortOrder.indexOf(b) > -1 ) {
return 1
}
if(sortOrder.indexOf(a) > -1 && sortOrder.indexOf(b) === -1 ) {
return -1
}
return sortOrder.indexOf(a) - sortOrder.indexOf(b)
})
It works but I get the feeling that it's not easy to read or understand. Is there a better or shorter way to do it?

Not saying this is better or more efficient but you can first check if all sortOrder items are in the array, then remove sortOrder items from array and concat the two:
const array = [6,1,2,3,4,5]
const sortOrder = [3,2,1]
const shouldSortTo = [3,2,1,6,4,5]
let result = sortOrder.filter(s => array.includes(s)).concat(array.filter(i => !sortOrder.includes(i)))
console.log(result)
Edit:
Based on #epascarello's comment, this may be a better option if array contains duplicate values:
const array = [6,1,2,3,4,5,2,3]
const sortOrder = [3,2,1]
let sorted = sortOrder.reduce((s, i) => [...s, ...array.filter(x => x === i)], [])
let result = sorted.concat(array.filter(i => !sortOrder.includes(i)))
console.log(result)

I would not keep lookin up the index. Just read it once and use it. You can apply it to your own code.
const array = [6,1,2,3,4,5]
const sortOrder = [3,2,1]
array.sort((a,b)=> {
const aI = sortOrder.indexOf(a);
const bI = sortOrder.indexOf(b);
if (aI === -1 && bI > -1 ) return 1;
else if (aI > -1 && bI === -1 ) return -1;
return aI - bI;
})
console.log(array);
I would check if they are both equal and return zero.
I would check for either to be -1.
And finally I would sort base on the index.
const array = [6,1,2,3,4,5]
const sortOrder = [3,2,1]
array.sort((a,b)=> {
const aI = sortOrder.indexOf(a);
const bI = sortOrder.indexOf(b);
if (aI === bI) return 0;
else if (aI === -1) return 1;
else if (bI === -1) return -1;
else return aI - bI;
})
console.log(array);
Other option, if -1, set to the array's length and just subtract
const array = [6,1,2,3,4,5]
const sortOrder = [3,2,1]
const max = array.length;
array.sort((a,b)=> {
const aI = sortOrder.indexOf(a);
const bI = sortOrder.indexOf(b);
return (aI === -1 ? max : aI) - (bI === -1 ? max : bI);
})
console.log(array);
Or you can reserve the logic
const array = [6,1,2,3,4,5]
const sortOrder = [3,2,1]
const max = array.length;
const reversed = sortOrder.slice().reverse();
array.sort((a,b)=> {
const aI = max - reversed.indexOf(a);
const bI = max - reversed.indexOf(b);
return aI - bI;
})
console.log(array);

Related

How to return the element of an array containing the max value (index 1) (Javascript)?

Given the array below, how can I return the element containing the max number?
let ar = [["finalOrderData",1],["finalFabricData",3],["finalDecorationData",3],["finalHtData",3]]
Expected Result
let ar = ["finalFabricData",3]
This is the function I'm trying with, but it only returns the number itself:
function getMaxOf2DIndex(arr, idx) {
return Math.max.apply(null, arr.map(function (e) { return e[idx] }))
}
Appreciate any help!
Use Array.sort():
let ar = [['finalOrderData', 1], ['finalFabricData', 3], ['finalDecorationData', 3], ['finalHtData', 3]];
let res = ar.sort((a, b) => b[1] - a[1])[0];
console.log(res);
Note that this doesn't handle the alphabetical order of the word.
You can use the Array.reduce function
let ar = [["finalOrderData",1],["finalFabricData",3],["finalDecorationData",3],["finalHtData",3]]
let result = ar.reduce((acc,cur) => !acc || cur[1] > acc[1] ? cur : acc, undefined);
console.log(result);
function getMaxOf2DIndex(arr, idx) {
let maxItem = arr[0];
arr.forEach((item, i) => {
if(item[idx] >= maxItem[idx])
maxItem = item;
});
return maxItem;
}
I think the most straightforward way:
let ar = [["finalOrderData",1],["finalFabricData",3],["finalDecorationData",3],["finalHtData",3]]
var maxNumber = ar[0]
ar.forEach((element) => maxNumber = element[1] > maxNumber[1] ? element : maxNumber)
or the same written more verbosely:
let ar = [["finalOrderData",1],["finalFabricData",3],["finalDecorationData",3],["finalHtData",3]]
var maxNumber = ar[0]
for (element of ar) {
let number = element[1]
if (number > maxNumber[1])
maxNumber = number
}
Here are 2 examples:
const ar = [
["finalOrderData",1],
["finalFabricData",3],
["finalDecorationData",3],
["finalHtData",3]
]
// the 1st result of the biggest value:
const output1 = ar.sort((A,B) => B[1] - A[1])[0]
console.log(output1)
// output: ["finalFabricData",3]
// the last result of the biggest value:
const output2 = ar.sort((A,B) => B[1] <= A[1] ? -1 : 1)[0]
console.log(output2)
// output: ["finalHtData",3]
one more Solution using Array.reduce:
ar.reduce((res, val) => Math.max(res[1],val[1]) === res[1] ? res : val );
trick here is to check with already calculated result.

sample array n^2 times without the same number occurring twice in a row and not repeating every n

I'm trying to write a function that has the arguments of the array to sample arr and the number of samples, size (which is sqaured) and randomly samples from the original array:
arr = [1,2,3,4]
single_number = (x) => {
return x[Math.floor(Math.random()*x.length)];
}
randomize = (arr, size) => {
return Array(size*size).fill().map(x => single_number(arr))
}
randomize(arr, 5)
I want to add the additional requirements to my randomize function:
no number shows up twice in a row
make sure every sizeth item is not the same as the one before it
For example
randomize([1,2,3,4], 2)
[2,4,3,2,4,1,1,2,2,1,4,1,1,1,3,1,1,4,4,1,3,3,2,2,3]
CASE (1)
[
2,4,3,2,4,
2,1,
2,2, // illegal!
1,4,
1,1,1, // illegal!
3,
1,1, // illegal!
4,4, // illegal!
1,
3,3, // illegal!
2,2, // illegal!
3
]
CASE (2)
[
2,4,3,2,4, [0] === 2
2,1,2,2,1, [5] === 2 // illegal!
4,1,1,1,3,
1,1,4,4,1,
3,3,2,2,3
]
I'm trying to use functional programming and avoid a for loop if possible since I think I can do this with a nested for loop?
Well, this isn't as pretty as one would hope, but I think it accomplishes the objective: Iterate size^2 times and choose random elements from the input, taking care to exclude the last value and last nth value chosen...
const randomize = (array, size) => {
const rand = () => Math.floor(Math.random() * array.length);
const randExcept = exclude => {
let v = array[rand()];
while (exclude.includes(v)) v = array[rand()];
return v;
}
const indexes = Array.from(Array(size*size).keys());
let lastV = null, nthV = null;
return indexes.map(i => {
let exclude = nthV!==null && i%size===1 ? [lastV, nthV] : [lastV];
let v = randExcept(exclude);
lastV = v;
if (i%size===1) nthV = v;
return v;
})
}
console.log( JSON.stringify(randomize([1,2,3,4], 2)) )
This defines nth values by the count into the array, so for size===2, the constraint is that every second element (indexes 1,3,5...) can't be equal to the prior second element.
I'd probably do something like this:
const values = [1,2,3,4]
function randomize(values, size) {
let prev;
let prevNth;
return Array(size*size).fill().map( randomNumber );
function randomNumber(_,i) {
let value, ok;
do {
value = values[ Math.floor( Math.random() * values.length ) ];
ok = value != prev;
if ( i % size === 0) {
ok = ok && value != prevNth;
prevNth = value;
}
prev = value;
} while (!ok);
return value;
}
}
arr = randomize(values, 5)
console.log(JSON.stringify(arr));
Or this, using a generator to generate the appropriately sized stream of randomness:
const values = [1,2,3,4];
const arr1 = Array.from( randomValues(5,values) );
console.log(JSON.stringify(arr1));
function *randomValues(n, values) {
const limit = n*n;
let prev, prevNth;
for ( let i = 0 ; i < limit ; ++i ) {
const isNthValue = i % n === 0;
const value = randomValue( values, prev, isNthValue ? prevNth : undefined );
yield value;
prev = value;
prevNth = isNthValue ? value : prevNth;
}
}
function randomValue(values, test1, test2 ) {
let value;
do {
value = values[ Math.floor( Math.random() * values.length ) ];
} while (value === test1 || value === test2 );
return value;
}

Finding conditional index of an item in a list of objects

I need to find index of an item from a list of objects.
First I have to check if that item exist with status of WAITING.
If no item with that status exist, then find something else with any other status.
Is there any better solution for this?
x in this code coming from a map
MainArray.map((x) => {
let itemIndex = orders?.findIndex(item => item.status === 'WAITING' && item.slot=== (x));
if (itemIndex === -1) {
itemIndex = orders && orders.findIndex(item => item.slot === (x));
}
return itemIndex;
}
There won't be a reasonble "single line solution" (those are over-rated in any case; hard to read, hard to debug); but you can avoid searching through the array twice by using a for loop:
const indexes = MainArray.map((x) => {
let bySlotIndex;
for (let index = 0, length = orders?.length; orders && index < length; ++index) {
const order = orders[index];
if (item.slot === x) {
bySlotIndex = bySlotIndex ?? index;
if (item.status === "WAITING") {
return index; // Found a waiting one, we're done
}
}
}
return bySlotIndex ?? -1;
});
Or if you really want to use findIndex, you can avoid some searching by finding the first one with a matching slot first:
const indexes = MainArray.map((x) => {
const bySlotIndex = orders?.findIndex(order => order.slot === x) ?? -1;
if (bySlotIndex === -1) {
return -1;
}
const waitingIndex = orders.findIndex(
order => order.status === 'WAITING' && order.slot === x,
bySlotIndex // Start at the first one with a matching slot
);
return waitingIndex === -1 ? bySlotIndex : waitingIndex;
});
Note that both of the above return -1 if orders is falsy, rather than undefined. Tweak if you really wanted undefined.
You can use findIndex to look for an item with status equal to 'WAITING'. in case, no item exists, use findIndex to return the first item with a status.
const arr = [{status: "WAITING", slot : 1}, {status: "NOTWAITING", slot: 2}];
const getIndex = (arr) => {
const idx = arr.findIndex(x => x.status === 'WAITING');
return idx !== -1 ? idx : arr.findIndex(x => x.status);
}
console.log(getIndex(arr));

Javascript - Counting array elements by reduce method until specific value occurs doesn't give a correct output

const arr = [5,6,0,7,8];
const sum = (arr,num) => arr.reduce((total)=>(num==0 ? total : total+num), 0)
console.log(sum(arr, 0))
Please check how can I make it work. Did some mistake but don't know what exactly. Output is a function instead of a result.
This is awkward to do in .reduce because it goes through the entire array. If we do a naive implementation you can see the problem:
const arr = [5,6,0,7,8];
const sum = (arr,num) => arr.reduce((total, x)=>(num==x ? total : total+x), 0)
console.log(sum(arr, 0))
We now make the check correctly - num==x will return true when x is zero (the value of num). However, the result is wrong because this only returns true once but any other iteration it's still true. And here is the same thing with more logging that describes each step of the process:
const arr = [5,6,0,7,8];
const sum = (arr,num) => arr.reduce((total, x)=> {
const boolCheck = num==x;
const result = boolCheck ? total : total+x;
console.log(
`total: ${total}
num: ${num}
x: ${x}
boolCheck: ${boolCheck}
result: ${result}`);
return result;
}, 0)
console.log(sum(arr, 0))
So, you need to add some flag that persists between iterations, so it doesn't get lost.
One option is to have an external flag that you change within the reduce callback:
const arr = [5,6,0,7,8];
const sum = (arr,num) => {
let finished = false;
return arr.reduce((total, x) => {
if(x === num)
finished = true;
return finished ? total : total+x;
}, 0)
}
console.log(sum(arr, 0))
Alternatively, you can have that flag internal to the reduce callback and pass it around between calls. It works the same way in the end but makes the callback function pure. At the cost of some unorthodox construct:
const arr = [5,6,0,7,8];
const sum = (arr,num) => {
return arr.reduce(({total, finished}, x) => {
if(x === num)
finished = true;
total = finished ? total : total+x;
return {total, finished};
}, {total: 0, finished: false})
.total
}
console.log(sum(arr, 0))
If you want to use reduce but you're OK with using other methods, then you can use Array#indexOf to find the first instance of a value and Array#slice the array that contains any value up to the target value:
const arr = [5,6,0,7,8];
const sum = (arr,num) => {
const endIndex = arr.indexOf(num);
return arr.slice(0, endIndex)
.reduce((total, x)=> total+x, 0)
}
console.log(sum(arr, 0))
Or in as one chained expression:
const arr = [5,6,0,7,8];
const sum = (arr,num) => arr
.slice(0, arr.indexOf(num))
.reduce((total, x)=> total+x, 0);
console.log(sum(arr, 0))
Other libraries may have a takeUntil or takeWhile operation which is even closer to what you want - it gets you an array from the beginning up to a given value or condition. You can then reduce the result of that.
Here is an example of this using Lodash#takeWhile
By using chaining here, Lodash will do lazy evaluation, so it will only go through the array once, instead of scanning once to find the end index and going through the array again to sum it.
const arr = [5,6,0,7,8];
const sum = (arr,num) => _(arr)
.takeWhile(x => x !== num)
.reduce((total, x)=>total+x, 0)
console.log(sum(arr, 0))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
As a note, if you are using Lodash, then you may as well use _.sum(). I didn't above just to illustrate how a generic takeUntil/takeWhile looks.
const arr = [5, 6, 0, 7, 8];
const sum = (arr, num) => _(arr)
.takeWhile(x => x !== num)
.sum()
console.log(sum(arr, 0))
<script src="https://cdn.jsdelivr.net/npm/lodash#4.17.15/lodash.min.js"></script>
Since you need to stop summing values part way through the array, this might be most simply implemented using a for loop:
const arr = [5, 6, 0, 7, 8];
const num = 0;
let sum = 0;
for (let i = 0; i < arr.length; i++) {
if (arr[i] == num) break;
sum += arr[i];
}
console.log(sum);
If you want to use reduce, you need to keep a flag that says whether you have seen the num value so you can stop adding values from the array:
const arr = [5, 6, 0, 7, 8];
const sum = (arr, num) => {
let seen = false;
return arr.reduce((c, v) => {
if (seen || v == num) {
seen = true;
return c;
}
return c + v;
}, 0);
}
console.log(sum(arr, 0));
console.log(sum(arr, 8));
call it as follows:
console.log(sum(arr, 0)());
You need parenthesis to execute the function ()
sum(arr, 0)
Without parenthesis you store a reference to the function in the variable

Finding a Single Integer in an array using Javascript

I was able to pull all single integers after 'reduce', but not working when there's all duplicates and output should be 0, not hitting my else or else if - code keeps outputting 0 vs the single integers
var singleNumber = function(nums) {
var sorted_array = nums.sort();
for (var i=0; i < sorted_array.length; i++){
var previous = sorted_array[i-1];
var next = sorted_array[i+1];
var singles = {key: 0};
var singlesArray = [];
if (sorted_array[i] !== previous && sorted_array[i] !== next){
singlesArray.push(sorted_array[i]);
singlesArray.reduce(function(singles, key){
singles.key = key;
//console.log('key', key);
return singles.key;
},{});
}
else if(singlesArray.length === 0) {
singles.key = 0;
return singles.key;
}
}
console.log('singles.key', singles.key);
return singles.key;
};
console.log(singleNumber([2,1,3,4,4]));
// tests
const n1 = [1,2,3,4,4] //[1,2,3]
const n2 = [1] //[1]
const n3 = [1,1] //0
const n4 = [1,1,1] //0
const n5 = [1,5,3,4,5] //[1,3,4]
const n6 = [1,2,3,4,5] //[1,2,3,4,5]
const n7 = [1,5,3,4,5,6,7,5] //[1,3,4,6,7]
const singleNumber = numbers => {
const reducer = (acc, val) => {
// check to see if we have this key
if (acc[val]) {
// yes, so we increment its value by one
acc[val] = acc[val] + 1
} else {
// no, so it's a new key and we assign 1 as default value
acc[val] = 1
}
// return the accumulator
return acc
}
// run the reducer to group the array into objects to track the count of array elements
const grouped = numbers.reduce(reducer, {})
const set = Object.keys(grouped)
// return only those keys where the value is 1, if it's not 1, we know its a duplicate
.filter(key => {
if (grouped[key] == 1) {
return true
}
})
// object.keys makes our keys strings, so we need run parseInt to convert the string back to integer
.map(key => parseInt(key))
// check to array length. If greater than zero, return the set. If it is zero, then all the values were duplicates
if (set.length == 0) {
return 0
} else {
// we return the set
return set
}
}
console.log(singleNumber(n7))
https://jsbin.com/sajibij/edit?js,console

Categories