What does "Sets are to arrays as maps are to objects" mean? - javascript

I'm learning about ES6 and stumbled upon this phrase in this video that states:
'you could say that Sets are to Arrays as Maps are to Objects'.
What does this phrase mean ? Why is a Set more linked to arrays than maps are ? (and vice-versa for objects).
I know this is a really specific question, but my head is really turning since i've heard this phrase!
Thank you in advance, i'm new to question on SO so any comment is appreciated.

A Set is a collection of values, just like an array is a collection of values (no keys involved, except .length / .size)
A Map is a collection of key-value pairs, just like an object is a collection of key-value pairs. (though the keys of a Map can be anything, not just strings)
Of course, there are many more differences, but the distinction between values and key-value pairs is what's most relevant for what you're asking.
Map and object example:
const key = 'key';
const value = 'value';
const map = new Map();
const obj = {};
map.set(key, value);
obj[key] = value;
Set and array example:
const value = 'value';
const set = new Set();
const arr = [];
set.add(value);
arr.push(value);

Related

Is there an object (or array-like object) with O(1) lookup for bigints as the key/index?

Let's say I have 50,000 objects with a structure like:
{
hash: bigint
value: number
name: string
}
Ideally, I would do something like:
const myArray = [];
for (let item of items) { // where item has a structure like the above object
myArray[item.hash] = item;
}
But arrays don't allow you do use bigints as array indexes, so that is invalid.
Maps and Sets allow keys to be bigints, but in my case the keys are not unique so those two don't work. Using array.filter() works, but it's much slower than a key or index lookup.
Is there an object that supports non-unique bigints as keys or a way to do an O(1) lookup on such an object?
There is Map for this:
const mymap = new Map();
// ...
mymap.set(item.hash, item);
While object keys are limited to strings (and symbols), Map supports primitive data types for its keys (including BigInt), and object references (irrelevant here). So a Map is preferrable to objects in this case.
You can even use the constructor callback to load the data:
const mymap = new Map(items.map(item => [item.hash, item]));
Now to the point of non-unique keys. If you have nothing else to uniquely identify an item, then there is nothing to uniquely look up either: you cannot get further than to make a scan among the items that have the same key in a (smaller) array associated with that key.
Something like:
// Initialise the arrays
const mymap = new Map(items.map(item => [item.hash, []]));
// Populate them by hash
for (const item of items) map.get(item.hash).push(item);
Here the lookup by hash has good time complexity, but it will give you an array. What next needs to happen with that array will determine the overall complexity.

What's an Associative Array?

I'm trying to understand what an associative array really is and what steps are needed to acquire one. I have seen many explanations that are totally different.
I have tried testing it out on my own but can't seem to really get it in the end.
var array = ["one", "two", "three"];
var test = array["one"];
console.log(test);
I expected for it to target the index in which the string "one" is in, but an error occurs.
You are likely looking for a JavaScript Object, what is basically the same as an associative array, dictionary, or map in other languages: It maps strings to values. Unlike Arrays, which use square brackets [] for declaration, Objects use curly braces {} (please note that there are some exceptions).
Try to think of an Object as an associative array:
const arr = {one: 1, two: 2, three: 3};
console.log(arr['one']);
console.log(arr.one);
It is worth noting that Array's in JavaScript are technically objects.
The JavaScript Array object is a global object that is used in the construction of arrays; which are high-level, list-like objects.
The main difference between Array's and Object's is that Arrays are numerically indexed.
const arr = ['fooValue', 'barValue'];
const obj = {foo: 'fooValue', bar: 'barValue'};
console.log('arr: ', arr[0], arr[1]);
console.log('obj: ', obj.foo, obj.bar);
It is worth noting, that unlike primitive types in JavaScript, Object's (and Array's, which are also Object's) are passed by reference, so extra care is needed when attempting to copy the object.
function test(obj) {
obj['oops'] = 'this will modify the object';
}
const obj = {one: 1, two: 2, three: 3};
test(obj);
console.log(obj); // Object was updated
To avoid accidentally mutating your object, you will have to create a new instance of the object before performing operations on it. There are multiple ways to accomplish this:
Destructuring/spreading you object let obj2 = {...obj};
Using Object.assign()
I'm trying to understand what an associative array really is...
JavaScript doesn't have associative arrays in the sense that, for instance, PHP does. JavaScript has:
Arrays, which are (effectively) numerically indexed (see my blog post for why I said "effectively")
Objects, which are collections of properties that have names, which are either strings or Symbols (and which have other features, like inheritance)
Maps, which are collections of key/value pairs where the keys can be any type (not just strings or Symbols)
Arrays
To find the index of an entry in an array, typically you use indexOf (for an === match) or findIndex if you want to provide a predicate function.
var array = ["one", "two", "three"];
console.log(array.indexOf("one")); // 0
Objects
If you wanted, you could create an object that mapped strings to numbers:
var obj = {"one": 1, "two": 2, "forty-two": 42};
console.log(obj["forty-two"]); // 42
Maps
Similarly, a Map could do that:
var map = new Map([
["one", 1],
["two", 2],
["forty-two", 42]
]);
console.log(map.get("forty-two")); // 42
Associative arrays are used to associate something throughout an array.
You can use this with the query string for example:
In order to attain the information from a forum submitted, you need to put the user data into an associative array.
You would start by getting the query string as follows:
var queryString = window.location.search;
queryString = queryString.substring(1);
The reason why I did substring(1) is so we could remove the '?' at the beginning.
Once you have the query string of the website, you'd need a loop to separate the values of data received:
while (queryString.indexOf("+") != -1)
queryString = queryString("+", " ");
This will replace all the '+' signs in the string to spaces, making you get the values without the '+' signs. You'll have "Name=John" for example.
Now we need to split the '&'s from the string.
We also need to make an array ready for the data from the user.
var array = queryString.split("&");
var userData = [];
Afterwards, make a for loop in order to target however amount of data submitted and to attain it individually while storing it into the array:
for (let x = 0; x < array.length; x++)
{
var equalSign = array[x].search("=");
var theKeyValue = array[x].substring(0, equal);
var userDataValue = array[x];
userDataValue = decodeURIComponent(userDataValue); //Puts symbols back
userData[theKeyValue] = userDataValue;
}
This is just an example to follow up with the usage of associative arrays, hopefully this helps. :)
See Wikipedia.
It is a data structure when you can look up a value by a key. This is typically implemented in JS using a Map or an Object.
const data = new Map([
['foo', 'one'],
['bar', 'two']
]);
console.log( data.get("bar") );
I expected for it to target the index in which the string "one" is in, but an error occurs.
You are attempting to look up the index of a property in an array by its value.
That has nothing to do with associative arrays and is achieved with the indexOf method.
var array = ["one", "two", "three"];
var test = array.indexOf("one");
console.log(test);
An associative array, is essentially a hashmap, or an array that associates relationships.
For example if I was to build an associative array for fruits let's say to prices it would look like.
const arr = {'apple': 1, 'orange': 2, 'pear': 3};
console.log(Object.keys(arr));
console.log(Object.values(arr));
Unlike other languages, array in Javascript are not limited by having only numeric indices. They can act as hashes as well (i.e. having a string as a key). An associative array is one where you set the key to be a non-numeric value.
By default, and in the example you provided, ascending numeric values are assigned to each member of the array. I.e.
var array = ["one", "two", "three"];
is equivalent to
var array = [];
array[0] = 'one';
array[1] = 'two';
array[2] = 'three';
An associative array would be one where instead of numeric values you assign a different value:
var array = [];
array['one'] = 'one';
array['two'] = 'two';
However this brings a few caveats in itself and it considered bad practice and the arrays become harder to manage. In cases like there it would be better to use either an object or a Map.
An associative array is a data structure, a data collection, which has the scope of associate a list of key to respective values (key: value).
You can read in Wikipedia here, that are example of associative array for example: Map, Dictionary, etc.
Sample of associative array are also PHP indexed array, e.g.:
$cars[0] = "Volvo";
$cars[1] = "BMW";
$cars[2] = "Toyota";
That's not an associative array, that's just a regular array, filled with text.
An associative array example (you can also use object syntax like Miroslav's answer):
var stuff = [];
stuff['one'] = "hello 1";
stuff['two'] = "hello 2";

Javascript "Set" for Objects and Arrays

Does Javascript have a built-in type for making a set out of data-objects and arrays?
let set = new Set();
set.add({"language": "ecmascript"});
set.add({"language": "ecmascript"});
set.add({"language": "ecmascript"});
set.add({"language": "ecmascript"});
set.add([1,2,3]);
set.add([1,2,3]);
set.add([1,2,3]);
set.add([1,2,3]);
console.log(set);
The Set I'm using above is only useful for primitives.
The Set I'm using above is only useful for primitives.
That's incorrect, it works just fine for objects. The problem is that distinct objects with the same properties and property values are not equal, so doing set.add({"language": "ecmascript"}); twice adds two non-equal objects to the set (both with the same property name and value).
If you add the same object more than once, it won't be added a second time:
const set = new Set();
const obj = {"language": "ecmascript"};
set.add(obj);
set.add(obj);
console.log(set.size); // 1
Does Javascript have a built-in type for...
If you want objects with the same properties and values to be treated as equal, then no. You'd need to be able to specify a comparison operation, and there's no built-in Set in JavaScript that lets you define the comparison operation to use.
Obviously, you can create one. As a starting point, I'd probably use a Map keyed by the names of the properties on the object, sorted and turned into a string via JSON.stringify. (Although that won't work if you want to have Symbol keys as part of the definition of equality.) For instance, if you're only considering own properties:
const key = JSON.stringify(Object.getOwnPropertyNames(object).sort());
The value for an entry could be either just an array of the objects with those keys that you do a linear search on, or a second Map keyed by some kind of hash of the property values, depending on how many objects you need to handle...
In comments, I asked:
Do you only need to handle objects with JSON-serializable values?
and you answered:
I have a bunch of objects that are already serialized, but there are duplicates that I'd like to eliminate and then re-serialize.
Yeah, you can use a Set for that if you don't mind re-serializing, or a Map if you want to skip the re-serializing part:
const unique = new Map();
for (const source of serializedObjects) {
const sourceObject = JSON.parse(source); // Or parse from whatever serialization it is
// Build object adding properties in alpha order for stability
const keyObj = {};
for (const key of Object.keys(sourceObject).sort()) {
keyObj[key] = sourceObject[key];
}
// Save it using JSON.stringify, which uses ES2015 property order
map.set(JSON.stringify(keyObj), source);
}
const uniqueSourceStrings = [...map.values()];
Or for the de-serialized objects themselves:
const unique = new Map();
for (const source of serializedObjects) {
const sourceObject = JSON.parse(source); // Or parse from whatever serialization it is
// Build object adding properties in alpha order for stability
const keyObj = {};
for (const key of Object.keys(sourceObject).sort()) {
keyObj[key] = sourceObject[key];
}
// Save it using JSON.stringify, which uses ES2015 property order
map.set(JSON.stringify(keyObj), sourceObject); // <=================== changed
}
const uniqueSourceObject = [...map.values()];
// ^^================================================================== changed

Creating object with varying keys

I want to create object similar to this using loop, where I have separate array of country codes and another array of latitude and longitudes
is there any way to achieve this in typescript,
var latlong={
'us': {'latitude':39.528019315435685, 'longitude':-99.1444409552122},
'fr': {'latitude':46.62065825554313, 'longitude':2.4521888685061306}
}
Assuming that your arrays have corresponding index, then you can use this sample code,
let country = ['us','fr'];
let lat = ['39.528019315435685','46.62065825554313'];
let long = ['-99.1444409552122','2.4521888685061306'];
let latlong = {};
country.forEach((code,index)=>{
latlong[code] = {};
latlong[code]['latitude'] = lat[index];
latlong[code]['longitude'] = long[index];
})
Assuming that both arrays are correctly mapped through their indices, you could try :
coordinatesArray.forEach((coordinate, index)=>{
latlong[countryArray[index]] = coordinate;
});
where coordinatesArray contains an array of latitude and longitude objects and countryArray contains the country string.
Here's small example of way to create desired object.
CodeSandbox-project
Basically you iterate through other array and with index reference to your other array. with obj[key] you can create object with variables as keys.
And just to open a bit discussion about achieving this in TypeScript. TypeScript basically only extends existing EcmaScript-syntax with types so this would work in vanillaJS or in TypeScript as expected. Benefit you get in TypeScript is if you would have typed your variables.
eg.
const country: string[] = ['fr','en']
So now your IDE can show you that you expect country-variable only to contain strings.

How to convert Map keys to array?

Lets say I have the following map:
let myMap = new Map().set('a', 1).set('b', 2);
And I want to obtain ['a', 'b'] based on the above. My current solution seems so long and horrible.
let myMap = new Map().set('a', 1).set('b', 2);
let keys = [];
for (let key of myMap)
keys.push(key);
console.log(keys);
There must be a better way, no?
Map.keys() returns a MapIterator object which can be converted to Array using Array.from:
let keys = Array.from( myMap.keys() );
// ["a", "b"]
EDIT: you can also convert iterable object to array using spread syntax
let keys =[ ...myMap.keys() ];
// ["a", "b"]
You can use the spread operator to convert Map.keys() iterator in an Array.
let myMap = new Map().set('a', 1).set('b', 2).set(983, true)
let keys = [...myMap.keys()]
console.log(keys)
OK, let's go a bit more comprehensive and start with what's Map for those who don't know this feature in JavaScript... MDN says:
The Map object holds key-value pairs and remembers the original
insertion order of the keys.
Any value (both objects and primitive
values) may be used as either a key or a value.
As you mentioned, you can easily create an instance of Map using new keyword...
In your case:
let myMap = new Map().set('a', 1).set('b', 2);
So let's see...
The way you mentioned is an OK way to do it, but yes, there are more concise ways to do that...
Map has many methods which you can use, like set() which you already used to assign the key values...
One of them is keys() which returns all the keys...
In your case, it will return:
MapIterator {"a", "b"}
and you easily convert them to an Array using ES6 ways, like spread operator...
const b = [...myMap.keys()];
I need something similiar with angular reactive form:
let myMap = new Map().set(0, {status: 'VALID'}).set(1, {status: 'INVALID'});
let mapToArray = Array.from(myMap.values());
let isValid = mapToArray.every(x => x.status === 'VALID');
Not exactly best answer to question but this trick new Array(...someMap) saved me couple of times when I need both key and value to generate needed array. For example when there is need to create react components from Map object based on both key and value values.
let map = new Map();
map.set("1", 1);
map.set("2", 2);
console.log(new Array(...map).map(pairs => pairs[0])); -> ["1", "2"]
Side note, if you are using a JavaScript object instead of a map, you can use Object.keys(object) which will return an array of the keys. Docs: link
Note that a JS object is different from a map and can't necessarily be used interchangeably!

Categories

Resources