How to optimize array lookup of JS object based on one property - javascript

We have a large array of objects:
var englishStudents = [
{StudentId: 1, Name: "John"},
{StudentId: 2, Name: "Jack"},
{StudentId: 3, Name: "Jane"}
];
Need to check if another similar object is contained in this array, just by comparing one property alone.
var randomStudent = {StudentId: 1337, Name: "Foo"};
This is what I have, and it seems as though it will work, but I don't think this is the best way to do this.
var studentIds = $.map(englishStudents, function (student, index) { return student.StudentId; });
var randomStudentLearnsEnglish = false;
for (var sId in studentIds) {
if (randomStudent.StudentId == sId) {
randomStudentLearnsEnglish = true;
break;
}
}
What would be the optimized way to do this?

You should keep student data in a hash table like JHashtable instead of the array. For mor complex scenarios, you can maintain more than one hash table, like studentsById, studentsByCountryCode, etc.

If all you want to know is if the ID exists can do this:
function checkIdExists( id){
/* map array of matching ID, if none exists length of array is zero*/
return $.map(englishStudents, function (student, index) {
return student.StudentId==id;
}).get().length;
});
Use:
if( checkIdExists( 1234)){
/* run exists code*/
}

If you really want, you can create a further indexing scheme:
var englishStudents = [
{StudentId: 1, Name: "John"},
{StudentId: 2, Name: "Jack"},
{StudentId: 3, Name: "Jane"}
];
//if doing this a lot of time, take the one time hit of construction and memory
var idToNameMap = createIdToNameMap(englishStudents); //returns {'1': 'John', '2': Jack' , '3': 'Jane'}
var randomStudent = getRandomStudent();
if( idToNameMap[ randomStudent.StudentId] != undefined){ ... }

Just do a hash instead of an array, so:
var englishStudents = {
1: {StudentId: 1, Name: "John"},
2: {StudentId: 2, Name: "Jack"},
3: {StudentId: 3, Name: "Jane"}
};
and then to retrieve, simply do:
var student = englishStudents[id];

Related

Convert array to an array object with some modifications

i'm pretty beginner in the JavaScript and I really need help to convert an array to an array object. There are many examples here in stackOverflow, but I need some modidfication during this process, which is why I couldn't do anything
For example I have:
data = [{id: 21, name: "jack"} , {id: 185, name: "yas"}]
and I need to convert it with something like that (id key change to student_id, and present = true, should be added), and the length of this array is dynamic and will change over time.
[
{
"student_id" : 21,
"present" = true
},
{
"student_id" : 185,
"present" = true
}
]
I need to add these array object to:
const data: any = {
here....
};
your help will be much appreciated.
Assuming your data actually looks more like this
data = [{id: 21, name: "jack"}, {id: 185, name: "yas"}]
This is a simple matter of mapping the array to a new format with the properties you want
const data = [{id: 21, name: "jack"}, {id: 185, name: "yas"}]
const newData = data.map(({ id }) => ({
student_id: id,
present: true
}))
console.log(newData)

Javascript array difference

I have two arrays like so
data = [{id: 1, name: apple},
{id: 2, name: mango},
{id: 3, name: grapes},
{id: 4, name: banana}]
data2 =[{id: 1, name: apple},
{id: 3, name grapes}]
My Expected result would be:
[{ id: 2, name: mango},
{id:4, name: banana}]
My code is
let finalData =[];
data.forEach(result => {
data2.find(datum => {
if(datum['id'] === result['id]{
finalData.push(result);
}
})
})
I am getting wrong result. What is the simplest code or library that I can use?
Your sample data doesn't make sense, but assuming you mean that all data items that have matching IDs also have matching names and also assuming you want a set of all items where the IDs are the same in the two sets of data, you could use a Set to keep track of which IDs are present in one array then filter the second array by those that have their IDs in the set:
const idsInFirst = new Set(data.map(d => d.id));
const intersection = data2.filter(d => idsInFirst.has(d.id));
The reason why an intermediate Set structure is used is because it allows O(1) lookups after a one-time scan, which is more efficient than repeatedly scanning the first array over and over.
If you meant to say you wanted a difference between data sets (items excluded from data that are in data2), you'd want to negate/inverse things a bit:
const idsToExclude = new Set(data2.map(d => d.id));
const difference = data.filter(d => !idsToExclude.has(d.id));
Edit
After your clarifying edit, it's that second block of code that you'll want.
I would say a good way to do that is filtering your longest array using a function that will validate if the object id is present in both arrays. Check this example:
const data = [
{id: 1, name: 'apple'},
{id: 2, name: 'mango'},
{id: 3, name: 'grapes'},
{id: 4, name: 'banana'}
]
const data2 =[
{id: 1, name: 'apple' },
{id: 3, name: 'grapes' }
]
const longest = data.length > data2.length ? data : data2;
const shortest = data.length <= data2.length ? data : data2;
const finalData = longest.filter( obj => !shortest.find( o => o.id === obj.id ) )
console.log(finalData)
Good luck!

How do I get an object parameter by its id?

I need to detect an object in an array with its Id.
My first array looks like that:
{ [id: 9, name: 'abc'], [id: 2, name 'def'], [id: 40, name: 'gh'] } (Id & name),
while that other array is:
{ [class: 'physics', Tid: 9], [class: 'computer science', Tid: 9], [class: 'Biology', Tid: 40] }.
I need to match the parameter "name" from the first array by its ID to its "class" (for example, "physics" relates to Tid=9 which is "abc" and "Biology" relates to Tid=40 which is "gh").
How can I elegantly do so without changing the way the data comes? (It comes from a database with ASP.NET web service in JSON)
You could use $http.get() which has success and error callback functions, which returns a promise object. Using this, you can setup a condition to map the id and get your desired result.
Something like this.
var myObject1 = {};
var myArray1 = [];
var myObject2 = {};
var myArray2 = [];
$http.get('json-file')
.success(function(data)) {
myObject1.myArray1 = data;
}
$http.get('json-file')
.success(function(data)) {
myObject2.myArray2 = data;
}
/* inside a loop if required */
if (myObject1.myArray1[index].id == myObject2.myArray2[index].Tid) {
/* perform logic */
}
This code would be present inside a service or a controller.
Haven't tested it so unsure of the syntax but promise objects are the way to go.
Hope this helps.
This returns an array of arrays. Each array in the array contains two objects matched by id === Tid. As far as I can tell that's what you want.
(Note that I think you provided broken sample arrays, I adjusted and scrambled the numbers around so you could see it work more clearly).
var arr1 = [ {id: 9, name: 'abc'}, {id: 2, name: 'def'}, {id: 40, name: 'gh'} ];
var arr2 = [ {class: 'physics', Tid: 2}, {class: 'computer science', Tid: 40}, {class: 'Biology', Tid: 9} ];
var arrFinal = arr1.map ( function ( d ) {
var matched = arr2.find ( function ( obj ) {
return obj.Tid === d.id;
} );
return [ d, matched ];
} );
If you iterate arrFinal you'll see it contains the matched objects.

I have two arrays, how do I find matchings elements and perform some action? (lodash)

var array1 = [{Age: 24, Name: "Test", StudentID: 101, Checked: false}, {Age:25, Name: "Test", StudentID: 102, Checked: false}];
var array2 = [{ID: 101}];
If any element in array1 has a property of StudentID that is equal to an ID property present in array2 I'd like to set the Checked property in array1 to true.
Any tips? I'd like to do this without writing nested _.each statements.
This is my first take; however, I believe _.some performs an interation anyway.
_.each($scope.array1, function(element1) {
if(_.some($scope.array2, { ID: element1.ID })) {
element1.Checked = true;
}
});
You''ll have to use two loops, since you have two arrays of random length. But you don't have to nest them. Create a map from the array of IDs and then check the index.
var availableIDs = array2.map(function ( item ) { return item.ID; });
array1.forEach(function ( item ) {
if (availableIDs.indexOf(item.StudentID) !== -1) item.Checked = true;
});
Using lodash, use a sequence in which you create a map of items in array1, using _.indexBy(). Create an array of ids from array2 using _.pluck(), and use them with _.at() to get the selected items. Iterate the returned objects using _.forEach() to set the Checked property to true, and .commit() to apply the changes:
function checkById(items, selected) {
_(items) // start chained sequence
.indexBy('StudentID') // create a map of student objects by ids
.at(_.pluck(selected, 'ID')) // create an array of IDs from the objects in the selected array
.forEach(function(item) { // change the items Checked to true
item.Checked = true;
})
.commit(); // executes the chained sequence
}
var array1 = [{
Age: 24,
Name: "Test1",
StudentID: 101,
Checked: false
}, {
Age: 25,
Name: "Test2",
StudentID: 102,
Checked: false
}, {
Age: 22,
Name: "Test3",
StudentID: 103,
Checked: false
}, {
Age: 28,
Name: "Test4",
StudentID: 104,
Checked: false
}];
var array2 = [{
ID: 101
}, {
ID: 104
}];
checkById(array1, array2);
console.table(array1);
document.getElementById('demo').innerText = JSON.stringify(array1, null, ' ');
<script src="https://cdn.jsdelivr.net/lodash/3.10.1/lodash.min.js"></script>
<pre id="demo"></pre>
using a simple mapping function you can compose an easy search through all objects
var array1 = [{Age: 24, Name: "Test", StudentID: 101, Checked: false}, {Age:25, Name: "Test", StudentID: 102, Checked: false}];
var array2 = [{ID: 101}];
function search(studentList,searchQuery) {
var results = [];
studentList.forEach(function(student,sIndex) {
searchQuery.forEach(function(search,qIndex) {
if(search.ID == student.StudentID) {
results.push(student);
}
});
})
return results;
}
search(array1,array2);
what the forEach function does is iterate over each element, passing along the object of the index it's iterating, and the index that object is at.
By having a double nested map it's easy to iterate over the objects and then compare them according to the rules you define.
Then by using a scoped variable you can push matching values into that array, giving you a nice, neat clean result to return.
Now please mind, this is not the most efficient way to handle this. You could do a test which arary is longest and have that one iterate the least time.
So if there are more students than search parameters iterate the students once. If there are more search parameters than students, iterate the search paramateters once.
also you could chooose to 'prefilter" the arrays by sorting them on the index you wish to sort on, skip the ones you don't need by simple min/max exclusion and such.
But you'd be better off using a database query for searching with large quantities of data.
But if you only have a dataset of about a 1000 or so this will suffice.
Try this snippet:
_.each(array1, function (el) {
el.Checked = !!(JSON.stringify(array2).indexOf(el.StudentID) + 1) || el.Checked;
});
Or, you can do without lo-dash.js(with pure JavaScript)
var array1 = [{Age: 24, Name: "Test", StudentID: 101, Checked: false}, {Age:25, Name: "Test", StudentID: 102, Checked: false}];
var array2 = [{ID: 101}];
var students = array1.filter(function(data){
var isTrue = !!(JSON.stringify(array2).indexOf(data.StudentID)+1);
data.Checked = isTrue || data.Checked;
return isTrue;
})
console.log(students)

$filter by nested object values only

I am filtering JSON data against an array of strings. An example of $scope.Data is below:
{
"exerciseDescription": "Lean forward onto a chair and allow the arm to hang down, gently swing the arm from side to side",
"exerciseID": "324",
"exerciseName": "Shoulder Pendular Exercise (Adduction/Abduction)",
"images": [
1008,
1009,
1010
],
"tags": [
"Body Part",
"Arm",
"Upper body",
"Equipment",
"Chair"
"Soft Tissue"
]
},
There is a total of 4500 sets of this data and I would like to filter it by clicking on checkboxes. On clicking a checkbox I push the value of the checkbox (which will be a tag) to an array.
I would then like to filter against ONLY the the nested tag values.
My watch function is here:
$scope.$watchCollection('ActiveFilters', function(newValue) {
if ($scope.ActiveFilters.length > 0) {
$scope.FilteredData = $scope.Data;
for (var i = 0; i < $scope.ActiveFilters.length; i++) {
$scope.FilteredData = $filter('filter')($scope.FilteredData, $scope.ActiveFilters[i]);
console.log($scope.FilteredData);
// console.log($scope.ActiveFilters);
}
}
else {
$scope.FilteredData = [];
}
});
So if $scope.FilteredData contains any 'ActiveFilters' in its nestedtag` array then it will show in the scope.
In brief - how can I only filter against the nested tag array.
Filtering by an value of object in array in javascript:
var items = [{
"exerciseDescription": "Lean forward onto a chair and allow the arm to hang down, gently swing the arm from side to side",
"exerciseID": "324",
"exerciseName": "Shoulder Pendular Exercise (Adduction/Abduction)",
"images": [
1008,
1009,
1010
],
"tags": [
"Body Part",
"Arm",
"Upper body",
"Equipment",
"Chair",
"Soft Tissue"
]
}];
var filter = function (obj) {
if (obj['tags'].indexOf("Soft Tissue") != -1) { // <--- filter by value in tags array
return true;
}
return false;
}
var filtered = items.filter(filter);
I think you will get the idea and adapt it to use with angular.
I don't think there's a good reason to use $filter unless you're trying to filter in your AngularJS markup/templates. It's only really useful in JS if you want to support AngularJS filter expressions in custom directives.
Here's a more complete example for doing a tag filter in vanilla JavaScript:
var filterTags = ['foo', 'bar', 'baz'];
var incomingData = [ // simplified example
{id: 'banana', tags: ['foo', 'qux']},
{id: 'potato', tags: ['qux', 'baz', 'foo', 'bar']},
{id: 'carrot', tags: ['qux', 'quux']},
{id: 'cabbage', tags: ['foo', 'baz', 'bar']}
];
var dataMatchingAllTags = incomingData.filter(function (obj) {
return filterTags.every(function (tag) {
return obj.tags.indexOf(tag) !== -1;
});
}); // [{id: 'potato', …}, {id: 'cabbage', …}]
var dataMatchingAnyTag = incomingData.filter(function (obj) {
return filterTags.some(function (tag) {
return obj.tags.indexOf(tag) !== -1;
});
}); // [{id: 'banana', …}, {id: 'potato', …}, {id: 'cabbage', …}]
var dataMatchingTagsExactly = incomingData.filter(function (obj) {
return (
obj.tags.length === filterTags.length &&
filterTags.every(function (tag) {
return obj.tags.indexOf(tag) !== -1;
})
);
}); // [{id: 'cabbage'}]
In your case $scope.ActiveFilters would be filterTags, $scope.Data would be incomingData and $scope.FilteredData would be either of dataMatchingAllTags, dataMatchingAnyTag or dataMatchingTagsExactly, depending on how you want the filters to work.
Note that this example assumes ES5 but considering AngularJS does so as well, I don't think this will be a problem.

Categories