How to duplicate elements in a js array without creating "dependent elements"? - javascript

I am trying to modify a single element from an array whose elements were previously duplicated n times. To perform the array duplication I just relied on a custom function duplicateElements(array, times)from this post (see #Bamieh answer). As shown in the exemple below, the problem is I can't modify a single element from the array without modifying other elements:
function duplicateElements(array, times) {
return array.reduce((res, current) => {
return res.concat(Array(times).fill(current));
}, []);
}
var myvar = duplicateElements([{ a: 1 }, { a: 2 }], 2);
myvar[0].a = 3;
console.log(myvar);
// (4) [{…}, {…}, {…}, {…}]
// 0: {a: 3}
// 1: {a: 3}
// 2: {a: 2}
// 3: {a: 2}
// length: 4
As you can see myvar[1].a was also modified although this wasn't intended. How can I avoid this issue?

The problem is that you're passing the reference to the original object in Array(times).fill(current) .
In this case the two copies of the first {a:2} are the same copy of the original (They reference to the same space in memory) so if you change one, the two of them will change as they reference the same object in memory.
You have to make a deepcloning function or maybe spread the object inside a new one. You can change your original function to work with objects and primitives like this:
function duplicateElements(elementsArray, times) {
//Make a new placeholder array
var newArray = [];
//Loop the array of elements you want to duplicate
for (let index = 0; index < elementsArray.length; index++) {
//Current element of the array of element
var currentElement = elementsArray[index];
//Current type of the element to check if it is an object or not
var currentType = typeof currentElement
//Loop over the times you want to copy the element
for (let index = 0; index < times; index++) {
//If the new element is not an object
if (currentType !== "object" && currentType){
//append the element
newArray.push(currentElement)
//if it is an Object
} else if (currentType === "object" && currentType){
//append an spreaded new Object https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
newArray.push({...currentElement})
}
}
}
return newArray;
}
This is not the optimal way to do this, but I think that maybe you're new to javascript and is better to learn the old way of looping before using more Array functionalities (as the answer from Jonas Wilms, that is also a good answer).
I would recommend javascript.info and eloquent javascript to learn more about the language

The main reason for this as specified in the Array.fill documentation is that when dealing with objects it will copy by reference:
When fill gets passed an object, it will copy the reference and fill
the array with references to that object.
With lodash (and _.cloneDeep) that is one line like this:
let dubFn = (arr, t=1) =>
_.concat(arr, _.flatMap(_.times(t, 0), x => _.cloneDeep(arr)))
let r1 = dubFn([{a:1},{b:3}]) // no parameter would mean just 1 dub
let r2 = dubFn([{a:1},{b:3},5,[1]], 2) // 2 dublicates
r1[0].a = 3
r2[0].a = 3
console.log(r1)
console.log(r2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>
Note that this now works with arrays/objects and primitives.
The idea is to use _.concat to return a new concatenated version of the input array with a combination of few functions which on the end return an array of cloned objects. We use _.times to return an array of in this case t elements and then for each of those elements we replace with a deep clone of the array. _.flatMap is needed to flatten the end result since we end up having array of arrays after the _.times call.
With ES6 you can do something like this:
let dubElements = (arr, t) =>
[...arr, ...new Array(t).fill().flatMap(x => arr.map(y => ({...y})))]
let r1 = dubElements([{a:1},{b:3}])
let r2 = dubElements([{a:1},{b:3}],2)
r1[0].a = 3
r2[0].a = 3
console.log(r1)
console.log(r2)
Where we concat arrays via the spread operator and we use new Array(t) to create the new duplicates array and make sure we fill it with undefined in this case after which we flatMap the results (which we map through the clone via the spread operator again.
Note that this works for your use case specifically. If you want to make it more generic you have to expand more in the last map function etc.
If you want to preserve the order of the elements you can do something like this:
let dubElements = (arr, t=1) => {
let _result = []
arr.forEach(x => {
for(let i=0; i<t+1; i++) {
_result.push({...x})
}
})
return _result
}
let result = dubElements([{a:1},{b:3}],2)
result[0].a = 3
console.log(result)

Replace
Array(times).fill(current)
which will add one reference to current multiple times to the array with:
Array.from({ length: times }, () => ({...current }))
which will shallow clone current. Note that the code will then only work with objects though, not with primitives.
I'd do:
const duplicateElements = (array, length) =>
array.flatMap(current => Array.from({ length }, () => ({ ...current }));

Related

Possible to push empty slot to an array?

I'm building my own map method to be as close as the native map method.
Since the native map pushes(i think) the changed values into a new array, it still keeps the empty slots. I wasn't able to find a solution to push an empty slot into an array, like this example below.
[1, 2, 3].push(some code) // [1, 2, 3, empty]
I tried pushing an array with one empty item prefixed with a spread operator arr.push(...(new Array(1))) or arr.push(...[,]) but that just pushes undefined.
I solved my problem by not using push and instead assigning values to the array index that way skipped indices will be set to empty.
But I'm writing this post to see if anyone knows that if it's possible to use the push method to push an empty slot to an array.
No, it's not possible, not with the push method. empty can only exist if the array has a certain length, but a whole number property of the array does not exist at some index. This is called a sparse array, and cannot be created with push (or other array methods, if they're called on and with non-sparse arrays).
The only way to do so would be to assign to an index for which a lower index doesn't exist yet.
Look at the results for the below two snippets in your browser console, not the snippet console:
const arr = [];
arr[1] = 'a';
console.log(arr);
Or to set the .length of the array above the last index that the array has:
const arr = [];
arr.length = 1;
console.log(arr);
But the two approaches above are very weird to do and probably have no good reason to be used. Better to avoid sparse arrays entirely.
Keep in mind that an empty slot is different from undefined, which is perfectly possible to have as an array value:
const arr = [];
arr.push(undefined);
console.log(arr);
You can create an empty slot in an array by incrementing the array length:
var a = []
a.push(1)
a.length++
a.push(3)
console.log(a)
console.log(1 in a) // anything at index 1?
Alternatively, you can push something and then delete it:
var a = []
a.push(1)
a.push(2)
a.push(3)
delete a[1]
console.log(a)
console.log(1 in a) // anything at index 1?
There is no need to actually push to a new array in your implementation. You can simply do new Array(this.length) where this.length is the array you are mapping through length.
For example consider this map implementation:
if (!Array.prototype.mapIt) {
Object.defineProperty(Array.prototype, "mapIt", {
value: function(fn) {
if (this === null) {
throw new TypeError('Array.prototype.mapIt called on null or undefined');
}
if (typeof fn !== 'function') {
throw new TypeError('predicate must be a function');
}
let _array = this.filter(x => x != null) // remove empty values
let result = new Array(_array.length) // the new array we will return
for (var i = 0; i < _array.length; i++) {
result[i] = fn.call(arguments[1], _array[i], i, _array) // call the predicate
}
return result;
}
});
}
let arr = [1, 2, , , 3] // the test array
let result = arr.mapIt((c, i, a) =>
console.log(`current: ${c}`, `index: ${i}`, `array: ${a}`) || c + 2)
console.log('result: ', result)
console.log('original array: ', arr)
Hope this helps you with an gives you an idea about a possible map implementation.

JavaScript - Filter array with mutation

I want to filter a array by keeping the same array without creating a new one.
with Array.filter() :
getFiltersConfig() {
return this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
}
what is the best way to get the same result by filtering by value without returning a new array ?
For completeness, I thought it might make sense to show a mutated array variant.
Below is a snippet with a simple function mutationFilter, this will filter the array directly, notice in this function the loop goes in reverse, this is a technique for deleting items with a mutated array.
Also a couple of tests to show how Array.filter creates a new array, and mutationFilter does not.
Although in most cases creating a new array with Array.filter is normally what you want. One advantage of using a mutated array, is that you can pass the array by reference, without you would need to wrap the array inside another object. Another advantage of course is memory, if your array was huge, inline filtering would take less memory.
let arr = ['a','b','a'];
let ref = arr; //keep reference of original arr
function mutationFilter(arr, cb) {
for (let l = arr.length - 1; l >= 0; l -= 1) {
if (!cb(arr[l])) arr.splice(l, 1);
}
}
const cond = x => x !== 'a';
const filtered = arr.filter(cond);
mutationFilter(arr, cond);
console.log(`ref === array -> ${ref === arr}`);
console.log(arr);
console.log(`ref === filtered -> ${ref === filtered}`);
console.log(filtered);
I want to filter a array by keeping the same array without creating a new one.
what is the best way to get the same result by filtering by value without returning a new array ?
I have an answer for the second criterion, but violates the first. I suspect that you may want to "not create a new one" specifically because you only want to preserve the reference to the array, not because you don't want to create a new array, necessarily (e.g. for memory concerns).
What you could do is create a temp array of what you want
var temp = this.config.filter((topLevelConfig) => topLevelConfig.name !== 'origin')
Then set the length of the original array to 0 and push.apply() the values "in-place"
this.config.length = 0; //clears the array
this.config.push.apply(this.config, temp); //adds what you want to the array of the same reference
You could define you custom method like so:
if(!Array.prototype.filterThis){
Array.prototype.filterThis = function (callBack){
if(typeof callBack !== 'function')
throw new TypeError('Argument must of type <function>');
let t = [...this];
this.length = 0;
for(let e of t) if(callBack(e)) this.push(e);
return this;
}
}
let a = [1,2,3,4,5,5,1,5];
a.filterThis(x=>x!=5);
console.log(a);
Warning: Be very cautious in altering built in prototypes. I would even say unless your making a polyfill don't touch. The errors it can cause can be very subtle and very hard to debug.
Not sure why would you want to do mutation but if you really want to do it, maybe assign it back to itself?
let arr = ['a','b','a'];
arr = arr.filter(x => x !== 'a');
console.log(arr)

How to keep an array with objects immutable in javascript?

I want to make an array based on two arrays - "ideaList" and "endorsements" declared globally. As ideaList and endorsements are used in other parts of the program I need them to be immutable, and I thought that .map and .filter would keep this immutability.
function prepareIdeaArray(){
var preFilteredIdeas=ideaList
.filter(hasIdeaPassedControl)
.map(obj => {obj.count = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber)
.reduce((sum, x)=>sum+x.count,0);
obj.like = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber && x.who===activeUser)
.reduce((sum, x)=>sum+x.count,0)===0?false:true
obj.position = generatePosition(obj.status)
obj.description = obj.description.replace(/\n/g, '<br>')
return obj;});
preFilteredIdeas.sort(compareOn.bind(null,'count',false)).sort(compareOn.bind(null,'position',true))
return preFilteredIdeas;
}
However, when I console.log ideaList after this function has been executed, I remark that objects of the array all have the "count", "like", "position" properties with values, which proves that the array has been mutated.
I tried by using .map only, but same result.
Would you know how I could prevent ideaList to get mutated? Also I would like to avoid to use const, as I declare ideaList globally first, and then assign to it some data in another function.
You're not mutating the array itself but rather the objects that the array contains references to. .map() creates a copy of the array but the references contained in it points to the exact same objects as the original, which you've mutated by adding properties directly to them.
You need to make copies of these objects too and add the properties to these copies. A neat way to do this is to use object spread in .map() callback:
.map(({ ...obj }) => {
obj.count = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber)
...
If your environment doesn't support object spread syntax, clone the object with Object.assign():
.map(originalObj => {
const obj = Object.assign({}, originalObj);
obj.count = endorsements
.filter(x=>x.ideaNumber===obj.ideaNumber)
...
In JS, the objects are referenced. When created, in other words, you get the object variable to point to a memory location which intends to be holding a meaningful value.
var o = {foo: 'bar'}
The variable o is now point to a memory which has {foo: bar}.
var p = o;
Now the variable p too is pointing to the same memory location. So, if you change o, it will change p too.
This is what happens inside your function. Even though you use Array methods which wouldn't mutate it's values, the array elements themselves are objects which are being modified inside the functions. It creates a new array - but the elements are pointing to the same old memory locations of the objects.
var a = [{foo: 1}]; //Let's create an array
//Now create another array out of it
var b = a.map(o => {
o.foo = 2;
return o;
})
console.log(a); //{foo: 2}
One way out is to create a new object for your new array during the operation. This can be done with Object.assign or latest spread operator.
a = [{foo: 1}];
b = a.map(o => {
var p = {...o}; //Create a new object
p.foo = 2;
return p;
})
console.log(a); // {foo:1}
To help having immutability in mind you could think of your values as primitives.
1 === 2 // false
'hello' === 'world' // false
you could extend this way of thinking to non-primitives as well
[1, 2, 3] === [1, 2, 3] // false
{ username: 'hitmands' } === { username: 'hitmands' } // false
to better understand it, please have a look at MDN - Equality Comparisons and Sameness
how to force immutability?
By always returning a new instance of the given object!
Let's say we have to set the property status of a todo. In the old way we would just do:
todo.status = 'new status';
but, we could force immutability by simply copying the given object and returning a new one.
const todo = { id: 'foo', status: 'pending' };
const newTodo = Object.assign({}, todo, { status: 'completed' });
todo === newTodo // false;
todo.status // 'pending'
newTodo.status // 'completed'
coming back to your example, instead of doing obj.count = ..., we would just do:
Object.assign({}, obj, { count: ... })
// or
({ ...obj, count: /* something */ })
there are libraries that help you with the immutable pattern:
Immer
ImmutableJS
You use the freeze method supplying the object you want to make immutable.
const person = { name: "Bob", age: 26 }
Object.freeze(person)
You could use the new ES6 built-in immutability mechanisms, or you could just wrap a nice getter around your objects similar to this
var myProvider = {}
function (context)
{
function initializeMyObject()
{
return 5;
}
var myImmutableObject = initializeMyObject();
context.getImmutableObject = function()
{
// make an in-depth copy of your object.
var x = myImmutableObject
return x;
}
}(myProvider);
var x = myProvider.getImmutableObject();
This will keep your object enclosed outside of global scope, but the getter will be accessible in your global scope.
You can read more on this coding pattern here
One easy way to make "copies" of mutable objects is to stringify them into another object and then parse them back into a new array. This works for me.
function returnCopy(arrayThing) {
let str = JSON.stringify(arrayThing);
let arr = JSON.parse(str);
return arr;
}
Actually, you can use spread opreator to make the original array stay unchanged, below y is the immutable array example:
const y = [1,2,3,4,5];
function arrayRotation(arr, r, v) {
for(let i = 0; i < r; i++) {
arr = [...y]; // make array y as immutable array [1,2,3,4,5]
arr.splice(i, 0, v);
arr.pop();
console.log(`Rotation ${i+1}`, arr);
}
}
arrayRotation(y, 3, 5)
If you don't use the spread operator, the y array will get changed when loop is running time by time.
Here is the mutable array result:
const y = [1,2,3,4,5];
function arrayRotation(arr, r, v) {
for(let i = 0; i < r; i++) {
arr = y; // this is mutable, because arr and y has same memory address
arr.splice(i, 0, v);
arr.pop();
console.log(`Rotation ${i+1}`, arr);
}
}
arrayRotation(y, 3, 5)
You assign these properties in your map function, you need to change this. (Just declare an empty object instead of using your current obj)

Modify an array without mutation

I am trying to solve a problem which states to remove(delete) the smallest number in an array without the order of the elements to the left of the smallest element getting changed . My code is -:
function removeSmallest(numbers){
var x = Math.min.apply(null,numbers);
var y = numbers.indexOf(x);
numbers.splice(y,1);
return numbers;
}
It is strictly given in the instructions not to mutate the original array/list. But I am getting an error stating that you have mutated original array/list .
How do I remove the error?
Listen Do not use SPLICE here. There is great known mistake rookies and expert do when they use splice and slice interchangeably without keeping the effects in mind.
SPLICE will mutate original array while SLICE will shallow copy the original array and return the portion of array upon given conditions.
Here Slice will create a new array
const slicedArray = numbers.slice()
const result = slicedArray.splice(y,1);
and You get the result without mutating original array.
first create a copy of the array using slice, then splice that
function removeSmallest(numbers){
var x = Math.min.apply(null,numbers);
var y = numbers.indexOf(x);
return numbers.slice().splice(y,1);
}
You can create a shallow copy of the array to avoid mutation.
function removeSmallest(numbers){
const newNumbers = [...numbers];
var x = Math.min.apply(null,newNumbers);
var y = newNumbers.indexOf(x);
newNumbers.splice(y,1);
return newNumbers;
}
array.slice() and [... array] will make a shallow copy of your array object.
"shallow" the word says itself.
in my opinion, for copying your array object the solution is:
var array_copy = copy(array);
// copy function
function copy(object) {
var output, value, key;
output = Array.isArray(object) ? [] : {};
for (key in object) {
value = object[key];
output[key] = (typeof value === "object") ? copy(value) : value;
}
return output;
}
Update
Alternative solution is:-
var arr_copy = JSON.parse(JSON.stringify(arr));
I'm not sure what the exact context of the problem is, but the goal might be to learn to write pure transformations of data, rather than to learn how to copy arrays. If this is the case, using splice after making a throwaway copy of the array might not cut it.
An approach that mutates neither the original array nor a copy of it might look like this: determine the index of the minimum element of an array, then return the concatenation of the two sublists to the right and left of that point:
const minIndex = arr =>
arr.reduce(
(p, c, i) => (p === undefined ? i : c < arr[p] ? i : p),
undefined
);
const removeMin = arr => {
const i = minIndex(arr);
return minIndex === undefined
? arr
: [...arr.slice(0, i), ...arr.slice(i + 1)];
};
console.log(removeMin([1, 5, 6, 0, 11]));
Let's focus on how to avoid mutating. (I hope when you say "remove an error" you don't mean "suppress the error message" or something like that)
There are many different methods on Array.prototype and most don't mutate the array but return a new Array as a result. say .map, .slice, .filter, .reduce
Telling the truth just a few mutate (like .splice)
So depending on what your additional requirements are you may find, say .filter useful
let newArray = oldArray.filter(el => el !== minimalElementValue);
or .map
let newArray = oldArray.map(el => el === minimalElementValue? undefined: el);
For sure, they are not equal but both don't mutate the original variable

Why does changing the value of a property change the value for all similarly named properties in an array?

Why does changing the value of a property change the value for all similar properties in an array and how do I get it to work right without using the this keyword for 'name'?
let Object = {
'name' : 'Test Object'
}
let Array = []
Array.push(Object)
Array.push(Object)
Array.push(Object)
Array[0]['name'] = 'Changed'
console.log(Array) // expect only the first name to change, but all 3 change...
You aren't changing "similarly named" objects, you are changing the same object.
For non-primitives (basically everything that isn't a string, number, or boolean), they are passed by reference. That means when you add them to something like an array or pass them to a function, you are basically passing their address. If you pass it 3 times, they all point to the same address; there is still only one copy. Change one, and you change them all.
const a = { b: 1 };
const arr = [a, a, a];
// All the same object
console.log(arr[0] === arr[1], arr[1] === arr[2], a === arr[0]);
a.b = 5;
// All 3 changed, because it is the same thing
console.log(arr.map(a => a.b));
function someFunc(obj) { obj.b = 10 };
someFunc(a);
// changed from inside function, same object
console.log(a.b);
If you want to create a handful of objects that all start the same, but then are able to change afterwards, you need to create the objects in a loop:
const template = { name: 'a' };
const arr = [];
for (let i = 0; i < 3; i++) {
arr.push({ ...template }); // or: arr.push(Object.create({}, template))
}
arr[1].name = 'b';
arr[2].name = 'c';
console.log(arr);
Or, even more concisely:
// Creates a new Array with 3 records and then puts a copy of the template in each.
const template = { name: 'a' };
const arr = new Array(3).fill(1).map(() => ({ ...template }));
// or (without needing template variable):
// const arr = new Array(3).fill(1).map(() => ({ name: 'a' }))
arr[1].name = 'b';
arr[2].name = 'c';
console.log(arr);
When you invoke Array.push(Object), you're pushing a reference to the same object into the array 3 times. Push does not make a copy of Object - only 1 Object exists.
If you want 3 identical objects in an array, try something like this:
let vArray = []
for(i = 0; i <= 2; i++) {
//We're going to loop this 3 times, creating 3 different
//objects and pushing each of them into the array
let vObject = {
'name' : 'Test Object'
}
vArray.push(vObject)
}
vArray[0]['name'] = 'Changed'
console.log(vArray) // Only the first one will have been changed.
The answer I was looking for was to change the syntax in my answer to Array.push({...Object}). This creates a 'new' object to be pushed, with only 5 additional characters...
I didn't know 'object spread syntax' essentially did this.

Categories

Resources