We use cookies to enhance your experience on the site
CodeWorlds

Map and Set

Working on Jurassic Park management systems, we often need to store collections of unique values or map keys to values. In JavaScript, before ES6, we typically used objects or arrays for these purposes, but they came with certain limitations. ECMAScript 6 introduced two new data structures that solve many of these problems:

Map
and
Set
.

Set — collections of unique values

Set
is a collection of unique values — each value can only appear once. This is an excellent solution when we need to store a set of elements without duplicates.

Creating a new Set

1// Creating an empty set
2const dinosaurs = new Set();
3
4// Creating a set with initial values
5const carnivores = new Set(['Tyrannosaurus', 'Velociraptor', 'Spinosaurus', 'Allosaurus']);
6
7// Creating a set from an array (automatically removes duplicates)
8const parkDinosaurs = new Set([
9  'Tyrannosaurus',
10  'Triceratops',
11  'Velociraptor',
12  'Stegosaurus',
13  'Tyrannosaurus',  // Duplicate - will be skipped
14  'Brachiosaurus',
15  'Velociraptor'    // Duplicate - will be skipped
16]);
17
18console.log(parkDinosaurs.size); // 5 - duplicates were removed
19console.log([...parkDinosaurs]); // Convert to array
20// ['Tyrannosaurus', 'Triceratops', 'Velociraptor', 'Stegosaurus', 'Brachiosaurus']

Basic Set operations

1// Adding elements to the set
2dinosaurs.add('Tyrannosaurus');
3dinosaurs.add('Velociraptor');
4dinosaurs.add('Triceratops');
5
6// Attempting to add a duplicate - will be ignored
7dinosaurs.add('Tyrannosaurus');
8
9console.log(dinosaurs.size); // 3
10
11// Checking if an element exists in the set
12console.log(dinosaurs.has('Velociraptor')); // true
13console.log(dinosaurs.has('Brachiosaurus')); // false
14
15// Removing an element from the set
16dinosaurs.delete('Triceratops');
17console.log(dinosaurs.size); // 2
18
19// Clearing the entire set
20dinosaurs.clear();
21console.log(dinosaurs.size); // 0

Iterating over a Set

1const dinosaurSpecies = new Set([
2  'Tyrannosaurus',
3  'Triceratops',
4  'Velociraptor',
5  'Stegosaurus',
6  'Brachiosaurus'
7]);
8
9// Iteration using for...of loop
10console.log('Dinosaur species in the park:');
11for (const species of dinosaurSpecies) {
12  console.log(`- ${species}`);
13}
14
15// Using forEach method
16dinosaurSpecies.forEach(species => {
17  console.log(`Species: ${species}`);
18});
19
20// Converting Set to array
21const speciesArray = [...dinosaurSpecies];
22console.log('Array of species:', speciesArray);
23
24// Iteration with access to indices (through array conversion)
25[...dinosaurSpecies].forEach((species, index) => {
26  console.log(`${index + 1}. ${species}`);
27});

Practical uses of Set

1. Removing duplicates from an array

1// Array with repeating values
2const dinoSightings = [
3  'Tyrannosaurus', 'Velociraptor', 'Triceratops', 'Tyrannosaurus',
4  'Stegosaurus', 'Velociraptor', 'Brachiosaurus', 'Tyrannosaurus'
5];
6
7// Removing duplicates using Set
8const uniqueDinoSightings = [...new Set(dinoSightings)];
9
10console.log('Unique species observed in the park:');
11console.log(uniqueDinoSightings);
12// ['Tyrannosaurus', 'Velociraptor', 'Triceratops', 'Stegosaurus', 'Brachiosaurus']

2. Storing unique dinosaur characteristics

1// Cataloging unique dinosaur characteristics
2const dinoFeatures = [
3  { species: 'Tyrannosaurus', features: ['sharp teeth', 'small arms', 'powerful jaws'] },
4  { species: 'Velociraptor', features: ['sharp claws', 'fast runner', 'pack hunter'] },
5  { species: 'Triceratops', features: ['three horns', 'neck frill', 'herbivore'] },
6  { species: 'Stegosaurus', features: ['back plates', 'tail spikes', 'herbivore'] }
7];
8
9// Set of unique features from all dinosaurs
10const allFeatures = new Set();
11
12dinoFeatures.forEach(dino => {
13  dino.features.forEach(feature => allFeatures.add(feature));
14});
15
16console.log('All unique dinosaur features:');
17console.log([...allFeatures]);
18// ['sharp teeth', 'small arms', 'powerful jaws', 'sharp claws', 'fast runner', 'pack hunter', 'three horns', 'neck frill', 'herbivore', 'back plates', 'tail spikes']
19
20// Counting dinosaurs that are herbivores
21const herbivoreCount = dinoFeatures.filter(dino =>
22  dino.features.includes('herbivore')
23).length;
24
25console.log(`Number of herbivore species: ${herbivoreCount}`); // 2

3. Set operations

We can implement set theory operations such as union, difference, or intersection:

1// Dinosaurs in the first enclosure
2const paddockA = new Set(['Tyrannosaurus', 'Velociraptor', 'Dilophosaurus']);
3
4// Dinosaurs in the second enclosure
5const paddockB = new Set(['Triceratops', 'Stegosaurus', 'Velociraptor', 'Gallimimus']);
6
7// Union — all unique dinosaurs from both enclosures
8const allDinosaurs = new Set([...paddockA, ...paddockB]);
9console.log('All dinosaurs:', [...allDinosaurs]);
10// ['Tyrannosaurus', 'Velociraptor', 'Dilophosaurus', 'Triceratops', 'Stegosaurus', 'Gallimimus']
11
12// Difference — dinosaurs in the first enclosure but not the second
13const uniqueToPaddockA = new Set(
14  [...paddockA].filter(dino => !paddockB.has(dino))
15);
16console.log('Dinosaurs unique to paddock A:', [...uniqueToPaddockA]);
17// ['Tyrannosaurus', 'Dilophosaurus']
18
19// Intersection — dinosaurs present in both enclosures
20const commonDinosaurs = new Set(
21  [...paddockA].filter(dino => paddockB.has(dino))
22);
23console.log('Dinosaurs present in both enclosures:', [...commonDinosaurs]);
24// ['Velociraptor']

Map — key-value pair collections

Map
is a collection of key-value pairs where keys can be of any type (not just strings, as in plain objects). A Map preserves the order of element insertion and enables easy iteration.

Creating a new Map

1// Creating an empty map
2const dinoStats = new Map();
3
4// Creating a map with initial key-value pairs
5const dinoHeights = new Map([
6  ['Tyrannosaurus', 5.6],
7  ['Velociraptor', 1.8],
8  ['Triceratops', 3.0],
9  ['Stegosaurus', 4.0],
10  ['Brachiosaurus', 13.0]
11]);
12
13console.log(dinoHeights.size); // 5

Basic Map operations

1// Adding key-value pairs
2dinoStats.set('Tyrannosaurus', { height: 5.6, weight: 7000, diet: 'carnivore' });
3dinoStats.set('Velociraptor', { height: 1.8, weight: 15, diet: 'carnivore' });
4dinoStats.set('Triceratops', { height: 3.0, weight: 6000, diet: 'herbivore' });
5
6// Getting a value by key
7const trexStats = dinoStats.get('Tyrannosaurus');
8console.log('T-Rex stats:', trexStats);
9// { height: 5.6, weight: 7000, diet: 'carnivore' }
10
11// Checking if a key exists in the map
12console.log(dinoStats.has('Velociraptor')); // true
13console.log(dinoStats.has('Brachiosaurus')); // false
14
15// Removing an element from the map
16dinoStats.delete('Triceratops');
17console.log(dinoStats.size); // 2
18
19// Clearing the entire map
20// dinoStats.clear();
21// console.log(dinoStats.size); // 0

Using different key types

One of the main advantages of Map over plain objects is the ability to use keys of any type:

1// Map with different key types
2const dinoRecords = new Map();
3
4// Key: string
5dinoRecords.set('fastestRunner', 'Gallimimus');
6
7// Key: number
8dinoRecords.set(42, 'Number of dinosaurs in the park');
9
10// Key: object
11const paddock1 = { id: 'A1', name: 'T-Rex Kingdom' };
12dinoRecords.set(paddock1, ['Tyrannosaurus']);
13
14// Key: function
15function feedingTime() { return 'Time to feed the dinosaurs!'; }
16dinoRecords.set(feedingTime, '09:00 and 17:00');
17
18console.log(dinoRecords.get('fastestRunner')); // 'Gallimimus'
19console.log(dinoRecords.get(42)); // 'Number of dinosaurs in the park'
20console.log(dinoRecords.get(paddock1)); // ['Tyrannosaurus']
21console.log(dinoRecords.get(feedingTime)); // '09:00 and 17:00'

Iterating over a Map

1// Map with dinosaur data
2const dinosaurData = new Map([
3  ['Tyrannosaurus', { diet: 'carnivore', era: 'Late Cretaceous', danger: 5 }],
4  ['Velociraptor', { diet: 'carnivore', era: 'Late Cretaceous', danger: 4 }],
5  ['Triceratops', { diet: 'herbivore', era: 'Late Cretaceous', danger: 3 }],
6  ['Stegosaurus', { diet: 'herbivore', era: 'Late Jurassic', danger: 2 }],
7  ['Brachiosaurus', { diet: 'herbivore', era: 'Late Jurassic', danger: 1 }]
8]);
9
10// Iterating over key-value pairs
11console.log('Dinosaur information:');
12for (const [species, info] of dinosaurData) {
13  console.log(`${species} - ${info.diet}, danger level: ${info.danger}`);
14}
15
16// Iterating only over keys
17console.log('Dinosaur species:');
18for (const species of dinosaurData.keys()) {
19  console.log(`- ${species}`);
20}
21
22// Iterating only over values
23console.log('Danger levels:');
24for (const info of dinosaurData.values()) {
25  console.log(`Danger level: ${info.danger}, Diet: ${info.diet}`);
26}
27
28// Using forEach method
29dinosaurData.forEach((info, species) => {
30  console.log(`${species} from ${info.era} era, danger level: ${info.danger}`);
31});

Conversions between Map and other structures

1// Converting Map to array of key-value pairs
2const entriesArray = [...dinosaurData];
3console.log('Entries array:', entriesArray);
4// [['Tyrannosaurus', {...}], ['Velociraptor', {...}], ...]
5
6// Converting Map to array of keys
7const speciesArray = [...dinosaurData.keys()];
8console.log('Array of species:', speciesArray);
9// ['Tyrannosaurus', 'Velociraptor', 'Triceratops', 'Stegosaurus', 'Brachiosaurus']
10
11// Converting Map to array of values
12const infoArray = [...dinosaurData.values()];
13console.log('Array of info:', infoArray);
14// [{ diet: 'carnivore', era: 'Late Cretaceous', danger: 5 }, ...]
15
16// Converting object to Map
17const dinoObject = {
18  Ankylosaurus: { weight: 5000, armor: 'high' },
19  Parasaurolophus: { weight: 2500, ability: 'vocalization' }
20};
21
22const dinoObjectMap = new Map(Object.entries(dinoObject));
23console.log('Map from object:', dinoObjectMap);
24
25// Converting Map to object
26const herbivores = new Map([
27  ['Triceratops', { horns: 3, frill: true }],
28  ['Stegosaurus', { backPlates: 17, tailSpikes: 4 }]
29]);
30
31const herbivoresObject = Object.fromEntries(herbivores);
32console.log('Object from map:', herbivoresObject);
33// { Triceratops: { horns: 3, frill: true }, Stegosaurus: { backPlates: 17, tailSpikes: 4 } }

Practical uses of Map

1. Storing data about dinosaur enclosures

1// Map for managing dinosaur enclosures
2const enclosures = new Map();
3
4// Adding enclosure data
5enclosures.set('T-REX-01', {
6  dinosaurs: ['Tyrannosaurus'],
7  status: 'active',
8  securityLevel: 'maximum',
9  lastInspection: new Date('2023-06-10')
10});
11
12enclosures.set('RAP-02', {
13  dinosaurs: ['Velociraptor', 'Velociraptor', 'Velociraptor', 'Velociraptor'],
14  status: 'maintenance',
15  securityLevel: 'high',
16  lastInspection: new Date('2023-06-15')
17});
18
19enclosures.set('HERB-03', {
20  dinosaurs: ['Triceratops', 'Stegosaurus', 'Parasaurolophus'],
21  status: 'active',
22  securityLevel: 'medium',
23  lastInspection: new Date('2023-06-12')
24});
25
26// Function finding all active enclosures
27function getActiveEnclosures(enclosuresMap) {
28  const active = new Map();
29
30  for (const [id, data] of enclosuresMap) {
31    if (data.status === 'active') {
32      active.set(id, data);
33    }
34  }
35
36  return active;
37}
38
39const activeEnclosures = getActiveEnclosures(enclosures);
40console.log('Active enclosures:', [...activeEnclosures.keys()]);
41// ['T-REX-01', 'HERB-03']
42
43// Function finding enclosures needing inspection (last inspection older than 7 days)
44function findEnclosuresNeedingInspection(enclosuresMap) {
45  const today = new Date();
46  const sevenDaysAgo = new Date(today);
47  sevenDaysAgo.setDate(today.getDate() - 7);
48
49  return [...enclosuresMap].filter(([_, data]) =>
50    data.lastInspection < sevenDaysAgo
51  ).map(([id]) => id);
52}
53
54// Note: in a real example, dates would be more than a few days apart
55console.log('Enclosures needing inspection:', findEnclosuresNeedingInspection(enclosures));

2. Tracking dinosaur statuses

1// Map for tracking dinosaur statuses
2const dinosaurStatuses = new Map();
3
4// Adding and updating statuses
5function updateDinosaurStatus(id, status) {
6  const currentTime = new Date();
7
8  if (dinosaurStatuses.has(id)) {
9    const currentStatus = dinosaurStatuses.get(id);
10
11    // Keep status history
12    currentStatus.history.push({
13      status: currentStatus.current,
14      time: currentStatus.updatedAt
15    });
16
17    // Update status
18    currentStatus.current = status;
19    currentStatus.updatedAt = currentTime;
20
21    dinosaurStatuses.set(id, currentStatus);
22  } else {
23    // New entry for dinosaur
24    dinosaurStatuses.set(id, {
25      current: status,
26      updatedAt: currentTime,
27      history: []
28    });
29  }
30
31  return `Dinosaur ${id} status updated to: ${status}`;
32}
33
34// Using the function to track statuses
35console.log(updateDinosaurStatus('T-REX-001', 'feeding'));
36console.log(updateDinosaurStatus('V-RAP-004', 'resting'));
37
38// Later we update the status
39setTimeout(() => {
40  console.log(updateDinosaurStatus('T-REX-001', 'hunting'));
41  console.log(updateDinosaurStatus('V-RAP-004', 'active'));
42
43  // Displaying status history
44  console.log('T-REX-001 status history:', dinosaurStatuses.get('T-REX-001'));
45}, 1000);

3. Storing relationships between entities (dinosaur kinship graph)

1// Map representing dinosaur kinship graph
2const dinoRelationships = new Map();
3
4// Adding dinosaurs and their relationships
5dinoRelationships.set('Blue', ['Delta', 'Echo', 'Charlie']); // Velociraptor and its siblings
6dinoRelationships.set('Delta', ['Blue', 'Echo', 'Charlie']);
7dinoRelationships.set('Echo', ['Blue', 'Delta', 'Charlie']);
8dinoRelationships.set('Charlie', ['Blue', 'Delta', 'Echo']);
9
10dinoRelationships.set('Rexy', ['Junior']); // T-Rex and its offspring
11dinoRelationships.set('Junior', ['Rexy']);
12
13// Function finding all related dinosaurs (network)
14function findRelatedDinosaurs(name, relationshipsMap) {
15  const visited = new Set();
16  const toVisit = [name];
17
18  while (toVisit.length > 0) {
19    const current = toVisit.pop();
20
21    if (!visited.has(current)) {
22      visited.add(current);
23
24      // Add related dinosaurs to visit
25      const related = relationshipsMap.get(current) || [];
26      toVisit.push(...related);
27    }
28  }
29
30  // Remove the original dinosaur from results
31  visited.delete(name);
32
33  return [...visited];
34}
35
36console.log('Dinosaurs related to Blue:', findRelatedDinosaurs('Blue', dinoRelationships));
37// ['Charlie', 'Echo', 'Delta']
38
39console.log('Dinosaurs related to Rexy:', findRelatedDinosaurs('Rexy', dinoRelationships));
40// ['Junior']

Comparison of Map and objects, Set and arrays

Map vs. Object

| Feature | Map | Object | |---------|-----|--------| | Keys | Any type | Only strings and symbols | | Size |

.size
property | Requires
Object.keys(obj).length
| | Iteration | Built-in methods and interface | Requires additional methods | | Order | Preserves insertion order | Implementation-dependent (undefined before ES2015) | | Default keys | None | Inherits from prototype | | Performance | Better for frequent add/remove | Better for simple cases |

When to use Map:

  • When keys are not strings
  • When order of elements matters
  • When you frequently add and remove key-value pairs
  • When you need to easily iterate over the collection
  • When you need to know the number of elements

When to use objects:

  • When keys are simple strings
  • When you need JSON.stringify()
  • When you need access to the prototype and methods
  • For simple data structures

Set vs. Array

| Feature | Set | Array | |---------|-----|-------| | Unique values | Automatic duplicate removal | Can contain duplicates | | Element access | Only through iteration | Direct by index | | Operations | Add/remove/check in O(1) time | Presence check in O(n) time | | Order | Preserves insertion order | Indexed, preserves order | | Manipulation | Basic methods | Rich API with many methods |

When to use Set:

  • When you need to store unique values
  • When you frequently check if a value exists (has)
  • When you need to easily remove duplicates from arrays
  • When performing set operations (union, difference, intersection)

When to use arrays:

  • When order of elements matters
  • When you need access by index
  • When you need to manipulate elements (sort, filter)
  • When duplicates are allowed or desired

Performance of Map and Set

One of the main advantages of Map and Set is their performance. Operations such as adding, removing, and checking presence are performed in constant O(1) time, unlike array operations that may require O(n) time.

1// Performance comparison: array vs Set for checking presence
2
3// Creating large collections
4const largeArray = [];
5const largeSet = new Set();
6
7// Adding many elements
8for (let i = 0; i < 1000000; i++) {
9  largeArray.push(`item-${i}`);
10  largeSet.add(`item-${i}`);
11}
12
13// Measuring search time in array
14console.time('Array search');
15const arrayHasItem = largeArray.includes('item-500000');
16console.timeEnd('Array search');
17
18// Measuring search time in Set
19console.time('Set search');
20const setHasItem = largeSet.has('item-500000');
21console.timeEnd('Set search');
22
23console.log(`Array search result: ${arrayHasItem}`);
24console.log(`Set search result: ${setHasItem}`);
25
26// Results may vary, but Set should be significantly faster
27// Array search: 15ms
28// Set search: 0.01ms

Nested structures and complex examples

Map containing Sets

1// Map assigning abilities to dinosaurs
2const dinosaurAbilities = new Map();
3
4// Adding ability sets for different dinosaurs
5dinosaurAbilities.set('Tyrannosaurus', new Set([
6  'powerful bite', 'intimidating roar', 'keen sense of smell'
7]));
8
9dinosaurAbilities.set('Velociraptor', new Set([
10  'speed', 'pack hunting', 'intelligence', 'sharp claws'
11]));
12
13dinosaurAbilities.set('Triceratops', new Set([
14  'charging', 'defensive horns', 'thick skin'
15]));
16
17// Adding new abilities
18dinosaurAbilities.get('Tyrannosaurus').add('night vision');
19
20// Checking if a dinosaur has a specific ability
21function checkDinosaurAbility(species, ability) {
22  if (!dinosaurAbilities.has(species)) {
23    return `Unknown dinosaur species: ${species}`;
24  }
25
26  const abilities = dinosaurAbilities.get(species);
27  if (abilities.has(ability)) {
28    return `${species} has the ability: ${ability}`;
29  } else {
30    return `${species} does not have the ability: ${ability}`;
31  }
32}
33
34console.log(checkDinosaurAbility('Velociraptor', 'intelligence')); // Has it
35console.log(checkDinosaurAbility('Tyrannosaurus', 'flying')); // Does not have it
36console.log(checkDinosaurAbility('Brachiosaurus', 'height')); // Unknown species
37
38// Finding dinosaurs with a specific ability
39function findDinosaursWithAbility(ability) {
40  const result = [];
41
42  for (const [species, abilities] of dinosaurAbilities) {
43    if (abilities.has(ability)) {
44      result.push(species);
45    }
46  }
47
48  return result;
49}
50
51console.log('Dinosaurs with "intelligence" ability:',
52  findDinosaursWithAbility('intelligence')); // ['Velociraptor']

Set containing Maps

1// Set of dinosaur reports (each report is a map)
2const dinoReports = new Set();
3
4// Creating several reports as Maps
5const report1 = new Map([
6  ['date', '2023-06-15'],
7  ['observer', 'Dr. Grant'],
8  ['species', 'Velociraptor'],
9  ['count', 3],
10  ['behavior', 'hunting'],
11  ['location', 'Sector B']
12]);
13
14const report2 = new Map([
15  ['date', '2023-06-16'],
16  ['observer', 'Dr. Sattler'],
17  ['species', 'Tyrannosaurus'],
18  ['count', 1],
19  ['behavior', 'feeding'],
20  ['location', 'Sector C']
21]);
22
23const report3 = new Map([
24  ['date', '2023-06-16'],
25  ['observer', 'Dr. Malcolm'],
26  ['species', 'Velociraptor'],
27  ['count', 2],
28  ['behavior', 'resting'],
29  ['location', 'Sector D']
30]);
31
32// Adding reports to the set
33dinoReports.add(report1);
34dinoReports.add(report2);
35dinoReports.add(report3);
36
37// Searching reports based on criteria
38function findReports(reports, criteria) {
39  return [...reports].filter(report => {
40    for (const [key, value] of Object.entries(criteria)) {
41      if (report.get(key) !== value) {
42        return false;
43      }
44    }
45    return true;
46  });
47}
48
49// Finding Velociraptor reports
50const raptorReports = findReports(dinoReports, { species: 'Velociraptor' });
51console.log('Velociraptor reports:', raptorReports.length);
52raptorReports.forEach(report => {
53  console.log(`${report.get('date')} - ${report.get('behavior')} - ${report.get('location')}`);
54});
55
56// Finding reports from a specific day
57const reportsFrom16 = findReports(dinoReports, { date: '2023-06-16' });
58console.log('Reports from June 16:', reportsFrom16.length);

Summary

Map and Set are powerful data structures introduced in ES6 that enrich JavaScript's capabilities for organizing and manipulating data:

Set

  • Stores unique values of any type
  • Automatically removes duplicates
  • Offers fast add, delete, and presence check operations
  • Useful for set operations like union, difference, and intersection
  • Ideal solution for removing duplicates from arrays

Map

  • Stores key-value pairs where keys can be of any type
  • Preserves the order of element insertion
  • Offers convenient methods for managing and iterating over data
  • More efficient than objects for frequent add/remove operations
  • No "built-in" keys unlike objects

In the context of our Jurassic Park, these data structures allow us to:

  • Efficiently manage collections of unique dinosaurs and their traits (Set)
  • Create relationships between different park entities like dinosaurs, enclosures, and staff (Map)
  • Better track statuses, history, and other dynamic data (Map)
  • Perform faster search and check operations on large datasets

Skillful use of these structures can significantly improve the performance and readability of code in JavaScript applications.

Go to CodeWorlds