How to find existence of an array in a set? - javascript

Since set in Javascript is able to take object, including arrays, how do I find the existence of an array list in a set?
I tried the following code:
var sello = new Set();
sello.add(["a","b"])
console.log(sello.has(["a","b"])) // outputs false
I'm assuming
sello.has(["a","b"])
should have output true since I've added the exact array in the set sello. Am I missing out some falsy truthy thing or any logical error?
Note:
I'm not looking to match only the character "a" and/or "b", I'm
looking to match the whole array ["a","b"].
I'm looking to match the array. I just need the content to be the
same, the elements doesn't have to be the same order.

What you're trying to do won't work because in Javascript you can't compare arrays like that, even if they have the same values. This is because arrays are reference types, not value types, and for reference types Javascript determines whether they are equal or not based on whether they are referencing the same object (i.e. the same place in memory). For instance, just try:
console.log(['a','b'] == ['a','b']); // false
Despite having the same values, each array is a new reference, so they are not equal to each other.
In contrast, the comparison in the code below does involve arrays referencing the same object on both sides of the equation:
let a = ['a','b'];
console.log(a == a); // true
And therefore:
let sello = new Set();
sello.add(a);
console.log(sello.has(a)); // true
To remedy this, you'll want to create a function that compares arrays based on their values. You can first check if the arrays have the same length. If not, then they're not equal. You can then loop through the items in each and see if any are different for any given position. If so, they're not equal. Otherwise, assuming you're dealing with a flat array of primitive values (no nested objects of reference type), then the arrays are equal. This is what I do in 'isEqual' below:
function isEqual(x,y) {
if (x.length != y.length)
return false;
for (let i in x)
if (x[i] != y[i])
return false;
return true;
}
Test it if you like:
console.log(isEqual(['a','b'],['a','b'])); // true
Now, unfortunately, Set.has() doesn't accept a function, so we can't use it with isEqual. But you can just loop through the values of the set. If creating a one-liner is the goal, the best way I have found to do this is to convert the set to an array and use the some method. some accepts a function that evaluates each row, and if it returns true for any row, then the result is true, else false.
console.log(
[...sello].some(item => isEqual(item, ['a','b']))
);
// true

In JavaScript, Arrays are Objects, and no two separate Objects are ever considered equal.
MDN shows the same error with a standard object:
var set1 = new Set();
var obj1 = {'key1': 1};
set1.add(obj1);
set1.has(obj1); // returns true
set1.has({'key1': 1}); // returns false because they are different object references
The easiest way to use .has with an Object (such as an Array) is to get a reference to the Object, like:
let sello = new Set();
let myArray = ["a","b"];
sello.add(myArray);
console.log(sello.has(myArray)); // outputs true
If you can't get a reference to the Array, you'll probably need to check each Array in the Set by iterating through the Array and comparing each element individually.
You could do this more concisely, but this explicit example clarifies the process:
// Declares and populates the Set
let sello = new Set();
sello.add( ["a", "c"] );
sello.add( ["a", "b"] );
sello.add( ["b", "c"] );
// Tests the `setHasArray` function
let result = setHasArray(sello, ["a", "b"]);
console.log(`result: ${result}`);
// Defines the `setHasArray` function
function setHasArray(theSet, arrayToMatch){
// Creates a flag
let isMatch = false;
// Iterates through the Set
for (let member of theSet){
// Logs the Array we're about to compare elements of
console.log("comparing:", member);
// Makes sure this member is an Array before proceeding
if(Array.isArray(member)){
// Tentatively sets the flag to `true`
isMatch = true;
// Iterates through the Array, comparing each value
arrayToMatch.forEach( (_, index) => {
// Logs the comparison for the current value
console.log(
member[index]
+ (member[index] === arrayToMatch[index] ? " === " : " !== ")
+ arrayToMatch[index]
);
// Even one non-matching element means the Array doesn't match
if(member[index] !== arrayToMatch[index]){
console.log("Rats! Looked like a possible match there for a second.");
isMatch = false;
}
});
// Logs a successful match for the current member of the Set
if(isMatch){
console.log("Found a match!")
// Stops checking Arrays lest the flag get reset and give us a false negative
break;
}
}
}
// Returns our result
return isMatch;
}
(See .forEach on MDN if you're not familiar with this method.)

Related

Javascript: Remove specific items from an array and return the original array without those items

New to JS. Doing a practice exercise in which I want to remove some specific items from an array and return the original array back without those items. I did it one way, but the sample solution was completely different and I want to try and understand it.
Is my line of thinking correct (see comments after each line of code below)? I think I understand, but I'm not sure if my reasoning is the correct reasoning or if I just lucked into it. I want to be sure I fully understand the next time I see something like this.
const pull = (arr, ...args) => {
// In this case, arr = arra1 and ...args = the values "a" and "c"
let argState = Array.isArray(args[0]) ? args[0] : args;
// I think this is saying, "Is the first item (index[0]) in arra1 included in the list of the arguments that follow (in this case, "a" and "c")?"
// The value of arra1[0] is "a", which is indeed one of the arguments that follow, so the ternary operator would return false and give you the "args", which are "a" and "c" in this case.
// "a" and "c" form an array (do they? I may be wrong here), so Array.isArray() would evaluate to true, and therefore argState = true
let pulled = arr.filter((v, i) => !argState.includes(v));
// I think this is saying, "Loop through arra1. v is the first value, "a" in this case. "Does argState contain the value "a"?
// It does, so it will come back true which will evaluate to false when we apply the "!". So "a" is NOT filtered into the new array called "pulled".
// Second loop, does argState contain the value "b"? It does not, so it will come back false, which will evaluate to true when we apply the "!". So "b" IS filtered into the new array called "pulled".
// And so on and so forth, and we would end up with "pulled = [ b , b ]" for this line.
arr.length = 0;
// I believe this just empties the original arr, or arra1 in this case. So arra1 = [ ]
pulled.forEach(v => arr.push(v));
// Now we loop through "pulled" and push each value onto arra1
return pulled;
// This will return the original arra1 with the new values [ b, b ]
};
let arra1 = ['a', 'b', 'c', 'a', 'b', 'c'];
console.log(pull(arra1, 'a', 'c')); // will return ["b","b"]
My main confusion stems from the !argState.includes(v) part. If argState in our previous line ended up with a value of true or false, it doesn't make sense to me that we can check if argState includes a value (v) from the arra1 array (i.e. "a", "b", or "c" in this exercise). How could argState include values like this when it was already set to just the value true or false because of the Array.IsArray() check?
How could argState include values like this when it was already set to just the value true or false because of the Array.IsArray() check?
It's not set to true or false. It's set to an array:
let argState = Array.isArray(args[0]) ? args[0] : args;
If args[0] is an array, it's set to that array. Otherwise, it's set to the whole args array.
This is equivalent to
let argState;
if (Array.isArray(args[0])) {
argState = args[0];
} else {
argState = args;
}
This allows callers to use either the format
pull(someArr, ['foo', 'bar'])
or
pull(someArr, 'foo', 'bar')
and the use of the conditional operator to construct argState then collects an array of ['foo', 'bar'] regardless of the format the caller used.
Question has been answered! My disconnect occurred at this line:
let argState = Array.isArray(args[0]) ? args[0] : args;
My mind kept reading this line as: "If args[0] is true, return args[0]. If false, return args. Then, if what is returned is an array, make argState = true. If not, make argState = false."
Instead of the correct way to read it: "If args[0] is an array and therefore true, return args[0]. If false, return args."
This would set argState equal to some values, rather than just true or false.
Many thanks to #CertainPerformance for helping me realize my mistake!

Array.prototype.every return true when testing empty value in an array has length?

I am trying to check is every element is array has truthy value.
But I am confused when testing an array has some empty value.
var arr = [];
arr[10] = 1;
arr; // [empty x 10, 1];
arr.every(item => Boolean(item)); // true ???
Boolean(arr[0]); // false ???!!!
this is what I get when running the code above on chrome devtool console
every, some, map, filter, and most of the others only visit array entries that exist, they don't visit the gaps in sparse arrays like yours. So the result is only based on checking the values of elements that actually exist.
You can see that if you step through the callback or add logging to it:
var arr = [];
arr[10] = 1;
arr.every((item, index) => {
console.log(`Visited index ${index}, item = ${item}`);
return Boolean(item);
});
// =>
// Visited index 10, item = 1
// Note that 0-9 are gaps:
console.log(`0 in arr? ${0 in arr}`); // false
console.log(`9 in arr? ${9 in arr}`); // false
console.log(`10 in arr? ${10 in arr}`); // true
As you can see, the every callback in that only outputs one line because it's only called once.
If you want that array to actually have undefined for elements 0 through 9, you could use fill; then every would test element 0 and return false since Boolean(undefined) is false:
var index = 10;
var arr = Array(index + 1).fill();
arr[index] = 1;
console.log(arr.every(Boolean)); // false
Most array operations don't operate on unassigned indexes in sparse arrays. From the MDN documentation:
callback is invoked only for array indexes which have assigned
values. It is not invoked for indexes which have been deleted, or
which have never been assigned values.
You only have one index with an assigned value which is truthy, so your call to every() will return true.

Is indexOf slower than trying to index an object?

I don't know much at all about performance. But, if someArray.includes(someString) has to search the whole array, would trying to find an index of an object be more direct (and thus faster, and also a bit less code)?
// create an array of number strings
const arr = Array(100).from(n => n.toString());
// find whether a string is in the array
const isStringInArr_includes_true = arr.includes("50"); // true
const isStringInArr_includes_false = arr.includes("500"); // true
// instead of creating an array, create an object with the array items as keys
// EDIT: actually you can just use the array itself
// and look up the string as a key
const isStringInArr_lookup_true = !!arr["50"]; // !!("50") === true
const isStringInArr_lookup_false = !!arr["500"]; // !!(undefined) === false
Obviously this would only work with an array of strings.

Can't create JS object with array indices as key & value?

Task: convert an array into an object with one key-value pair, where the first array item is the key, and the last array item is the value.
E.g., [1,2,3] should convert to {1: 3}
I can't get it to work as:
function transformFirstAndLast(array) {
var firstLast = {
array[0]: array[-1]
};
return firstLast
}
But only as:
function transformFirstAndLast(array) {
var firstLast = {};
firstLast[array[0]] = array[array.length - 1];
return firstLast
}
...why doesn't the first work? Why can't you index the array for the key & value?
You could pop the last element and take a computed property for the object. (For the first element, you could take Array#shift, if you like to do it in the same manner.)
function transformFirstAndLast(array) {
return { [array[0]]: array.pop() };
}
console.log(transformFirstAndLast([1, 2, 3]));
ES5 with a temporary variable.
function transformFirstAndLast(array) {
var temp = {};
temp[array[0]] = array.pop();
return temp;
}
console.log(transformFirstAndLast([1, 2, 3]));
Take the first is easy, take the last is the size minus one like this:
function firstAndLast(array) {
var ary = {};
ary[array[0]] = array[array.length - 1];
return ary;
}
console.log(firstAndLast([1,2,3]))
First, you must remember than an array is a type of JavaScript object and, in JavaScript, an object property (a.k.a. "key") can be accessed or assigned in two ways:
via "dot notation"
object.property = value;
via array syntax
object["property"] = value;
Next, remember that, in JavaScript, if you assign a value to a property that doesn't exist (using either syntax from above), the property will be created, like in the following:
console.log(window.someNewProperty); // undefined
window.someNewProperty = 17; // This causes the property to be created
window["someOtherNewProperty"] = "Voilla!"; // So does this, just with array syntax
console.log(window.someNewProperty); // 17
console.log(window["someOtherNewProperty"]); // "Voilla!"
Now, moving on to the specifics of an array, it's critical to understand the difference between an object property/key name (which is always represented as a string) and an array index (which is always a non-negative integer up to the max integer in JavaScript). So, if you have an array and seemingly assign a value to a negative index, you are actually creating a property that is named the negative index and not actually adding to the length of the array or making a new indexed position in the array. We can see that here:
var myArray = ["a", "b", "c"];
myArray[-1] = 15;
console.log(myArray.length); // 3 not 4
console.log(myArray[-1]); // 15
// Now to prove that -1 is a string name for a new property and not an index:
console.log(myArray); // Notice no 15 in the indexed values?
// And, if we enumerate the object (not just the indexes), we'll see that we actually created
// a property with [-1], not a new index.
for(var prop in myArray){
// Note that prop is not the value of the property, it's the property name itself
console.log(typeof prop, prop, myArray[prop]);
}
So, to sum up, Arrays have non-negative integer indexes to store the items that make up the length of the array, but Arrays are also objects and have properties, like all other objects do. Any bracket assignments that use anything other than non-negative integers as the key name will become new properties, not array indices.

Remove value from js array without reordering keys

I have an array, and I want to remove just one element, but without reordering keys. Is there an easy way without using delete or rebuilding the entire array?
Or alternatively clean up after delete to get rid of the undefined values, fixing the length again.
var array = ["valueone", "valuetwo"];
console.dir(array); // keys 0 and 1
array.splice(0, 1);
console.dir(array); // key 1 is now 0, do not want!
You can delete the elements of an array:
a = ['one', 'two'];
delete a[0];
// a is now [undefined, 'two'];
alternatively, set a[0] explicitly to undefined.
Note that an arrays .length parameter is automatically maintained by the system. If you intentionally set it to a higher number, you'll just get a whole load of undefined values for the missing keys:
a.length = 10;
// a is now [undefined, 'two', undefined x 8]
If these semantics are not acceptable to you, then you should consider using an Object instead. This will preserve your keys, and perhaps be more efficient, but you lose the .length property.
couldn't you just explicitly set the value to undefined or null or an empty string. What are you trying to achieve?
var arr = ['aaaa','bbb','ccc','ddd'];
arr[0]= undefined;
//or
arr[0]= null;
///or
arr[0]= "";
arr.length; <--- 4
Update 2018-09-07
This answer isn't very good, in my opinion. I provided an answer on How do I remove a property from a JavaScript Object that has received much more attention from me over the years and covers this case and goes into much more detail.
The point is, you should be using Array.prototype.splice and Array.prototype.slice.
array.splice(start, n) returns a subset of array from index start with n sequential elements, and removes this subset from the original array, creating a new array in the process.
let array = [1,2,3,4,5,6];
array.splice(2,3); // [3,4,5]
array; // [1,2,6]
array.slice(start, end) returns a subset of array from index start to index end without mutating the original. The behavior is a little different from splice, which is why I prefer to call it as array.slice(start, start + n).
let array = [1,2,3,4,5,6];
array.slice(2, 2 + 3); // [3,4,5]
array; // [1,2,3,4,5,6]
Of course you could set the index to a sentinel value like null or "", but if you are wanting the array to stay in the same order after a deletion, perhaps you should change your approach--why does "valuetwo" have to be at index 1? What useful information is even being held in this data structure if the contents are always the same as the keys needed to access them?
The original answer is below. And if I am going to keep the original text, perhaps I should elaborate on why it's bad advice.
You can use javascript's delete keyword.
delete array[index];
Don't do this. If your array is homogeneous (as it ought to be), then this will corrupt your array by introducing a second type (undefined). You should use array.splice() as discussed above, which will create a new array with the specified range omitted.
Unfortunately, this creates an undefined index inside of the array
var arr = ['pie', 'cake', 'fish'];
delete arr[1];
arr; // ['pie', undefined, 'fish']
Case in point.
You could also do this:
var arr = [9,8,7,6];
arr[1] = null;
arr; // [9,null,7,6]
arr.length; // 4
var i = -1;
while(++i < arr.length){
if(arr[i] && typeof(arr[i] === "number")){
alert(arr[i]);
}
}
You could, but you shouldn't. Not only is this unnecessary, and doesn't do anything useful (because all it's doing is calling alert), but it's actually broken.
if(arr[i] && typeof(arr[i] === "number")){
alert(arr[i]);
}
You might expect this to only print our element if it is a non-zero number, but will in fact also run for values like "foo", [] and document.createElement("p"), because typeof(arr[i] === "number") will always return the value "boolean", which is a non-empty string, which is truthy and will therefore evaluate true. Which means the only requirement for alert to be called is that arr[i] is truthy. There are only six values in the entire language that will cause this if statement to not execute, and those are:
undefined
null
0
"" (pronounced "empty string")
false
NaN
Or, if you don't NEED to use arrays, you could use an object and make everything easier:
var obj = {
0: "first",
1: "second",
2: "third"
};
delete obj[1];
obj; // {0: "first", 2: "third"}
for(var i in obj){
alert(obj[i]);
}
Which would instantaneously erase all of the advantages to using an array. Now you have a data set which may or may not be heterogeneous, which can't be filtered, mapped, reduced or transformed in any sane way, and you have to resort to things like for(i in obj) (which is extremely bug-prone if you dare to use a library like jQuery) to iterate over it. Luckily today we have fancy stuff like Object.keys(obj).map(k => obj[k]).forEach(function(el){ ... }), but that's no excuse to have bad data structures.
To get the length of an object:
getLength = function(obj){
var i = 0, l = 0;
for(i in obj){
l++;
}
return l;
}
getLength(obj); // 3
Again, with arrays, this is unnecessary.
But remember that objects sort their indices by date of creation, not > by name. This shouldn't result in a road block, though.
To sort the indices of an object alphabetically:
sortObject = function (){
var arr = [], i;
for(i in this){
arr.push({index:i,content:this[i]});
delete this[i];
}
arr.sort();
for(i in arr){
var item = arr[i];
this[item.index] = item.content;
}
return this; // make chainable
}
var obj = {
acronym: "OOP",
definition: "Object-Oriented Programming",
article: "http://wikipedia.org/OOP"
};
sortObject.apply(obj); // indices are "acronym", "article", "definition"
array.sort(fn)
The whole point of an object is that its properties are unsorted, anyway. Sorting an unsorted list will hardly do anything useful.
Just to illustrate how much better arrays are at doing array things:
let array = ["pie", "cake", "fish", "brownie", "beef", ...];
/* do some stuff... */
array
.filter(el => exclude.indexOf(el) === -1)
.forEach(function(el){
console.log(el);
});
if exclude is ["cake", "brownie"], then this will log the following to the console:
pie
fish
beef
...
Just try to imagine how many unnecessary lines of code it would take to do the same using the approach from the previous version of this answer.
Hope this helped
Hopefully this update helped.

Categories