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
Related
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.
Is there a way to use Set as object keys
let x = {}
const a = new Set([3, 5])
x[a] = 1
console.log(x) // >{[object Set]: 1}
const b = new Set([1, 4])
x[b] = 2
console.log(x) // >{[object Set]: 2}
The keys are being overwritten even though the sets are not equal.
Thanks!
No this is not possible because Object keys must be strings or symbols. If you would like to use a Set as a key you can try using a Map. Maps are similar to objects except you can use other objects as keys for a map.
One thing to keep in mind is that you cannot use maps exactly like you use Objects.
This is directly from the Mozilla docs.
The following IS NOT A VALID USE OF A MAP.
let wrongMap = new Map()
wrongMap['bla'] = 'blaa'
wrongMap['bla2'] = 'blaaa2'
console.log(wrongMap) // Map { bla: 'blaa', bla2: 'blaaa2' }
But that way of setting a property does not interact with the Map data structure. It uses the feature of the generic object. The value of 'bla' is not stored in the Map for queries. Other operations on the data fail:
Correct use of a map looks like the below:
let map = new Map()
// setting values
map.set(key, value)
// getting values
map.get(key)
Remember that if you use an Object like a Set as a key, the reference of the Set is what matters.
If you instantiate two sets separately, even if they both have the same contents, they will have different references and be considered different keys.
Do you mean that the map in ES6 like this:
x = new Map()
a = new Set([3, 5])
x.set(a, 1)
console.log(x);
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);
I have some data set represented as array, which i want to put in an object as property. I want each property to be accessible by generated key. Number and type of elements in array is unknown. I want it to look something like that:
// array - array on unknown data
let object = {};
object[createUniqueKey(array)] = array;
// and later i want to use it the same way
console.log(object[createUniqueKey(array)])
Hope i've described well, thanks for help in advance
The easiest way to create a unique key to avoid having your object's data overwritten is to use a counter and increment it on every insertion. Alternatively, you could implement your own UUID-generating function, but I wouldn't go as far.
Example:
let object = {};
let createUniqueKey = ((counter) => () => counter++)(0);
let array = ["a", "b", "c"];
/* Create a unique key. */
let key = createUniqueKey();
/* Create a unique key to store the array. */
object[key] = array;
/* Use the key to get the array from the object. */
console.log(object[key]);
If instead, you want to use the array and based on that to create your key, the only solution that comes to my mind now is using a combination of toString and btoa (or simply the latter as it accepts array arguments). However, this method has some restrictions such as when the array contains objects and functions.
Example:
let object = {};
let createKey = (array) => btoa(array);
let array = ["a", "b", "c"];
/* Create a key to store the array. */
object[createKey(array)] = array;
/* Use the array to recreate the key and access the array inside the object. */
console.log(object[createKey(array)]);
In Javascript, I have an array of objects, users, such that users[1].name would give me the name of that user.
I want to use the ID of that user as the index instead of the ever increasing counter.
For example, I can initiate the first user as users[45].
However, I found that once I do users[45], javascript would turn it into a numeric array, such that when I do users.length, I get 46.
Is there anyway to force it to treat the number as string in this case. (" " doesn't work)?
You cannot use arrays for this sort of function in JavaScript — for more information, see "Javascript Does Not Support Associative Arrays."
Make sure you initialize the users variable as an Object instead. In this object you can store the arbitrary, non-sequential keys.
var users = new Object();
// or, more conveniently:
var users = {};
users[45] = { name: 'John Doe' };
To get the number of users in the object, here's a function stolen from this SO answer:
Object.size = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) size++;
}
return size;
};
var users = {};
// add users..
alert(Object.size(users));
Hans has the right answer here. Use keys on an object, not an array. I'd suggest you read these two references:
http://www.quirksmode.org/js/associative.html and
http://blog.xkoder.com/2008/07/10/javascript-associative-arrays-demystified/
Trying to make an associative array out of an array object is non-standard and can cause problems (for example .length will be zero). Use keys on an object instead.