I have 2 array objects and I want to get the difference between them as follows:
array1 = [{"name":"MPCC","id":"tool:mpcc"}, {"name":"APP","id":"tool:app"}, {"name":"AII","id":"tool:aii"}, {"name":"VZZ","id":"tool:vzz"}, {"name":"USU","id":"tool:usu"}]
array2 = [{"name":"APP","id":"tool:app"}, {"name":"USU","id":"tool:usu"}]
result = [{"name":"MPCC","id":"tool:mpcc"}, {"name":"AII","id":"tool:aii"}, {"name":"VZZ","id":"tool:vzz"}]
Here is the code:
$scope.initial = function(base, userData){
var result = [];
angular.forEach( base, function(baseItem) {
angular.forEach( userData, function( userItem ) {
if ( baseItem.id !== userItem.id ) {
if (result.indexOf(baseItem) < 0) {
result.push(baseItem);
}
}
});
});
return result;
}
$scope.initial(array1, array2);
The problem with the above code is I dont get the desired result. Please let me know what is going wrong.
That is not related to Angular.
You could do something like this:
var result = array1.filter(function(item1) {
for (var i in array2) {
if (item1.id === array2[i].id) { return false; }
};
return true;
});
Or with ES6 syntax:
var result = array1.filter(i1 => !array2.some(i2 => i1.id === i2.id));
I think this is not related to Angular itself. You're looking for an algorithm to compute a difference between 2 sets.
The topic has already been discussed. You may also be interested on this underscore plugin
Related
When I try to grab the object from the array, the type is undefined. Therefore I cannot use a method from the undefined object as it doesn't exist. I am relatively new to JavaScript and I have come straight from Java so the way of retrieving objects is kind of new to me. This is what I currently have.
var fleetAmount = 0;
var fleets = [];
function Fleet(number) {
this.number = number;
this.activities = [];
this.addActivity = function (activity) {
this.activities.push(activity);
};
fleets.push(this);
}
var getFleet = function(fleetNumber) {
return fleets[fleetAmount - fleetNumber];
}
This is where I try to grab the object and preform the function
const Fl = require(‘fleet.js’);
const fleet = Fl.getFleet(fleetNumber);
fleet.addActivity(activity);
I am also working in Node.js, which is how I am using the require method.
In combination with the answer from #audzzy I changed the getFleet() function so that it would be more efficient. I tested it out and it worked. This is what I used
function getFleet(fleetNumber) {
let result = fleets.filter(function (e) {
return e.number = fleetNumber;
})
return result[0];
}
Thanks for the help! I appreciate it.
you want to create a new fleet object and add it, not "this"..
adding "this" would cause a circular reference, where
this.fleets[i] = this (and all fleets would have the same value)
when calling get fleet, I would check that a fleet was returned from get fleet
in case amount is less than the number you send to getFleet (where according to what you posted: 1 returns the last, 2 returns second to last etc..)..
I hope this explanation makes sense.. anyways, this should work..
var fleets = [];
doStuff();
function doStuff(){
addFleet(1);
addFleet(2);
addFleet(7);
addFleet(3);
// should return null
let fleet1 = getFleetByNumber(5);
// should return the fleet with number 7, and not change the fleet with number 1
let fleet2 = getFleetByNumber(7);
if(fleet2){
fleet2.addActivity("activity");
}
console.log(`fleets: ${JSON.stringify(fleets)} \nfleet1: ${JSON.stringify(fleet1)} \nfleet2: ${JSON.stringify(fleet2)}`);
}
function addFleet(number) {
let fleet = { number: number,
activities: [] };
fleet.addActivity = function (activity) {
this.activities.push(activity);
};
fleets.push(fleet);
}
function getFleetByNumber(fleetNumber) {
return fleets.find(function (e) {
return e.number == fleetNumber;
});
}
function getFleet(fleetNumber) {
let result = null;
if(fleets.length - fleetNumber >= 0){
result = fleets[fleets.length - fleetNumber];
}
return result;
}
It's driving me crazy. I've created a list with several entries. I added a filtering function, which seems to work fine. I've checked the number of results returned, but somehow it just showing the result number beginning at the first row.
For explanation:
Let's assume I search for "Zonen" and my filter function returns 4 rows with ID 23, 25, 59 and 60, the rows with ID's 1,2,3 and 4 are displayed. What I'm doing wrong!?
...
render() {
let filteredList = this.state.freights.filter((freight) => {
let search = this.state.search.toLowerCase();
var values = Object.keys(freight).map(function(itm) { return freight[itm]; });
var flag = false;
values.forEach((val) => {
if(val != undefined && typeof val === 'object') {
var objval = Object.keys(val).map(function(objitm) { return val[objitm]; });
objval.forEach((objvalue) => {
if(objvalue != undefined && objvalue.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
});
}
else {
if(val != undefined && val.toString().toLowerCase().indexOf(search) > -1) {
flag = true;
return;
}
}
});
if(flag)
return freight;
});
...
<tbody>
{
filteredList.map((freight)=> {
return (
<Freight freight={freight} onClick={this.handleFreightClick.bind(this)} key={freight.id} />
);
})
}
</tbody>
...
UPDATE
freights is loaded and filled via AJAX JSON result. One object of freights looks like this:
I have a textbox where a user can perform a search. This search should return all freight objects which properties contain the search string.
The filter is so complex, because I want to also to search in sub-objects of freight. Maybe there is a more simple way?
"Zones" was just an example for a search string the user can search for.
Now that your intentions are clearer, I suggest this much less complex solution.
First, you can write a recursive utility fn to get all values of all keys in an n-depth object. Like this, for example (I'm using lodash's utility fn isObject there):
const getAllValues = (obj) => {
return Object.keys(obj).reduce(function(a, b) {
const keyValue = obj[b];
if (_.isObject(keyValue)){
return a.concat(getAllValues(keyValue));
} else {
return a.concat(keyValue);
}
}, []);
}
Now that you have an array of all object's values, it makes your filter very simple:
let filteredList = this.state.freights.filter((freightItem) => {
const allItemValues = getAllValues(freightItem);
return allItemValues.includes(this.state.search);
});
That should be it. If something is not working, gimme a shout.
I have found the solution why the "wrong" freight entries are displayed.
I needed to add in freight component the componentWillReceiveProps method:
componentWillReceiveProps(nextProps) {
if(nextProps.freight) {
this.setState({
freight: nextProps.freight
});
}
}
Then everything worked fine.
I have two arrays, called 'objects' and 'appliedObjects'. I'm trying to come up with an elegant way in Javascript and/or Angular to move objects from one array to another.
Initially I did something like this:
$scope.remove = function () {
angular.forEach($scope.appliedObjects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.objects.push(element);
$scope.appliedObjects.splice(index, 1);
}
});
}
$scope.add= function () {
angular.forEach($scope.objects, function (element, index) {
if (element.selected) {
element.selected = false;
$scope.appliedObjects.push(element);
$scope.objects.splice(index, 1);
}
});
}
But then I realized that when the value was removed from the looping array, and it would not add or remove every other item, since it went by index.
Then I tried using a temporary array to hold the list of items to be added or removed, and I started getting strange referential issues.
I'm starting to spin a bit on what the best solution to this problem would be...any help and/or guidance would much appreciated.
function moveElements(source, target, moveCheck) {
for (var i = 0; i < source.length; i++) {
var element = source[i];
if (moveCheck(element)) {
source.splice(i, 1);
target.push(element);
i--;
}
}
}
function selectionMoveCheck(element) {
if (element.selected) {
element.selected = false;
return true;
}
}
$scope.remove = function () {
moveElements($scope.appliedObjects, $scope.objects, selectionMoveCheck);
}
$scope.add = function () {
moveElements($scope.objects, $scope.appliedObjects, selectionMoveCheck);
}
When a construct does too much automatically (like forEach, or even a for-loop, in this case), use a more primitive construct that allows you to say what should happen clearly, without need to work around the construct. Using a while loop, you can express what needs to happen without resorting to backing up or otherwise applying workarounds:
function moveSelected(src, dest) {
var i = 0;
while ( i < src.length ) {
var item = src[i];
if (item.selected) {
src.splice(i,1);
dest.push(item);
}
else i++;
}
}
You are altering the array while iterating on it, you will always miss some elements.
One way of doing it would be to use a third array to store the references of the objects that need to be removed from the array:
// "$scope.add" case
var objectsToRemove = [];
$scope.objects.forEach(function (value) {
if (value.selected) {
value.selected = false;
$scope.appliedObjects.push(value);
objectsToRemove.push(value);
}
});
objectsToRemove.forEach(function (value) {
$scope.objects.splice($scope.objects.indexOf(value), 1);
});
If you wish to move simply whole array you could do:
appliedObjects = objects;
objects = []
Of course it won't work if they were parameters of a function!
Otherwise I cannot see other way than copying in the loop, e.g.
while (objects.length) {
appliedObjects.push(objects[0]);
objects.splice(0,1);
}
or if you like short code :) :
while (objects.length) appliedObjects.push(objects.splice(0,1));
check fiddle http://jsfiddle.net/060ywajm/
Now this maybe is not a fair answer, but if you notice you are doing alot of complicated object/array manipulations, you should really check out lodash or underscore library. then you could solve this with on liner:
//lodash remove function
appliedObjects.push.apply( appliedObjects, _.remove(objects, { 'selected': true}));
//or if you want to insert in the beginning of the list:
appliedObjects.splice(0, 0, _.remove(objects, { 'selected': true}));
This is a first pass at what I think will work for you. I'm in the process of making a test page so that I can test the accuracy of the work and will update the tweaked result, which hopefully there will not be.
EDIT: I ran it and it seems to do what you are wanting if I understand the problem correctly. There were a couple of syntax errors that I edited out.
Here's the plunk with the condensed, cleaned code http://plnkr.co/edit/K7XuMu?p=preview
HTML
<button ng-click="transferArrays(objects, appliedObjects)">Add</button>
<button ng-click="transferArrays(appliedObjects, objects)">Remove</button>
JS
$scope.transferArrays = function (arrayFrom, arrayTo) {
var selectedElements;
selectedElements = [];
angular.forEach(arrayFrom, function(element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function(element) {
arrayTo.push(arrayFrom.splice(
arrayFrom.map(function(x) {
return x.uniqueId;
})
.indexOf(element.uniqueId), 1));
});
};
Old code
$scope.remove = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.appliedObjects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.objects.push($scope.appliedObjects.splice(
$scope.appliedObjects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
$scope.add = function () {
var selectedElements;
selectedElements = [];
angular.forEach($scope.objects, function (element) {
if (element.isSelected) {
element.isSelected = false;
selectedElements.push(element);
}
});
angular.forEach(selectedElements, function (element) {
$scope.appliedObjects.push($scope.objects.splice(
$scope.objects.map(function (x) { return x.uniqueId; })
.indexOf(element.uniqueId), 1));
});
};
You can use this oneliner as many times as many items you need to move from arr1 to arr2 just prepare check func
arr2.push(arr1.splice(arr1.findIndex(arr1El => check(arr1El)),1)[0])
You can use this to concat 2 arrays:
let array3 = [...array1, ...array2];
Guys, please don't answer me to use a JavaScript library to solve this problem, I'm using VanillaJS.
Suppose I have an array with 10,000 string records, same as following:
var arr = [
'John',
'Foo',
'Boo',
...
'Some',
'Beer'
];
Please note that the array doesn't follow any sort.
Now, I want to find items with oo in the text, what is the best way to do? Should I populate a new array or just pop items that don't match with the criteria?
You can make use of the filter method, which will create a new array with all the elements that passes the condition.
arr.filter(function(x){ return x.indexOf ('oo') > -1});
If you want to use filter method in every browser you could add the polyfill method (see link) in your code.
Another option (slightly faster) with basic javascript would be:
Looping trough the array with a simple for loop and test on your condition.
var filtered = [];
for(var i=0, length=arr.length; i<length; i++){
var current = arr[i];
if(current.indexOf('oo') > -1){
filtered.push(current);
}
}
my approch
forEach function
function forEach(array, action) {
for(var i=0; i<array.length; i++)
action(array[i]);
}
partial function
function asArray(quasiArray, start) {
var result = [];
for(var i = (start || 0); i < quasiArray.length; i++)
result.push(quasiArray[i]);
return result;
}
function partial(func) {
var fixedArgs = asArray(arguments, 1);
return function() {
return func.apply(null, fixedArgs.concat(asArray(arguments)));
};
}
contains method for String obj
if (!String.prototype.contains) {
String.prototype.contains = function (arg) {
return !!~this.indexOf(arg);
};
}
filter function:
function filter(test, array) {
var result = [];
forEach(array, function(element) {
if (test(element))
result.push(element);
});
return result;
}
test function for test array items
function test(key, el) {
return el.contains(key);
}
finally
filter(partial(test, 'oo'), arr);
NO shortcuts ( p.s. you said I want to find items , not filter - hence my answer)
simple loop :
var g=arr.length; //important since you have big array
for( var i=0;i<g;i++)
{
if ( g[i].indexOf('oo')>-1)
{
console.log(g[i]);
}
}
If you want to filter (without polyfill)
var g=arr.length; //important since you have big array
var h=[];
for( var i=0;i<g;i++)
{
if ( g[i].indexOf('oo')>-1)
{
h.push(g[i]);
}
}
//do something with h
A very simple way, use forEach:
var result = [];
arr.forEach(function (value) {
if (value.indexOf('oo') !== -1) {
result.push(value);
}
});
or map:
var result = [];
arr.map(function (value) {
if (value.indexOf('oo') !== -1) {
result.push(value);
}
});
I'm currently using javascript to do some experiments and although I'm not new to JS I have a doubt that I hope the good folks at SO will be able to help.
Basically I'm making a jsonp request to a webservice that returns me the amount/length on the reply (just counting objects).
Then I'm randomly selecting 9 of those objects to place in an array and here lies the problem. I would like to make sure that none of those 9 objects is repeated.
To achieve this I'm using the following code:
function randomizer() {
return Math.ceil(Math.random()*badges.length);
}
function dupsVerify(array, number) {
array.forEach(function () {
if(array === number) {
return true;
}
});
return false;
}
// Randomly choose 9 array indexes
var randomBadge = function () {
var selectedIndexes = new Array();
while(selectedIndexes.length < 9) {
var found = false;
var randomNumber = randomizer();
if(!dupsVerify(selectedIndexes, randomNumber)) {
selectedIndexes.push(randomNumber);
} else {
newRandom = randomizer();
dupsVerify(selectedIndexes, newRandom);
}
}
return selectedIndexes;
}
I've tried a couple different methods of doing this verification but I've been thinking if it wouldn't be possible to do the following:
Generate the random number and go through the array to verify if it already exists in the array. If it exists, generate another random number (randomize call) and verify again.. If it doesn't exist in the array, then push it to the "final" array.
How can I achieve this? Using callbacks?
Am I doing this right or should I chance the code? Is there a simpler way of doing this?
Best Regards,
This would get you the desired behavior:
function getRandomPositions(sourcearray, desiredcount){
var result = [];
while(result.lentgth < desiredcount){
var rnd = Math.ceil(Math.random()*sourcearray.length);
if (result.indexOf(rnd) == -1){
result.push(rnd);
}
}
return result;
}
Instead of generating X random numbers, just generate a random number, but don't add it if it already exists.
I ended up finding the best solution for this scenario by using the following code:
function randomizer() {
return Math.ceil(Math.random()*badges.length);
}
function dupsVerify(array, number) {
var result;
if(array.length === 0) {result = false;}
array.forEach(function (item) {
if(item === number) {
result = true;
} else {
result = false;
}
});
return result;
}
// Randomly choose 9 array indexes
function randomBadge() {
while(cards.length < 9) {
var randomNumber = randomizer();
if(!dupsVerify(cards, randomNumber)) {
cards.push(randomNumber);
} else {
randomBadge();
}
}
return cards;
}
This represents the same behavior (and a few minor code corrections) but ensures that I will never get an array with 2 repeated objects.