JS Decrease property after specific value - javascript

I have an array of objects, objects that contain an order: number; property.
I want for each object in that array that has the order property higher than a specific value to have it decreased it by one.
Any simple way to achieve this?
myArray.forEach(x => x.order >= someValue)...

You can map the result, updating the value when >= someValue
const someValue = 3;
const result = [
{ order: 1 },
{ order: 2 },
{ order: 3 },
{ order: 4 }
].map(x => x.order >= someValue ? { ...x, order: x.order - 1 } : x);
console.log(result);

With array.reduce
const value = 2;
const testArray = [{ order: 1 }, { order: 1 }, { order: 3 }];
const result = testArray.reduce((acc, curr) => {
if (curr.order > value) {
return [...acc, { ...curr, order: curr.order - 1 }];
}
return [...acc, curr];
}, []);
console.log(result);

Several options to reach this:
Adding functionality to Array.prototype.
if(!Array.prototype.decreaseOrderIfBiggerThan) {
Array.prototype.decreaseOrderIfBiggerThan = function(value) {
if(!value || typeof value != 'number') return this;
return this.map((el) => ({
...el,
order: el?.order > value ? --el.order : el.order
}))
}
}
Simply mapping:
myArray = myArray.map((el) => ({
...el,
order: el?.order > value ? --el.order : el.order
}))
Mutating original array: it's basically #2 method but without assigning new array to original one and with forEach.
Hopes this is what you needed.

Related

Using map to change object's properties of an array of objects

I have an object that has the key ID and some status key. I wanna count how many times the same status repeats itself.
My object will look like this
const items = { id: 2, status_a: 1, status_b: 1, status_c: 3 };
This is my code
curr.userBuyer is a number.
curr.status is a string, for each equal status, i want to add 1 to it
const userBuyers: any[] = buyers.reduce((acc: any[], curr) => {
if (acc.filter((item) => item.id == curr.userBuyer).length == 0) {
//If the there's no item in acc, then it gets pushed into it
acc.push({ id: curr.userBuyer, [curr.status]: 1 });
return acc;
} else {
//If acc already have that item
acc = acc.map((retail) => {
//Check if that key already exists
const isKey = retail[curr.status] ? true : false;
//If the key exists, then it will add 1 to the existing value, else it will set it as 1
return { ...retail, [curr.status]: isKey ? retail[curr.status]++ : 1 };
});
return acc;
}
}, []);
This is what is returning
[
{
id: 713,
delivered: 1,
sold: 1,
in_delivery: 1,
},
{
id: 833,
delivered: 1,
sold: 1,
in_delivery: 1,
},
];
It's not adding 1 to the status that already exists.
What am i doing wrong?
You have two errors:
You are using the postfix operator to increment count retail[curr.status]++, instead, use the prefix operator ++retail[curr.status] or simply (retail[curr.status] + 1).
You are updating all the items, in an account, only the matching item should be updated.
Try this:
const userBuyers: any[] = buyers.reduce((acc: any[], curr) => {
if (acc.filter((item) => item.id == curr.userBuyer).length == 0) {
acc.push({ id: curr.userBuyer, [curr.status]: 1 });
return acc;
} else {
acc = acc.map((retail) => {
if (retail.id === curr.userBuyer) {
const isKey = retail[curr.status] ? true : false;
return { ...retail, [curr.status]: isKey ? retail[curr.status] + 1 : 1 };
}
return retail;
});
return acc;
}
}, []);

JavaScript: better/cleaner functional way to find item from a nested array

I am trying to write a find function to find items from matched items from a potentially nested array (without having to flat the array first) and I am trying to write in a FP way.
Here is my attempt:
const nestedArray = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
function findTarget(arr, predicate) {
const helper = ([x, ...xs]) =>
x === undefined
? null
: predicate(x)
? x
: Array.isArray(x)
? helper(x) ?? helper(xs)
: helper(xs)
return helper(arr)
}
findTarget(nestedArray, (item) => item.id === 5)
I think it works but it is not super readable and I am sure there are better ways to write such a function.
Here's how I would implement this using recursion:
function findTarget(value, predicate) {
const isArray = Array.isArray(value);
// Base case: if value is not array and predicate matches, we found a match
if (!isArray) {
if (predicate(value)) return value;
return null;
}
// value must be an array, so run recursion and see if value exists
for (const item of value) {
const foundItem = findTarget(item, predicate);
if (foundItem !== null) {
return foundItem;
}
}
// nothing found
return null;
}
does the same thing that your code does and imo looks cleaner.
Since your example is calling predicate(x) in the first place, it will return a false positive when matching an array with an id: 5 property, so the Array.isArray(x) should go first to avoid this:
const nestedArray = [
Object.assign([{ id: 1 }], { id: 5 }),
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }], null, [[{ id: 5 }]]],
{ id: 6 },
]
function findTargetLoop (arr, match) {
if (!Array.isArray(arr))
return arr && match(arr) ? arr : null;
let item, i = 0;
while (!(item = findTargetLoop(arr[i++], match)) && i < arr.length);
return item ?? null;
}
const findTargetFunc = (arr, match, next) =>
(next = ([item, ...rest]) =>
Array.isArray(item) ? next(item) ?? next(rest)
: item && match(item) ? item
: rest.length ? next(rest) : null)(arr);
const match = item => item.id === 5;
console.log('with iterations', findTargetLoop(nestedArray, match));
console.log('pure functional', findTargetFunc(nestedArray, match));
Here's one approach I can think of. It uses the init function as a sentinel value to distinguish whether the element being searched for has already been found. Before returning, it invokes the accumulated value which is either () => undefined, or () => curr capturing the first element that matches the predicate.
const flatFind = (array, predicate) => {
const init = () => undefined
const reducer = (prev, curr) => (
prev === init
? Array.isArray(curr)
? curr.reduce(reducer, init)
: predicate(curr)
? () => curr
: init
: prev
)
return array.reduce(reducer, init)()
}
const nestedArray = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
console.log(flatFind(nestedArray, item => item.id === 5))
Your form is a good start but the helper is unnecessary and the order of conditions should be changed. Here we use inductive reasoning -
If x is undefined, there is nothing left to match. Return no match.
(inductive) x is defined. If x is an array, find within x or find within xs
(inductive) x is defined and x is a non-array. If x matches predicate f, return the match
(inductive) x is defined and x is a non-array that does not match the predicate f. Find within the sub-problem, xs.
const find = ([x, ...xs], f) =>
x === undefined // 1
? null
: Array.isArray(x) // 2
? find(x, f) ?? find(xs, f)
: f(x) // 3
? x
: find(xs, f) // 4
const t = [
[{ id: 1 }],
[{ id: 2 }],
[{ id: 3 }, [{ id: 4 }]],
{ id: 5 },
]
console.log(find(t, (item) => item?.id === 5)) // { id: 5 }
console.log(find(t, (item) => item?.id === 9)) // null
Note, the behavior of your findTarget checks child arrays against the predicate and allows for return of arrays that match the predicate. This is inconsistent behaviour as it's not possible to match the outermost array and the predicate checks for .id properties on arrays. The find solution above avoids this by changing the order of conditions 2 and 3. If you want the ability to return array matches with your function, you can change the order to 1,3,2,4.

Issue arranging values in ascending order [duplicate]

This question already has answers here:
Sort Array Elements (string with numbers), natural sort
(8 answers)
Closed 11 months ago.
I am trying to arrange given values in ascending orders
const value = [
{ val: "11-1" },
{ val: "12-1b" },
{ val: "12-1a" },
{ val: "12-700" },
{ val: "12-7" },
{ val: "12-8" },
];
I am using code below to sort this in ascending order:
value.sort((a,b)=>(a.val >b.val)? 1:((b.val>a.val)?-1:0));
The result of this sort is in the order 11-1,12-1a, 12-1b, 12-7, 12-700, 12-8. However, I want the order to be 11-1,12-1a, 12-1b, 12-7, 12-8, 12-700.
How can I achieve that?
If you're only interested of sorting by the value after the hyphen you can achieve it with this code:
const value = [
{val:'12-1'},
{val:'12-700'},
{val:'12-7'},
{val:'12-8'},
];
const sorted = value.sort((a,b) => {
const anum = parseInt(a.val.split('-')[1]);
const bnum = parseInt(b.val.split('-')[1]);
return anum - bnum;
});
console.log(sorted);
updated the answer as your question update here's the solution for this:
const value = [{ val: '11-1' }, { val: '12-1b' }, { val: '12-1a' }, { val: '12-700' }, { val: '12-7' }, { val: '12-8' }];
const sortAlphaNum = (a, b) => a.val.localeCompare(b.val, 'en', { numeric: true });
console.log(value.sort(sortAlphaNum));
You can check the length first and then do the sorting as follow:
const value = [
{ val: "12-1" },
{ val: "12-700" },
{ val: "12-7" },
{ val: "12-8" },
];
const result = value.sort(
(a, b)=> {
if (a.val.length > b.val.length) {
return 1;
}
if (a.val.length < b.val.length) {
return -1;
}
return (a.val >b.val) ? 1 : ((b.val > a.val) ? -1 : 0)
}
);
console.log(result);
little change's to #Christian answer it will sort before and after - value
const value = [{ val: '12-1' }, { val: '12-700' }, { val: '11-7' }, { val: '12-8' }];
const sorted = value.sort((a, b) => {
const anum = parseInt(a.val.replace('-', '.'));
const bnum = parseInt(b.val.replace('-', '.'));
return anum - bnum;
});
console.log(sorted);
If you want to check for different values both before and after the hyphen and include checking for letters, the solution at the end will solve this.
Here's what I did:
Created a regex to split the characters by type:
var regexValueSplit = /(\d+)([a-z]+)?-(\d+)([a-z]+)?/gi;
Created a comparison function to take numbers and letters into account:
function compareTypes(alpha, bravo) {
if (!isNaN(alpha) && !isNaN(bravo)) {
return parseInt(alpha) - parseInt(bravo);
}
return alpha > bravo;
}
Split the values based on regexValueSplit:
value.sort((a, b) => {
let valuesA = a.val.split(regexValueSplit);
let valuesB = b.val.split(regexValueSplit);
This produces results as follows (example string "12-1a"):
[
"",
"12",
null,
"1",
"a",
""
]
Then, since all the split arrays should have the same length, compare each value in a for loop:
for (let i = 0; i < valuesA.length; i++) {
if (valuesA[i] !== valuesB[i]) {
return compareTypes(valuesA[i], valuesB[i]);
}
}
// Return 0 if all values are equal
return 0;
const value = [{
val: "11-1"
},
{
val: "12-1b"
},
{
val: "12-1a"
},
{
val: "12-700"
},
{
val: "12-7"
},
{
val: "12-8"
},
];
var regexValueSplit = /(\d+)([a-z]+)?-(\d+)([a-z]+)?/gi;
function compareTypes(alpha, bravo) {
if (!isNaN(alpha) && !isNaN(bravo)) {
return parseInt(alpha) - parseInt(bravo);
}
return alpha > bravo;
}
value.sort((a, b) => {
let valuesA = a.val.split(regexValueSplit);
let valuesB = b.val.split(regexValueSplit);
for (let i = 0; i < valuesA.length; i++) {
if (valuesA[i] !== valuesB[i]) {
return compareTypes(valuesA[i], valuesB[i]);
}
}
return 0;
});
console.log(JSON.stringify(value, null, 2));
Since you are sorting on string values, try using String.localeCompare for the sorting.
Try sorting on both numeric components of the string.
const arr = [
{val:'12-1'},
{val:'11-900'},
{val:'12-700'},
{val:'12-7'},
{val:'11-1'},
{val:'12-8'},
{val:'11-90'},
];
const sorter = (a, b) => {
const [a1, a2, b1, b2] = (a.val.split(`-`)
.concat(b.val.split(`-`))).map(Number);
return a1 - b1 || a2 - b2; };
console.log(`Unsorted values:\n${
JSON.stringify(arr.map(v => v.val))}`);
console.log(`Sorted values:\n${
JSON.stringify(arr.sort(sorter).map(v => v.val))}`);

Summing nested value if it exists

I have an array that looks like this:
const values = [
{
value: 2000,
items: [
{
value: 300,
},
],
},
]
I want to sum the total of all of the values in values.value, and if a values.items.value exists I also want to include it in my sum.
values.reduce((total, obj) => obj.value + total, 0);
What would be the correct way to key into the nested array so it sums both the top and nested value key in my reduce function? The output I'd like is 2300 but right now I can only get one level deep, and it's outputting 2000.
You could nest your reduce approach to handle the inner array while ensuring that the key exists in the inner array objects.
const values = [
{
value: 2000,
items: [
{
value: 300,
},
],
},
];
const total = values.reduce((acc, obj) => {
acc += obj.value;
acc += obj.items.reduce((a, o) => 'value' in o ? o.value + a : a, 0);
return acc;
}, 0);
console.log(total);
// 2300
You can use reduce add value and check for items if it's there then add the value of items arrays as well
const values = [{value: 2000,items: [{value: 300,},],},]
let op = values.reduce((op,{value,items}) => {
op+= value
if(items && items.length) {
items.forEach(({value})=> op+=value )
}
return op
},0)
console.log(op)
I will add an inner reduce() to get the accumulated sum for the items array. Also, I will add some checks with isNaN() and Array.isArray() just for safety:
const values = [
{value: 2000, items: [{value: 300}]},
{value: 3000},
{value: 2000, items: [{value: 300}, {foo: 20}]},
{nothing: "nothing"}
];
let res = values.reduce((acc, {value, items}) =>
{
acc += isNaN(value) ? 0 : value;
acc += Array.isArray(items) ?
items.reduce((sum, {value}) => sum + (isNaN(value) ? 0 : value), 0) :
0;
return acc;
}, 0);
console.log(res);
.as-console {background-color:black !important; color:lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
You could iterate keys/values of the object and sum the a nested object and take the wanted key or zero as start value.
function sum(object, key) {
return Object.entries(object).reduce((s, [k, v]) => {
if (v && typeof v === 'object') return s + sum(v, key);
return s;
}, key in object ? object[key] : 0);
}
const values = [{ value: 2000, items: [{ value: 300 }] }];
console.log(sum(values, 'value'));

How can I check if the array of objects have duplicate property values?

I need some help with iterating through array, I keep getting stuck or reinventing the wheel.
values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName1' },
{ name: 'someName1' }
]
How could I check if there are two (or more) same name value in array? I do not need a counter, just setting some variable if array values are not unique. Have in mind that array length is dynamic, also array values.
Use array.prototype.map and array.prototype.some:
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName2' }
];
var valueArr = values.map(function(item){ return item.name });
var isDuplicate = valueArr.some(function(item, idx){
return valueArr.indexOf(item) != idx
});
console.log(isDuplicate);
ECMA Script 6 Version
If you are in an environment which supports ECMA Script 6's Set, then you can use Array.prototype.some and a Set object, like this
let seen = new Set();
var hasDuplicates = values.some(function(currentObject) {
return seen.size === seen.add(currentObject.name).size;
});
Here, we insert each and every object's name into the Set and we check if the size before and after adding are the same. This works because Set.size returns a number based on unique data (set only adds entries if the data is unique). If/when you have duplicate names, the size won't increase (because the data won't be unique) which means that we would have already seen the current name and it will return true.
ECMA Script 5 Version
If you don't have Set support, then you can use a normal JavaScript object itself, like this
var seen = {};
var hasDuplicates = values.some(function(currentObject) {
if (seen.hasOwnProperty(currentObject.name)) {
// Current name is already seen
return true;
}
// Current name is being seen for the first time
return (seen[currentObject.name] = false);
});
The same can be written succinctly, like this
var seen = {};
var hasDuplicates = values.some(function (currentObject) {
return seen.hasOwnProperty(currentObject.name)
|| (seen[currentObject.name] = false);
});
Note: In both the cases, we use Array.prototype.some because it will short-circuit. The moment it gets a truthy value from the function, it will return true immediately, it will not process rest of the elements.
In TS and ES6 you can create a new Set with the property to be unique and compare it's size to the original array.
const values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName3' },
{ name: 'someName1' }
]
const uniqueValues = new Set(values.map(v => v.name));
if (uniqueValues.size < values.length) {
console.log('duplicates found')
}
To know if simple array has duplicates we can compare first and last indexes of the same value:
The function:
var hasDupsSimple = function(array) {
return array.some(function(value) { // .some will break as soon as duplicate found (no need to itterate over all array)
return array.indexOf(value) !== array.lastIndexOf(value); // comparing first and last indexes of the same value
})
}
Tests:
hasDupsSimple([1,2,3,4,2,7])
// => true
hasDupsSimple([1,2,3,4,8,7])
// => false
hasDupsSimple([1,"hello",3,"bye","hello",7])
// => true
For an array of objects we need to convert the objects values to a simple array first:
Converting array of objects to the simple array with map:
var hasDupsObjects = function(array) {
return array.map(function(value) {
return value.suit + value.rank
}).some(function(value, index, array) {
return array.indexOf(value) !== array.lastIndexOf(value);
})
}
Tests:
var cardHand = [
{ "suit":"spades", "rank":"ten" },
{ "suit":"diamonds", "rank":"ace" },
{ "suit":"hearts", "rank":"ten" },
{ "suit":"clubs", "rank":"two" },
{ "suit":"spades", "rank":"three" },
]
hasDupsObjects(cardHand);
// => false
var cardHand2 = [
{ "suit":"spades", "rank":"ten" },
{ "suit":"diamonds", "rank":"ace" },
{ "suit":"hearts", "rank":"ten" },
{ "suit":"clubs", "rank":"two" },
{ "suit":"spades", "rank":"ten" },
]
hasDupsObjects(cardHand2);
// => true
if you are looking for a boolean, the quickest way would be
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName1' },
{ name: 'someName1' }
]
// solution
var hasDuplicate = false;
values.map(v => v.name).sort().sort((a, b) => {
if (a === b) hasDuplicate = true
})
console.log('hasDuplicate', hasDuplicate)
const values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName4' }
];
const foundDuplicateName = values.find((nnn, index) =>{
return values.find((x, ind)=> x.name === nnn.name && index !== ind )
})
console.log(foundDuplicateName)
Found the first one duplicate name
const values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName4' }
];
const foundDuplicateName = values.find((nnn, index) =>{
return values.find((x, ind)=> x.name === nnn.name && index !== ind )
})
You just need one line of code.
var values = [
{ name: 'someName1' },
{ name: 'someName2' },
{ name: 'someName4' },
{ name: 'someName2' }
];
let hasDuplicates = values.map(v => v.name).length > new Set(values.map(v => v.name)).size ? true : false;
Try an simple loop:
var repeat = [], tmp, i = 0;
while(i < values.length){
repeat.indexOf(tmp = values[i++].name) > -1 ? values.pop(i--) : repeat.push(tmp)
}
Demo
With Underscore.js A few ways with Underscore can be done. Here is one of them. Checking if the array is already unique.
function isNameUnique(values){
return _.uniq(values, function(v){ return v.name }).length == values.length
}
With vanilla JavaScript
By checking if there is no recurring names in the array.
function isNameUnique(values){
var names = values.map(function(v){ return v.name });
return !names.some(function(v){
return names.filter(function(w){ return w==v }).length>1
});
}
//checking duplicate elements in an array
var arr=[1,3,4,6,8,9,1,3,4,7];
var hp=new Map();
console.log(arr.sort());
var freq=0;
for(var i=1;i<arr.length;i++){
// console.log(arr[i-1]+" "+arr[i]);
if(arr[i]==arr[i-1]){
freq++;
}
else{
hp.set(arr[i-1],freq+1);
freq=0;
}
}
console.log(hp);
You can use map to return just the name, and then use this forEach trick to check if it exists at least twice:
var areAnyDuplicates = false;
values.map(function(obj) {
return obj.name;
}).forEach(function (element, index, arr) {
if (arr.indexOf(element) !== index) {
areAnyDuplicates = true;
}
});
Fiddle
Adding updated es6 function to check for unique and duplicate values in array. This function is modular and can be reused throughout the code base. Thanks to all the post above.
/* checks for unique keynames in array */
const checkForUnique = (arrToCheck, keyName) => {
/* make set to remove duplicates and compare to */
const uniqueValues = [...new Set(arrToCheck.map(v => v[keyName]))];
if(arrToCheck.length !== uniqueValues.length){
console.log('NOT UNIQUE')
return false
}
return true
}
let arr = [{name:'joshua'},{name:'tony'},{name:'joshua'}]
/* call function with arr and key to check for */
let isUnique = checkForUnique(arr,'name')
checkDuplicate(arr, item) {
const uniqueValues = new Set(arr.map((v) => v[item]));
return uniqueValues.size < arr.length;
},
console.log(this.checkDuplicate(this.dutyExemptionBase, 'CI_ExemptionType')); // true || false
It is quite interesting to work with arrays
You can use new Set() method to find duplicate values!
let's assume you have an array of objects like this...
let myArray = [
{ id: 0, name: "Jhon" },
{ id: 1, name: "sara" },
{ id: 2, name: "pop" },
{ id: 3, name: "sara" }
]
const findUnique = new Set(myArray.map(x => {
return x.name
}))
if(findUnique.size < myArray.length){
console.log("duplicates found!")
}else{
console.log("Done!")
}
const duplicateValues = [{ name: "abc" }, { name: "bcv" }, { name: "abc" }];
const isContainDuplicate = (params) => {
const removedDuplicate = new Set(params.map((el) => el.name));
return params.length !== removedDuplicate.size;
};
const isDuplicate = isContainDuplicate(duplicateValues);
console.log("isDuplicate");

Categories