Related
Let's say I have 4 arrays:
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
And I want to return the child/sub arrays that start with both 1 and 2, what type of loop would I need?
Currently, this is what I have:
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
var selected = [1, 2]; // These are the values that need to match
var result = [];
for (var i = 0; i < selected.length; i++) {
for (var j = 0; j < arrays.length; j++) {
if (arrays[i][j] === selected[i]) {
result.push(arrays[i]);
}
}
}
When there's more than 1 value in the selected array, it seems to return all the ones that match 2 on the second index, so the result would be:
[
[1, 2, 1],
[1, 2, 3],
[0, 2, 2]
]
The loop needs to ensure that on the second iteration it's making sure the first value is still true, as my intended result would be:
[
[1, 2, 1],
[1, 2, 3]
]
Please someone help me, I've had my head trying hundreds of different loop and checks variations for 2-3 days.
Thanks so much!!
Jake
Your current code pushes to the result array whenever any given index matches between arrays and selected. Instead you will need to reverse your loops and iterate over selected for every sub array and check if every element matches, if not break the inner loop and don't push.
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2],
];
const selected = [1, 2]; // These are the values that need to match
const result = [];
for (let i = 0; i < arrays.length; i++) {
let match = true;
for (let j = 0; j < selected.length; j++) {
if (arrays[i][j] !== selected[j]) {
match = false;
break;
}
}
if (match) {
result.push(arrays[i]);
}
}
console.log(result);
A more modern solution would be to use filter() with a nested every() call on selected.
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2],
];
var selected = [1, 2];
const result = arrays.filter(arr => selected.every((n, i) => n === arr[i]));
console.log(result);
Here is another approach where you turn both arrays to string and check it those inner arrays start with selected array.
var arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
var selected = [1, 2];
const result = arrays.filter(e => e.toString().startsWith(selected.toString()))
console.log(result)
Let's try to put your condition into words. That way, an implementation may come to mind more easily.
A short wording may be: "Take all arrays that match (rather: start with) a certain sub-array." In code, it may look like this:
const arrays = [
[1, 2, 1],
[1, 3, 4],
[1, 2, 3],
[0, 2, 2]
];
const selection = [1, 2];
const result = filterArrays(arrays, selection);
console.log(result);
function filterArrays(arrays, selection) {
const selectedArrays = [];
for (let i = 0; i < arrays.length; ++i) {
const array = arrays[i];
const subarray = array.slice(0, selection.length); // Get starting sub-array
if (compareArrays(subarray, selection)) {
selectedArrays.push(array);
}
}
return selectedArrays;
}
/*Ignore; helper function*/
function compareArrays(array1, array2) {
if (array1.length !== array2.length) return false;
const length = array1.length;
for (let i = 0; i < length; ++i) {
if (array1[i] !== array2[i]) return false;
}
return true;
}
.as-console-wrapper {max-height:100%!important}
Another, more specific wording may be: "Take all arrays that match a selection at an index." Note that we only reworded the "match a sub-array" part. I believe this is what you tried.
Refer to pilchard's answer for an implementation. Note that their implementation assumes the arrays in arrays to be at least the same length as selected.
I see you used var instead of the preferred modern let/const declarators. Here's a short outline of their differences:
let/const declarators:
Block-scoped.
Narrower scope means less name-space pollution.
More similar to declarators in other well-known languages:
Variables of these declarators cannot be used before their declaration (see TDZ).
var declarator:
Function-scoped.
Hoisted and with no TDZ, resulting in this (perhaps confusing) behaviour:
Variables declared with var can be used even before their declaration.
Duplicate declarations are allowed since they are effectively the same.
Also, JavaScript has different kinds of for-loops:
for-loop: The for-loops you used are this kind. It is the most versatile kind.
for...of-loop: A loop to iterate over an iterable object (see iterators). For example, arrays are iterable, so you can get its values with a for...of-loop:
const values = [1, 2, 3];
let sum = 0;
for (const value of array) {
sum += value;
}
console.log(sum); // -> 6
for...in-loop: A loop to iterate over enumerable properties of an object. It is easily confused with a for...of-loop, but MDN's example demonstrates the differences understandably.
In my code example above, the for-loop in filterArrays() can be replaced with a for...of-loop to better convey my intention: To iterate over all arrays in arrays, disregarding their index:
for (let i = 0; i < arrays.length; ++i) {
const array = arrays[i];
// ...
}
// Same as
for (const array of arrays) {
// ...
}
Im trying to copy elements of arr1 into arr2 at index n. The elements must be copied in the exact order they're in. I can get the code to work when I loop through the arrow backwards but I cant pass the tests because its not in order.
function frankenSplice(arr1, arr2, n) {
let newArr = arr2.splice(" ");
for(let i = 0; i < arr1.length;i++) {
newArr.splice(n,0,arr1[i]);
}
return console.log(newArr);
}
An example of how this should be called is frankenSplice([1, 2, 3], [4, 5, 6], 1);
Expected output is [4, 1, 2, 3, 5]
I keep getting [ 4, 1, 2, 3, 5, 6 ]
The reason your output is coming backwards is because you keep inserting at the same position n. This pushes the previous element after it, so they end up in reverse order. If you increment n each time through the loop, you'll insert them in order.
But there's no need for a loop, use spread syntax to use all of arr1 as the arguments in a single call to splice().
function frankenSplice(arr1, arr2, n) {
let newArr = [...arr2]; // make copy of arr2
newArr.splice(n, 0, ...arr1); // splice arr1 into the copy
return newArr;
}
console.log(frankenSplice([1, 2, 3], [4, 5, 6], 1));
I don't understand why you don't expect 6 in the output.
Not sure why the 6 is getting deleted but maybe something like this:
function frankenSplice(arr1, arr2, n) {
let newArr = arr2
newArr.splice(n+1)
for(let i = arr1.length - 1; i >= 0; i--) {
newArr.splice(n,0,arr1[i]);
}
return console.log(newArr);
}
frankenSplice([1, 2, 3], [4, 5, 6], 1);
This question already has answers here:
Why does changing an Array in JavaScript affect copies of the array?
(12 answers)
Closed 4 years ago.
This code seems straightforward enough
let arr = [1,2,3,4,5,6]
let store = [];
for(i = 0; i < arr.length; i++){
console.log(arr)
store.push(arr)
arr.push(arr.shift())
}
console.log('store', JSON.stringify(store))
I'm expecting it to return
[[1,2,3,4,5,6],[2,3,4,5,6,1],[3,4,5,6,1,2],[4,5,6,1,2,3],[5,6,1,2,3,4],[6,1,2,3,4,5]]
But when the loop is complete, it shows
[[1,2,3,4,5,6],[1,2,3,4,5,6],[1,2,3,4,5,6],[1,2,3,4,5,6],[1,2,3,4,5,6],[1,2,3,4,5,6]]
When printing the values in the console, they appear correct, but when the store variable is logged, they are all reordered.
[1, 2, 3, 4, 5, 6]
[2, 3, 4, 5, 6, 1]
[3, 4, 5, 6, 1, 2]
[4, 5, 6, 1, 2, 3]
[5, 6, 1, 2, 3, 4]
[6, 1, 2, 3, 4, 5]
Why is this occurring?
There is a similar question here, but it doesn't really provide an answer for my case.
Creating a pair of arrays from an array resulting in circular array
Just make sure you are inserting not the same reference to your array:
let arr = [1,2,3,4,5,6]
let store = [];
for(i = 0; i < arr.length; i++){
store.push(Array.from(arr)) // <-- Make sure not the same ref
arr.push(arr.shift())
}
console.log('store', JSON.stringify(store))
store contains the same instance of arr 6 times. You modify it and print it every time, then add the same object to store, then modify it again.
To get your desired behaviour you need to add a new instance every time e.g. by cloning it when you add it.
You could push a copy which does not keep the same object reference.
let arr = [1, 2, 3, 4, 5, 6]
let store = [];
for (i = 0; i < arr.length; i++) {
store.push(arr.slice()) // just take a copy
arr.push(arr.shift())
}
console.log(store.map(a => a.join(' ')))
Alternate approach leaving original array untouched and using splice() to remove beginning elements and concat() to add to the end
let arr = [1,2,3,4,5,6];
let store = arr.map((_,i)=> {
let a = arr.slice();
return i ? a.concat(a.splice(0,i)) : a;
})
console.log(JSON.stringify(store))
On every loop, you are pushing the reference of the array to store
Example:
Loop - reference of arr - 0x112, then store[ i ] = reference to 0x112
At end:
arr - refer to 0x112 - value = [1, 2, 3, 4, 5, 6]
store[ ] = [ref(0x112), ref(0x112), ref(0x112), ref(0x112), ref(0x112), ref(0x112)]
Use this:
let arr = [1,2,3,4,5,6]
let store = [];
for(i = 0; i < arr.length; i++){
console.log(arr)
let newArr = arr.slice();
store.push(newArr)
arr.push(arr.shift())
}
console.log('store', JSON.stringify(store))
I'm calling a function inside of my script main
function func(a) {
var idx;
a=a.sort();
for (idx = 0; idx < a.length + 1; idx += 1) {
if (a.indexOf(idx) === -1) {
return idx;
}
}
return 0;
}
var array = [2,1,0];
var b = func(array);
console.log(array);
The function argument (a) is an array that is being passed to the func function.
When I try to access the array in the main body of the program after calling this function it's sorted.
Does node.js link the scope between the array passed to the 'func' function and the array that was passed to it?
After calling this code, array is sorted. Why is that?
This is even true if I declare a new variable, b inside the function scope.
function func(a) {
var idx, b;
b = a;
b = b.sort();
for (idx = 0; idx < a.length + 1; idx += 1) {
if (a.indexOf(idx) === -1) {
return idx;
}
}
return 0;
}
var array = [2,1,0];
var b = func(array);
console.log(array);
The above code has the same issue.
It's not a scope leak but a twofold reason for what is happening:
because sort directly modifies the array it is applied on (while also returning it)
functions in JavaScript work with pass by reference for objects
For reason #1 look at this simple example:
var a = [3,4,1,2];
a; // [3, 4, 1, 2]
a.sort(); // [1, 2, 3, 4]
a; // [1, 2, 3, 4]
As you can see it returns the sorted array which is nothing more than the array itself that has been modified by the sort.
For reason #2 look at this simple example:
function foo(a) { a.push('hello'); }
var arr = [1,2,3,4];
arr; // [1, 2, 3, 4]
foo(arr); // undefined
arr; // [1, 2, 3, 4, "hello"]
So combining those two reasons you can see that you are passing a reference to the array into the function and then directly modifying it with a sort.
If you want to do the same thing without modifying the original array you could use Array.prototype.slice() which returns a shallow copy of the original array.
var a = [3,4,1,2];
var arr = a.slice();
a; // [3, 4, 1, 2]
arr; // [3, 4, 1, 2]
arr.sort(); // [1, 2, 3, 4]
arr; // [1, 2, 3, 4]
a; // [3, 4, 1, 2]
I am trying to understand a point of confusion I have with JavaScript objects. Specifically, I am interested in finding what, if anything, causes an object reference to break.
To demonstrate the phenomenon, I have included a copy of some output from Chrome's JavaScript console. Note that I am working with arrays here, but we would expect objects to behave similarly given the subtle distinction between arrays and objects in JS. I have added comments for clarity.
// Set x to some array literal
> x = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
// Set y to x
> y = x
[1, 2, 3, 4, 5]
> x
[1, 2, 3, 4, 5] // as expected
> y
[1, 2, 3, 4, 5] // as expected
As demonstrated above, both x and y output the expected value. Now I shuffle the values of x using a function called shuffle (specified at the bottom of this question).
// Shuffle x
> x = shuffle(x)
[5, 1, 4, 2, 3]
> x
[5, 1, 4, 2, 3] // x changes as expected
> y
[5, 1, 4, 2, 3] // y changes as expected
Again, everything works as expected above. The variables x and y have maintained reference to the same object. However, when we repeat this operation, the results are strange.
// Shuffle x
> x = shuffle(x)
[3, 1, 5, 4, 2]
> x
[3, 1, 5, 4, 2] // x changes as expected
> y
[5, 1, 4, 2, 3] // y didn't change this time
Below is the shuffle function, adapted from here. Its purpose is to shuffle the contents of an array (parameter r1) and to return the first n items of the mixed array.
function shuffle(r1,n) {
var i = r1.length, j, tempi, tempj, r2;
r2 = r1;
while (--i) {
j = Math.floor(Math.random() * (i + 1));
tempi = r2[i];
tempj = r2[j];
r2[i] = tempj;
r2[j] = tempi;
}
return r2.slice(0,n);
}
I have since fixed the problem by rewriting my shuffle function based on this function. However, I would still like to understand what's going on. For a quick look at the code in action, I have made a jsFiddle.
Any ideas? I appreciate your time.
If you remove the .slice(0,n);, it will behave the way you expect. slice makes a new array.
So the first time you call shuffle, within your loop you modify the array x = y = r1 = r2. Then you make a copy of it on that last line and assign that to x. Now x !== y, but they contain the exact same elements. You can test that they are distinct objects after your first call to shuffle:.
The next time you call shuffle you are shuffling the copy of x you made and y is untouched.
.slice() makes a shallow copy of the Array, and so you're overwriting x with a new Array.
// The original was shuffled, but now `x` is a new Array
x = shuffle(x);
That's why y showed the first shuffle (because you hadn't sliced it yet), but none thereafter. The subsequent shuffle was on the overwritten x, and y still references the original.
If you wanted to truncate the original Array, just change its .length.
So instead of this:
return r2.slice(0,n);
Do this:
r2.length = n;
...though you're not passing anything to n currently.