Before, I was combining 2 arrays into one array and using sort(), I was able to sort them by created_at.
let result = [...item.messages, ...item.chat_messages]
result.sort((a, b) => new Date(b.created_at) - new Date(a.created_at))
item.messages = result
Now, I am in another scenario. I want to add one more array (sms_messages) into this array and however want it to order by its scheduled_at field.
Is it possible to achieve it with this approach?
let result = [...item.messages, ...item.chat_messages, ...item.sms_messages]
// and order by messages' and chat_messages' created_at (like above) together as
// sms_messages' sheduled_at
You could use || to get the first of both properties it finds to be there, assuming that each object has at least one of both properties, but scheduled_at gets precedence:
result.sort((a, b) =>
new Date(b.scheduled_at || b.created_at) - new Date(a.scheduled_at || a.created_at))
Just check which which property exists and use it to sort the objects.
const sortByDate = props => {
return (a, b) => {
let propA = props.find(p => a.hasOwnProperty(p));
let propB = props.find(p => b.hasOwnProperty(p));
if(!propA || !propB) return 0;
return new Date(b[propB]) - new Date(a[propA]);
};
}
result.sort(sortByDate(['scheduled_at', 'created_at']));
Related
{
"lion":{
"age_in_years":"10",
"name":"king",
"country":"africa"
},
"elephant":{
"age_in_years":"15",
"name":"hero",
"country":"usa"
},
"racoon":{
"age_in_years":"5",
"name":"thanos",
"country":"syria"
},
}
This is the data I'm getting through a web socket in react-native. I want to sort it in ascending order based on the "age_in_years". So the oldest animal's data should be shown at top and the youngest data at the last.
You sould better work with an array insted of object as below, first map it into array and parse the age_in_years and sort it.
const obj2Array = Object.entries(<YourObject>).map(([key, value]) => ({...value, _id: key, age_in_years: parseInt(value.age_in_years)}));
const sorted = obj2Array.sort((a, b) => a.age_in_years - b.age_in_years);
Then you can use .reduce if you want the object back, nevertheless you can use the sorted array to render it.
Sort by age in years oldest first
// use slice() to copy the array
var byAge = array.slice(0);
byAge.sort(function(a,b) {
return a.age_in_years - b.age_in_years ;
});
Store the values in array and make a sort function.
const numbers = [info.lion.age_in_years, info.elephant.age_in_years, info.racoon.age_in_years];
const ascNumbers = numbers.sort((a,b) => a-b);
If you need descending make it like this:
const descNumbers = numbers.sort((a,b) => b-a);
I have two arrays [a,b,c,d] and [b,d,f,h].
I want to get an array back with the common elements [b,d].
I can achieve that with a combination of filter and indexOf:
[a,b,c,d].filter(el => [b,d,f,h].indexOf(el) !== -1)
but I was wondering if and how I can do the same with reduce.
I admit that, despite looking at many examples, reduce still is to me one of the most obscure JS methods, so I'd really appreciate some advice.
ES6, a propoosal with Array#includes
The includes() method determines whether an array includes a certain element, returning true or false as appropriate.
On every loop of aa, reduce adds the element to the result array if the value is found in the test array bb. If not found, the former result is returned.
var aa = ['a','b','c','d'],
bb = ['b','d','f','h'],
cc = aa.reduce((r, a) => bb.includes(a) && r.concat(a) || r, []);
console.log(cc);
Just a smarter approach with using a single array which contains all arrays.
var aa = ['a','b','c','d'],
bb = ['b','d','f','h'],
result = [aa, bb].reduce((a, b) => a.filter(c => b.includes(c)));
console.log(result);
Reduce is designed to return a single value from a list of items. So filter makes much more sense here.
A good use for reduce would be to return the total number of common elements. Check it out here: https://jsfiddle.net/c69vgzL4/
var a = ['a','b','c','d']
var b = ['b','d','f','h']
var number_of_common = b.reduce(function(prev, next) {
return prev + (a.indexOf(next) + 1 ? 1 : 0)
}, 0)
$('body').html(number_of_common)
Not only two arrays but for an intersection of n arrays... Let's invent Array.prototype.intersect()
Array.prototype.intersect = function(...a) {
return [this,...a].reduce((p,c) => p.filter(e => c.includes(e)));
}
var arrs = [[0,2,4,6,8],[4,5,6,7],[4,6]],
arr = [0,1,2,3,4,5,6,7,8,9];
console.log(JSON.stringify(arr.intersect(...arrs)));
// or just do
console.log(JSON.stringify(["a","b","c","d"].intersect(["b","d","f","h"])));
So, struggling a little with this one.
I have an object which looks like this:
const search = something.map((some) => {
const search = {
writer: some.writers, // (this is an array)
title: some.title.toLowerCase(),
reference: some.reference.toLowerCase(),
};
return search;
});
I am trying to do a search on all the text inside the values of the object.
The some.writer field is an array that can have numerous writers inside.
I am currently filtering through like this:
const filtered = search.filter((entry) => Object.values(entry).some((val) => typeof val === 'string'
&& val.includes(searchTerm)));
That filter looks for the search term inside the object and returns all strings which contain the term or part of the term.
The problem is, it only looks at the top level and does not go inside the writers array to search inside there too.
How can I do that?
See below for an option where you don't need the search array (provided you're doing full-string matches, not substrings), but if you keep the search array then When building it from the something array, I'd also make all the writers lower case (and I'd use toLocaleLowerCase for locale-awareness in the "to lower" operation):
const search = something.map((some) => {
const entry = {
writer: some.writers.map(writer => writer.toLocaleLowerCase()),
title: some.title.toLocaleLowerCase(),
reference: some.reference.toLocaleLowerCase(),
};
return entry;
});
Then when filtering, instead of building a bunch of intermediate arrays, I'd search more directly:
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(({title, reference, writers}) => {
return title === val || reference === val || writers.includes(val);
});
But if you want to make it dynamic so adding new properties to the entries works without modifying the search code (other than when you create search), then as has been pointed out in comments, you can use flat to flatten the array of values so writers is spread out into the top level of the array:
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(entry => Object.values(entry).flat().includes(val));
Both are fairly simple to tweak to make substring searches:
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(({title, reference, writers}) => {
return title.includes(val) || reference.includes(val) || writers.some(writer => writer.includes(val));
});
and
// I'm assuming `val` has already been put through `toLocaleLowerCase`
const filtered = search.filter(entry => Object.values(entry).flat().some(e => e.includes(val)));
You might also want to use normalize on the search values and val to handle discrepancies in the different ways some "characters" can be represented in Unicode. If so, basically change all the .toLocaleLowerCase() above to .normalize().toLocaleLowerCase().
If you're doing exact matches, you can avoid having to create the search array and doing all those toLowerCase/toLocaleLowerCase calls. This also has the advantage that the search results have the text in its original form. To do that you'd use an Intl.Collator object telling it that you want case-insensitive matching via the sensitivity option:
const {compare} = new Intl.Collator(undefined, {
sensitivity: "accent" // Or you might want `"base"`
});
const match = (a, b) => compare(a, b) === 0);
const filtered = something.filter(({title, reference, writers}) => {
return match(title, val) || match(reference, val) || writers.some(writer => match(writer, val));
});
You can use Array.prototype.flat to turn something like [1, [2, 3], 4] into [1, 2, 3, 4]:
const filtered = search.filter((entry) => Object.values(entry).flat().some((val) => typeof val === 'string' && val.includes(searchTerm)));
// ^^^^^^^
I have an array of numbers
counter[7,3,9,5,1]
if i do counter.sort() it will change the array to
counter[1,3,5,7,9]
is it possible to sort an array and just get the return of the placements NOT CHANGE THE ORDER
something like this
sortedArrayByPlacment[3,1,4,2,0];
You can copy the array, sort it, then turn it into an object (for quick lookup) and map the original array onto the object to identify the new indicies.
You'll also need to pass a comparator function; .sort without any arguments will sort lexicographically, resulting in, eg, 11 coming before 2, which almost certainly isn't desirable.
const arr = [7,3,9,5,1];
const indexByNum = Object.fromEntries(
[...arr]
.sort((a, b) => a - b)
.map((num, i) => [num, i])
);
const indicies = arr.map(num => indexByNum[num]);
console.log(indicies);
I'm using Object.fromEntries to make the mapping less computationally complex, but you could do without it if you wanted:
const arr = [7,3,9,5,1];
const sorted = [...arr].sort((a, b) => a - b);
const indicies = arr.map(num => sorted.indexOf(num));
console.log(indicies);
PS: These snippets work only if the values are unique in the array
Given an array and variable as following..
array = ['complete','in_progress','planned'];
value = 'planned';
I want to sort the array always starting with the 'value' variable
the output should display.
array = ['planned','in_progress','complete'];
eg.
array = ['complete','in_progress','planned'];
value = 'in_progress';
output would be...
array = ['in_progress','complete','planned'];
I tried different ways but I failed to come with a decent solution. Any clean and short ideas?
You can sort and give higher priority to value - if it matches one of the arguments, then it will be sorted before anything. If neither of the two items is value, then just sort normally.
const array = ['complete','in_progress','planned'];
const value = 'in_progress';
array.sort((a, b) => {
//a comes first
if (a == value) return -1;
//b comes first
if (b == value) return 1;
//neither matches `value` - compare them normally
return a.localeCompare(b);
});
console.log(array);
And this is the shorter version of the same by (somewhat) abusing type coercion:
const array = ['complete','in_progress','planned'];
const value = 'in_progress';
array.sort((a,b) => ((a == value) * -1) || b == value || a.localeCompare(b));
console.log(array);
From the top of my head you can try
array = ['complete','in_progress','planned'];
target = 'in_progress';
const answer = array.filter((element) => element !== target)
.sort()
.reduce((accumulator, value) => [...accumulator, value], [target]);
edit: I forgot it needs to be sorted.