Packages

map

Look values up by key with map(), whose keys can be strings, numbers or booleans, and know when a typed map or an object fits better.

import "std:map"built in, no import needed

A map stores values under keys and finds them again by key. OSL has three tools for this, and picking the right one is most of the skill:

  • An object has string keys and values of any type. It is the shape JSON has.
  • A typed map such as string[int] has string keys and one value type, checked by the compiler.
  • map(), the subject of this page, is a map object with explicit methods (set, get, has) whose keys may be strings, numbers or booleans.

map is part of the language, so there is nothing to import.

Making a map and using it#

main.osl
map ages = map()
ages.set("ada", 36)
ages.set("bo", 41)

log ages.get("ada")
log ages.size()
log ages.has("bo") ages.has("cy")
Click the code to edit it

set stores a value, replacing any earlier one under that key. get returns it, has asks whether the key is present, and size() counts the keys.

Missing keys#

get gives null for a key that isn't there, so ?? supplies a default:

main.osl
map ages = map()
ages.set("ada", 36)

log ages.get("zed")
log ages.get("zed") ?? 0
Click the code to edit it

A key can hold null as its value, and has is how you tell that apart from a missing key:

main.osl
map notes = map()
notes.set("draft", null)

log notes.get("draft") == null
log notes.has("draft")
log notes.has("final")
Click the code to edit it

Changing and removing#

delete(key) removes a key and clear() removes everything. Both leave the map itself in place:

main.osl
map stock = map()
stock.set("apples", 12)
stock.set("pears", 0)
stock.set("plums", 7)

stock.delete("pears")
log stock.size()
stock.clear()
log stock.size()
Click the code to edit it

set returns the map, so setting can be chained: stock.set("a", 1).set("b", 2).

Keys can be numbers#

This is what map() has that an object doesn't. An object's keys are always strings, but a map keeps numbers and booleans as they are:

main.osl
map names = map()
names.set(1, "one")
names.set(2, "two")
names.set(true, "yes")

log names.get(1)
log names.get(true)
log names.get("1")
Click the code to edit it

The number 1 and the text "1" are different keys. The number 1 and 1.0 are the same key. Text keys are matched exactly, so "Ada" and "ada" are different, unlike == on strings.

Keys can be strings, numbers, booleans or null. An array or object as a key stops the program with a TypeError: use a string made from it instead, such as x ++ "," ++ y for a grid position.

Looking at everything#

keys(), values() and entries() return arrays. entries() is an array of [key, value] pairs. A map has no fixed order, so sort before printing:

main.osl
map prices = map()
prices.set("tea", 3)
prices.set("coffee", 4)
prices.set("juice", 5)

log prices.keys().toSorted()
log prices.values().toSorted()
log prices.entries().toSorted()
Click the code to edit it

You can also loop over the map directly with for key, value in:

main.osl
map prices = map()
prices.set("tea", 3)
prices.set("coffee", 4)

int total = 0
for _, price in prices (
  total += price
)
log total
Click the code to edit it

getKeys() and getValues() are older names for keys() and values() and give a deprecation warning.

Counting#

The standard use is counting how often something happens: one key per thing, the count as the value. get(k) ?? 0 handles the first time a key is seen:

main.osl
map counts = map()
for word in "the cat and the hat and the end".split(" ") (
  counts.set(word, (counts.get(word) ?? 0) + 1)
)

for word in counts.keys().toSorted() (
  log word counts.get(word)
)
Click the code to edit it

Grouping#

Values are untyped (any), so when you store arrays, say what you expect when you take them out, with .<string[]>:

main.osl
map byLetter = map()
for word in ["apple", "avocado", "banana", "blueberry", "cherry"] (
  string letter = word.left(1)
  string[] group = (byLetter.get(letter) ?? []).<string[]>
  group.append(word)
  byLetter.set(letter, group)
)

for letter in byLetter.keys().toSorted() (
  log letter byLetter.get(letter)
)
Click the code to edit it

Caching results#

A map remembers the answer for an input, so an expensive function runs only once per value. Check with has, so a stored null or 0 is still a hit:

main.osl
map cache = map()
int calls = 0

def square(int n) int (
  if cache.has(n) ( return cache.get(n) )
  calls++
  int answer = n * n
  cache.set(n, answer)
  return answer
)

log square(4) square(4) square(5)
log calls
Click the code to edit it

For a cache that expires or has a size limit, use std:cache instead.

Typed maps#

When every value has the same type, a typed map checks that for you. string[int] has string keys and int values, and is written and read with brackets, like an object. The objects lesson introduces it. Typed maps have their own methods, which differ from map()'s:

main.osl
string[int] stock = {apples: 12, pears: 0, plums: 7}

log stock["apples"]
log stock.len
log stock.contains("apples") stock.contains("kiwi")
log stock.getKeys().toSorted()
log stock.getValues().toSorted()
log stock.pick("apples", "plums")
log stock.omit("pears")
log stock.merge({kiwi: 3})
Click the code to edit it

pick, omit and merge return new maps and leave stock as it was. Two things differ from map(). Reading a key that isn't there gives the zero value for the type (0 for an int, "" for a string), not null, so use contains to test for presence. And the helpers are named len, getKeys and getValues, where map() has size(), keys() and values().

stock.toMap() converts a typed map or an object into a map().

Which one#

You haveUse
Data from JSON, or keys you don't controlan object
String keys, all values one type, checked by the compilera typed map: string[int]
Number or boolean keys, or you want has, set and get as explicit callsmap()
A fixed set of named fieldsa struct
Unique values only, no values attacheda set
Quick check

What does this print?

osl
map names = map()
names.set(1, "one")
log names.get("1")
Your turn

Most common word

Count the words in the sentence with a map(), then print the word that appears most often and how many times it appeared.

main.osl
Ctrl Enter runs
Expected output
the 3

Reference#

map has no package, so its API is listed here. value is a map made with map().

MethodReturnsWhat it does
map()mapMakes an empty map.
value.set(key, v)mapStores v under key, replacing any earlier value, and returns the map.
value.get(key)anyThe value under key, or null when there is none.
value.has(key)booleanTrue if key is present, even when its value is null.
value.delete(key)nothingRemoves key. Does nothing if it isn't there.
value.size()intThe number of keys.
value.clear()nothingRemoves every key.
value.keys()arrayThe keys, in no fixed order.
value.values()arrayThe values, in no fixed order.
value.entries()arrayAn array of [key, value] pairs, in no fixed order.

Keys must be strings, numbers, booleans or null. getKeys() and getValues() are deprecated aliases of keys() and values().