Lists

A shopping list, the scores in a game, the names in a class: most things worth a program come in lists. In TJS a list is called an array: values in order, inside square brackets, separated by commas.

Making a list

const fruits = ['apple', 'banana', 'cherry']

preview.append(`The first fruit is ${fruits[0]}, and there are ${fruits.length} of them.`)

test 'reading a list' {
  expect(fruits[0]).toBe('apple')
  expect(fruits[2]).toBe('cherry')
  expect(fruits.length).toBe(3)
}

Ask the console for fruits[1], then fruits[3]: there is no fourth fruit, so the answer is undefined, meaning "nothing here".

Going through a list

for … of runs a block once for each item, with a name for the current one:

function total(prices: [0.0]) {
  let sum = 0
  for (const price of prices) {
    sum = sum + price
  }
  return sum
}

preview.append(`The bill comes to ${total([2.5, 4, 1.25])}.`)

test 'adding up a list' {
  expect(total([2.5, 4, 1.25])).toBe(7.75)
  expect(total([])).toBe(0)
}

The example [0.0] says what prices is: a list of numbers. An empty list is still a list of numbers, so total([]) is fine (and 0). A list with a word in it, or a single number that is not in a list, is refused with an error that says what was expected.

Growing a list

push adds an item to the end. A loop and push together build a list:

function squares(n: 0) {
  const result = []
  for (let i = 1; i <= n; i++) {
    result.push(i * i)
  }
  return result
}

preview.append(squares(6).join(', '))

test 'the first squares' {
  expect(squares(4)).toEqual([1, 4, 9, 16])
  expect(squares(0)).toEqual([])
}

join(', ') turns a list into one string, with ', ' between the items. In a test, compare lists with toEqual, which looks at what is inside them.

Changing every item: map

map makes a new list by doing the same thing to every item:

const prices = [10, 20, 30]
const withTax = prices.map((price) => price * 1.5)

preview.append(withTax.join(', '))

test 'map does the same thing to every item' {
  expect(withTax).toEqual([15, 30, 45])
  expect(prices).toEqual([10, 20, 30])
}

(price) => price * 1.5 is a small function with no name: given a price, it gives back price * 1.5. The => arrow is a short way to write a function you only need here. map calls it once for each item and collects the answers. The original list is not changed: the second check in the test says so.

Keeping some items: filter

filter makes a new list of only the items your function says yes to:

const scores = [42, 87, 65, 91, 38, 75]
const passing = scores.filter((score) => score >= 50)

preview.append(`${passing.length} passed: ${passing.join(', ')}`)

test 'filter keeps what passes the test' {
  expect(passing).toEqual([87, 65, 91, 75])
}

Showing a list on the page

Put map and the building blocks from Hello, Screen together, and a list of data becomes a list on the page:

import { elements } from 'tosijs'

const { ul, li } = elements

const planets = ['Mercury', 'Venus', 'Earth', 'Mars']

preview.append(ul(...planets.map((planet) => li(planet))))

ul makes a list and li makes one item in it. planets.map(…) turns each name into an item, and the ... in front spreads the items out, so ul gets them one by one instead of as a single list. Add a planet to the array and it appears on the page.

Try it

Write longest(words: ['']) that gives back the longest word in a list, and test it with a few lists, including one with two words of the same length (decide which one should win, and test that). Then show the words from a list that are longer than four letters, as a list on the page.