# JavaScript Array & Object Lab

20 challenges. Each transform must leave the JSON input unchanged. Browser execution is isolated and time-limited.

## 1. Double every number

map creates an output entry for each input element. Return the new value from the callback. It is useful when the shape stays an array but each item needs a transformation. The original input should remain unchanged.

**Task:** Return every number multiplied by 2.

```javascript
function transform(data) {
  return data.map(n => n * 2);
}
```

## 2. Keep positive values

filter keeps elements for which a predicate is truthy. It does not transform the retained values. Be precise about whether zero should be included: positive means strictly greater than zero.

**Task:** Return only numbers greater than zero.

```javascript
function transform(data) {
  return data.filter(n => n > 0);
}
```

## 3. Total an array

reduce carries an accumulator through the array. Supply an initial value so the empty case has a defined result. For a sum, zero is the identity: adding it leaves the other value unchanged.

**Task:** Return the sum, or 0 for an empty array.

```javascript
function transform(data) {
  return data.reduce((total, n) => total + n, 0);
}
```

## 4. Sort numbers without mutation

sort changes the array on which it is called. Its default comparison is based on string representations, which is usually wrong for numeric ordering. Copy the array first and supply a numeric comparator.

**Task:** Return numbers in ascending numeric order without changing data.

```javascript
function transform(data) {
  return [...data].sort((a, b) => a - b);
}
```

## 5. Find a matching item

find returns the first matching element, or undefined if none matches. A deliberate fallback makes the output contract easier to use. Here an ID is compared strictly, so text and numeric IDs are distinct.

**Task:** Return the object with id 2, or null.

```javascript
function transform(data) {
  return data.find(item => item.id === 2) ?? null;
}
```

## 6. Check whether any item qualifies

some stops once a predicate succeeds and returns a boolean. It is useful when you only need to know whether a match exists, not build an array of matches. The empty array contains no matching item.

**Task:** Return whether any number is greater than 10.

```javascript
function transform(data) {
  return data.some(n => n > 10);
}
```

## 7. Check every item

every returns true when no element fails the predicate. It returns true for an empty array because there is no counterexample. If your application requires at least one item, that is a separate condition.

**Task:** Return whether every number is nonnegative; empty input returns true.

```javascript
function transform(data) {
  return data.every(n => n >= 0);
}
```

## 8. Remove duplicate primitives

Set stores unique values and preserves insertion order during iteration. Spread the set into an array when your output needs array methods. This is primitive-value deduplication, not a general way to merge different objects with similar fields.

**Task:** Return unique numbers in their first-seen order.

```javascript
function transform(data) {
  return [...new Set(data)];
}
```

## 9. Normalize names

Text normalization often combines whitespace trimming and a case convention. Choose a convention appropriate for the application. This lesson lowercases text for a simple matching key; it does not claim to solve every international name-matching problem.

**Task:** Trim each string and convert it to lowercase.

```javascript
function transform(data) {
  return data.map(s => s.trim().toLowerCase());
}
```

## 10. Project object fields

A projection picks the fields required by the next step. Object destructuring names fields clearly. Returning a new object avoids accidentally changing or exposing unrelated fields in the source record.

**Task:** Return new objects containing only id and name.

```javascript
function transform(data) {
  return data.map(({id, name}) => ({id, name}));
}
```

## 11. Update records immutably

An object spread makes a shallow copy. Replacing a top-level field on the new object does not modify the original object. Nested objects remain shared unless separately copied; the copy explorer makes that distinction visible.

**Task:** Return records with active set to true, preserving other fields.

```javascript
function transform(data) {
  return data.map(item => ({...item, active: true}));
}
```

## 12. Preserve zero with defaults

The nullish-coalescing operator uses a fallback only for null or undefined. Logical OR also replaces values such as zero and false. Choose the operator according to the field’s contract rather than treating all falsey values as missing.

**Task:** Return each record’s count, defaulting missing or null counts to 10.

```javascript
function transform(data) {
  return data.map(item => item.count ?? 10);
}
```

## 13. Flatten one level

flatMap maps each element and flattens the resulting arrays by one level. It is useful for expanding a record into several values. It does not recursively flatten arbitrary nested structures.

**Task:** Return one array of all tags from the records.

```javascript
function transform(data) {
  return data.flatMap(item => item.tags);
}
```

## 14. Build a lookup object

Object.fromEntries turns key-value pairs into an object. Repeated keys are overwritten by later entries. This can be useful for a lookup, but only if last-write-wins is the policy you want.

**Task:** Build an object mapping each id to its name; later duplicate ids win.

```javascript
function transform(data) {
  return Object.fromEntries(data.map(item => [item.id, item.name]));
}
```

## 15. Count categories

A count report combines many records into one summary. Map avoids accidental collisions with inherited object properties while accumulating arbitrary text keys. Convert its entries at the end if a JSON object is needed.

**Task:** Return an object counting each category string.

```javascript
function transform(data) {
  const counts = new Map();
  for (const category of data) counts.set(category, (counts.get(category) ?? 0) + 1);
  return Object.fromEntries(counts);
}
```

## 16. Make a safe page slice

slice returns a portion without mutating the source. Its end position is excluded. A slice beyond the array length simply stops at the available end, making the empty and short-array cases straightforward.

**Task:** Return elements at indexes 1 and 2, if present.

```javascript
function transform(data) {
  return data.slice(1, 3);
}
```

## 17. Filter then project

Chaining transformations can express a data question directly: choose records, then select the field to present. Order matters because later callbacks receive the shape produced by earlier steps. Keep the intermediate shapes clear.

**Task:** Return names of active records, preserving input order.

```javascript
function transform(data) {
  return data.filter(item => item.active === true).map(item => item.name);
}
```

## 18. Calculate a cart total

A cart line has quantity and unit price; total each line, then accumulate. These examples use integer cents to avoid representing currency as approximate decimal fractions. Real applications also define tax and rounding rules.

**Task:** Return the sum of quantity × cents for every line.

```javascript
function transform(data) {
  return data.reduce((sum, item) => sum + item.quantity * item.cents, 0);
}
```

## 19. Find the maximum or nothing

Math.max with no inputs returns negative infinity, which is not the contract here. Handle the empty array explicitly and choose a starting value that also works when all numbers are negative. This reducer avoids spreading a potentially huge input.

**Task:** Return the greatest number, or null if empty.

```javascript
function transform(data) {
  return data.length ? data.reduce((best, n) => Math.max(best, n), data[0]) : null;
}
```

## 20. Capstone: a ranked report

Combine filtering, copying, sorting and projection. A numeric secondary sort key makes tied scores deterministic. The goal is a new report, leaving the original records and their order untouched. Test empty input and ties as well as the obvious case.

**Task:** Return names of active people sorted by score descending, then numeric id ascending.

```javascript
function transform(data) {
  return data.filter(x => x.active).sort((a, b) => b.score - a.score || a.id - b.id).map(x => x.name);
}
```

