Sort JS object of objects(nested) - javascript

let tags = {
"01": {
contentID: [10, 20],
occurrences: 1
},
"02": {
contentID: [10, 20],
occurrences: 1
},
"04": {
contentID: [10, 20],
occurrences: 3
},
"05": {
contentID: [10, 20],
occurrences: 6
}
};
How to sort this in descending order by taking occurrences as base, This is a global variable. So I want to store it in a different object and sort that one with descending order..

You can not sort objects, but you can transform it to array of values:
Object.values(tags).sort((a, b) => b.occurrences - a.occurrences)
The result will be:
[
{
contentID: [10, 20],
occurrences: 6
},
{
contentID: [10, 20],
occurrences: 3
},
{
contentID: [10, 20],
occurrences: 1
},
{
contentID: [10, 20],
occurrences: 1
}
]
However you can sort your keys separately if you need them as well:
Object.keys(tags).sort((a, b) => tags[b].occurrences - tags[a].occurrences)
And your result will be an array ['05', '04', '02', '01']

Kindly note that objects in javascript dont have any sense of order.
However you can create and array to hold your result.
To get it in descending order ,you can do
Object.values(tags).sort((a,b) => b.occurrences - a.occurrences)

Other suggestions to think about this as an array are probably sound. It's not a good idea to think of Javascript objects as ordered, even though there is a key-ordering specification. But, if you want to keep the results in the same format, you can do so, and still end up sorting the keys:
let tags = {"01": {"contentID": [10, 20], "occurrences": 1}, "02": {"contentID": [10, 20], "occurrences": 1}, "04": {"contentID": [10, 20], "occurrences": 3}, "05": {"contentID": [10, 20], "occurrences": 6}}
const sorted = Object.entries(tags).sort(([, {occurrences: a}], [, {occurrences: b}]) => b - a)
.reduce((a, [k, v]) => Object.assign(a, {[k]: v}), {})
console.log(sorted)
I can't see what this is really good for, though. The only place you can see a difference in behavior is if you use something like Object.keys, Object.values, or Object.entries on it, again creating arrays.

Related

Basic Javascript, How can i filter an array by numeric value?

this is my first question on this community and i'm a novice programmer with JavaScript.
I have something like this:
let dog = [["extra small", 2], ["small", 5], ["medium", 7], ["big", 9], ["extra big", 12]];
Taking the data of the previous array, i want to create a new array just with the numeric values, for example:
ages = [2, 5, 7, 9, 12]
I tried to use "filter", but i don't know how to properly use it, also i tried to search some references to make it work but i couldn't.
Thanks in advance (and sorry for my poor english, but i suppose you get the idea).
You can first use Array#map to get just the numbers and then Array#sort to sort the numbers
let dog = [
["extra small", 2],
["small", 5],
["medium", 7],
["big", 9],
["extra big", 12]
];
let ages = dog.map(([size, age]) => age).sort((a, b) => a - b);
console.log(ages);
Here are my thoughts on how to achieve this.
Using Array#Map and Array#filter.
Basically, mapping each element of the array, then checking for the numeric values in the subArray using JavaScript#isNaN() and
returning the numbers.
isNaN() checks if the value type is not a number. !isNaN() reverses that response.
flat() is used to flatten the final result to a single array. Alternatively, you can change map() to flatMap()
// map(), isNaN(), filter(), flat()
let newArr = dog.map((arr) => arr.filter((val) => !isNaN(val))).flat();
console.log(newArr); // [ 2, 5, 7, 9, 12 ]
// flatMap(), isNaN(), filter()
let newArr = dog.flatMap((arr) => arr.filter((val) => !isNaN(val)));
console.log(newArr); // [ 2, 5, 7, 9, 12 ]
Using function.
This is similar to the first, however, instead of using map() we use a Array#forEach to loop through the array.
function getNumeric(array) {
let result = [];
array.forEach((arr) => {
let res = arr.filter((a) => !isNaN(a));
result.push(...(res));
});
return result;
}
let newArr = getNumeric(dog);
console.log(newArr); // [ 2, 5, 7, 9, 12 ]
let dog = [
["extra small", 2],
["small", 5],
["medium", 7],
["big", 9],
["extra big", 12]
];
const newArr = dog.map(item => {
return item[1]
})
console.log(newArr);

what is the difference between filtering on accumulator/current in reduce method

When chaining the Array.prototype.reduce with Array.prototype.filter what is the difference (conceptually and under the hood) when filtering on the current value instead of the accumulator value?
// function union creates a union of all values that appear among arrays
// example A
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = acc.filter(el => !curr.includes(el));
return curr.concat(newElements);
});
};
console.log(union([1, 10, 15, 20], [5, 88, 1, 7], [1, 10, 15, 5]));
// output (7) [1, 10, 15, 5, 88, 7, 20]
// example B
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = curr.filter(el => !acc.includes(el));
return acc.concat(newElements);
});
};
console.log(union([1, 10, 15, 20], [5, 88, 1, 7], [1, 10, 15, 5]));
//output (7) [1, 10, 15, 20, 5, 88, 7]
The difference in output would suggest that the order in which the arrays are being evaluated is 'opposite'. As far as I can tell when using arr.filter the values are evaluated from end to beginning with the opposite being true for curr.filter . Besides from that are they any other consequences dependent on if you filter through the accumulator or current value? Could this throw an error in a different context?
The issue isn't about the use of filter inside of reduce, so much as it is about the order in which you're using acc and curr.
When I'm running into seemingly strange inconsistencies like this, the first step I usually take is to create a test case and run through it manually. Here, you've already created a test case for us...
const testData = [
[1, 10, 15, 20],
[5, 88, 1, 7],
[1, 10, 15, 5],
]
Now we need to run through each version of the function and see what the output is at each stage.
One thing to note (which I didn't know until this evening!) is that if reduce doesn't receive an initialValue as the second argument, it will use the first item in the array as the initialValue. This means we only need to consider 2 executions of each function instead of 3. 😅
Example A
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = acc.filter(el => !curr.includes(el))
return curr.concat(newElements)
})
}
In the first version of the function, the short description of what's happening is that we're looping over the accumulator (acc) and removing all items that already exist in the array that we're currently comparing (curr). Then we add that list to the end of curr.
The fact that we’re pushing newElements onto the end of curr is important. This is why the order is different for the 2 different versions.
First execution
const acc = [1, 10, 15, 20]
const curr = [5, 88, 1, 7]
const newElements = [10, 15, 20] // these elements exist in acc but not in curr
curr.concat(newElements) === [5, 88, 1, 7, 10, 15, 20]
Second execution
const acc = [5, 88, 1, 7, 10, 15, 20] // carried over from first execution
const curr = [1, 10, 15, 5]
const newElements = [88, 7, 20] // these elements exist in acc but not in curr
curr.concat(newElements) === [1, 10, 15, 5, 88, 7, 20]
Example B
const union = (...arrays) => {
return arrays.reduce((acc, curr) => {
const newElements = curr.filter(el => !acc.includes(el))
return acc.concat(newElements)
})
}
In the first version of the function, the short description of what's happening is that we're looping over the array that we’re currently comparing (curr) and removing all items that already exist in the accumulator (acc). Then we add that list to the end of acc.
You can already see at the end of the first execution below that the results are turning out in a much different order.
First execution
const acc = [1, 10, 15, 20]
const curr = [5, 88, 1, 7]
const newElements = [5, 88, 7] // these elements exist in curr but not in acc
acc.concat(newElements) === [1, 10, 15, 20, 5, 88, 7]
Second execution
const acc = [1, 10, 15, 20, 5, 88, 7] // carried over from first execution
const curr = [1, 10, 15, 5]
const newElements = [] // these elements exist in acc but not in curr
acc.concat(newElements) === [1, 10, 15, 20, 5, 88, 7]
Conclusion
The short answer to your question is that the difference between filtering on the accumulator and the current array is that the results are going to be different so long as the inputs are different. 🤷🏻‍♂️
Besides from that are they any other consequences dependent on if you filter through the accumulator or current value? Could this throw an error in a different context?
Fortunately, there’s not any concern about errors. It is notable, however, that the second version of your function is ~10% faster than the first version. I’d guess that this is purely circumstantial. A different test data set may produce different performance results.
In example 1, by the time you concat the two lists, you make sure that the accumulator won't contain any element from current.
In example 2, on the other hand, you make sure that current won't contain any element that is already present in accumulator.
The difference is on the final order in which the elements will appear
I think both examples are not efficient since they both involve O(n2) time complexity, since you are nesting iterations. The second one, as stated by others, might be a little more performant since the nested iterations would be made on a chunk that is presumably shorter than the accumulator.
I'd rather write more or less like this:
const union = (...tuples) => Array.from(
new Set(
tuples.flatMap(n => n),
)
);
console.log(
union([1, 10, 15, 20], [5, 88, 1, 7], [1, 10, 15, 5]),
);

Why doesn't JS complier recognize this call to Array.prototype.filter

I am trying to figure out why my call to .prototype.filter is giving me a TypeError: curr.filter is not a function.
const intersection = (arrays) => {
return arrays.reduce((acc, curr) => {
return curr.filter(el => acc.includes(el));
});
};
console.log(intersection([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20]));
To my understanding I am declaring a function const intersection which takes in arrays and then returns the result of calling arrays.reduce which 'reduces' the results of filtering the current value and creating a new array that includes all instances of accumulator acc including the current value curr.
Since filter creates a new array on runtime I figured this would work as is yet it does not. What am I not seeing?
Use array rest parameter to get all parameter as an array. In the given code you are taking just first argument and ignoring the rest.
try this.
const intersection = (...arrays) => {
console.log("arrays: ", arrays);
return arrays.reduce((acc, curr) => {
return curr.filter(el => acc.includes(el));
});
};
console.log("Result:" , intersection([5, 10, 15, 20], [15, 88, 1, 5, 7], [1, 10, 15, 5, 20]));

How to create a variable that is the lowest possible number?

What I have here is an algorithm that finds the highest value in each subarray and pushes it onto a separate array (final).
I want to let the variable 'value' equal to the lowest possible number so that any negative number can be considered higher than 'value'
function largestOfFour(arr) {
var final=[];
arr.map(sub => {
let value = 0; //issue
sub.map(num => {
if(num>value){value=num};
})
final.push(value)
})
return final;
}
console.log(largestOfFour([[17, 23, 25, 12], [25, 7, 34, 48], [4, -10, 18, 21], [-72, -3, -17, -10]]));
In this example the last subarray returns 0 since non of the numbers in that subarray were higher than the initial value of 'value' which is 0.
I want it to return '-3' instead since it's the highest number in the subarray.
It would appear you're simply looking for the max of each array.
Using Array#map and Math#max and spread syntax you could do something like this.
const data = [[17, 23, 25, 12], [25, 7, 34, 48], [4, -10, 18, 21], [-72, -3, -17, -10]];
const res = data.map(arr=>Math.max(...arr));
console.log(res);
You can just set value to Number.NEGATIVE_INFINITY but for what it's worth, I'd recommend simply using reduce instead of map in your inner function. That way, the inner loop will start with sub[0] as an initial value rather than depending on any placeholder.
function largestOfFour(arr) {
var final = arr.map(sub => sub.reduce((num, value) => Math.max(num, value)));
return final;
}
console.log(largestOfFour([
[17, 23, 25, 12],
[25, 7, 34, 48],
[4, -10, 18, 21],
[-72, -3, -17, -10]
]));

How to count and display the occurrence of a number in a json array of numbers

Please forgive me if this is a dumb or basic question but I have not been able to find a good solution. I have a json array of numbers:
[30, 37,34,56,76,87,54,34,2,4,2,5,5,3,4,3,4, 90]
I would like to count how many times each number occurs and use that data to produce a graph using d3js. How can I go about doing this? If there is a D3 method that does this, that would be great. But a javascript/jquery solution would do as well.
In plain Javascript:
var items = [30, 37, 34, 56, 76, 87, 54, 34, 2, 4, 2, 5, 5, 3, 4, 3, 4, 90],
histogram = items.reduce(function (r, a) {
r[a] = (r[a] || 0) + 1;
return r;
}, {});
document.write('<pre>' + JSON.stringify(histogram, 0, 4) + '</pre>');
For the graphing, check out c3. This can be easily done with something like this:
var chart = c3.generate({
data: {
x: 'x',
columns: [
numbers.unshift('x'),
occurrences.unshift('occurrences'),
],
type: 'bar'
} });
where numbers is an array of all distinct numbers and occurrences is an array of the numbers of time each occurs.
Demo

Categories