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));
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 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));
}
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