change array passed to function - javascript

I pass 2 arrays to a function and want to move a specific entry from one array to another. The moveDatum function itself uses underscorejs' methods reject and filter. My Problem is, the original arrays are not changed, as if I was passing the arrays as value and not as reference. The specific entry is correctly moved, but as I said, the effect is only local. What do I have to change, to have the original arrays change as well?
Call the function:
this.moveDatum(sourceArr, targetArr, id)
Function itself:
function moveDatum(srcDS, trgDS, id) {
var ds = _(srcDS).filter(function(el) {
return el.uid === uid;
});
srcDS = _(srcDS).reject(function(el) {
return el.uid === uid;
});
trgDS.push(ds[0]);
return this;
}
Thanks for the help

As mentioned in the comments, you're assigning srcDS to reference a new array returned by .reject(), and thus losing the reference to the array originally passed in from outside the function.
You need to perform your array operations directly on the original array, perhaps something like this:
function moveDatum(srcDS, trgDS, id) {
var ds;
for (var i = srcDS.length - 1; i >= 0; i--) {
if (srcDS[i].uid === id) {
ds = srcDS[i];
srcDS.splice(i,1);
}
}
trgDS.push(ds);
return this;
}
I've set up the loop to go backwards so that you don't have to worry about the loop index i getting out of sync when .splice() removes items from the array. The backwards loop also means ds ends up referencing the first element in srcDS that matches, which is what I assume you intend since your original code had trgDS.push(ds[0]).
If you happen to know that the array will only ever contain exactly one match then of course it doesn't matter if you go forwards or backwards, and you can add a break inside the if since there's no point continuing the loop once you have a match.
(Also I think you had a typo, you were testing === uid instead of === id.)

Copy over every match before deleting it using methods which modify Arrays, e.g. splice.
function moveDatum(srcDS, trgDS, id) { // you pass an `id`, not `uid`?
var i;
for (i = 0; i < srcDS.length; ++i) {
if (srcDS[i].uid === uid) {
trgDS.push(srcDS[i]);
srcDS.splice(i, 1);
// optionally break here for just the first
i--; // remember; decrement `i` because we need to re-check the same
// index now that the length has changed
}
}
return this;
}

Related

What is wrong with my topology sort

var input = ["KittenService: ", "Leetmeme: Cyberportal", "Cyberportal: Ice", "CamelCaser: KittenService", "Fraudstream: Leetmeme", "Ice: "];
var output = [];
function valid(input) {
for (var i = 0; i < input.length; i++) {
var array = input[i].trim().split(':');
var packageName = array[0].trim();
var dependencyName = array[1].trim();
if (array.length > 1 && dependencyName === '') {
if (output.indexOf(packageName) === -1) {
output.push(packageName);
} else {
return;
}
} else if (array.length > 1 && dependencyName !== '') {
if (output.indexOf(dependencyName) === -1) {
output.push(dependencyName);
if (output.indexOf(dependencyName) > -1) {
if (output.indexOf(packageName) > -1) {
continue;
} else {
output.push(packageName);
}
}
} else if (output.indexOf(dependencyName) > -1) {
output.push(packageName);
}
}
}
return output.join(', ');
}
valid(input);
console.log(output);
I am trying to figure out a way to get the following output:
"KittenService, Ice, Cyberportal, Leetmeme, CamelCaser, Fraudstream"
Right now it logs:
'KittenService, Cyberportal, Leetmeme, Ice, CamelCaser, Fraudstream'
I heard topology sort can fix this, but I am not sure how to implement tsort. How can I do this, or there is other method I can use to fix this? I want to do this without additional library.
Some issues:
The second push in your code is not verifying whether the item that is pushed has some dependencies. For example, the dependency Cyberportal is pushed, but you did not check whether there is maybe a pair where Cyberportal itself has a dependency: which there is... In general, this might then also repeat for the dependency you may find: that also can have another dependency. So you need an iterative or recursive solution here.
The return after the first else is wrong. In some cases this will make your output incomplete. Try your code with the same input, but then "CamelCaser: KittenService" moved to the start of the input array. You'll only get two elements in your output because of this bug.
It is useless to check just after that push whether the pushed element is in the array. You don't need that first if (output.indexOf(dependencyName) > -1): it will always be true.
The other occurrence of if (output.indexOf(dependencyName) > -1) is also useless, as it also will always be true.
It is useless to check for array.length > 1 when you have just applied a method (trim()) to the second element.
You should not let a function put its result in a global variable. Instead create output as a variable local to the function, and return that variable (not a .join() result).
Even with corrections, the algorithm does not have a linear time complexity, since you have indexOf (which is O(n)) within a loop that is also O(n), resulting in a O(n²) time complexity. Yet this can be done in linear time.
Solution
I assume that a certain value is only dependent on at most one other element -- not counting indirect dependencies via that single dependency --, as your notation (with the colon) seems to imply that.
My suggestion is to make use of hash tables, using Set and Map, which provide constant access to their elements. Secondly, with recursion you can make sure to output an element's dependencies first before outputting the element itself.
Here is the ES6 code:
function valid(input) {
let result = new Set(),
pairs = new Map(input.map(s => s.split(': '))),
loop = (_, key) => { // Recursive function to add key with dependencies
if (pairs.get(key)) { // Has dependency
loop(_, pairs.get(key)); // Recurse to add dependencies first
pairs.set(key, null); // Clear dependency
}
// After dependencies were added, now add this key
result.add(key); // Duplicates are ignored
};
pairs.forEach(loop); // Call loop for every element
return [...result]; // Convert Set to Array: order is kept
}
// Sample run:
var input = ["KittenService: ", "Leetmeme: Cyberportal", "Cyberportal: Ice",
"CamelCaser: KittenService", "Fraudstream: Leetmeme", "Ice: "];
var result = valid(input);
console.log(result);
If you do not have ES6 support, you can use plain objects instead of Set and Map. And use normal functions instead of arrow notation.

Javascript delete loop

I have a school assignment and I have an issue with deleting something from an array of indexes.
This is what I currently have.
Function that returns array length:
function DolzinaPolja(polje){
return polje.length-1;
}
new constructor
function oseba(ime, priimek, stranka) {
this.ime=ime;
this.priimek=priimek;
this.stranka=stranka;
}
function that creates an object and pushes it into an array
function UstvariObjekt(ime,priimek, stranka) {
if (ime.length == 0 || priimek.length == 0 || stranka.length == 0) {
throw "Parametri niso popolni!";
}
else {
var novaoseba=new oseba(ime, priimek, stranka);
polje.push(novaoseba);
console.log(novaoseba.ime, novaoseba.priimek, novaoseba.stranka);
}
}
function that deletes an object from the array
function OdstraniIzPolja(x) {
if(x > polje.length - 1 || x == polje.length) {
throw"Polje ni tako veliko!";
}
for(var i=0; i<=polje.length-1;i++) {
if(x==polje[i]) {
polje.splice(x,1);
return true;
}
return false;
}
}
I am having an issue with deleting an object from the array.
Here are my tests.
var polje = [];
var x=0;
UstvariObjekt("Rene","Vucko","Stranka");
UstvariObjekt("R","V","S");
UstvariObjekt("X","Y","Z");
OdstraniIzPolja(x);
console.log(polje[0]);
console.log(polje[1]);
console.log(polje[2]);
console.log(DolzinaPolja(polje));
SO my array should be the length of 2. Since I start with 0,1,2. What I don't understand is why doesn't my function that deletes an object from the array delete the object? I've played around a little bit but often it just deletes the wrong object.
Also is the if clause for x if the length is smaller than the array length written ok?
---UPDATE----
IF I write
polje.slice(2,1) without the function just in the program, it deletes the right one. So obviously something is wrong with the loop.
This is my updated code.
function DolzinaPolja(polje){
return polje.length-=1;
}
function OdstraniIzPolja(x)
{
if(x>polje.length-1 || x==polje.length)
{
throw"Polje ni tako veliko!";
}
for(var i=polje.length-1;i>=0;i--)
{
if(x==polje[i]){
polje.splice(i,1);
return true;
}
return false;
}
}
--EDIT--
here's the code https://jsfiddle.net/2y07wtkL/
It's because you splice the object with wrong index.
If you splice using
polje.splice(x,1);
you remove the element at x index. That means when you remove first element from array, the second element from array becomes first, so the next iteration won't delete the second element you want to be deleted, but the second element from array will be deleted instead (it doesn't have to be the element you are currently iterating on).
try
function DolzinaPolja(polje){
return polje.length -= 1;
}
as in your solution you just subtract the lenght, but you dont asign it
Whenever you are deleting from a collection with indices you should do it from the end so that you are indices don't change after each delete. I think that's what is happening. Change the direction like this
for(var i=polje.length-1;i>=0;i--)
{
........
}

How do I add elements to a dynamic array and exclude exsisting elements

function addNumifnotThere(numer){
var numCent = [];
numCent.forEach(function(){
if(numer in numCent)
console.log("you logged that");
else
numCent.push(numer);
});
return numCent;
}
This is my current code, what its attempting to do is read an array and if there is already an element exits the loop and says "you already logged that", obviously if it cannot find a similar element then it pushes it to the array.
I want this to work dynamically so we cannot know the size of the array beforehand, so the first element passed as an argument should be put into the array, (addNum(1) should have the array print out [1], calling addNum(1) again should print "you already logged that")
However there are two problems with this
1) Trying to push to a new array without any entries means everything is undefined and therefore trying to traverse the array just causes the program to print [].
2) Adding some random elements to the array just to make it work, in this case numCent=[1,2,3] has other issues, mainly that adding a number above 3 causes the code to print incorrect information. In this case addNum(5) should print [1,2,3,5] but instead prints [1,2,3,5,5,5]
I know this has to be a simple mistake but I've been dragging myself too long to not ask for help.
EDIT: Thanks to the many outstanding answers here I have now leanred about the indexOf method, thank you guys so much.
For every non-match you are pushing the number. Use something like this
var numCent = [];
function addNumifnotThere(numer)
{
var index = numCent.indexOf(number);
if(index >=0)
{
console.log("you logged that");
}
else
{
numCent.push(number);
}
return numCent;
}
Use Array.prototype.indexOf
var numCent = [];
function addNum(numer){
if (numCent.indexOf(numer) > -1)
{
console.log("Number already in array");
}
else
{
numCent.push(numer);
}
}
//DEMO CODE, not part of solution
document.querySelector("button").addEventListener("click", function(){
if (document.querySelector("input").value.length > 0)
{
addNum(document.querySelector("input").value);
document.querySelector("div").innerHTML = numCent.join(", ");
}
}, false);
Output
<div id="output"></div>
<input />
<button>Add number</button>
indexOf tests if an element is inside the array and returns its index. If not found it will return -1. You can test for this. You can try it for your self in this snippet. It will only allow you to add a number (or any string, in this example) once.
I also was confused by the newCent array declaration inside the function. I think, based upon the content of your question, you meant this.
If you want the array held in the instance, you can do it like this.
function AddIf(arr){
if( arr || !this.arr ) {
this.arr = arr || [];
}
return function(number) {
if( this.arr.indexOf(number) >= 0 ) {
console.log("Already Present!");
} else {
this.arr.push(number);
}
return this.arr;
}.bind(this);
}
// Usage would be like this:
// var addIf = new AddIf([1, 2, 3]);
// addIf(10); // returns [1, 2, 3, 10]
// addIf(10); // logs "Already Present!", doesn't add 10 to array
This basically returns a function, with this bound to the function being called. If you pass in an initial array, it will use that array to compare to when adding it to the array.
You can catch the return function and call it as you would want to. If you don't call new when invoking however, it will share the same array instance (and have a funky way of being called, AddIf()(10)).
I used fn.bind() to ensure the function gets called in the correct context every time, if you were wondering why I called it like that.
Do do this cleanly, I'd consider prototyping the global Array object and adding a method to push values but only if they're unique to the array. Something like this:
Array.prototype.pushUnique = function (item) {
if (this.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
this.push(item);
}
}
If you're not comfortable prototypeing global types like Array, you can build the same thing in a procedural pattern:
function arrayPushUnique (arr, item) {
if (arr.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
arr.push(item);
}
}
Then to use it, simply create a new empty array and start pushing things to it.
var numCent = [];
// The Array.prototype method
numCent.pushUnique(number);
// The procedural method
arrayPushUnique(numCent, number);

How can I remove an object from an array if I know the value of one field of the object?

I tried to find out some good examples but SO seems to have mainly examples from 4-5 years ago and I would like to use a solution that would work using modern browser capabilities.
Ihave an array of test objects:
var tests;
Each test object contains a testId.
How can I remove test object with testId = 25 from the array tests. I was thinking of a for loop but is there a cleaner way to do this?
The best answer depends on whether you know in advance whether there's at most one match, or potentially more than one (and in the latter case whether you want to remove all of them or just the first)
Removing all matches
The "simplest" way is to use filter, although strictly that produces a new array without the matching element:
tests = tests.filter(function(e) {
return e.testId !== 25;
});
This is OK, unless other code is holding a reference to the original array.
Modifying the array safely "in-place" still appears to require a combination of a for loop with .splice:
for (var i = 0; i < tests.length; ) { // nb: deliberate .length test
if (tests[i].testId === 25) {
tests.splice(i, 1);
} else {
++i;
}
}
The "safely" caveat is because the functional methods of iterating through an entire array will get confused if the current element in the array is removed. That is not a concern in the "first match" methods shown below.
Removing first (or only) match
The plain for method is still pretty simple (and probably most efficient, too!)
for (var i = 0, n = tests.length; i < n; ++i) {
if (tests[i].testId === 25) {
tests.splice(i, 1);
break;
}
}
The .some method per Johan's answer can iterate through an array and then exit on first match (although some may object on philosophical grounds to a boolean predicate function also mutating the array):
var didRemove = tests.some(function(e, i, a) {
if (e.testId === 25) {
a.splice(i, 1);
return true; // causes the loop to exit
}
});
In ES6-draft there's .findIndex, which is a generalisation of .indexOf:
var index = tests.findIndex(function(e) {
return e.testId === 25;
});
if (index >= 0) {
tests.splice(index, 1);
}
One way is to loop through all objects and splice a matching object out of the array.
Instead of forEach I use some (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some). Because with the some method you can "break" the loop by returning true so that you don't need to go through all objects if a match has been found.
tests.some(function(testObject, index) {
if (testObject.testId === 25) {
tests.splice(index, 1);
return true;
}
});
Or wrap it in a function
var removeObjectById = function(id) {
tests.some(function(testObject, index) {
if (testObject.testId === id) {
tests.splice(index, 1);
return true;
}
});
}
removeObjectById(25)

Create an array with tree elements in Javascript

I need to create an array from tree elements in Javascript and being a newbie I don't know how to achieve this.
pseudo-code :
function make_array_of_tree_node(tree_node)
{
for (var i = 0; i < tree_node.childCount; i ++) {
var node = tree_node_node.getChild(i);
if (node.type ==0) {
// Here I'd like to put a link (node.title) in an array as an element
} else if (node.type ==6) {
// Here the element is a folder so a I need to browse it
make_array_of_tree_node(node)
}
}
}
// Some code
make_array_of_tree_node(rootNode);
// Here I'd like to have access to the array containing all the elements node.title
You can declare an array like this:
var nodes = [];
Then you can add things to it with:
nodes.push(something);
That adds to the end of the array; in that sense it's kind-of like a list. You can access elements by numeric indexes, starting with zero. The length of the array is maintained for you:
var len = nodes.length;
What you'll probably want to do is make the array another parameter of your function.
edit — To illustrate the pattern, if you've got a recursive function:
function recursive(data, array) {
if ( timeToStop ) {
array.push( data.whatever );
}
else {
recursive(data.subData, array);
}
}
Then you can use a second function to be the real API that other code will use:
function actual(data) {
var array = [];
recursive(data, array); // fills up the array
return array;
}
In JavaScript, furthermore, it's common to place the "recursive" function inside the "actual" function, which makes the recursive part private and keeps the global namespace cleaner.

Categories