OxL Справочник OxL
РусскийEnglish

Текст и командная строка

Частоты слов#

Задача. Посчитать, сколько раз встречается каждое слово, и вывести самые частые.

oxl✓ проверяется в CI
const TEXT = "the quick brown fox jumps over the lazy dog the fox"

fn countWords(text: str) -> Map<str, u64>  pure {
  var counts: Map<str, u64> = {:}
  for w in text.splitWhitespace() {
    counts[w.lower()] = (counts.get(w.lower()) ?? 0) + 1  // get возвращает u64?, ?? 0 — если слова ещё не было
  }
  counts
}

fn main() io {
  let counts = countWords(TEXT)
  let top = from (w, n) in counts order by n desc, w take 3 select "{w}: {n}"  // топ-3: по убыванию частоты, затем по алфавиту
  for line in top { print(line) }
  print("distinct words: {counts.len()}")
}
Вывод
the: 3
fox: 2
brown: 1
distinct words: 8
  • counts[w.lower()] = (counts.get(w.lower()) ?? 0) + 1 — классический счётчик: get возвращает u64?, ?? 0 задаёт начальное значение.
  • Запрос from (w, n) in counts order by n desc, w take 3 select … сортирует пары словаря и берёт первые три. Второй ключ сортировки (w) делает порядок при равных частотах детерминированным.
  • Подводный камень: Map итерируется в порядке вставки, а не по алфавиту — если нужен алфавитный порядок, сортируйте явно.

Разбор CSV с ошибками#

Задача. Прочитать табличные данные, найти плохие строки и агрегировать хорошие.

oxl✓ проверяется в CI
data Sale { region: str; product: str; qty: i64; price: i64 }

@error data BadRow { line: usize; why: str }

const CSV = "region,product,qty,price\nnorth,pen,10,3\nsouth,ink,2,15\nnorth,ink,1,15\nsouth,pen,7,3"

fn parseRow(n: usize, line: str) -> Sale | BadRow {
  let cols = line.split(",")
  if cols.len() != 4 { fail BadRow{ line: n, why: "expected 4 columns, got {cols.len()}" } }
  let qty = match cols[2].trim().parseInt() {
    i64 v => v
    ParseError => fail BadRow{ line: n, why: "qty is not a number" }
  }
  let price = match cols[3].trim().parseInt() {
    i64 v => v
    ParseError => fail BadRow{ line: n, why: "price is not a number" }
  }
  Sale{ region: cols[0], product: cols[1], qty, price }
}

fn parseAll(text: str) -> List<Sale> | BadRow {
  var rows: List<Sale> = []
  for (i, line) in text.lines().iter().enumerate().skip(1) {  // пропускаем заголовок
    rows.push(parseRow(i + 1, line)?)
  }
  rows
}

fn main() io {
  match parseAll(CSV) {
    List<Sale> rows => {
      let byRegion = from s in rows group by s.region into g select (g.key, g.count())
      for (region, n) in byRegion { print("{region}: {n} sales") }
      let revenue = rows.iter().map(s => s.qty * s.price).sum()
      print("revenue: {revenue}")
    }
    BadRow e => print("line {e.line}: {e.why}")
  }
  match parseAll("h\nnorth,pen,x,3") {
    List<Sale> rows => print("{rows.len()} rows")
    BadRow e => print("line {e.line}: {e.why}")
  }
}
Вывод
north: 2 sales
south: 2 sales
revenue: 96
line 2: qty is not a number
  • parseRow возвращает Sale | BadRow: ошибка знает номер строки и причину. fail BadRow{ … } — ранний выход.
  • match s.parseInt() разбирает i64 | ParseError; ошибку преобразуем в свою, более осмысленную.
  • rows.push(parseRow(i + 1, line)?) — ? останавливает разбор на первой плохой строке. Чтобы собрать все ошибки, накопите их в List<BadRow> вместо ?.
  • from s in rows group by s.region into g select (g.key, g.count()) — группировка; rows.iter().map(…).sum() — агрегат.
  • Для настоящих CSV с кавычками используйте библиотеку формата; split(",") годится для простых данных.

Аргументы командной строки#

Задача. Разобрать --name Ann --times 3 --loud без внешних библиотек.

oxl✓ проверяется в CI
use std.os { args }

@error data Usage { msg: str }

data Options { name: str; times: i64; loud: bool }

fn parseArgs(argv: List<str>) -> Options | Usage {
  var opts = Options{ name: "world", times: 1, loud: false }
  var i = 0
  while i < argv.len() {
    match argv[i] {
      "--name" => {
        opts.name = argv.get(i + 1) ?? fail Usage{ msg: "--name needs a value" }
        i += 1
      }
      "--times" => {
        let raw = argv.get(i + 1) ?? fail Usage{ msg: "--times needs a value" }
        match raw.parseInt() {
          i64 n => opts.times = n
          ParseError => fail Usage{ msg: "--times: not a number: {raw}" }
        }
        i += 1
      }
      "--loud" => opts.loud = true
      other => fail Usage{ msg: "unknown option {other}" }
    }
    i += 1
  }
  opts
}

fn main() -> () | Usage  io {
  let opts = parseArgs(args().iter().skip(1).toList())?  // args()[0] — имя программы
  for _ in 0..opts.times {
    let text = "Hello, {opts.name}!"
    print(if opts.loud { text.upper() } else { text })
  }
  print("{parseArgs(["--times", "2", "--loud", "--name", "Ann"]) is Options}")
  match parseArgs(["--bogus"]) {
    Options _ => print("ok")
    Usage e => print("usage error: {e.msg}")
  }
}
Вывод
Hello, world!
true
usage error: unknown option --bogus
  • std.os.args() возвращает List<str>; нулевой элемент — имя программы, поэтому skip(1). Запуск: oxl run prog.oxl -- --name Ann --loud.
  • Разбор вынесен в чистую функцию parseArgs(argv): её удобно тестировать без запуска программы (в примере она вызывается с готовыми списками).
  • argv.get(i + 1) ?? fail Usage{…} — «значение или ошибка использования»: fail — выражение типа never, поэтому его можно написать справа от ??.
  • fn main() -> () | Usage io — необработанная Usage завершит процесс с кодом 1 и сообщением.

Выровненная таблица#

oxl✓ проверяется в CI
fn row(cols: List<str>, widths: &List<usize>) -> str  pure {
  var parts: List<str> = []
  for (i, c) in cols.iter().enumerate() {
    parts.push(c.padEnd(widths[i], ' '))  // padEnd/padStart выравнивают по ширине
  }
  parts.iter().fold("", (a, p) => if a.isEmpty() { p } else { "{a} | {p}" })
}

fn main() io {
  let items = [("pen", 3, 10), ("notebook", 120, 2), ("ink", 15, 1)]
  let widths: List<usize> = [10, 6, 5]
  print(row(["item", "price", "qty"], &widths))
  print("-".repeat(25))
  var total = 0
  for (name, price, qty) in items {
    print(row([name, "{price}".padStart(5, ' '), "{qty}".padStart(5, ' ')], &widths))
    total += price * qty
  }
  print("total: {total}")
}
Вывод
item       | price  | qty
-------------------------
pen        |     3  |    10
notebook   |   120  |     2
ink        |    15  |     1
total: 285

padEnd и padStart дополняют строку до ширины; "-".repeat(25) рисует линию. Ширины колонок передаются заимствованием (&widths): список остаётся у вызывающего и используется многократно (без & второй вызов дал бы use-after-move).

Файлы#

Задача. Записать файл, дописать в него, прочитать, посмотреть метаданные и каталог, аккуратно обработать отсутствие файла.

oxl✓ проверяется в CI
use std.io { File, fs }

fn main() -> () | IoError  io {
  let dir = fs.tempDir()?  // временный каталог, удалим в конце
  let path = "{dir}/notes.txt"
  File.writeText(path, "first\nsecond\n")?
  File.appendText(path, "third\n")?
  let text = File.readText(path)?
  let lines = text.lines()
  print("{lines.len()} lines, last: {lines.last() ?? "-"}")
  let meta = File.metadata(path)?
  print("size {meta.size} bytes, is file: {meta.isFile()}")
  File.writeText("{dir}/other.txt", "x")?
  let entries = fs.list(dir)?
  let names = entries.iter().map(e => e.name).sortBy(n => n).toList()
  print("{names}")
  match File.readText("{dir}/missing.txt") {  // ошибка ввода-вывода — значение
    str _ => print("unexpected")
    IoError e => print("cannot read: {e.kind}")
  }
  fs.removeAll(dir)?
  print("cleaned: {!File.exists(path)}")
}
Вывод
3 lines, last: third
size 19 bytes, is file: true
["notes.txt", "other.txt"]
cannot read: NotFound
cleaned: true
  • Все операции std.io.File возвращают … | IoError и требуют эффекта io. fn main() -> () | IoError io позволяет писать ? везде.
  • fs.tempDir() создаёт временный каталог — безопасно для примеров и тестов; fs.removeAll удаляет его.
  • IoError — значение с полем kind (NotFound, PermissionDenied, …); так можно реагировать по-разному: создать файл, если его нет, но остановиться при отказе в доступе.
  • Для больших файлов читайте по частям потоками (std.stream), а не readText целиком.

Вернуться: кулинарная книга.

См. также

OxL 1.0.0-beta · Версия документации 1.0.0-beta© OxwaldLink · сгенерировано из Стандарта, исходников std и руководства