Array group by order by and last in angular controller - angularjs

My array Looks like this
var List = [
{qid: 1, ID: 1, text: "XXX",...},
{qid: 1, ID: 2, text: "XXX",...},
{qid: 1, ID: 3, text: "XXX",...},
{qid: 2, ID: 4, text: "XXX",...},
{qid: 2, ID: 5, text: "XXX",...},
{qid: 3, ID: 6, text: "XXX",...},
{qid: 3, ID: 7, text: "XXX",...},
{qid: 3, ID: 8, text: "XXX",...},
];
I want to query the array so that the final list looks like this
var FinalList = [
{qid: 1, ID: 3, text: "XXX",...},
{qid: 2, ID: 5, text: "XXX",...},
{qid: 3, ID: 8, text: "XXX",...},
];
qid can have multiple ID but the last entry will be the one selected for the FinalList[] array
I want to use something like group by on qid and get last row entered based on qid in angularcontroller.js.
I tried using the reduce function but it does not give me exactly what i want
its been just a month since I have started using angularjs any help will be greatly appreciated
Addition:
I tried doing this
angular.forEach($scope.selectedList, function (item) {
var found = $filter('filter')($scope.selectedList, { QuesID: item.qid});
$scope.FinalList .push(found[found.length - 1]);
$scope.List = $scope.List .filter(function (o1) {
return !$scope.List.some(function (o2) {
return o1.qid=== o2.qid;
});
});
});
I get the first item not the subsequent

You can use a combination of reduce and map to solve this.
First use reduce to group your array by qid:
var grouped = List.reduce(function(agg, x){
(agg[x['qid']] = agg[x['qid']] || []).push(x);
return agg;
}, {});
then map the values of this group, with the element with the highest ID. You can find the element with the highest ID using another reduce within this map:
var result = Object.values(grouped).map(function(grp){
return grp.reduce(function(a, b){
return a['ID'] > b['ID'] ? a : b;
});
});
This is in my opinion the most clean solution.
Here is a Plunker showing it in action

Its a Javascript question more than an angularJS one.
Ok, here goes:
Sort the array first: (Credits : https://stackoverflow.com/a/8837511/6347317)
var sortedList = List.sort(function(a, b){
var keyA = a.qid;
keyB = b.qid;
if(keyA < keyB) return -1;
if(keyA > keyB) return 1;
return 0;
});
Assign the required variables:
var firstqid=sortedList[0].qid; //first object
var finObjArr = []; //final array
var finObj={}; //empty object to keep track of the object to be inserted in final array
var lastObj = sortedList.slice(-1)[0]; //last object of sorted array
var flag = 0; //flag to not insert any more object to final result if the last unique qid is reached
Loop through array and get the result: (finObjArr will have the desired output)
sortedList.forEach(function(obj) {
var qidkey = obj.qid;
//we are checking if current qid is same as the last one. this is to determine the last object with a qid key and then pushing the previous object (held in finObj) to the final array
if (qidkey != firstqid)
{
firstqid=qidkey;
finObjArr.push(finObj);
}
//If the qid is same as earlier one, then make the current object as finObj and if its the last unique qid, inset the last object in the array in final array and set the flag so that no more inserts happen into final array
if (qidkey == firstqid)
{
finObj = obj;
if (qidkey == lastObj.qid && flag == 0) {
finObjArr.push(lastObj);
flag=1;
}
}
})
Please check if this works.
Note: I was not sure if you need the sort on the array. If it already comes in the required order, no need to sort and you can directly run the forEach. Else you will have to write a sort function to have the array in the required order for forEach

Related

Filter array by matching id properties

I feel like this has to be answered some where, but I have been searching for a few days with no luck. I have an example below. I have an array of users and I need to filter them down to the ones that have a matching ID property, I know the code below doesn't compile.. would be very grateful for any help with this.
struct User {
var id: Int
var name: String
}
let userArray = [
User(id: 1, name: "A"),
User(id: 2, name: "B"),
User(id: 1, name: "C"),
User(id: 3, name: "D"),
]
let newArray = userArray.filter({ $0.id == $1.id })
// This is what i want to achieve
// newArray = [User(id: 1, name: "A"), User(id: 1, name: "C")]
In the actual project, the id is dynamically returned. So I just need to be able to check for what is matching, without knowing what the id will actually be.
Your approach won't work as filter only takes one dynamic parameter and only processes one item at a time. Therefore it can't match two separate array entries.
You example also doesn't specify how you want to handle the situation where you have multiples of different User.id. This answer assumes you want to be able to separate them into separate arrays.
Dictionary has a handy initialiser that will do the bulk of the work for you and group on a defined property. Grouping on id will give you a dictionary where the key is the id and the values an array of matching User records. You can then filter the dictionary to get a dictionary where there are multiple users for any id.
let multiples = Dictionary(grouping: userArray, by: \.id).filter{$0.value.count > 1}
Using your data you will end up with a dictionary of:
[1: [User(id: 1, name: "A"), User(id: 1, name: "C")] ]
Your condition in filter does not compare to a given id value. Below is one added called, which I call matchingId:
struct User {
var id: Int
var name: String
}
let userArray = [
User(id: 1, name: "A"),
User(id: 2, name: "B"),
User(id: 1, name: "C"),
User(id: 3, name: "D"),
]
let matchingId = 1 // or: let matchingId = someFunctionCallReturningAnId()
let result = userArray.filter { $0.id == matchingId }
print(result)

How to create an array from 1 to N in TypeScript?

What I want to do is creating an array from 1 to N and call it first_array, then combining it with another array(second array) with the same length and make a new array object like below:
new_array = [
{
"name": "first_array[0]",
"value": second_array[0]
},
{
"name": "first_array[1]",
"value": second_array[2]
},
"name": "first_array[2]",
"value": second_array[2]
];
If you want an array from 1 to N, you can create a new array of length N, fill it with a filler value, then populate the values using their indices with .map.
For example:
const n = 10;
const myArray = new Array(n).fill(null).map((_, i) => i + 1);
with result for myArray:
[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
However in this case, it seems you don't need that array if you're just using it alongside another array. Instead you can use the index from that other array (second_array) as values for the "name" key.
Like this:
const newArray = secondArray.map((e, i) => ({
name: i + 1,
value: e,
}));
Example case:
Input
const secondArray = [100, 200, 300];
Result (for newArray)
[
{ name: 1, value: 100 },
{ name: 2, value: 200 },
{ name: 3, value: 300 }
]
First create an array with the maximum number of expected elements. Then reduce this array of empty elements to the merged array:
let array_1th = [1,2,3,4]
let array_2nd = [5,6,7,8];
let merged = Array(Math.max(array_1th.length, array_2nd.length))
.fill().map((_, i) => ({ name: array_1th[i], value: array_2nd[i] }));
console.log(merged);
A small downside of the above example is that two arrays would be created, with the prepared empty array being discarded afterwards.
A slightly nicer solution is to use reduce to mutate the input array directly so that only one array is created:
let array_1th = [1,2,3,4]
let array_2nd = [5,6,7,8];
let merged = Array(Math.max(array_1th.length, array_2nd.length)).fill()
.reduce((_, __, i, array) => (array[i] = { name: array_1th[i], value: array_2nd[i] }, array), null);
console.log(merged);

In an array of obects watched with Vue-watch, how to get index of object that was changed?

I have an array of objects, like this:
myArray: [{
name: "First",
price: 10,
rebate: 5,
listPrice: 15,
outcome: 0
},{
name: "Second",
price: 11,
rebate: 5,
listPrice: 16,
outcome: 0
}
I want to recalculate the outcome-value whenever any of the other values in the same object change.
I already have a setup like this, but it looks for changes in any object and then recalculates the whole array. I've managed to set this up by using a combination of computed and watch functions. However they watch the whole array for changes and then recalculate the outcome-value for all objects in the array.
How can I watch for changes and then recalculate only the changed object?
Below is my current functions for recalculating the whole array (watching another property), but what I'm looking for could be completely different.
computed:
myArrayWasChanged() {
return [this.myArray.reduce((a, {vendors}) => a + vendors, 0), this.myArray.filter(item => item.discounted == false).length]
watch:
myArrayWasChanged: {
handler: function (val, oldVal) {
this.recalculateIsVendor();
Given the outcome is completely dependent on the other properties, it isn't really part of the component's state. Thus, in the component's data you could store the array without the outcome, and then calculate a new version of the array with the outcome as a computed property.
data: function () {
return {
myArrayWithoutOutcome: [
{
name: "First",
price: 10,
rebate: 5,
listPrice: 15
},
{
name: "Second",
price: 11,
rebate: 5,
listPrice: 16
}]
}
},
computed: {
myArrayWithOutcome: function () {
return this.myArrayWithoutOutcome.map(x => {
return {...x, outcome: this.calculateOutcome(x)}
})
}
},
methods: {
calculateOutcome(item) {
// Logic to calculate outcome from item goes here
return 0
}
}

Merge arrays with condition

I would like to merge two arrays with specific condition and update objects that they are containing.
First my struct that is in arrays:
struct Item {
var id:Int
var name:String
var value:Int
}
Second elements for the two arrays:
let fisrt = Item(id: 1, name: "Oleg", value: 3)
let second = Item(id: 2, name: "Olexander", value:5)
let fisrtInSecond = Item(id: 1, name: "Bogdan", value: 6)
let secondInSecond = Item(id: 2, name: "Max", value: 9)
Arrays:
var fisrtArray = [fisrt, second]
let secondArray = [fisrtInSecond, secondInSecond]
I woudl like to use zip and map functions of the collection to achive result. Result is that fisrtArray elements names are updated by id.
Example: fisrtArray = [Item(id: 1, name: "Bogdan", value:3), Item(id: 2, name: "Max", value:5)]
I know how to do this via simple loops. But i am looking for more advanced usage of the functional programing is Swift.
My experiment:
fisrtArray = zip(fisrtArray, secondArray).map()
The main problem i do not know how to write condition in the map function. Condition should be:
if ($0.id == $1.id) {
$0.name = $1.name
}
From the comment discussing it is possible to highlight that zip is not suitable in my case because we should iterate over all array to find if we have similar id's that are not in the same order.
The following code does work independently by the order of the elements inside the 2 arrays
firstArray = firstArray.map { (item) -> Item in
guard
let index = secondArray.index(where: { $0.id == item.id })
else { return item }
var item = item
item.name = secondArray[index].name
return item
}
"[Item(id: 1, name: "Bogdan", value: 3), Item(id: 2, name: "Max", value: 5)]\n"
Update
The following version uses the first(where: method as suggested by Martin R.
firstArray = firstArray.map { item -> Item in
guard let secondElm = secondArray.first(where: { $0.id == item.id }) else { return item }
var item = item
item.name = secondElm.name
return item
}
A solution for your specific problem above would be:
struct Item {
var id: Int
var name: String
}
let first = Item(id: 1, name: "Oleg")
let second = Item(id: 2, name: "Olexander")
let firstInSecond = Item(id: 1, name: "Bogdan")
let secondInSecond = Item(id: 2, name: "Max")
let ret = zip([first, second], [firstInSecond, secondInSecond]).map({
return $0.id == $1.id ? $1 : $0
})
=> But it requires that there are as many items in the first as in the second array - and that they have both the same ids in the same order...
The map function cannot directly mutate its elements. And since you're using structs (passed by value), it wouldn't work anyway, because the version you see in $0 would be a different instance than the one in the array. To use map correctly, I'd use a closure like this:
fisrtArray = zip(fisrtArray, secondArray).map() {
return Item(id: $0.id, name: $1.name, value: $0.value)
}
This produces the result you're expecting.
Now, if your structs were objects (value types instead of reference types), you could use forEach and do the $0.name = $1.name in there.

AngularJS filter object by property on tree structure

I am posting this because I never found a precise answer for filtering nested objects (tree sturcture).
Let's say we have an JSON tree structure that looks like this:
$scope.tree = [{
id: 1,
parent_id: 0,
name: 'Root Item',
items: [
{
id: 2,
parent_id: 1,
name: '1st Child of 1'
},
{
id: 3,
parent_id: 1,
name: '2nd Child of 1'
},
{
id: 4,
parent_id: 1,
name: '3rd Child of 1',
items:[
{
id:5,
parent_id: 4,
name:'1st Child of 5'
},
{
id:6,
parent_id: 4,
name:'2nd Child of 5'
}
]}
]
}]
How do we traverse the tree with a filter to get object with id 6 for example?
If we use the following filter for example:
<div data-ng-init="selectedItem = (tree | filter:{id:6})">
<h1>The name of item with id:6 is selectedItem.name</h1>
</div>
It will only iterate through the first level in which will only find id:1.
So, in order to get nested level objects we must use a recursive filter like this one:
angular.module("myApp",[])
.filter("filterTree",function(){
return function(items,id){
var filtered = [];
var recursiveFilter = function(items,id){
angular.forEach(items,function(item){
if(item.id === id){
filtered.push(item);
}
if(angular.isArray(item.items) && item.items.length > 0){
recursiveFilter(item.items,id);
}
});
};
recursiveFilter(items,id);
return filtered;
};
});
});
So, to use this filter in the markup you would call it like this:
<div data-ng-init="selectedItem = (tree | filterTree:6)">
<h1>The name of item with id:6 is selectedItem.name</h1>
</div>
Hope you find this useful, it took me some time to digest recursive filters.
Of course, this filter works to get 1 item since it returns [0] first object of filtered array. But if you want it to return more than 1 result you'll have to remove only that [0] at the return function and then use ng-repeat to iterate over filtered resutls.

Resources