JavaScript and TypeScript course Β· Module 2: Functions, Arrays and Objects

Array Iteration (forEach, map, filter, reduce)

12 min read
In this lesson9

In our Jurassic Park we collect a mountain of data about the dinosaurs - their species, measurements, feeding habits, aggression levels and much more. Once all of that lands in an array, we hardly ever care about a single entry: we want to visit every element, reshape them, pull out the ones that matter, or boil the whole herd down to one number for the control room. JavaScript ships a family of array iteration methods built for exactly that, and each of them has a job of its own.

The forEach Method

The forEach method runs a function once for every element of an array. It is the most basic way of iterating - we reach for it when we want to do something with each element, such as printing it or sending it to the monitoring station, and we do not need a new array in return.

1// An array of objects representing the dinosaurs in our park
2const dinosaurs = [
3  { name: "Tyrannosaurus", diet: "carnivore", aggressionLevel: 10 },
4  { name: "Triceratops", diet: "herbivore", aggressionLevel: 4 },
5  { name: "Velociraptor", diet: "carnivore", aggressionLevel: 9 },
6  { name: "Stegosaurus", diet: "herbivore", aggressionLevel: 2 },
7  { name: "Brachiosaurus", diet: "herbivore", aggressionLevel: 1 }
8];
9
10// We use forEach to print the names of all the dinosaurs
11console.log("List of dinosaurs in the park:");
12dinosaurs.forEach(function(dino) {
13  console.log(dino.name);
14});
15
16// We can also use an arrow function for a more concise notation
17console.log("List of dinosaurs with their aggression level:");
18dinosaurs.forEach(dino => {
19  console.log(`${dino.name}: Aggression level ${dino.aggressionLevel}/10`);
20});

The forEach method hands nothing back - to be exact, it returns undefined - so we use it when we only want to perform an action on the elements, without building a new array. It is worth knowing what it cannot do either: you cannot stop it halfway through. A return inside the callback ends that one call and the walk moves straight on to the next dinosaur, and break is not even allowed in there. When you need to bail out early, that is a job for find, some, every or a classic for loop.

The map Method

The map method creates a new array containing the results of calling a provided function on every element of the original array. It is remarkably useful whenever we need to convert data from one shape into another - turning full dinosaur records into a plain list of names, or into short safety summaries the wardens can actually read.

1// We use map to build a new array containing only the dinosaur names
2const dinoNames = dinosaurs.map(dino => dino.name);
3console.log("Dinosaur names:", dinoNames);
4// Result: ["Tyrannosaurus", "Triceratops", "Velociraptor", "Stegosaurus", "Brachiosaurus"]
5
6// We can also build more complex transformations
7const dinoSummaries = dinosaurs.map(dino => {
8  return {
9    name: dino.name,
10    dangerLevel: dino.diet === "carnivore" ? "High" : "Low",
11    warning: dino.aggressionLevel > 7 ? "Handle with extreme caution!" : "Standard safety procedures"
12  };
13});
14
15console.log("Dinosaur safety summary:", dinoSummaries);

Unlike forEach, the map method always returns a new array of the same length as the original, holding the transformed elements. That is exactly the key difference between the two: map() returns a new array, while forEach() returns undefined. It is not a question of speed - map() is not faster, both methods visit every element exactly once - and they certainly do not work identically. Neither of them can be stopped mid-run, so that is no difference between them either. Reach for map when you want the result, and for forEach when you only care about the side effect.

The filter Method

The filter method creates a new array containing only the elements that satisfy a given condition. It is the perfect tool when we need to pull a subset out of a bigger collection - every carnivore, every animal due for a health check, every enclosure in the eastern sector.

1// We filter to get only the carnivorous dinosaurs
2const carnivores = dinosaurs.filter(dino => dino.diet === "carnivore");
3console.log("Carnivorous dinosaurs:", carnivores);
4
5// We can combine conditions to build more complex filters
6const dangerousDinos = dinosaurs.filter(dino => {
7  return dino.diet === "carnivore" && dino.aggressionLevel > 7;
8});
9console.log("The most dangerous dinosaurs:", dangerousDinos);
10
11// We can also combine filter with map, to narrow down first and transform afterwards
12const safetyWarnings = dinosaurs
13  .filter(dino => dino.aggressionLevel > 5)
14  .map(dino => `WARNING: ${dino.name} has a high aggression level!`);
15
16console.log("Safety warnings:", safetyWarnings);

The filter method returns a new array which may be shorter than the original, holding only the elements that made it through the "filter" - the ones for which the test function returned true. Notice that the dinosaurs array itself is left completely untouched: just like map, filter never edits the collection it was called on, it only reports on it.

The reduce Method

The reduce method is the most powerful of the iteration methods, and also the most involved. It lets us "reduce" an array to a single value by running a function over every element while carrying the result of the previous step along with us.

1// We calculate the sum of the aggression levels of all the dinosaurs
2const totalAggressionLevel = dinosaurs.reduce((total, dino) => {
3  return total + dino.aggressionLevel;
4}, 0); // 0 is the initial value
5
6console.log("Total aggression level in the park:", totalAggressionLevel);
7
8// We can use reduce to build richer structures, such as an object grouping dinosaurs by diet
9const dinosByDiet = dinosaurs.reduce((groups, dino) => {
10  // If the group for this diet does not exist yet, we create it
11  if (!groups[dino.diet]) {
12    groups[dino.diet] = [];
13  }
14
15  // We add the dinosaur to the matching group
16  groups[dino.diet].push(dino.name);
17
18  return groups;
19}, {}); // {} is the initial value (an empty object)
20
21console.log("Dinosaurs grouped by diet:", dinosByDiet);
22// Result: { "carnivore": ["Tyrannosaurus", "Velociraptor"], "herbivore": ["Triceratops", "Stegosaurus", "Brachiosaurus"] }

So what is the reduce() method on an array used for? It reduces an array to a single value - a sum, an average, the heaviest specimen, or one grouped object like the one above. The name misleads a lot of people, so let us be precise about what it does not do: it does not remove elements from an array, it does not reduce the size of the array (our dinosaurs array still holds five records afterwards), and it has nothing to do with sorting in descending order - that job belongs to sort.

The reduce method takes two parameters:

  1. A reducer function, which itself takes at least two parameters:
    • The accumulator: the value returned from the previous iteration (or the initial value)
    • The current element: the element of the array being processed right now
  2. An initial value (optional, but strongly recommended)

The find Method

The find method returns the first element of an array that satisfies a given condition. When the park systems need one specific record rather than a whole list, this is the method to reach for.

1// We look for a dinosaur with a given name
2const stegosaurus = dinosaurs.find(dino => dino.name === "Stegosaurus");
3console.log("Stegosaurus information:", stegosaurus);
4
5// We look for the first carnivore with an aggression level above 8
6const dangerousCarnivore = dinosaurs.find(dino => {
7  return dino.diet === "carnivore" && dino.aggressionLevel > 8;
8});
9console.log("First dangerous carnivore:", dangerousCarnivore); // Tyrannosaurus

Unlike filter, which returns an array of every matching element, find returns only the first match, and when nothing matches it returns undefined. It also stops the moment it gets a hit, so the remaining dinosaurs are never examined. Its close twin findIndex behaves the same way but gives you the position of that first match instead of the element itself, and returns -1 when there is no match at all.

The some and every Methods

The some and every methods return a boolean value and are useful for checking whether the elements of an array meet certain criteria. Neither of them builds a new array - they answer a yes-or-no question about the whole collection.

some

The some method checks whether at least one element of the array satisfies the given condition.

1// We check whether there are any dangerous dinosaurs in the park
2const hasHighAggressionDinos = dinosaurs.some(dino => dino.aggressionLevel > 8);
3console.log("Does the park have high aggression dinosaurs?", hasHighAggressionDinos); // true
4
5// We check whether there are any omnivorous dinosaurs in the park
6const hasOmnivores = dinosaurs.some(dino => dino.diet === "omnivore");
7console.log("Does the park have omnivorous dinosaurs?", hasOmnivores); // false

The some method short-circuits: it stops as soon as it finds the first element that passes the test, because it already knows the answer is true and looking at the rest of the herd would change nothing.

every

The every method checks whether all the elements of the array satisfy the given condition.

1// We check whether every dinosaur has an aggression level assigned
2const allHaveAggressionLevel = dinosaurs.every(dino => dino.aggressionLevel !== undefined);
3console.log("Do all the dinosaurs have an aggression level?", allHaveAggressionLevel); // true
4
5// We check whether all the dinosaurs are herbivores
6const allHerbivores = dinosaurs.every(dino => dino.diet === "herbivore");
7console.log("Are all the dinosaurs herbivores?", allHerbivores); // false

The every method short-circuits the other way round: it stops at the first element that fails the test, because a single counterexample is enough to answer false. Read out loud, the pair sounds almost like plain English - "is there some dangerous dinosaur?" and "is every dinosaur fed?".

The sort Method

The sort method orders the elements of an array and returns the sorted array. By default it compares elements as strings, which can lead to unexpected results when sorting numbers - by default [10, 9, 100] comes out as [10, 100, 9]. That is why we usually pass it a comparison function.

1// We sort the dinosaurs by name (alphabetically)
2const dinosSortedByName = [...dinosaurs].sort((a, b) => {
3  return a.name.localeCompare(b.name);
4});
5console.log("Dinosaurs sorted alphabetically:", dinosSortedByName);
6
7// We sort the dinosaurs by aggression level (from the least to the most aggressive)
8const dinosSortedByAggression = [...dinosaurs].sort((a, b) => {
9  return a.aggressionLevel - b.aggressionLevel;
10});
11console.log("Dinosaurs sorted by aggression (ascending):", dinosSortedByAggression);
12
13// We sort the dinosaurs by aggression level (from the most to the least aggressive)
14const mostDangerousFirst = [...dinosaurs].sort((a, b) => {
15  return b.aggressionLevel - a.aggressionLevel;
16});
17console.log("Dinosaurs sorted by aggression (descending):", mostDangerousFirst);

Notice that we use the spread operator (...) to create a copy of the array before sorting, because sort modifies the original array and we want to preserve the original order. The comparison function decides the direction: a negative result puts a first, which is why a.aggressionLevel - b.aggressionLevel sorts ascending, and flipping the two operands sorts descending.

Combining Methods (Chaining)

One of the most powerful features of these methods is that we can join them into "chains" and carry out complex operations on our data in a single readable expression. It works because filter, map and sort all give back an array, so the next method can be called straight on the result.

1// Let us find the names of all the herbivorous dinosaurs, sorted alphabetically
2const sortedHerbivoreNames = dinosaurs
3  .filter(dino => dino.diet === "herbivore")  // First we filter
4  .map(dino => dino.name)                     // Then we transform into an array of names
5  .sort();                                   // Finally we sort alphabetically
6
7console.log("Herbivores (alphabetically):", sortedHerbivoreNames);
8// Result: ["Brachiosaurus", "Stegosaurus", "Triceratops"]
9
10// Let us calculate the average aggression level of the carnivores
11const averageCarnivoreAggression = dinosaurs
12  .filter(dino => dino.diet === "carnivore")   // First we keep only the carnivores
13  .reduce((sum, dino, index, array) => {       // Then we reduce it down to an average
14    sum += dino.aggressionLevel;               // We add the aggression level to the sum
15
16    // If this is the last element, we divide by the number of elements
17    if (index === array.length - 1) {
18      return sum / array.length;
19    }
20
21    return sum;                               // We return the partial sum
22  }, 0);
23
24console.log("Average carnivore aggression level:", averageCarnivoreAggression); // 9.5

Read a chain from left to right and the order of its links is always the same: the array comes first, then the method that narrows it down, then the method that transforms whatever survived. The array is the receiver, so dinosaurs always opens the chain, then comes .filter(...), and only then .map(...).

1// If every record carried a ready dangerous flag, the chain would read like a sentence
2const dangerousNames = dinosaurs
3  .filter(d => d.dangerous)
4  .map(d => d.name);
5
6// Our records store a diet string instead, so the test is a little longer -
7// but the order of the links in the chain never changes
8const carnivoreNames = dinosaurs
9  .filter(d => d.diet === "carnivore")
10  .map(d => d.name);
11
12console.log("Carnivore names:", carnivoreNames);

Swapping those two links around would break everything: after .map(d => d.name) we are holding an array of plain strings, and a string has no dangerous property left to filter on. Filter first, map second - that is the order worth memorising.

Summary

The array iteration methods in JavaScript are an invaluable tool when working with collections of data. Each of them has its own speciality:

  • forEach: when we want to perform an operation on every element, without creating a new array
  • map: when we want to transform every element of the array into something else
  • filter: when we want to select the subgroup of elements that meet given criteria
  • reduce: when we want to "reduce" the array to a single value or a new structure
  • find: when we are looking for the first element that meets a given criterion
  • some/every: when we want to check whether some/all elements meet a given criterion
  • sort: when we want to put the elements of the array in order

In our Jurassic Park these methods are indispensable for managing dinosaur data efficiently, monitoring safety and making decisions based on data. As Dr. Rex puts it, iteration methods are the park's morning briefing: forEach walks the daily checklist, filter picks out only the animals that need attention, map turns them into a report, and reduce gives the control room a single number to act on.

Code for this lesson: index.js
1// Iterating over arrays - Jurassic Park
2console.log("Advanced Iterations");
3console.log("forEach, map, filter, reduce\n");
4
5// ===========================================
6// TEST DATA
7// ===========================================
8const dinosaurs = [
9  { id: 1, name: "Rexy", species: "T-Rex", age: 8, weight: 8000, sector: "A", health: 95, carnivore: true, fed: true },
10  { id: 2, name: "Blue", species: "Velociraptor", age: 5, weight: 80, sector: "B", health: 100, carnivore: true, fed: false },
11  { id: 3, name: "Charlie", species: "Velociraptor", age: 5, weight: 80, sector: "B", health: 85, carnivore: true, fed: false },
12  { id: 4, name: "Delta", species: "Velociraptor", age: 5, weight: 80, sector: "B", health: 45, carnivore: true, fed: true },
13  { id: 5, name: "Trike", species: "Triceratops", age: 7, weight: 9000, sector: "C", health: 70, carnivore: false, fed: true },
14  { id: 6, name: "Stego", species: "Stegosaurus", age: 6, weight: 5000, sector: "C", health: 92, carnivore: false, fed: false },
15  { id: 7, name: "Brachio", species: "Brachiosaurus", age: 10, weight: 50000, sector: "D", health: 88, carnivore: false, fed: true }
16];
17
18// ===========================================
19// 1. forEach() - CLASSIC ITERATION
20// ===========================================
21console.log("=== 1. forEach() - Iteration without a return value ===");
22
23console.log("\nList of all dinosaurs:");
24dinosaurs.forEach((dino, index) => {
25  const icon = dino.carnivore ? "carnivore" : "herbivore";
26  console.log(`  ${index + 1}. ${icon} ${dino.name} (${dino.species})`);
27});
28
29// forEach with side effects
30let totalWeight = 0;
31dinosaurs.forEach(dino => {
32  totalWeight += dino.weight;
33});
34console.log(`\nTotal weight: ${totalWeight}kg`);
35
36// ===========================================
37// 2. map() - DATA TRANSFORMATION
38// ===========================================
39console.log("\n=== 2. map() - Transformation of each element ===");
40
41// Example 1: Simple value extraction
42const names = dinosaurs.map(dino => dino.name);
43console.log("\nNames only:", names.join(", "));
44
45// Example 2: Creating new objects
46const summaries = dinosaurs.map(dino => ({
47  name: dino.name,
48  type: dino.carnivore ? "Carnivore" : "Herbivore",
49  status: dino.health >= 80 ? "Healthy" : "Needs attention"
50}));
51
52console.log("\nSummaries:");
53summaries.forEach(s => {
54  console.log(`  ${s.name}: ${s.type}, Status: ${s.status}`);
55});
56
57// Example 3: Calculations
58const healthPercentages = dinosaurs.map(dino => {
59  const healthStatus = dino.health >= 90 ? "*" : dino.health >= 70 ? "OK" : "!";
60  return `${healthStatus} ${dino.name}: ${dino.health}%`;
61});
62
63console.log("\nHealth levels:");
64healthPercentages.forEach(h => console.log(`  ${h}`));
65
66// ===========================================
67// 3. filter() - FILTERING
68// ===========================================
69console.log("\n=== 3. filter() - Selecting elements ===");
70
71// Example 1: One condition
72const carnivores = dinosaurs.filter(dino => dino.carnivore);
73console.log(`\nCarnivores (${carnivores.length}):`, carnivores.map(d => d.name).join(", "));
74
75// Example 2: Multiple conditions
76const healthyCarnivores = dinosaurs.filter(dino =>
77  dino.carnivore && dino.health >= 80
78);
79console.log(`Healthy carnivores (${healthyCarnivores.length}):`, healthyCarnivores.map(d => d.name).join(", "));
80
81// Example 3: Complex conditions
82const needsAttention = dinosaurs.filter(dino =>
83  dino.health < 80 || !dino.fed
84);
85console.log(`\nNeed attention (${needsAttention.length}):`);
86needsAttention.forEach(dino => {
87  const reasons = [];
88  if (dino.health < 80) reasons.push(`health: ${dino.health}%`);
89  if (!dino.fed) reasons.push("hungry");
90  console.log(`  ${dino.name}: ${reasons.join(", ")}`);
91});
92
93// Example 4: Filtering by sector
94const sectorB = dinosaurs.filter(dino => dino.sector === "B");
95console.log(`\nSector B (${sectorB.length}):`, sectorB.map(d => d.name).join(", "));
96
97// ===========================================
98// 4. reduce() - AGGREGATION
99// ===========================================
100console.log("\n=== 4. reduce() - Data aggregation ===");
101
102// Example 1: Sum
103const totalHealthPoints = dinosaurs.reduce((sum, dino) => sum + dino.health, 0);
104console.log(`\nSum of health points: ${totalHealthPoints}`);
105
106// Example 2: Average
107const avgHealth = totalHealthPoints / dinosaurs.length;
108console.log(`Average health: ${avgHealth.toFixed(1)}%`);
109
110// Example 3: Finding max/min
111const oldest = dinosaurs.reduce((max, dino) =>
112  dino.age > max.age ? dino : max
113);
114console.log(`\nOldest: ${oldest.name} (${oldest.age} years)`);
115
116const youngest = dinosaurs.reduce((min, dino) =>
117  dino.age < min.age ? dino : min
118);
119console.log(`Youngest: ${youngest.name} (${youngest.age} years)`);
120
121// Example 4: Grouping
122const bySector = dinosaurs.reduce((acc, dino) => {
123  if (!acc[dino.sector]) acc[dino.sector] = [];
124  acc[dino.sector].push(dino.name);
125  return acc;
126}, {});
127
128console.log("\nGrouping by sector:");
129Object.entries(bySector).sort().forEach(([sector, dinos]) => {
130  console.log(`  Sector ${sector}: ${dinos.join(", ")}`);
131});
132
133// Example 5: Counting occurrences
134const speciesCount = dinosaurs.reduce((acc, dino) => {
135  acc[dino.species] = (acc[dino.species] || 0) + 1;
136  return acc;
137}, {});
138
139console.log("\nCount by species:");
140Object.entries(speciesCount).forEach(([species, count]) => {
141  console.log(`  ${species}: ${count}`);
142});
143
144// Example 6: Building a statistics object
145const stats = dinosaurs.reduce((acc, dino) => {
146  acc.totalDinos++;
147  acc.totalWeight += dino.weight;
148  acc.totalAge += dino.age;
149  if (dino.carnivore) acc.carnivores++;
150  if (dino.health >= 90) acc.veryHealthy++;
151  return acc;
152}, { totalDinos: 0, totalWeight: 0, totalAge: 0, carnivores: 0, veryHealthy: 0 });
153
154console.log("\nSummary statistics:");
155console.log(`  Total dinosaurs: ${stats.totalDinos}`);
156console.log(`  Total weight: ${stats.totalWeight}kg`);
157console.log(`  Average age: ${(stats.totalAge / stats.totalDinos).toFixed(1)} years`);
158console.log(`  Carnivores: ${stats.carnivores}`);
159console.log(`  Very healthy (β‰₯90%): ${stats.veryHealthy}`);
160
161// ===========================================
162// 5. CHAINING METHODS
163// ===========================================
164console.log("\n=== 5. Method chaining ===");
165
166// Example 1: filter + map + sort
167const topHealthyCarnivores = dinosaurs
168  .filter(d => d.carnivore)
169  .filter(d => d.health >= 80)
170  .map(d => ({ name: d.name, health: d.health }))
171  .sort((a, b) => b.health - a.health)
172  .slice(0, 3);
173
174console.log("\nTOP 3 healthy carnivores:");
175topHealthyCarnivores.forEach((d, i) => {
176  console.log(`  ${i + 1}. ${d.name} - ${d.health}%`);
177});
178
179// Example 2: filter + map + reduce
180const heavyHerbivoreWeight = dinosaurs
181  .filter(d => !d.carnivore)
182  .filter(d => d.weight > 1000)
183  .map(d => d.weight)
184  .reduce((sum, weight) => sum + weight, 0);
185
186console.log(`\nTotal weight of heavy herbivores: ${heavyHerbivoreWeight}kg`);
187
188// Example 3: Complex processing
189const sectorReport = Object.entries(
190  dinosaurs
191    .filter(d => d.health >= 70)
192    .reduce((acc, d) => {
193      if (!acc[d.sector]) {
194        acc[d.sector] = { count: 0, avgHealth: 0, totalHealth: 0 };
195      }
196      acc[d.sector].count++;
197      acc[d.sector].totalHealth += d.health;
198      return acc;
199    }, {})
200)
201.map(([sector, data]) => ({
202  sector,
203  count: data.count,
204  avgHealth: (data.totalHealth / data.count).toFixed(1)
205}))
206.sort((a, b) => b.avgHealth - a.avgHealth);
207
208console.log("\nSector report (health β‰₯70%):");
209sectorReport.forEach(s => {
210  console.log(`  Sector ${s.sector}: ${s.count} dinosaurs, average health: ${s.avgHealth}%`);
211});
212
213// ===========================================
214// 6. COMPARING APPROACHES
215// ===========================================
216console.log("\n=== 6. Comparing approaches ===");
217
218console.log("\nTask: Find the names of carnivores in sector B");
219
220// Way 1: for loop (imperative)
221const result1 = [];
222for (let i = 0; i < dinosaurs.length; i++) {
223  if (dinosaurs[i].carnivore && dinosaurs[i].sector === "B") {
224    result1.push(dinosaurs[i].name);
225  }
226}
227console.log("For loop:", result1);
228
229// Way 2: forEach (partly functional)
230const result2 = [];
231dinosaurs.forEach(dino => {
232  if (dino.carnivore && dino.sector === "B") {
233    result2.push(dino.name);
234  }
235});
236console.log("forEach:", result2);
237
238// Way 3: filter + map (functional)
239const result3 = dinosaurs
240  .filter(d => d.carnivore && d.sector === "B")
241  .map(d => d.name);
242console.log("filter + map:", result3);
243
244// ===========================================
245// 7. COMPLEX EXAMPLE
246// ===========================================
247console.log("\n=== 7. Reporting system - complex example ===");
248
249class ParkReportingSystem {
250  constructor(dinosaurs) {
251    this.dinosaurs = dinosaurs;
252  }
253
254  // Main dashboard
255  getDashboard() {
256    const total = this.dinosaurs.length;
257
258    const healthStats = this.dinosaurs.reduce((acc, d) => {
259      if (d.health >= 90) acc.excellent++;
260      else if (d.health >= 70) acc.good++;
261      else acc.poor++;
262      acc.total += d.health;
263      return acc;
264    }, { excellent: 0, good: 0, poor: 0, total: 0 });
265
266    const feedingStats = this.dinosaurs.reduce((acc, d) => {
267      if (d.fed) acc.fed++;
268      else acc.hungry++;
269      return acc;
270    }, { fed: 0, hungry: 0 });
271
272    return {
273      totalDinosaurs: total,
274      avgHealth: (healthStats.total / total).toFixed(1),
275      healthBreakdown: {
276        excellent: healthStats.excellent,
277        good: healthStats.good,
278        poor: healthStats.poor
279      },
280      feeding: feedingStats
281    };
282  }
283
284  // Critical alerts
285  getCriticalAlerts() {
286    return this.dinosaurs
287      .filter(d => d.health < 50 || (d.carnivore && !d.fed))
288      .map(d => ({
289        name: d.name,
290        sector: d.sector,
291        issue: d.health < 50 ? `Critical health: ${d.health}%` : "Hungry carnivore!",
292        priority: d.health < 50 && d.carnivore && !d.fed ? "EXTREME" : "HIGH"
293      }))
294      .sort((a, b) => {
295        const priorityOrder = { "EXTREME": 0, "HIGH": 1 };
296        return priorityOrder[a.priority] - priorityOrder[b.priority];
297      });
298  }
299
300  // Health ranking
301  getHealthRanking(limit = 5) {
302    return this.dinosaurs
303      .map(d => ({
304        name: d.name,
305        species: d.species,
306        health: d.health,
307        sector: d.sector
308      }))
309      .sort((a, b) => b.health - a.health)
310      .slice(0, limit);
311  }
312
313  // Sector analysis
314  getSectorAnalysis() {
315    const sectorData = this.dinosaurs.reduce((acc, d) => {
316      if (!acc[d.sector]) {
317        acc[d.sector] = {
318          dinosaurs: [],
319          totalHealth: 0,
320          carnivores: 0,
321          unfed: 0
322        };
323      }
324      acc[d.sector].dinosaurs.push(d.name);
325      acc[d.sector].totalHealth += d.health;
326      if (d.carnivore) acc[d.sector].carnivores++;
327      if (!d.fed) acc[d.sector].unfed++;
328      return acc;
329    }, {});
330
331    return Object.entries(sectorData)
332      .map(([sector, data]) => ({
333        sector,
334        count: data.dinosaurs.length,
335        avgHealth: (data.totalHealth / data.dinosaurs.length).toFixed(1),
336        carnivores: data.carnivores,
337        unfed: data.unfed,
338        dinosaurs: data.dinosaurs
339      }))
340      .sort((a, b) => a.sector.localeCompare(b.sector));
341  }
342
343  // Display full report
344  displayFullReport() {
345    console.log("\n" + "=".repeat(70));
346    console.log("JURASSIC PARK - COMPREHENSIVE REPORT");
347    console.log("=".repeat(70));
348
349    // Dashboard
350    const dashboard = this.getDashboard();
351    console.log("\nDASHBOARD");
352    console.log(`   Total dinosaurs: ${dashboard.totalDinosaurs}`);
353    console.log(`   Average health: ${dashboard.avgHealth}%`);
354    console.log(`   Health - Excellent: ${dashboard.healthBreakdown.excellent}, Good: ${dashboard.healthBreakdown.good}, Poor: ${dashboard.healthBreakdown.poor}`);
355    console.log(`   Feeding - Fed: ${dashboard.feeding.fed}, Hungry: ${dashboard.feeding.hungry}`);
356
357    // Alerts
358    const alerts = this.getCriticalAlerts();
359    if (alerts.length > 0) {
360      console.log("\nCRITICAL ALERTS");
361      alerts.forEach((alert, i) => {
362        console.log(`   ${i + 1}. [${alert.priority}] ${alert.name} (Sector ${alert.sector}): ${alert.issue}`);
363      });
364    } else {
365      console.log("\nNo critical alerts");
366    }
367
368    // Ranking
369    const ranking = this.getHealthRanking(5);
370    console.log("\nTOP 5 - HEALTH RANKING");
371    ranking.forEach((d, i) => {
372      console.log(`   ${i + 1}. ${d.name} (${d.species}) - ${d.health}% - Sector ${d.sector}`);
373    });
374
375    // Sector analysis
376    const sectorAnalysis = this.getSectorAnalysis();
377    console.log("\nSECTOR ANALYSIS");
378    sectorAnalysis.forEach(sector => {
379      console.log(`   Sector ${sector.sector}:`);
380      console.log(`     Dinosaurs: ${sector.count} - Average health: ${sector.avgHealth}%`);
381      console.log(`     Carnivores: ${sector.carnivores}, Hungry: ${sector.unfed}`);
382      console.log(`     List: ${sector.dinosaurs.join(", ")}`);
383    });
384  }
385}
386
387// Run the reporting system
388const reportSystem = new ParkReportingSystem(dinosaurs);
389reportSystem.displayFullReport();
390
391console.log("\nCongratulations! You've mastered advanced iterations!");

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What is the key difference between map() and forEach()?

  2. 2. What is the reduce() method on an array used for?

Hands-on tasks in the game

  • Horizontal ordering

    Arrange the elements of filtering and mapping an array:

Useful articles