I feel this is a basic problem but I am having a hard time coming up with a more elegant solution.
So my problem is that I have an array object with a field for Priority.
The priority would be sequential from 1 being the highest.
For example I have:
[{id: 1, priority: 1}, {id: 2, priority: 2}, {id: 3, priority: 3}]
Then I want to change the priority of id: 3 into 1 which the resulting array would be
[{id: 1, priority: 2}, {id: 2, priority: 3}, {id: 3, priority: 1}]
Right now my solution is looping the lenght of array starting with the Priority value that's going to be change and adding + 1. A lot of complication comes up with this approach and I feel I'm making it to complicated on what it should be
An easy, if not necessarily completely efficient, solution would be to temporarily allow non-integer priorities, sort the list by priority, then renumber the priorities.
E.g. if you have
[
{ id: "a", priority: 1 },
{ id: "b", priority: 2 },
{ id: "c", priority: 3 },
]
and you'd need to add a d with a priority between b and c, you could temporarily assign it (2 + 3) / 2, i.e. 2.5:
[
{ id: "a", priority: 1 },
{ id: "b", priority: 2 },
{ id: "c", priority: 3 },
{ id: "d", priority: 2.5 }
]
Then sort the list by priority (using whichever method you like), so you get
[
{ id: "a", priority: 1 },
{ id: "b", priority: 2 },
{ id: "d", priority: 2.5 }
{ id: "c", priority: 3 },
]
and then renumber the priorities with
itemsInPriorityOrder.forEach((item, index) => {
item.priority = index + 1;
});
to end up with
[
{ id: "a", priority: 1 },
{ id: "b", priority: 2 },
{ id: "d", priority: 3 },
{ id: "c", priority: 4 }
]
This will of course also work for any other modification of priority.
You could exploit array indices to reorder your priorities! The below code moves things around so that the items are in an array (sorted by priority, but without an actual priority field), then just moves around the items in the list and restores the field:
const setPriority = (items, id, priority) => {
const _items = [...items].sort(byPriority)
const index = _items.findIndex(x => x.id === id)
const stripped = stripPriorities(_items)
moveItem(stripped, index, priority - 1)
return restorePriorities(stripped)
}
const moveItem = (arr, oldIndex, newIndex) => {
const temp = arr.splice(oldIndex, 1)[0]
arr.splice(newIndex, 0, temp)
}
const stripPriorities = arr =>
arr.map(({ priority, ...x }) => x)
const restorePriorities = arr =>
arr.map((x, i) => ({ ...x, priority: i + 1 }))
const byPriority = (x, y) =>
x.priority > y.priority
Here's a working demo: https://jsfiddle.net/kadft7ep/3/
Increase Priority by 1
Your objects
let data = [{id: 1, priority: 1}, {id: 2, priority: 2}, {id: 3, priority: 3}]
Object to whome you want to increase priority by 1
lets say id=2
first find the object, i am assuming you found it in let obj.
now
obj.priority-=1.5
// resulting data will be [{id: 1, priority: 1}, {id: 2, priority: 0.5}, {id: 3, priority: 3}]
after sorting data would look something like
[{id: 2, priority: 0.5}, {id: 1, priority: 1}, {id: 3, priority: 3}]
now the same approach to make them as whole numbers
data = data.map((obj, index) => {
obj.priority = index + 1;
});
// result [{id: 2, priority: 1}, {id: 1, priority: 2}, {id: 3, priority: 3}]
Make Most Prioritize
Instead of -1.5 just make priority 0 & then sort & then whole numbers again
Related
I have next array:
const arr = [
{
id: 1,
food: ['cola', 'pizza pie'],
size: [{value: 12}, {value: 14}],
},
{
id: 2,
food: ['water', 'wine', 'pasta'],
size: [{value: 15}, {value: 19}],
},
{
id: 3,
food: ['water', 'wine', 'pasta'],
size: [{value: 1}, {value: 13}],
},
];
I need to filter this array in the next way:
If I have the same 'food' values, the array item which has the highest 'size->value', should left, other removed.
Expected result:
const arr = [
{
id: 1,
food: ['cola', 'pizza pie'],
size: [{value: 12}, {value: 14}],
},
{
id: 2,
food: ['water', 'wine', 'pasta'],
size: [{value: 15}, {value: 19}],
},
];
What is the best way for this?
Your requirement as a little vague - particularly when you said:
If I have the same 'food' values, the array item which has the highest 'size->value', should left, other removed.
But here is an approach using Array.prototype.reduce (assuming the comparison is between the total sum of size values when duplicates are found). I've commented the code to give an idea on what's happening.
const arr = [{
id: 1,
food: ['cola', 'pizza pie'],
size: [{
value: 12
}, {
value: 14
}],
},
{
id: 2,
food: ['water', 'wine', 'pasta'],
size: [{
value: 15
}, {
value: 19
}],
},
{
id: 3,
food: ['water', 'wine', 'pasta'],
size: [{
value: 1
}, {
value: 13
}],
},
];
function process(arr) {
return arr.reduce((acc, curr) => {
const item = acc.filter(x => curr.food.sort().toString() === x.food.sort().toString()) // check if there is another entry with the same `food` value
if (item.length) {
// comparision logic goes here
const previousSizes = item[0].size.reduce((a, b) => a + b.value, 0) // previous item's total size
const currentSizes = curr.size.reduce((a, b) => a + b.value, 0) // current item's total size
if (currentSizes > previousSizes) {
return [...acc.filter(x => x !== item[0]), curr] //remove the previous item and add the new one
} else return acc // don't change
} else return [...acc, curr] // curr is a new item.. so just add it
}, [])
}
console.log(process(arr))
The following sorts the input by food items to gather similar items together. It then walks the sorted input deleting items as it finds new maximums. It records the current maximum for each food grouping in a hashtable.
I think time complexity is n log(n) and the space complexity is n.
This implementation assumes the largest value in each size array is what you care about.
Pseudocode:
00. Create hashtable `hash`
01. Sort array `arr` by food
02. For each item `i` in `arr`
03. Let `key` be a unique key for `i` based on food
04. Let `largestSize` be the largest number in `i.size`
05. if `hash` does not contain `key`
06. Set value of `key` in `hash` to `largestSize`
07. else
08. if `largestSize` is larger than hash.key
09. replace `m` and delete previous item in `arr` because current item is larger
10. else
11. delete current item in `arr` because it is too small
12. end if
13. end if
14. end for
15. return `arr`
function filter(arr) {
const hash = {}
arr.sort(({food: a},{food: b})=>String(a).localeCompare(String(b)))
for (let x = 0; x < arr.length; x++) {
const {food, size} = arr[x]
const [{ value: largestSize }] = size.sort(({value: a},{value: b})=>a - b).slice(-1)
const key = String(food)
if (!hash[key])
hash[key] = largestSize
else {
if (largestSize > hash[key]) {
arr.splice(x - 1, 1)
hash[key] = largestSize
} else
arr.splice(x, 1)
--x
}
}
return arr
}
const arr = [
{
id: 1,
food: ['cola', 'pizza pie'],
size: [{value: 12}, {value: 14}],
},
{
id: 2,
food: ['water', 'wine', 'pasta'],
size: [{value: 15}, {value: 19}],
},
{
id: 3,
food: ['water', 'wine', 'pasta'],
size: [{value: 1}, {value: 13}],
},
]
console.log(filter(arr))
I'm doing a complex filter, for which I have an initial list of objects with unique ids.
And a map with N properties with a list of corresponding object copies for each prop; and three states for each prop: idle: 0, show: 1, hide: 2.
For now I managed to do this with lodash's differenceBy and intersectionBy. My filter function takes in an array of objects and mutates the array, by checking and filtering the array with every map prop.
But concerning efficiency and growing number of complexity, should this kind of problem be solved differently?
For example:
If this filter is applied to a big array of hex colors (length 100, 1000 or more)
colors [1, 2, 3, ...1000]
And prop map has a growing number of props, like tags, by which a user can mark colors and show/hide them on filter. Or at some point new prop states will be added.
prop1 0, 1, 2, ...10
prop2 0, 1, 2, ...10
prop3 0, 1, 2, ...10
...
prop100 0, 1, 2, ...10
Should this kind of problem be solved via graph or matrix algorithms or some other method respectively? And, if yes, to what I should look into?
My code for optimisation and efficiency concerns:
const propMap = [
{ name: 'prop1', value: 0, items: [] },
{ name: 'prop2', value: 1, items: [ { id: 1}, { id: 2} ] },
{ name: 'propN', value: 2, items: [ { id: 2} ] },
];
const someArr = [
{ id: 1}, { id: 2}, { id: 3}, { id: 4},{ id: 5},
]
function filterByPropMap (arr) {
// Filter hidden from array
propMap.forEach(prop => {
if (prop.value === 2) {
arr = _.differenceBy(arr, prop.items, 'id');
}
});
// Filter intersecting objects to show
propMap.forEach(prop => {
if (prop.value === 1) {
arr = _.intersectionBy(arr, prop.items, 'id');
}
});
return [...arr];
}
console.log(filterByPropMap(someArr));
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js"></script>
In general, you very often don't need Lodash. Consider the following, which uses only vanilla ES6.
It returns { id: 1 } twice, because it looks like the deduplication is an unintended side effect of your code. At least you never do so explicitly.
const propMap = [
{ name: 'prop1', value: 0, items: [] },
{ name: 'prop2', value: 1, items: [ { id: 1}, { id: 2} ] },
{ name: 'propN', value: 2, items: [ { id: 2} ] },
];
const someArr = [
{ id: 1}, { id: 2}, { id: 3}, { id: 4},{ id: 1},
];
function filterByPropMap(arr) {
const hiddenItems = propMap
.filter(p => p.value === 2)
.map(p => p.items)
.flat();
const intersectingItems = propMap
.filter(p => p.value === 1)
.map(p => p.items)
.flat();
const isEqual = (a, b) => a.id === b.id;
return arr
.filter(v => !hiddenItems.some(h => isEqual(h, v)) &&
intersectingItems.some(i => isEqual(i, v)));
}
console.log(filterByPropMap(someArr));
I have an example array:
arr = [{id:1, count: 2}, {id: 2, count: 6}, {id: 2, count: 4}, {id: 1, count:4}]
I need transform it to include arrays with objects inside based on id:
[[{id:1, count: 2}, {id: 1, count:4}], [{id: 2, count: 6}, {id: 2, count: 4}]]
If I will have 3 different ids - then it will have 3 arrays inside and so on.
If you know any good solutions - let me know. Lodash could be ok as well.
You can use groupBy from lodash to get a map like this:
{
'1': [{id:1, count: 2}, {id: 1, count:4}],
'2': [{id: 2, count: 6}, {id: 2, count: 4}]]
}
Then you can transform it to an array using Object.values()
Essentially you need these two lines:
const groupedById = _.groupBy(items, item => item.id);
const result = Object.values(groupedById);
Pure JS, with reduce:
arr.reduce((acc, curr) => {
let existing = acc.findIndex(elem => elem.some(obj => obj.id === curr.id));
if (existing > -1) {
acc[existing].push(curr);
}
else {
acc[acc.length] = [curr]
}
return acc;
}, []);
As you mentioned in your question lodash solution could also work for you, then loadash has one out of the box method groupBy which can achieve your desired result.
import { groupBy } from "lodash";
const arr = [{id:1, count: 2}, {id: 2, count: 6}, {id: 2, count: 4}, {id: 1, count:4}]
const result = groupBy(arr, 'id');
console.log(result)
Working DEMO
#domenikk showed a really good example! Also, you could use Ramda instead of Lodash to have a point-free function =)
const arr = [
{id:1, count: 2},
{id: 2, count: 6},
{id: 2, count: 4},
{id: 1, count:4}
]
const groupById = R.compose(
R.values,
R.groupBy(R.prop('id'))
)
console.log(groupById(arr))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.min.js"></script>
I am wondering if there is a way to dynamically add objects to an array of objects based on a value? For example, I have an array of objects:
[
{category:A, num:5},
{category:B, num:2}
]
I want to create another array of objects where objects would be the same, but repeated based on the value of num (so 5 times for category A and 2 times for category B) :
[
{category:A, num:5, repeated:1},
{category:A, num:5, repeated:2},
{category:A, num:5, repeated:3},
{category:A, num:5, repeated:4},
{category:A, num:5, repeated:5},
{category:B, num:2, repeated:1},
{category:B, num:2, repeated:2}
]
I have tried map, forEach, for loop, but nothing worked.
I am quite new to javascript, how some one could help!
You can do this using a combination of flatMap and map:
var input = [
{category:"A", num:5},
{category:"B", num:2}
] ;
var result = input.flatMap(e => [...new Array(e.num)].map( (x,i) => ({
category:e.category,
num: e.num,
repeated: i+1
})));
console.log(result);
You could do it using flatMap -
const repeat = ({ num = 0, ...t }) =>
num === 0
? []
: [ ...repeat({ ...t, num: num - 1 }), { ...t, num, repeated: num } ]
const input =
[ { category: "A", num: 5}, { category: "B", num: 2 } ]
const output =
input.flatMap(repeat)
console.log(output)
Output -
[
{ category: "A", num: 1, repeated: 1 },
{ category: "A", num: 2, repeated: 2 },
{ category: "A", num: 3, repeated: 3 },
{ category: "A", num: 4, repeated: 4 },
{ category: "A", num: 5, repeated: 5 },
{ category: "B", num: 1, repeated: 1 },
{ category: "B", num: 2, repeated: 2 }
]
This question already has answers here:
JavaScript "new Array(n)" and "Array.prototype.map" weirdness
(14 answers)
Closed 4 years ago.
I have an array of objects that looks like this:
const input = [
{id: 3, value: 2},
{id: 0, value: 3},
{id: 2, value: 8},
{id: 1, value: 5},
{id: 0, value: 2},
{id: 1, value: 6}
]
And I am trying to build an array of the maximum value by id, with id as index. For our example the desired output is the following:
const output = [ 3, 6, 8, 2 ]
I am also assuming that I know the number of unique ids ahead of time, and that they start at 0 and rise sequentially. My first whack at this was to .map() over the an empty array of the right length and build intermediate arrays with .filter() for each id, then use Math.max() on the filtered arrays.
const myAttempt = Array(4).map((_, index) => {
const filtered = input.filter(item => item.id === index);
return Math.max(filtered);
});
All I get out of this is:
myAttempt = [ 4 empty slots ];
I suspect I'm totally off-base with the Array(4) bit, and that the answer might involve .reduce(), but I never really got the hang of reduce so any help would be very much appreciated.
PS: I'd prefer answers that avoid the use of libraries in the vein of lodash or jQuery.
Use Array.reduce() to collect the values highest value of each key. Convert to array using Array.values():
const input = [
{id: 3, value: 2},
{id: 0, value: 3},
{id: 2, value: 8},
{id: 1, value: 5},
{id: 0, value: 2},
{id: 1, value: 6}
]
const result = Object.values(input.reduce((r, { id, value }) => {
r[id] = +r[id] > value ? r[id] : value;
return r;
}, {}));
console.log(result);
If all ids from 0 on wards appear in the array, you can add the values by their id (index) to an array accumulator:
const input = [
{id: 3, value: 2},
{id: 0, value: 3},
{id: 2, value: 8},
{id: 1, value: 5},
{id: 0, value: 2},
{id: 1, value: 6}
]
const result = input.reduce((r, { id, value }) => {
r[id] = +r[id] > value ? r[id] : value;
return r;
}, []);
console.log(result);