Can you close empty gaps between array indices in JavaScript? - javascript

I have an algorithm where some array will be given a new index, but the new index is not sequential. For the sake of time, just assume the new index is given to the array at random. Let's say I have this array:
const arr = [];
arr[5] = 'a'
arr[2] = 'b'
arr[9] = 'c'
arr[0] = 'd'
If I console.log this array to the output window, I get this:
['d', empty, 'b', empty Γ— 2, 'a', empty Γ— 3, 'c']
I'm wondering if it's possible to close/remove those empty gaps between values so the array entries are sorted by their actual index, so I can get this output:
['d', 'b', 'a', 'c']
Is there a short and easy way to do this?

You can just use Array.prototype.filter and filter out undefined values. You can take a shortcut by coercing them into boolean using the !! operator:
arr.filter(x => !!x);
This check is loose/dirty because anything falsy will be coerced to false, e.g. the numeric 0. If you really want to be strict and only filter out undefined, then you need a stricter predicate:
array.filter(x => typeof x !== 'undefined');
const arr = [];
arr[5] = 'a';
arr[2] = 'b';
arr[9] = 'c';
arr[0] = 'd';
console.log(arr);
console.log(arr.filter(x => !!x));
console.log(arr.filter(x => typeof x !== 'undefined'));

simply use Array.splice() method
to "clean up" your array, wthiut creating a new one.
need to use reverse index progression
const arr = [];
arr[5] = 'a';
arr[2] = 'b';
arr[9] = 'c';
arr[0] = 'd';
console.log(JSON.stringify(arr));
for (let i=arr.length; i-- > 0;)
{
if (arr[i]===undefined) arr.splice(i,1);
}
console.log(JSON.stringify(arr));

Related

Conditionally join some of an array's items in JavaScript

Let's say I have two arrays of same length:
const a = ['a', 'b', 'c', 'd'];
const b = [1, 1, 2, 1];
I want to join (.join('')) all consecutive items in a whose corresponding value in b (i.e. at the same index) are equal.
In this scenario, what I want to get is:
const result = ['ab', 'c', 'd']
Because a[0] and a[1] have the same corresponding value in b (i.e. b[0] === b[1]) and are consecutive in a, they are joined into the same string, forming a single item in result. However, although a[3]'s corresponding value in b is equal to a[0] and a[1]'s one, it's not joined to any of the latter as it isn't consecutive.
Just check if the value at b is the same at the given index and the sucessor.
const
a = ['a', 'b', 'c', 'd'];
b = [1, 1, 2, 1],
result = a.reduce((r, v, i) => {
if (b[i - 1] === b[i]) r[r.length - 1] += v;
else r.push(v);
return r;
}, []);
console.log(result);
You can use two seporate loops; one to combine the two arrays in an object and merge consecutive objects with the same id, and one to grab the other value from the first loop and create a new array.
const a = ['a', 'b', 'c', 'd'];
const b = [1, 1, 2, 1];
var step = [];
for (n = 0; n < a.length; n++) {
if(step[n-1] !== undefined && step[n-1].id === b[n]) {
step[step.length-1].text = step[step.length-1].text + a[n];
} else {
step.push({ id: b[n], text: a[n] });
}
}
var result = [];
for (i = 0; i < step.length; i++) {
result.push(step[i].text);
}
console.log(a);
console.log(b);
//console.log(step); //for testing
console.log(result);

Append To Multi-dimensional array where matching matching another array

I have two arrays:
a = [
[a, b],
[c, d],
[e, f],
[g, h]
]
b = [
[a, 4],
[1, 2],
[e, 3]
]
when a[i][0], matches b[i][0], I need to add a value to the current index of a. For this example, when a[0][1] matches b[0][1], a[0][1] should look like [a,b,new_value].
If this means creating a new array with all of the values of a, that is fine, but the original values and order of a cannot change.
I have tried numerous variations of for loops and reverse for loops. I am at a loss.
Thanks in advance.
Not too bad with a map + find. For each item of the a array, see if there is a matching element in the b array, and if so, add your new value:
const a = [
["a","b"],
["c","d"],
["e","f"],
["g","h"],
];
const b = [
["a",4],
[1,2],
["e",3],
];
const mapped = a.map(x => {
const match = b.find(y => y[0] === x[0]);
if (match) return [...x, "new value"] // Replace "new value" with whatever you want to add...
return x;
});
console.log(mapped)
Iterate the 1st array with Array#map. Compare each sub array's 1st item to a sub array in the 2nd array at the same index. If they match, concat a value to the sub array from the 1st array, and return it. If not, return the sub array.
Note: concat and map create new arrays, and don't change the original.
var a = [["a","b"],["c","d"],["e","f"],["g","h"]];
var b = [["a",4],[1,2],["e",3]];
var result = a.map(function(item, i) {
return b[i] && item[0] === b[i][0] ? item.concat(b[i][1]) : item; // replace b[i][1] with whatever value you want to add
});
console.log(result);
You could map the result of the check by using a default value, if the length of the given arrays are different.
var array1 = [['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']],
array2 = [['a', 4], [1, 2], ['e', 3]],
result = array1.map((a, i) => a.concat(a[0] === (array2[i] || [])[0] ? array2[i][1] : []));
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Eliminate duplicates of several arrays

I have 3 arrays:
array1 = [ 'A', 'B', 'A', 'B']
array2 = [ 5, 5, 7, 5]
array3 = [true,true,true,true]
I was wondering if there is any easy way (maybe with lodash) to eliminate the duplicates and end with this:
array1 = [ 'A', 'B', 'A']
array2 = [ 5, 5, 7]
array3 = [true,true,true]
I know I can do a function and compare the previous value, but is there a more clever way to do it?
Update
Please note that I don't need to eliminate the duplicates of each array.
What I looking is a way to eliminate the duplicates "vertically"
Update 2
Please note that each "column" is a record.
record1 = ['A',5,true]
record2 = ['B',5,true]
record3 = ['A',7,true]
record1 = ['B',5,true]
TL;DR
const records = array1.map((a, i) => [a, array2[i], array3[i]]);
const index = {};
records.filter(column => {
const key = JSON.stringify(column);
return key in index ? false : index[key] = true;
});
Huh?
There are a lot of ways to solve this, with varying degrees of efficiency, and the best solution will depend on the size of your data. A simple but naΓ―ve solution iterates over each "column" and checks all of the preceding columns for equality. It looks like this:
const array1 = [ 'A', 'B', 'A', 'B'];
const array2 = [ 5, 5, 7, 5];
const array3 = [true,true,true,true];
const newArray1 = array1.slice(0,1); // column 0 is never duplicate
const newArray2 = array2.slice(0,1);
const newArray3 = array3.slice(0,1);
// loop over columns starting with index 1
outer: for (let i = 1; i < array1.length; i++) {
const a = array1[i];
const b = array2[i];
const c = array3[i];
// check all preceding columns for equality
for (let j = 0; j < i; j++) {
if (a === array1[j] && b === array2[j] && c === array3[j]) {
// duplicate; continue at top of outer loop
continue outer;
}
}
// not a duplicate; add to new arrays
newArray1.push(a);
newArray2.push(b);
newArray3.push(c);
}
console.log(newArray1);
console.log(newArray2);
console.log(newArray3);
.as-console-wrapper{min-height:100%}
As you can see, we have to check each row within each column for equality, every time. If you're curious, the complexity of this is 𝑂(𝑛(𝑛+1)/2) (technically 𝑂(π‘šπ‘›(𝑛+1)/2), where π‘š is 3 for three columns).
For a larger data sets it's advantageous to keep track of values you've already seen in a data structure that's quick to access: A hash, a.k.a. a JavaScript object. Since all of your values are primitive, a quick way to construct a key is JSON.stringify. Some might consider this a "hack"β€”and it's important to note that it will fail with values that can't be represented in JSON, e.g. Infinity or NaNβ€”but it's a fast and easy one with data this simple.
const array1 = ['A', 'B', 'A', 'B'];
const array2 = [5, 5, 7, 5];
const array3 = [true, true, true, true];
const newArray1 = [];
const newArray2 = [];
const newArray3 = [];
const index = {};
for (let i = 0; i < array1.length; i++) {
const a = array1[i];
const b = array2[i];
const c = array3[i];
const key = JSON.stringify([a,b,c]);
if (key in index) {
// duplicate; skip to top of loop
continue;
}
// not a duplicate; record in index and add to new arrays
index[key] = true;
newArray1.push(a);
newArray2.push(b);
newArray3.push(c);
}
console.log(newArray1);
console.log(newArray2);
console.log(newArray3);
.as-console-wrapper{min-height:100%}
The complexity of this is 𝑂(𝑛), or maybe 𝑂(2π‘šπ‘›) where π‘š, again,
is 3 for three columns, and the 2 is another π‘š to very roughly account for the cost of JSON.stringify. (Figuring out the cost of hash access is left as an exercise for the pedants among us; I'm content to call it 𝑂(1).)
That's still pretty verbose. Part of the reason is that using three different variables for the dataβ€”which is really a single "table"β€”leads to a lot of repetition. We can preprocess the data to make it easier to deal with. Once it's "transposed" into a single two-dimensional array, we can use Array.prototype.filter with the key technique from above, for some very terse code:
const array1 = ['A', 'B', 'A', 'B'];
const array2 = [5, 5, 7, 5];
const array3 = [true, true, true, true];
// turn "columns" into "rows" of a 2D array
const records = array1.map((a, i) => [a, array2[i], array3[i]]);
const index = {};
const newData = records.filter(column => {
const key = JSON.stringify(column);
return key in index ? false : index[key] = true;
});
console.log(newData);
.as-console-wrapper{min-height:100%}
Of course, pre-processing isn't free, so this code isn't any more performant than the more verbose version; you'll have to decide how important that is to you. If you want you can now extract the columns from newData into three variables (newData.forEach(([a,b,c]) => { newArray1.push(a); newArray2.push(b); /* ... */ })), but for many purposes the "transposed" 2D array will be easier to work with.
You can use ES6 Set https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
Set -> lets you store unique values of any type, whether primitive values or object references.
and then convert back to an array
check this snippet
const array1 = ['A','B','A','B']
const array2 = [5,5,7,5]
const array3 = [true,true,true,true]
const uniqA1= new Set(array1)
const uniqA2= new Set(array2)
const uniqA3= new Set(array3)
console.log(Array.from(uniqA1))
console.log(Array.from(uniqA2))
console.log(Array.from(uniqA3))
Hope it helps
You need to find duplicate elements with same indexes in all arrays and then filter out those elements.
var array1 = ['A', 'B', 'A', 'B'],
array2 = [5, 5, 7, 5],
array3 = [true, true, true, true];
var dupes = []
var arrays = [array1, array2, array3];
arrays.forEach(function(arr, i) {
arr.forEach((e, j) => !this[e] ? this[e] = true : dupes[i] = (dupes[i] || []).concat(j))
}, {})
var index = dupes[0].filter(e => dupes.every(a => a.includes(e)))
var result = arrays.map(e => e.filter((a, i) => !index.includes(i)))
console.log(result)
You're going to need a couple of helper functions (lodash provides them also):
let zip = (...arys) => arys[0].map((_, i) => arys.map(a => a[i]));
let uniq = (ary, key) => uniq2(ary, ary.map(key), new Set);
let uniq2 = (ary, keys, set) => ary.filter((_, i) => !set.has(keys[i]) && set.add(keys[i]))
// test
var array1 = ['A', 'B', 'A', 'B'];
var array2 = [5, 5, 7, 5];
var array3 = [true, true, true, true];
var [x, y, z] = zip(
...uniq(
zip(array1, array2, array3),
JSON.stringify
)
);
console.log(x, y, z)
Another way, with filter():
array1 = ['A', 'B', 'A', 'B'];
array2 = [5, 5, 7, 5];
array3 = [true, true, true, true];
uniqueArray1 = array1.filter(function(item, pos) {
return array1.indexOf(item) == pos;
})
uniqueArray2 = array2.filter(function(item, pos) {
return array2.indexOf(item) == pos;
})
uniqueArray3 = array3.filter(function(item, pos) {
return array3.indexOf(item) == pos;
})
console.log(uniqueArray1);
console.log(uniqueArray2);
console.log(uniqueArray3);
One method I can think of is using an object to keep track, which will also coincidentally remove any duplicates as keys have to be unique. The only thing is I can think of how to extract it back into an array for now. I will think about it tomorrow.
This utilizes jquery for deep cloning. If you want it only in vanilla javascript, you could probably just implement a deep clone function.
var array1 = [ 'A', 'B', 'A', 'B'];
var array2 = [ 5, 5, 7, 5];
var array3 = [true,true,true,true];
all_arrays = [array1, array2, array3];
let obj = {};
for (let i = 0; i < all_arrays[0].length; i++)
{
let new_obj = recursive_objects(all_arrays, 0, i)
$.extend(true, obj, new_obj);
}
console.log(obj);
function return_array(array, temp_obj)
{
let keys = Object.keys(temp_obj);
for (let key of keys)
{
}
}
function recursive_objects(arrays, arrays_index, index)
{
let obj = {}
if (arrays_index < arrays.length)
{
obj[arrays[arrays_index][index]] = recursive_objects(arrays, ++arrays_index, index);
}
return obj;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Remove all adjacent repeating characters from input

The function unique_in_order which takes as argument a sequence and returns a list of items without any elements with the same value next to each other and preserving the original order of elements.
For example:
uniqueInOrder('AAAABBBCCDAABBB') == ['A', 'B', 'C', 'D', 'A', 'B']
uniqueInOrder('ABBCcAD') == ['A', 'B', 'C', 'c', 'A', 'D']
uniqueInOrder([1,2,2,3,3]) == [1,2,3]
What you want to do first is normalize your input such that whether it's a string or an array, it doesn't matter.
const input = Array.isArray(x) ? x : x.split('');
After the above, the input will always be an array. Now the logic for skipping is duplicates is quite simple
for (let i = 0; i < input.length; ++i) {
if (input[i] == input[i + 1]) continue
result.push(input[i])
}
You continue, i.e. skip each index in the input that is the same as the next for each index. Then for each element that isn't duplicated, you push into your result array.
The whole thing looks like this:
function uniqueInOrder(x) {
const result = [];
const input = Array.isArray(x) ? x : x.split('');
for (let i = 0; i < input.length; ++i) {
if (input[i] == input[i + 1]) continue
result.push(input[i])
}
return result
}
console.log(uniqueInOrder('AAAABBBCCDAABBB'));
console.log(uniqueInOrder('ABBCcAD'));
console.log(uniqueInOrder([1, 2, 2, 3, 3]));
If you're familiar with the filter function you can make it a one liner.
function uniqueInOrder(x) {
return (Array.isArray(x) ? x : x.split(''))
.filter((c, i) => c !== x[i + 1]);
}
console.log(uniqueInOrder('AAAABBBCCDAABBB'));
console.log(uniqueInOrder('ABBCcAD'));
console.log(uniqueInOrder([1, 2, 2, 3, 3]));
Offered is two different solutions, depending on what you want:
You can use Set() for unique sets. Simply adding the value to a set will retain the order and prevent unique values from being added.
For contiguously unique sets, you can use filter to iterate over an array.
If the argument is a string, this array is created by splitting on a blank string (.split(''))
When iterating over the array of input values, you can compare the current value to the previous; if they are different then return true
A true response will include the value in the array you will be creating as a result of the filter (which holds your unique values)
uniqueInOrder('AAAABBBCCDAABBB') == ['A', 'B', 'C', 'D', 'A', 'B']
uniqueInOrder('ABBCcAD') == ['A', 'B', 'C', 'c', 'A', 'D']
uniqueInOrder([1, 2, 2, 3, 3]) == [1, 2, 3]
function unique(input) {
var set = new Set(),
arr = input;
if (typeof input == 'string')
arr = arr.split('');
arr.forEach(val => {
set.add(val)
});
console.log('input:',input);
console.log('unique:',[...set]);
return [...set];
}
function uniqueInOrder(input) {
var arr = input;
if (typeof input == 'string')
arr = arr.split('');
var unique = arr.filter((val,i,arr)=>{
return val !== arr[i-1];
});
console.log('input:', JSON.stringify(input));
console.log('uniqueInOrder:', JSON.stringify(unique));
return unique;
}

How to change an element in for-of loop

How to I change the object in the array with For-of loop?
Following code:
let arr = ['a', 'b', 'c', 'd', 'e'];
for (let v of arr) {
if (v === 'c') {
v = 'f';
break;
}
}
console.log(arr);
I want to find the first letter c and change it to an f, but arr doesn't get changed, probably because it is not referenced ? But shouldn't the v of arr make that the object v is the same as the one in arr ?
Javascript does not create references to simple values such String. To get array referrenced, you need to let array be an array of objects like [{char : 'a'}, {char : 'b'}, ...]. Then in your iterator you can change elements of array through changing of the char property
let arr = [{char: 'a'}, {char :'b'}, ...];
for (let v of arr) {
if (v.char === 'c') {
v.char = 'f';
break;
}
}
v is not a reference to the array element, it's just a variable that is assigned the value that the array iterator yields. If you assign to it, only the variable v changes but not the array.
To do that, you need to explicitly reference the property with its index and assign to it:
let arr = ['a', 'b', 'c', 'd', 'e'];
for (let [i, v] of arr.entries()) {
if (v === 'c') {
arr[i] = 'f';
break;
}
}
console.log(arr); // ['a', 'b', 'f', 'd', 'e']
An iterator does not provide a way to mutate the underlying structure, you need to do it yourself.
As detailed in the MDN spec, let is declaring a new variable from a copy of your array element. So changing the v will not change the value in the array. This is shared by var and const and is simply just javascripts behaviour. When you create a new object, it starts empty.
Your loop is basically saying "For every element in arr, declare a variable holding a copy of it and then if that variable is c, change that copy to f"
Before you change v both, the array and v point to - or in this case have - the same value:
arr[2] -> 'c' <- v
After you change v it has a different value, but you didn't change the array:
arr[2] -> 'c' v -> 'f'
v and arr[2] are only placeholders, but different ones.
The correct answer:
let arr = ['a', 'b', 'c', 'd', 'e'];
let i = 0;
for (let v of arr) {
if (v == 'c') {
arr[i] = 'f';
break;
}
i++;
}
console.log(arr);
:-P

Categories