I need help simplifying code that compares various properties of objects - javascript

I have a strong suspicion my code is way too clunky and can be written in a more concise and efficient manner. The intent is to compare an array of objects (representing players) to find the player with the highest majorityScore property. If there are more than one player sharing the same high score, then compare the co-winners' factions with a map (priorityMap) to determine who wins.
players = [
{
majorityScore: 4,
faction: 'AR'
},
{
majorityScore: 8,
faction: 'MOU'
},
{
majorityScore: 2,
faction: 'MOB'
},
{
majorityScore: 8,
faction: 'I'
}
];
const priorityMap = {
'MOB': 1,
'I': 2,
'MOU': 3,
'AR': 4,
'S' : 0
}
let winner;
let highScore = -1;
let duplicates = [];
for(let i = 0; i < players.length; i++){
if(players[i].majorityScore > highScore){
highScore = players[i].majorityScore;
winner = players[i]
duplicates = [winner];
} else if (players[i].majorityScore === highScore){
duplicates.push(players[i]);
};
}
if(duplicates.length > 1){
let highFactionScore = duplicates.reduce((a,v) => {
if(priorityMap[v.faction] > a){
a = priorityMap[v.faction];
}
return a;
}, 0);
let winningFaction = Object.keys(priorityMap).find((k) => {
return priorityMap[k] === highFactionScore;
});
winner = duplicates.filter((v) => {
return v.faction === winningFaction
})
}

Since you are reducing an array of objects into one object, the reduce function can be used here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
Instead of checking the factions after getting the players with the highest majority score, you can check the factions whenever you end up comparing players with the same majority score.
let winner = players.reduce((accum, currVal) => {
if (accum.majorityScore < currVal.majorityScore) {
return currVal;
} else if (accum.majorityScore > currVal.majorityScore) {
return accum;
} else {
return priorityMap[accum.faction] > priorityMap[currVal.faction] ? accum : currVal
}
});

Here's a slightly shorter method. It uses a temp object to maintain a dictionary where the scores are the keys. As we iterate we add the value of priorityMap[faction] to the score property, and change it only if the value is greater than the one stored.
const players=[{majorityScore:4,faction:"AR"},{majorityScore:8,faction:"MOU"},{majorityScore:8,faction:"MOU12"},{majorityScore:8,faction:"MOU20"},{majorityScore:2,faction:"MOB"},{majorityScore:2,faction:"MOB2"},{majorityScore:8,faction:"I"}],priorityMap={MOB:1,I:2,MOU:3,MOB2:4,AR:4,S:0,MOU12:21,MOU20:22};
function getGroups(arr) {
// Create the temp object
// We'll be using the scores as keys, and using
// the value in priorityMap as the value
// As we iterate we check the new priorityMap value
// against the old one
const temp = {};
// Iterate over the players array
return arr.reduce((acc, c) => {
const { majorityScore: score, faction } = c;
// If the accumulator object doesn't have a key that
// matches the score, add a new object
acc[score] = (acc[score] || { majorityScore: score, faction });
// If the new value of priorityMap[faction] is
// greater than the one stored in temp
// update the faction in the accumulator for that score
if (priorityMap[faction] > temp[score]) {
acc[score].faction = faction;
}
// Update the temp object with the
// priorityMap[faction] value
temp[score] = priorityMap[faction];
// Return the accumulator for the next iteration
return acc;
}, {});
}
console.log(getGroups(players));

Related

Find closest object values inside object of objects

Let's start with an example.
I have a list of fruits and it's nutritions stored in JS objects. Then I get a vegetable which is not in the list but has same types of values as the keys in the fruits object.
How can I get the closest fruit from the fruit object to the vegetable given vegetable if 1) the nutrition values (sugar,salt,...) has same values (sugar = salt) 2) the nutrition values (sugar,salt,...) have different values (sugar > salt, so basicly check only for sugar). I know that's not a good explanation, but let's check the example below.
let fruits = {
apple:{
sugar:12,
salt:5
},
banana:{
sugar:13,
salt:3
},
}
let cucumber = {
sugar:12,
salt:3
}
let closestFruitToCucumber = closest(cucumber, fruits)
// apple (if checking only sugar)
// banana (if both sugar and salt are valued the same)
function closest(vegetable,fruitList){
// here is the part which I am looking for.
}
I can post the code, which I have tried. There were plenty of them but none of them worked at all.
Following up on my comment:
I guess that you want to calculate the absolute difference for every matching key in the objects, and then sum these differences for each comparison, finally selecting the comparison with the lowest sum.
I'd use a functional approach so that you can refine your result by a query parameter (property key(s)). This approach will continue to work even if the two objects don't share the same property keys, and it will also allow you to easily refactor if the standard property keys change in your data:
const defaultKeys = ['salt', 'sugar'];
function getSimilarityScore (a, b, keys = defaultKeys) {
let score = 0;
for (const key of keys) score += Math.abs((a[key] ?? 0) - (b[key] ?? 0));
return score;
}
function closest (objList, obj, keys = defaultKeys) {
let minScore = Number.POSITIVE_INFINITY;
let result;
for (const o of Object.values(objList)) {
const score = getSimilarityScore(obj, o, keys);
if (score >= minScore) continue;
minScore = score;
result = o;
}
return result;
}
const fruits = {
apple: {
salt: 5,
sugar: 12,
},
banana: {
salt: 3,
sugar: 13,
},
};
const cucumber = {
salt: 3,
sugar: 12,
};
const closestToCucumber = closest(fruits, cucumber);
const closestToCucumberBySalt = closest(fruits, cucumber, ['salt']);
const closestToCucumberBySugar = closest(fruits, cucumber, ['sugar']);
console.log(closestToCucumber === fruits.banana); // true
console.log(closestToCucumberBySalt === fruits.banana); // true
console.log(closestToCucumberBySugar === fruits.apple); // true
You can implement a function for this purpose and compute the sum of absolute differences, finding the minimum such sum and returning the fruit corresponding this value. I returned its name, but if you want a different return format, let me know.
let fruits = {
apple:{
sugar:12,
salt:5
},
banana:{
sugar:13,
salt:3
},
}
let cucumber = {
sugar:12,
salt:3
}
function getClosestFruit(fruits, vegetable) {
let output = undefined;
let score = 0;
for (let key in fruits) {
let fruit = fruits[key];
let currentScore = Math.abs(fruit.sugar - vegetable.sugar) + Math.abs(fruit.salt - vegetable.salt);
if ((!output) || (score > currentScore)) {
output = key;
score = currentScore;
}
}
return output;
}
console.log(getClosestFruit(fruits, cucumber));

Very simple increment function in objects list

I’m trying to replicate a very simple function that I can get to work with arrays but not with objects. I just want to be able to run a function that logs the next object number as with the numbers array.
Take this working array as an example:
var numbers = [4,2,6],
count = 0;
incrementArr();
function incrementArr() {
if (count < numbers.length) { // if not last array element
console.log(numbers[count]);
count++;
} else {
console.log(numbers[0]);
count = 1;
}
}
Whenever you run the incrementArr function, it’ll just log the next number and then return to the start if the current state (count) is at the end.
However, I cannot replicate the same principle with this object list:
var objs = {
first: { // doesn't have to have 'first'
"number": 4
},
second: { // doesn't have to have 'second'
"number": 2
},
third: { // doesn't have to have 'third'
"number": 6
}
},
count = 0;
incrementObj();
function incrementObj() {
if (count < Object.keys(objs).length) { // if not last obj element
//objs["first"].number
console.log(objs[count].number);
count++;
} else {
console.log(objs["first"].number); // what if no "first" in objects?
count++;
}
}
How could the incrementObj function work the same way that the previous incrementArr function works?
It seems that I can’t pick the specific object instance (e.g. numbers[1] from the array would pick the 2nd number, but only objs[“second”].number would pick the 2nd object, which isn’t iterable if you know what I mean). How could I get a workaround for typical circumstances like this?
So essentially, what’s the difference between this:
first: { // doesn't have to have 'first'
"number": 4
}
and:
{ // doesn't have to have 'first'
"number": 4
}
Why have the "first" etc? (called the key?)
Is there generally a better way of going about object lists (it's difficult to explain)? Thanks for any advice here.
You could take a closure over the object and get the keys and store an index. The returned function get the value and increment and adjusts the index.
function increment(object) {
var keys = Object.keys(object),
index = 0;
return function() {
var value = object[keys[index]].number;
index++;
index %= keys.length;
return value;
};
}
var objs = { first: { number: 4 }, second: { number: 2 }, third: { number: 6 } },
incrementObj = increment(objs);
console.log(incrementObj());
console.log(incrementObj());
console.log(incrementObj());
console.log(incrementObj());
Try this, it access keys through the array generated from keys, objects are unordered list that means you will have to at least order the keys and access them in the array order.
const keysArr = Object.keys(objs);
function incrementObj() {
if (count < keysArr.length) { // if not last obj element
//
console.log(objs[keysArr[count]].number);
count++;
} else {
console.log(objs["first"].number); // what if no "first" in objects?
count++;
}
}
I propose using iterators
See this codepen
If your object have specific shapes, then you use this as a lens to find the number property you want. I'm not sure how you want to use the iterator and have return both the key and the value as separate properties, but you can as well return { [keys[nextIndex]]: values[nextIndex] } or find other shape (the world is your oyster).
Provided you go this length, why not try use RxJs to make your object an observable?
var objs = {
first: { // doesn't have to have 'first'
"number": 4
},
second: { // doesn't have to have 'second'
"number": 2
},
third: { // doesn't have to have 'third'
"number": 6
}
}
function propertyIterator(obj) {
const keys = Object.keys(obj)
const values = Object.values(obj)
const length = keys.length
let nextIndex = 0
return {
next: function() {
const value = {
key: keys[nextIndex],
value: values[nextIndex]
}
let done = false
if (nextIndex >= length) {
done = true
}
nextIndex += 1
return { current: value, done: done}
}
}
}
const incrementObj = propertyIterator(objs)
let result = incrementObj.next()
console.log(result.current.key, result.current.value.number || NaN)
result = incrementObj.next()
console.log(result.current.key, result.current.value.number || NaN)
result = incrementObj.next()
console.log(result.current.key, result.current.value.number || NaN)
using generators, see this codepen:
const objs = {
first: { // doesn't have to have 'first'
"number": 4
},
second: { // doesn't have to have 'second'
"number": 2
},
third: { // doesn't have to have 'third'
"number": 6
}
}
const inc = defaultValue => prop => function* (obj) {
for(let key in obj) {
yield obj[key][prop] || defaultValue
}
}
const getNumber = inc(NaN)('number')
const it = getNumber(objs)
let result = it.next()
while (!result.done) {
console.log(result.value)
result = it.next()
}

How can I determine the type of array and return either its duplicated or unique number in JavaScript

How can I determine the of an array (in this case, 'duplicate' or 'unique') to make it return either the duplicated or unique number. The array arr contains positive integers. It may be one of the following:
There are numbers 1 to n, only one number is duplicate (repeated two times), the other numbers are unique.
There are numbers 1 to n, only one number is unique, the other numbers are repeated two times.
I have attached my current code below, but since it's way too slow; I am wondering if it is possible to solve the problem in another way.
function duplicateOrUnique(arr) {
var duplicate = 0,
output = 0,
n = 0,
num = {}
arr.forEach(function(item) { // Inserts every item in 'arr' to list 'num'
num[item] = 0;
})
arr.forEach(function(item) { // Applies value (count) to every item list 'num'
num[item] += 1;
})
arr.forEach(function(item) { // Check how many times an item have been duplicated
if (num[item] > 1) {
duplicate += 1;
}
})
// Detertime wether 'arr' includes duplicated or unique numbers
if (duplicate > 2) { // unique
arr.forEach(function(item) {
if (num[item] == 1) {
output = item;
}
})
} else { // duplicated
arr.forEach(function(item) {
if (num[item] >= 2) {
output = item;
}
})
}
return output;
}
Note
All numbers are positive integers that from 1 to n.
The length of the array will always be more than 5.
Examples
[1,2,3,6,5,4,1] should return 1
[1,2,3,1,2,3,4] should return 4
[3,6,9,2,5,8,1,4,8,7] should return 8
[9,8,7,1,2,3,9,7,1,2,3,4,4,5,5,6,6] should return 8
If ES6 is not a problem, you can use a couple of sets to see how many duplicates are found (pre-ES6, a hash object could be used):
function duplicateOrUnique(arr) {
const set= new Set(), dupes= new Set();
for(let i of arr)
(set.has(i) ? dupes : set).add(i); //if set contains i -> add to dupes
if(dupes.size === 1) //one duplicate
return [...dupes][0]; //return first entry of duplicates
return [...set].find(i => !dupes.has(i)); //one unique-> filter out all dupes from the set
}
console.log(duplicateOrUnique([1,2,3,6,5,4,1]));
console.log(duplicateOrUnique([1,2,3,1,2,3,4]));
You can do it in the following way
function getVal(arr){
arr.sort();
let counUniq = 0, countDup = 0;
let uniq = -1, dup = -1;
for(let i=1; i<arr.length; i++){
if(arr[i] == arr[i-1]){
countDup++;
dup = arr[i];
}
else if(arr[i] != arr[i+1]) {
counUniq++;
uniq = arr[i];
}
}
//console.log(counUniq, countDup, uniq, dup);
if(counUniq == 1){
return uniq;
}
else {
return dup;
}
}
console.log(getVal([1,2,3,6,5,4,1]))
console.log(getVal([1,2,3,1,2,3,4]))
console.log(getVal([3,6,9,2,5,8,1,4,8,7]))
console.log(getVal([9,8,7,1,2,3,9,7,1,2,3,4,4,5,5,6,6]))
it uses sorting, and just checks which array type is it
Solution
function duplicateOrUnique(array) {
const unique = [...new Set(array)]
const lookup = unique.map(function (value) {
return this.indexOf(value) === this.lastIndexOf(value)
}, array)
return unique[
lookup.indexOf(
lookup.reduce((a, b) => a + b, 0) === 1
)
]
}
console.log(
duplicateOrUnique([1,2,3,6,5,4,1])
) // should return 1
console.log(
duplicateOrUnique([1,2,3,1,2,3,4])
) // should return 4
console.log(
duplicateOrUnique([3,6,9,2,5,8,1,4,8,7])
) // should return 8
console.log(
duplicateOrUnique([9,8,7,1,2,3,9,7,1,2,3,4,4,5,5,6,6])
) // should return 8
Explanation
const unique = [...new Set(array)]
Generates an array containing every unique value.
const lookup = unique.map(function (value) {
return this.indexOf(value) === this.lastIndexOf(value)
}, array)
Creates a lookup table to determine if each value is unique in the original array (true if unique, false if duplicate), by testing whether its first index is equivalent to its last index.
lookup.reduce((a, b) => a + b, 0) === 1
Determines which type of array by summing the lookup table of booleans. If only one value in the array is unique, then the result is true, otherwise false.
return unique[lookup.indexOf(...)]
Performs a reverse-lookup of the number to return by getting the index of the desired type (unique or duplicate) based on the type of array.
You could just check the set where the value belongs and then render the result for only one item.
function fn(array) {
var one = new Set,
two = new Set;
array.forEach(a => two.has(a) || one.has(a) && (one.delete(a), two.add(a)) || one.add(a));
return (one.size === 1 ? one : two).values().next().value;
}
console.log(fn([1, 2, 3, 6, 5, 4, 1])); // 1
console.log(fn([1, 2, 3, 1, 2, 3, 4])); // 4
console.log(fn([3, 6, 9, 2, 5, 8, 1, 4, 8, 7])); // 8
console.log(fn([9, 8, 7, 1, 2, 3, 9, 7, 1, 2, 3, 4, 4, 5, 5, 6, 6])); // 8
.as-console-wrapper { max-height: 100% !important; top: 0; }
A readable but not very performant solution:
var arr1 = [1,2,3,6,5,4,1];
var arr2 = [1,2,3,1,2,3,4];
var duplicatedNumber = arr1.find((it, itIndex) => arr1.some((some, someIndex) => it === some && itIndex !== someIndex));
var uniqueNumber = arr2.find((it, itIndex) => !arr2.some((other, otherIndex) => it === other && itIndex !== otherIndex));
console.log("Duplicated: ", duplicatedNumber);
console.log("Unique: ", uniqueNumber);
// To make a function that returns either the duplicated or unique number in an array:
function findUniqueOrDuplicated(arr) {
return arr.find((it, itIndex) => arr.some((other, othersIndex) => it === other && itIndex !== othersIndex)) ||
arr.find((it, itIndex) => !arr.some((other, othersIndex) => it === other && itIndex !== othersIndex));
}

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

Return highest value or equal values javascript

I'm creating a voting system and I have the following object:
var obj1 = { Mcds: 2, Pret: 2, kfc: 2, BK: 1 }
or (depending on the votes) it could be:
var obj2 = { Mcds: 2, Pret: 2, BK: 3 }
What I want is to display the most voted restaurant or restaurants.
I can achieve this with the obj2 example using the following code:
var obj2Keys = Object.keys(obj2);
var mostVoted = obj2Keys.reduce(function(previousValue, currentValue){
return obj2[previousValue] > obj2[currentValue] ? previousValue : currentValue;
}); // returns 'BK'
When I use the above code on the obj1 example I get 'kfc' what I want is 'kfc' 'Pret' 'Mcds' (in no particular order).
You need to accumulate all the winning names into an array. When you get a tie for the high vote, you add the element to the array; when you get a higher vote, you start a new array.
var obj1 = {
Mcds: 2,
Pret: 2,
kfc: 2,
BK: 1
}
var high_vote = 0;
var winners;
for (var key in obj1) {
if (obj1.hasOwnProperty(key)) {
if (obj1[key] > high_vote) {
winners = [key];
high_vote = obj1[key];
} else if (obj1[key] == high_vote) {
winners.push(key);
}
}
}
alert(winners);
Add this below your existing code:
var mostVotedArray = obj2Keys.filter(function(currentValue){
return obj2[currentValue] == obj2[mostVoted];
});
The new variable will list the "winners" as an array.

Categories