Related
I'm wondering if there is a way to clear or empty an array of objects, let's say for example i have this array of objects, which i borrow from firebase firestore, i do this with a snapshot, here is the code:
let siteButtonsData = firebase.firestore()
.collection('app_settings')
.doc('dashboard')
.collection('main_panel')
.onSnapshot(doc => {
doc.forEach(doc => {
this.arrObj.push(doc.data())
});
})
Then my array of objects is populated correctly, and it's shown like this:
data() {
return {
arrObj: [
{
action: 'one',
id: 1,
text: 'hello1'
},
{
action: 'two',
id: 2,
text: 'hello2'
},
{
action: 'three',
id: 3,
text: 'hello3'
},
]
}
}
With v-for i iterate over this.buttons to create some dynamic content in my app, but when something changes on my firestore database, and the snapshot updates it, i got duplicates, i was hoping to clear the array of objects before updating from firestore, so i would have no duplicates, but it's not working, so far i have tried:
this.arrObj = [{}]
this.arrObj = [] //This option leaves me with no way to push the array of objects
this.arrObj.length = 0
No matter which of this methods i use, i can't update correctly the array of objects, the first time it does the job well gathering the data from firestore, but once i update the database i got duplicates.
thanks in advance for any help or hint
When working with arrays you always need to make sure that Vue's reactivity system is catching the changes and updates the view. Vue is automatically observing some array mutation methods to make sure to update, them being push(), pop(), shift(), unshift(), splice(), sort() and reverse().
See https://v2.vuejs.org/v2/guide/list.html#Mutation-Methods
As for your example, what should work is then:
let siteButtonsData = firebase.firestore()
.collection('app_settings')
.doc('dashboard')
.collection('main_panel')
.onSnapshot(doc => {
this.arrObj.splice(0, this.arrObj.length);
// I would think this works as well:
// this.arrObj = [];
doc.forEach(doc => {
this.arrObj.push(doc.data())
});
})
What if you want to empty an entire array and just dump all of it's elements?
There are a couple of techniques you can use to create an empty or new array.
The simplest and fastest technique is to set an array variable to an empty array:
var ar = [1, 2, 3, 4, 5, 6];//do stuffar = [];//a new, empty array!
The problem this can create is when you have references to the variable. The references to this variable will not change, they will still hold the original array's values. This of course can create a bug🐛.
This is an over simplified example of this scenario:
var arr1 = [1, 2, 3, 4, 5, 6];var arr2 = arr1; // Reference arr1 by another variable arr1 = [];console.log(arr2); // Output [1, 2, 3, 4, 5, 6]
A simple trick to clear an array is to set its length property to 0.
var ar = [1, 2, 3, 4, 5, 6];console.log(ar); // Output [1, 2, 3, 4, 5, 6]ar.length = 0;console.log(ar); // Output []
Another, sort of unnatural technique, is to use the splice method, passing the array length as the 2nd parameter. This will return a copy of the original elements, which may be handy for your scenario.
var ar = [1, 2, 3, 4, 5, 6];console.log(ar); // Output [1, 2, 3, 4, 5, 6]ar.splice(0, ar.length);console.log(ar); // Output []
The last two techniques don't create a new array, but change the array's elements. This means references should also update.
There is another way, using a while loop. It feels a little odd to me, but at the same time looks fancy, so it may impress some friends!
var ar = [1, 2, 3, 4, 5, 6];console.log(ar); // Output [1, 2, 3, 4, 5, 6] while (ar.length) { ar.pop(); }console.log(ar); // Output []
Not a way I would go about clearing a JavaScript array, but it works and it is readable. Some performance test have also shown this to be the fastest technique, so maybe it is better than I originally thought!
Let's suppose I wanted a sort function that returns a sorted copy of the inputted array. I naively tried this
function sort(arr) {
return arr.sort();
}
and I tested it with this, which shows that my sort method is mutating the array.
var a = [2,3,7,5,3,7,1,3,4];
sort(a);
alert(a); //alerts "1,2,3,3,3,4,5,7,7"
I also tried this approach
function sort(arr) {
return Array.prototype.sort(arr);
}
but it doesn't work at all.
Is there a straightforward way around this, preferably a way that doesn't require hand-rolling my own sorting algorithm or copying every element of the array into a new one?
You need to copy the array before you sort it. One way with es6:
const sorted = [...arr].sort();
The spread-syntax as array literal (copied from mdn):
var arr = [1, 2, 3];
var arr2 = [...arr]; // like arr.slice()
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator
Just copy the array. There are many ways to do that:
function sort(arr) {
return arr.concat().sort();
}
// Or:
return Array.prototype.slice.call(arr).sort(); // For array-like objects
Try the following
function sortCopy(arr) {
return arr.slice(0).sort();
}
The slice(0) expression creates a copy of the array starting at element 0.
You can use slice with no arguments to copy an array:
var foo,
bar;
foo = [3,1,2];
bar = foo.slice().sort();
You can also do this
d = [20, 30, 10]
e = Array.from(d)
e.sort()
This way d will not get mutated.
function sorted(arr) {
temp = Array.from(arr)
return temp.sort()
}
//Use it like this
x = [20, 10, 100]
console.log(sorted(x))
Update - Array.prototype.toSorted() proposal
The Array.prototype.toSorted(compareFn) -> Array is a new method which was proposed to be added to the Array.prototype and is currently in stage 3 (Soon to be available).
This method will keep the target Array untouched and returns a copy of it with the change performed instead.
Anyone who wants to do a deep copy (e.g. if your array contains objects) can use:
let arrCopy = JSON.parse(JSON.stringify(arr))
Then you can sort arrCopy without changing arr.
arrCopy.sort((obj1, obj2) => obj1.id > obj2.id)
Please note: this can be slow for very large arrays.
Try this to sort the numbers. This does not mutate the original array.
function sort(arr) {
return arr.slice(0).sort((a,b) => a-b);
}
There's a new tc39 proposal, which adds a toSorted method to Array that returns a copy of the array and doesn't modify the original.
For example:
const sequence = [3, 2, 1];
sequence.toSorted(); // => [1, 2, 3]
sequence; // => [3, 2, 1]
As it's currently in stage 3, it will likely be implemented in browser engines soon, but in the meantime a polyfill is available here or in core-js.
I think that my answer is a bit too late but if someone come across this issue again the solution may be useful.
I can propose yet another approach with a native function which returns a sorted array.
This code still mutates the original object but instead of native behaviour this implementation returns a sorted array.
// Remember that it is not recommended to extend build-in prototypes
// or even worse override native functions.
// You can create a seperate function if you like
// You can specify any name instead of "sorted" (Python-like)
// Check for existence of the method in prototype
if (typeof Array.prototype.sorted == "undefined") {
// If it does not exist you provide your own method
Array.prototype.sorted = function () {
Array.prototype.sort.apply(this, arguments);
return this;
};
}
This way of solving the problem was ideal in my situation.
You can also extend the existing Array functionality. This allows chaining different array functions together.
Array.prototype.sorted = function (compareFn) {
const shallowCopy = this.slice();
shallowCopy.sort(compareFn);
return shallowCopy;
}
[1, 2, 3, 4, 5, 6]
.filter(x => x % 2 == 0)
.sorted((l, r) => r - l)
.map(x => x * 2)
// -> [12, 8, 4]
Same in typescript:
// extensions.ts
Array.prototype.sorted = function (compareFn?: ((a: any, b: any) => number) | undefined) {
const shallowCopy = this.slice();
shallowCopy.sort(compareFn);
return shallowCopy;
}
declare global {
interface Array<T> {
sorted(compareFn?: (a: T, b: T) => number): Array<T>;
}
}
export {}
// index.ts
import 'extensions.ts';
[1, 2, 3, 4, 5, 6]
.filter(x => x % 2 == 0)
.sorted((l, r) => r - l)
.map(x => x * 2)
// -> [12, 8, 4]
I am trying to use the splice method to add the values from the first array into the second array in the index location provided by the third parameter. I was confident that my solution would work, even verified that my understanding of slice was correct. However, when I log this in console it returns as an empty array.
I feel like I am really close, but something is missing.
function frankenSplice(arr1, arr2, n) {
//copies made as to not disrupt referece.
let array1 = arr1.slice(0, arr1.length);
let array2 = arr2.slice(0, arr2.length);
let mutatedArray = array2.splice(n,0,...array1);
return mutatedArray;}
frankenSplice([1, 2, 3], [4, 5], 1) should return [4, 1, 2, 3, 5].
Also, is this a good usage of creating copies of the arrays in the function? I'm learning about referencing non-primitives and if I'm not mistaken creating the copies in the scope of the function protects the original reference from being modified. It's not pointless in this context is it?
The problem is that the return value for splice is not the array that got mutated, but the removed elements, in an array:
Return value: An array containing the deleted elements. If only one element is removed, an array of one element is returned. If no elements are removed, an empty array is returned.
You aren't removing any elements with splice, so the result is an empty array. Don't assign the result of splice to a variable, and return array2 instead:
function frankenSplice(arr1, arr2, n) {
//copies made as to not disrupt referece.
let array1 = arr1.slice(0, arr1.length);
let array2 = arr2.slice(0, arr2.length);
array2.splice(n, 0, ...array1);
return array2;
}
console.log(frankenSplice([1, 2, 3], [4, 5], 1))
Also note that you can use slice() without any arguments to create a shallow copy of an array, no need for (0, arr.length):
function frankenSplice(arr1, arr2, n) {
//copies made as to not disrupt referece.
let array1 = arr1.slice();
let array2 = arr2.slice();
array2.splice(n, 0, ...array1);
return array2;
}
console.log(frankenSplice([1, 2, 3], [4, 5], 1))
Another option is to immediately return an array into which you spread sliced sections of the original arrays, which might be clearer than using splice:
function frankenSplice(arr1, arr2, n) {
return [
...arr2.slice(0, n),
...arr1,
...arr2.slice(n)
];
}
console.log(frankenSplice([1, 2, 3], [4, 5], 1))
I'm wondering if there is a way by using a transducer for flattening a list and filter on unique values?
By chaining, it is very easy:
import {uniq, flattenDeep} from 'lodash';|
const arr = [1, 2, [2, 3], [1, [4, 5]]];
uniq(flattendDeep(arr)); // -> [1, 2, 3, 4, 5]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.min.js"></script>
But here we loop twice over the list (+ n by depth layer). Not ideal.
What I'm trying to achieve is to use a transducer for this case.
I've read Ramda documentation about it https://ramdajs.com/docs/#transduce, but I still can't find a way to write it correctly.
Currently, I use a reduce function with a recursive function inside it:
import {isArray} from 'lodash';
const arr = [1, 2, [2, 3], [1, [4, 5]]];
const flattenDeepUniq = (p, c) => {
if (isArray(c)) {
c.forEach(o => p = flattenDeepUniq(p, o));
}
else {
p = !p.includes(c) ? [...p, c] : p;
}
return p;
};
arr.reduce(flattenDeepUniq, []) // -> [1, 2, 3, 4, 5]
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.core.min.js"></script>
We have one loop over the elements (+ n loop with deep depth layers) which seems better and more optimized.
Is this even possible to use a transducer and an iterator in this case?
For more information about Ramda transduce function: https://gist.github.com/craigdallimore/8b5b9d9e445bfa1e383c569e458c3e26
Transducers don't make much sense here. Your data structure is recursive. The best code to deal with recursive structures usually requires recursive algorithms.
How transducers work
(Roman Liutikov wrote a nice introduction to transducers.)
Transducers are all about replacing multiple trips through the same data with a single one, combining the atomic operations of the steps into a single operation.
A transducer would be a good fit to turn this code:
xs.map(x => x * 7).map(x => x + 3).filter(isOdd(x)).take(5)
// ^ ^ ^ ^
// \ \ \ `------ Iteration 4
// \ \ `--------------------- Iteration 3
// \ `-------------------------------------- Iteration 2
// `----------------------------------------------------- Iteration 1
into something like this:
xs.reduce((r, x) => r.length >= 5 ? res : isOdd(x * 7 + 3) ? res.concat(x * 7 - 3) : res, [])
// ^
// `------------------------------------------------------- Just one iteration
In Ramda, because map, filter, and take are transducer-enabled, we can convert
const foo = pipe(
map(multiply(7)),
map(add(3)),
filter(isOdd),
take(3)
)
foo([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) //=> [17, 31, 45]
(which iterates four times through the data) into
const bar = compose(
map(multiply(7)),
map(add(3)),
filter(isOdd),
take(3)
)
into([], bar, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) //=> [17, 31, 45]
which only iterates it once. (Note the switch from pipe to compose. Tranducers compose in an order opposite that of plain functions.)
Note the key point of such transducers is that they all operate similarly. map converts a list to another list, as do filter and take. While you could have transducers that operate on different types, and map and filter might also work on such types polymorphically, they will only work together if you're combining functions which operate on the same type.
Flatten is a weak fit for transducers
Your structure is more complex. While we could certainly create a function that will crawl it in in some manner (preorder, postorder), and could thus probably start of a transducer pipeline with it, the logical way to deal with a recursive structure is with a recursive algorithm.
A simple way to flatten such a nested structure is something like this:
const flatten = xs => xs.reduce(
(a, x) => concat(a, isArray(x) ? flatten(x) : [x]),
[]
);
(For various technical reasons, Ramda's code is significantly more complex.)
This recursive version, though, is not well-suited to work with transducers, which essentially have to work step-by-step.
Uniq poorly suited for transducers
uniq, on the other hand, makes less sense with such transducers. The problem is that the container used by uniq, if you're going to get any benefit from transducers, has to be one which has quick inserts and quick lookups, a Set or an Object most likely. Let's say we use a Set. Then we have a problem, since our flatten operates on lists.
A different approach
Since we can't easily fold existing functions into one that does what you're looking for, we probably need to write a one-off.
The structure of the earlier solution makes it fairly easy to add the uniqueness constraint. Again, that was:
const flatten = xs => xs.reduce(
(a, x) => concat(a, isArray(x) ? flatten(x) : [x]),
[]
);
With a helper function for adding all elements to a Set:
const addAll = (set, xs) => xs.reduce((s, x) => s.add(x), set)
We can write a function that flattens, keeping only the unique values:
const flattenUniq = xs => xs.reduce(
(s, x) => addAll(s, isArray(x) ? flattenUniq(x) : [x]),
new Set()
)
Note that this has much the structure of the above, switching only to use a Set and therefore switching from concat to our addAll.
Of course you might want an array, at the end. We can do that just by wrapping this with a Set -> Array function, like this:
const flattenUniq = xs => Array.from(xs.reduce(
(s, x) => addAll(s, isArray(x) ? flattenUniq(x) : [x]),
new Set()
))
You also might consider keeping this result as a Set. If you really want a collection of unique values, a Set is the logical choice.
Such a function does not have the elegance of a points-free transduced function, but it works, and the exposed plumbing makes the relationships with the original data structure and with the plain flatten function much more clear.
I guess you can think of this entire long answer as just a long-winded way of pointing out what user633183 said in the comments: "neither flatten nor uniq are good use cases for transducers."
Uniq is now a transducer in Ramda so you can use it directly. And as for flatten you can traverse the tree up front to produce a bunch of flat values
const arr = [1, 2, [2, 3], [1, [4, 5]]];
const deepIterate = function*(list) {
for (const it of list) {
yield* Array.isArray(it) ? deepIterate(it) : [it];
}
}
R.into([], R.uniq(), deepIterate(arr)) // -> [1, 2, 3, 4, 5]
This lets you compose additional transducers
R.into([], R.compose(R.uniq(), R.filter(isOdd), R.take(5)), deepIterate(arr))
Put simply, I want to subtract one array from another.
The arrays are arrays of objects. I understand I can cycle through one array and on each item, comparing values in the other array, but that just seems a little messy.
Thanks for the help, hopefully this question isnt too basic, I have tried googling it with no luck :(
EDIT:
The Objects in the Arrays I wish to remove will have identical values but are NOT the same object (thanks #patrick dw). I am looking to completely remove the subset from the initial array.
This answer is copied from https://stackoverflow.com/a/53092728/7173655, extended with a comment and a solution with objects.
The code filters array A. All values included in B are removed from A.
const A = [1, 4, 3, 2]
const B = [0, 2, 1, 2]
console.log(A.filter(n => !B.includes(n)))
The same with objects:
const A = [{id:1}, {id:4}, {id:3}, {id:2}]
const B = [{id:0}, {id:2}, {id:1}, {id:2}]
console.log(A.filter(a => !B.map(b=>b.id).includes(a.id)))
http://phpjs.org/functions/index
There is no built-in method to do this in JavaScript. If you look at this site there are a lot of functions for arrays with similar syntax to PHP.
http://www.jslab.dk/library/Array
This site has some js functions on "sets"
I think you need the diff function.
It should remove all values from list a, which are present in list b keeping their order.
let a = [0, 2, 5, 6, 1];
let b = [2, 6, 2, 5, 0];
function arrayDiff() {
for (i of b) {
for (j of a) {
if (i === j) {
a.splice(a.indexOf(j), 1);
}
}
}
return a;
}