LMU ☀️ CMSI 3801
LANGUAGES AND AUTOMATA I
HOMEWORK #1 PARTIAL ANSWERS
exercises.js
import crypto from "crypto"

export function change(amount) {
  if (amount < 0) {
    throw new RangeError("amount cannot be negative")
  }
  let [coins, remaining] = [[], amount]
  for (let denomination of [25, 10, 5]) {
    coins.push(Math.floor(remaining / denomination))
    remaining %= denomination
  }
  coins.push(remaining)
  return coins
}

export function stretched(s) {
  return [...s.replaceAll(" ", "")].map((c, i) => c.repeat(i + 1)).join("")
}

export function powers(base, limit, consume) {
  for (let power = 1; power <= limit; power *= base) {
    consume(power)
  }
}

export function* powersGenerator(base, limit) {
  for (let power = 1; power <= limit; power *= base) {
    yield power
  }
}

/*
 * The world-famous curried say function.
 *
 * This function can be reduced to one line:
 *
 *   s = a => a===undefined ? '' : b => b===undefined ? a : s(a + ' ' + b)
 *
 * If you take some liberties with it, which you shouldn't, you can get:
 *
 *   s = a => !a ? '' : b => !b ? a : s(a + ' ' + b)
 *
 * or golfed even more:
 *
 *   s=a=>a?b=>b?s(a+' '+b):a:''
 *
 * but falsiness is not the same as undefined so, it's cute but not right. The
 * empty string (and for that matter 0, 0n, null, and NaN) are perfectly good
 * arguments and should be part of a chain.
 */
export function say(first) {
  if (first === undefined) {
    return ""
  }
  return (second) => {
    if (second === undefined) {
      return first
    }
    return say(`${first} ${second}`)
  }
}

/* Returns an array of two functions, an encyptor and a decryptor, each using a
 * given key, algorithm, and initialization vector. The encryptor turns a UTF-8
 * encoded string into a hex-string; the decryptor does the reverse.
 */
export function makeCryptoFunctions({ forKey: key, using: algorithm, withIV: iv }) {
  return [
    (data) => {
      const cipher = crypto.createCipheriv(algorithm, key, iv)
      return cipher.update(data, "utf-8", "hex") + cipher.final("hex")
    },
    (data) => {
      const cipher = crypto.createDecipheriv(algorithm, key, iv)
      return cipher.update(data, "hex", "utf-8") + cipher.final("utf-8")
    },
  ]
}

export function topTenScorers(stats) {
  return Object.entries(stats)
    .flatMap(([team, players]) => players.map((player) => [...player, team]))
    .filter(([, games, ,]) => games >= 15)
    .map(([name, games, points, team]) => ({ name, ppg: points / games, team }))
    .sort((p1, p2) => p2.ppg - p1.ppg)
    .slice(0, 10)
}

export async function pokemonInfo(name) {
  name = encodeURIComponent(name)
  const url = `https://pokeapi.co/api/v2/pokemon/${name}/`
  const response = await fetch(url)
  if (response.status < 200 || response.status > 299) {
    return { error: `No information for ${decodeURIComponent(name)}` }
  }
  const data = await response.json()
  return { id: data.id, name: data.name, weight: data.weight }
}

export class Quaternion {
  #a
  #b
  #c
  #d
  constructor(a, b, c, d) {
    this.#a = a
    this.#b = b
    this.#c = c
    this.#d = d
  }
  plus(q) {
    return new Quaternion(this.#a + q.#a, this.#b + q.#b, this.#c + q.#c, this.#d + q.#d)
  }
  times(q) {
    return new Quaternion(
      q.#a * this.#a - q.#b * this.#b - q.#c * this.#c - q.#d * this.#d,
      q.#a * this.#b + q.#b * this.#a - q.#c * this.#d + q.#d * this.#c,
      q.#a * this.#c + q.#b * this.#d + q.#c * this.#a - q.#d * this.#b,
      q.#a * this.#d - q.#b * this.#c + q.#c * this.#b + q.#d * this.#a
    )
  }
  coefficients() {
    return [this.#a, this.#b, this.#c, this.#d]
  }
}