// 由 tools/gen_adjacency_graphs.py 从 data/upstream/dropbox-zxcvbn/src/
// adjacency_graphs.coffee 生成(MIT, (c) Dropbox, Inc.),请勿手工编辑。
//
// 重新生成:python3 tools/gen_adjacency_graphs.py
//
// 邻接图语义与上游一致:
// * 键为键盘上的一个键(同时收录大小写两种形态,与上游相同);
// * 值为长度 6(qwerty/dvorak)或 8(keypad/mac_keypad)的方向槽数组,
//   下标即 spatial_match_helper 里 `cur_direction` 的递增序号;
// * 条目 `(未移位, 移位)`;`None` 表示该方向无相邻键;
// * 单字符条目(keypad 系)第二位填同字符,对应上游字符串 index 恒为 0 的语义。

///|
/// `qwerty` 邻接图(上游 `adjacency_graphs.qwerty`)。
///
/// 每个条目 `(未移位字符, 移位字符)`;`None` 是该方向没有相邻键。占位的 `None`
/// 必须保留——数组下标即 spatial_match_helper 的 direction 计数。
let qwerty : Lazy[Map[Char, Array[(Char, Char)?]]] = Lazy(() => {
  Map([
    (
      '!',
      [Some(('`', '~')), None, None, Some(('2', '@')), Some(('q', 'Q')), None],
    ),
    (
      '"',
      [
        Some((';', ':')),
        Some(('[', '{')),
        Some((']', '}')),
        None,
        None,
        Some(('/', '?')),
      ],
    ),
    (
      '#',
      [
        Some(('2', '@')),
        None,
        None,
        Some(('4', '$')),
        Some(('e', 'E')),
        Some(('w', 'W')),
      ],
    ),
    (
      '$',
      [
        Some(('3', '#')),
        None,
        None,
        Some(('5', '%')),
        Some(('r', 'R')),
        Some(('e', 'E')),
      ],
    ),
    (
      '%',
      [
        Some(('4', '$')),
        None,
        None,
        Some(('6', '^')),
        Some(('t', 'T')),
        Some(('r', 'R')),
      ],
    ),
    (
      '&',
      [
        Some(('6', '^')),
        None,
        None,
        Some(('8', '*')),
        Some(('u', 'U')),
        Some(('y', 'Y')),
      ],
    ),
    (
      '\'',
      [
        Some((';', ':')),
        Some(('[', '{')),
        Some((']', '}')),
        None,
        None,
        Some(('/', '?')),
      ],
    ),
    (
      '(',
      [
        Some(('8', '*')),
        None,
        None,
        Some(('0', ')')),
        Some(('o', 'O')),
        Some(('i', 'I')),
      ],
    ),
    (
      ')',
      [
        Some(('9', '(')),
        None,
        None,
        Some(('-', '_')),
        Some(('p', 'P')),
        Some(('o', 'O')),
      ],
    ),
    (
      '*',
      [
        Some(('7', '&')),
        None,
        None,
        Some(('9', '(')),
        Some(('i', 'I')),
        Some(('u', 'U')),
      ],
    ),
    (
      '+',
      [Some(('-', '_')), None, None, None, Some((']', '}')), Some(('[', '{'))],
    ),
    (
      ',',
      [
        Some(('m', 'M')),
        Some(('k', 'K')),
        Some(('l', 'L')),
        Some(('.', '>')),
        None,
        None,
      ],
    ),
    (
      '-',
      [
        Some(('0', ')')),
        None,
        None,
        Some(('=', '+')),
        Some(('[', '{')),
        Some(('p', 'P')),
      ],
    ),
    (
      '.',
      [
        Some((',', '<')),
        Some(('l', 'L')),
        Some((';', ':')),
        Some(('/', '?')),
        None,
        None,
      ],
    ),
    (
      '/',
      [Some(('.', '>')), Some((';', ':')), Some(('\'', '"')), None, None, None],
    ),
    (
      '0',
      [
        Some(('9', '(')),
        None,
        None,
        Some(('-', '_')),
        Some(('p', 'P')),
        Some(('o', 'O')),
      ],
    ),
    (
      '1',
      [Some(('`', '~')), None, None, Some(('2', '@')), Some(('q', 'Q')), None],
    ),
    (
      '2',
      [
        Some(('1', '!')),
        None,
        None,
        Some(('3', '#')),
        Some(('w', 'W')),
        Some(('q', 'Q')),
      ],
    ),
    (
      '3',
      [
        Some(('2', '@')),
        None,
        None,
        Some(('4', '$')),
        Some(('e', 'E')),
        Some(('w', 'W')),
      ],
    ),
    (
      '4',
      [
        Some(('3', '#')),
        None,
        None,
        Some(('5', '%')),
        Some(('r', 'R')),
        Some(('e', 'E')),
      ],
    ),
    (
      '5',
      [
        Some(('4', '$')),
        None,
        None,
        Some(('6', '^')),
        Some(('t', 'T')),
        Some(('r', 'R')),
      ],
    ),
    (
      '6',
      [
        Some(('5', '%')),
        None,
        None,
        Some(('7', '&')),
        Some(('y', 'Y')),
        Some(('t', 'T')),
      ],
    ),
    (
      '7',
      [
        Some(('6', '^')),
        None,
        None,
        Some(('8', '*')),
        Some(('u', 'U')),
        Some(('y', 'Y')),
      ],
    ),
    (
      '8',
      [
        Some(('7', '&')),
        None,
        None,
        Some(('9', '(')),
        Some(('i', 'I')),
        Some(('u', 'U')),
      ],
    ),
    (
      '9',
      [
        Some(('8', '*')),
        None,
        None,
        Some(('0', ')')),
        Some(('o', 'O')),
        Some(('i', 'I')),
      ],
    ),
    (
      ':',
      [
        Some(('l', 'L')),
        Some(('p', 'P')),
        Some(('[', '{')),
        Some(('\'', '"')),
        Some(('/', '?')),
        Some(('.', '>')),
      ],
    ),
    (
      ';',
      [
        Some(('l', 'L')),
        Some(('p', 'P')),
        Some(('[', '{')),
        Some(('\'', '"')),
        Some(('/', '?')),
        Some(('.', '>')),
      ],
    ),
    (
      '<',
      [
        Some(('m', 'M')),
        Some(('k', 'K')),
        Some(('l', 'L')),
        Some(('.', '>')),
        None,
        None,
      ],
    ),
    (
      '=',
      [Some(('-', '_')), None, None, None, Some((']', '}')), Some(('[', '{'))],
    ),
    (
      '>',
      [
        Some((',', '<')),
        Some(('l', 'L')),
        Some((';', ':')),
        Some(('/', '?')),
        None,
        None,
      ],
    ),
    (
      '?',
      [Some(('.', '>')), Some((';', ':')), Some(('\'', '"')), None, None, None],
    ),
    (
      '@',
      [
        Some(('1', '!')),
        None,
        None,
        Some(('3', '#')),
        Some(('w', 'W')),
        Some(('q', 'Q')),
      ],
    ),
    (
      'A',
      [
        None,
        Some(('q', 'Q')),
        Some(('w', 'W')),
        Some(('s', 'S')),
        Some(('z', 'Z')),
        None,
      ],
    ),
    (
      'B',
      [
        Some(('v', 'V')),
        Some(('g', 'G')),
        Some(('h', 'H')),
        Some(('n', 'N')),
        None,
        None,
      ],
    ),
    (
      'C',
      [
        Some(('x', 'X')),
        Some(('d', 'D')),
        Some(('f', 'F')),
        Some(('v', 'V')),
        None,
        None,
      ],
    ),
    (
      'D',
      [
        Some(('s', 'S')),
        Some(('e', 'E')),
        Some(('r', 'R')),
        Some(('f', 'F')),
        Some(('c', 'C')),
        Some(('x', 'X')),
      ],
    ),
    (
      'E',
      [
        Some(('w', 'W')),
        Some(('3', '#')),
        Some(('4', '$')),
        Some(('r', 'R')),
        Some(('d', 'D')),
        Some(('s', 'S')),
      ],
    ),
    (
      'F',
      [
        Some(('d', 'D')),
        Some(('r', 'R')),
        Some(('t', 'T')),
        Some(('g', 'G')),
        Some(('v', 'V')),
        Some(('c', 'C')),
      ],
    ),
    (
      'G',
      [
        Some(('f', 'F')),
        Some(('t', 'T')),
        Some(('y', 'Y')),
        Some(('h', 'H')),
        Some(('b', 'B')),
        Some(('v', 'V')),
      ],
    ),
    (
      'H',
      [
        Some(('g', 'G')),
        Some(('y', 'Y')),
        Some(('u', 'U')),
        Some(('j', 'J')),
        Some(('n', 'N')),
        Some(('b', 'B')),
      ],
    ),
    (
      'I',
      [
        Some(('u', 'U')),
        Some(('8', '*')),
        Some(('9', '(')),
        Some(('o', 'O')),
        Some(('k', 'K')),
        Some(('j', 'J')),
      ],
    ),
    (
      'J',
      [
        Some(('h', 'H')),
        Some(('u', 'U')),
        Some(('i', 'I')),
        Some(('k', 'K')),
        Some(('m', 'M')),
        Some(('n', 'N')),
      ],
    ),
    (
      'K',
      [
        Some(('j', 'J')),
        Some(('i', 'I')),
        Some(('o', 'O')),
        Some(('l', 'L')),
        Some((',', '<')),
        Some(('m', 'M')),
      ],
    ),
    (
      'L',
      [
        Some(('k', 'K')),
        Some(('o', 'O')),
        Some(('p', 'P')),
        Some((';', ':')),
        Some(('.', '>')),
        Some((',', '<')),
      ],
    ),
    (
      'M',
      [
        Some(('n', 'N')),
        Some(('j', 'J')),
        Some(('k', 'K')),
        Some((',', '<')),
        None,
        None,
      ],
    ),
    (
      'N',
      [
        Some(('b', 'B')),
        Some(('h', 'H')),
        Some(('j', 'J')),
        Some(('m', 'M')),
        None,
        None,
      ],
    ),
    (
      'O',
      [
        Some(('i', 'I')),
        Some(('9', '(')),
        Some(('0', ')')),
        Some(('p', 'P')),
        Some(('l', 'L')),
        Some(('k', 'K')),
      ],
    ),
    (
      'P',
      [
        Some(('o', 'O')),
        Some(('0', ')')),
        Some(('-', '_')),
        Some(('[', '{')),
        Some((';', ':')),
        Some(('l', 'L')),
      ],
    ),
    (
      'Q',
      [
        None,
        Some(('1', '!')),
        Some(('2', '@')),
        Some(('w', 'W')),
        Some(('a', 'A')),
        None,
      ],
    ),
    (
      'R',
      [
        Some(('e', 'E')),
        Some(('4', '$')),
        Some(('5', '%')),
        Some(('t', 'T')),
        Some(('f', 'F')),
        Some(('d', 'D')),
      ],
    ),
    (
      'S',
      [
        Some(('a', 'A')),
        Some(('w', 'W')),
        Some(('e', 'E')),
        Some(('d', 'D')),
        Some(('x', 'X')),
        Some(('z', 'Z')),
      ],
    ),
    (
      'T',
      [
        Some(('r', 'R')),
        Some(('5', '%')),
        Some(('6', '^')),
        Some(('y', 'Y')),
        Some(('g', 'G')),
        Some(('f', 'F')),
      ],
    ),
    (
      'U',
      [
        Some(('y', 'Y')),
        Some(('7', '&')),
        Some(('8', '*')),
        Some(('i', 'I')),
        Some(('j', 'J')),
        Some(('h', 'H')),
      ],
    ),
    (
      'V',
      [
        Some(('c', 'C')),
        Some(('f', 'F')),
        Some(('g', 'G')),
        Some(('b', 'B')),
        None,
        None,
      ],
    ),
    (
      'W',
      [
        Some(('q', 'Q')),
        Some(('2', '@')),
        Some(('3', '#')),
        Some(('e', 'E')),
        Some(('s', 'S')),
        Some(('a', 'A')),
      ],
    ),
    (
      'X',
      [
        Some(('z', 'Z')),
        Some(('s', 'S')),
        Some(('d', 'D')),
        Some(('c', 'C')),
        None,
        None,
      ],
    ),
    (
      'Y',
      [
        Some(('t', 'T')),
        Some(('6', '^')),
        Some(('7', '&')),
        Some(('u', 'U')),
        Some(('h', 'H')),
        Some(('g', 'G')),
      ],
    ),
    (
      'Z',
      [None, Some(('a', 'A')), Some(('s', 'S')), Some(('x', 'X')), None, None],
    ),
    (
      '[',
      [
        Some(('p', 'P')),
        Some(('-', '_')),
        Some(('=', '+')),
        Some((']', '}')),
        Some(('\'', '"')),
        Some((';', ':')),
      ],
    ),
    ('\\', [Some((']', '}')), None, None, None, None, None]),
    (
      ']',
      [
        Some(('[', '{')),
        Some(('=', '+')),
        None,
        Some(('\\', '|')),
        None,
        Some(('\'', '"')),
      ],
    ),
    (
      '^',
      [
        Some(('5', '%')),
        None,
        None,
        Some(('7', '&')),
        Some(('y', 'Y')),
        Some(('t', 'T')),
      ],
    ),
    (
      '_',
      [
        Some(('0', ')')),
        None,
        None,
        Some(('=', '+')),
        Some(('[', '{')),
        Some(('p', 'P')),
      ],
    ),
    ('`', [None, None, None, Some(('1', '!')), None, None]),
    (
      'a',
      [
        None,
        Some(('q', 'Q')),
        Some(('w', 'W')),
        Some(('s', 'S')),
        Some(('z', 'Z')),
        None,
      ],
    ),
    (
      'b',
      [
        Some(('v', 'V')),
        Some(('g', 'G')),
        Some(('h', 'H')),
        Some(('n', 'N')),
        None,
        None,
      ],
    ),
    (
      'c',
      [
        Some(('x', 'X')),
        Some(('d', 'D')),
        Some(('f', 'F')),
        Some(('v', 'V')),
        None,
        None,
      ],
    ),
    (
      'd',
      [
        Some(('s', 'S')),
        Some(('e', 'E')),
        Some(('r', 'R')),
        Some(('f', 'F')),
        Some(('c', 'C')),
        Some(('x', 'X')),
      ],
    ),
    (
      'e',
      [
        Some(('w', 'W')),
        Some(('3', '#')),
        Some(('4', '$')),
        Some(('r', 'R')),
        Some(('d', 'D')),
        Some(('s', 'S')),
      ],
    ),
    (
      'f',
      [
        Some(('d', 'D')),
        Some(('r', 'R')),
        Some(('t', 'T')),
        Some(('g', 'G')),
        Some(('v', 'V')),
        Some(('c', 'C')),
      ],
    ),
    (
      'g',
      [
        Some(('f', 'F')),
        Some(('t', 'T')),
        Some(('y', 'Y')),
        Some(('h', 'H')),
        Some(('b', 'B')),
        Some(('v', 'V')),
      ],
    ),
    (
      'h',
      [
        Some(('g', 'G')),
        Some(('y', 'Y')),
        Some(('u', 'U')),
        Some(('j', 'J')),
        Some(('n', 'N')),
        Some(('b', 'B')),
      ],
    ),
    (
      'i',
      [
        Some(('u', 'U')),
        Some(('8', '*')),
        Some(('9', '(')),
        Some(('o', 'O')),
        Some(('k', 'K')),
        Some(('j', 'J')),
      ],
    ),
    (
      'j',
      [
        Some(('h', 'H')),
        Some(('u', 'U')),
        Some(('i', 'I')),
        Some(('k', 'K')),
        Some(('m', 'M')),
        Some(('n', 'N')),
      ],
    ),
    (
      'k',
      [
        Some(('j', 'J')),
        Some(('i', 'I')),
        Some(('o', 'O')),
        Some(('l', 'L')),
        Some((',', '<')),
        Some(('m', 'M')),
      ],
    ),
    (
      'l',
      [
        Some(('k', 'K')),
        Some(('o', 'O')),
        Some(('p', 'P')),
        Some((';', ':')),
        Some(('.', '>')),
        Some((',', '<')),
      ],
    ),
    (
      'm',
      [
        Some(('n', 'N')),
        Some(('j', 'J')),
        Some(('k', 'K')),
        Some((',', '<')),
        None,
        None,
      ],
    ),
    (
      'n',
      [
        Some(('b', 'B')),
        Some(('h', 'H')),
        Some(('j', 'J')),
        Some(('m', 'M')),
        None,
        None,
      ],
    ),
    (
      'o',
      [
        Some(('i', 'I')),
        Some(('9', '(')),
        Some(('0', ')')),
        Some(('p', 'P')),
        Some(('l', 'L')),
        Some(('k', 'K')),
      ],
    ),
    (
      'p',
      [
        Some(('o', 'O')),
        Some(('0', ')')),
        Some(('-', '_')),
        Some(('[', '{')),
        Some((';', ':')),
        Some(('l', 'L')),
      ],
    ),
    (
      'q',
      [
        None,
        Some(('1', '!')),
        Some(('2', '@')),
        Some(('w', 'W')),
        Some(('a', 'A')),
        None,
      ],
    ),
    (
      'r',
      [
        Some(('e', 'E')),
        Some(('4', '$')),
        Some(('5', '%')),
        Some(('t', 'T')),
        Some(('f', 'F')),
        Some(('d', 'D')),
      ],
    ),
    (
      's',
      [
        Some(('a', 'A')),
        Some(('w', 'W')),
        Some(('e', 'E')),
        Some(('d', 'D')),
        Some(('x', 'X')),
        Some(('z', 'Z')),
      ],
    ),
    (
      't',
      [
        Some(('r', 'R')),
        Some(('5', '%')),
        Some(('6', '^')),
        Some(('y', 'Y')),
        Some(('g', 'G')),
        Some(('f', 'F')),
      ],
    ),
    (
      'u',
      [
        Some(('y', 'Y')),
        Some(('7', '&')),
        Some(('8', '*')),
        Some(('i', 'I')),
        Some(('j', 'J')),
        Some(('h', 'H')),
      ],
    ),
    (
      'v',
      [
        Some(('c', 'C')),
        Some(('f', 'F')),
        Some(('g', 'G')),
        Some(('b', 'B')),
        None,
        None,
      ],
    ),
    (
      'w',
      [
        Some(('q', 'Q')),
        Some(('2', '@')),
        Some(('3', '#')),
        Some(('e', 'E')),
        Some(('s', 'S')),
        Some(('a', 'A')),
      ],
    ),
    (
      'x',
      [
        Some(('z', 'Z')),
        Some(('s', 'S')),
        Some(('d', 'D')),
        Some(('c', 'C')),
        None,
        None,
      ],
    ),
    (
      'y',
      [
        Some(('t', 'T')),
        Some(('6', '^')),
        Some(('7', '&')),
        Some(('u', 'U')),
        Some(('h', 'H')),
        Some(('g', 'G')),
      ],
    ),
    (
      'z',
      [None, Some(('a', 'A')), Some(('s', 'S')), Some(('x', 'X')), None, None],
    ),
    (
      '{',
      [
        Some(('p', 'P')),
        Some(('-', '_')),
        Some(('=', '+')),
        Some((']', '}')),
        Some(('\'', '"')),
        Some((';', ':')),
      ],
    ),
    ('|', [Some((']', '}')), None, None, None, None, None]),
    (
      '}',
      [
        Some(('[', '{')),
        Some(('=', '+')),
        None,
        Some(('\\', '|')),
        None,
        Some(('\'', '"')),
      ],
    ),
    ('~', [None, None, None, Some(('1', '!')), None, None]),
  ])
})

///|
/// `dvorak` 邻接图(上游 `adjacency_graphs.dvorak`)。
///
/// 每个条目 `(未移位字符, 移位字符)`;`None` 是该方向没有相邻键。占位的 `None`
/// 必须保留——数组下标即 spatial_match_helper 的 direction 计数。
let dvorak : Lazy[Map[Char, Array[(Char, Char)?]]] = Lazy(() => {
  Map([
    (
      '!',
      [Some(('`', '~')), None, None, Some(('2', '@')), Some(('\'', '"')), None],
    ),
    (
      '"',
      [
        None,
        Some(('1', '!')),
        Some(('2', '@')),
        Some((',', '<')),
        Some(('a', 'A')),
        None,
      ],
    ),
    (
      '#',
      [
        Some(('2', '@')),
        None,
        None,
        Some(('4', '$')),
        Some(('.', '>')),
        Some((',', '<')),
      ],
    ),
    (
      '$',
      [
        Some(('3', '#')),
        None,
        None,
        Some(('5', '%')),
        Some(('p', 'P')),
        Some(('.', '>')),
      ],
    ),
    (
      '%',
      [
        Some(('4', '$')),
        None,
        None,
        Some(('6', '^')),
        Some(('y', 'Y')),
        Some(('p', 'P')),
      ],
    ),
    (
      '&',
      [
        Some(('6', '^')),
        None,
        None,
        Some(('8', '*')),
        Some(('g', 'G')),
        Some(('f', 'F')),
      ],
    ),
    (
      '\'',
      [
        None,
        Some(('1', '!')),
        Some(('2', '@')),
        Some((',', '<')),
        Some(('a', 'A')),
        None,
      ],
    ),
    (
      '(',
      [
        Some(('8', '*')),
        None,
        None,
        Some(('0', ')')),
        Some(('r', 'R')),
        Some(('c', 'C')),
      ],
    ),
    (
      ')',
      [
        Some(('9', '(')),
        None,
        None,
        Some(('[', '{')),
        Some(('l', 'L')),
        Some(('r', 'R')),
      ],
    ),
    (
      '*',
      [
        Some(('7', '&')),
        None,
        None,
        Some(('9', '(')),
        Some(('c', 'C')),
        Some(('g', 'G')),
      ],
    ),
    (
      '+',
      [
        Some(('/', '?')),
        Some((']', '}')),
        None,
        Some(('\\', '|')),
        None,
        Some(('-', '_')),
      ],
    ),
    (
      ',',
      [
        Some(('\'', '"')),
        Some(('2', '@')),
        Some(('3', '#')),
        Some(('.', '>')),
        Some(('o', 'O')),
        Some(('a', 'A')),
      ],
    ),
    (
      '-',
      [
        Some(('s', 'S')),
        Some(('/', '?')),
        Some(('=', '+')),
        None,
        None,
        Some(('z', 'Z')),
      ],
    ),
    (
      '.',
      [
        Some((',', '<')),
        Some(('3', '#')),
        Some(('4', '$')),
        Some(('p', 'P')),
        Some(('e', 'E')),
        Some(('o', 'O')),
      ],
    ),
    (
      '/',
      [
        Some(('l', 'L')),
        Some(('[', '{')),
        Some((']', '}')),
        Some(('=', '+')),
        Some(('-', '_')),
        Some(('s', 'S')),
      ],
    ),
    (
      '0',
      [
        Some(('9', '(')),
        None,
        None,
        Some(('[', '{')),
        Some(('l', 'L')),
        Some(('r', 'R')),
      ],
    ),
    (
      '1',
      [Some(('`', '~')), None, None, Some(('2', '@')), Some(('\'', '"')), None],
    ),
    (
      '2',
      [
        Some(('1', '!')),
        None,
        None,
        Some(('3', '#')),
        Some((',', '<')),
        Some(('\'', '"')),
      ],
    ),
    (
      '3',
      [
        Some(('2', '@')),
        None,
        None,
        Some(('4', '$')),
        Some(('.', '>')),
        Some((',', '<')),
      ],
    ),
    (
      '4',
      [
        Some(('3', '#')),
        None,
        None,
        Some(('5', '%')),
        Some(('p', 'P')),
        Some(('.', '>')),
      ],
    ),
    (
      '5',
      [
        Some(('4', '$')),
        None,
        None,
        Some(('6', '^')),
        Some(('y', 'Y')),
        Some(('p', 'P')),
      ],
    ),
    (
      '6',
      [
        Some(('5', '%')),
        None,
        None,
        Some(('7', '&')),
        Some(('f', 'F')),
        Some(('y', 'Y')),
      ],
    ),
    (
      '7',
      [
        Some(('6', '^')),
        None,
        None,
        Some(('8', '*')),
        Some(('g', 'G')),
        Some(('f', 'F')),
      ],
    ),
    (
      '8',
      [
        Some(('7', '&')),
        None,
        None,
        Some(('9', '(')),
        Some(('c', 'C')),
        Some(('g', 'G')),
      ],
    ),
    (
      '9',
      [
        Some(('8', '*')),
        None,
        None,
        Some(('0', ')')),
        Some(('r', 'R')),
        Some(('c', 'C')),
      ],
    ),
    (
      ':',
      [None, Some(('a', 'A')), Some(('o', 'O')), Some(('q', 'Q')), None, None],
    ),
    (
      ';',
      [None, Some(('a', 'A')), Some(('o', 'O')), Some(('q', 'Q')), None, None],
    ),
    (
      '<',
      [
        Some(('\'', '"')),
        Some(('2', '@')),
        Some(('3', '#')),
        Some(('.', '>')),
        Some(('o', 'O')),
        Some(('a', 'A')),
      ],
    ),
    (
      '=',
      [
        Some(('/', '?')),
        Some((']', '}')),
        None,
        Some(('\\', '|')),
        None,
        Some(('-', '_')),
      ],
    ),
    (
      '>',
      [
        Some((',', '<')),
        Some(('3', '#')),
        Some(('4', '$')),
        Some(('p', 'P')),
        Some(('e', 'E')),
        Some(('o', 'O')),
      ],
    ),
    (
      '?',
      [
        Some(('l', 'L')),
        Some(('[', '{')),
        Some((']', '}')),
        Some(('=', '+')),
        Some(('-', '_')),
        Some(('s', 'S')),
      ],
    ),
    (
      '@',
      [
        Some(('1', '!')),
        None,
        None,
        Some(('3', '#')),
        Some((',', '<')),
        Some(('\'', '"')),
      ],
    ),
    (
      'A',
      [
        None,
        Some(('\'', '"')),
        Some((',', '<')),
        Some(('o', 'O')),
        Some((';', ':')),
        None,
      ],
    ),
    (
      'B',
      [
        Some(('x', 'X')),
        Some(('d', 'D')),
        Some(('h', 'H')),
        Some(('m', 'M')),
        None,
        None,
      ],
    ),
    (
      'C',
      [
        Some(('g', 'G')),
        Some(('8', '*')),
        Some(('9', '(')),
        Some(('r', 'R')),
        Some(('t', 'T')),
        Some(('h', 'H')),
      ],
    ),
    (
      'D',
      [
        Some(('i', 'I')),
        Some(('f', 'F')),
        Some(('g', 'G')),
        Some(('h', 'H')),
        Some(('b', 'B')),
        Some(('x', 'X')),
      ],
    ),
    (
      'E',
      [
        Some(('o', 'O')),
        Some(('.', '>')),
        Some(('p', 'P')),
        Some(('u', 'U')),
        Some(('j', 'J')),
        Some(('q', 'Q')),
      ],
    ),
    (
      'F',
      [
        Some(('y', 'Y')),
        Some(('6', '^')),
        Some(('7', '&')),
        Some(('g', 'G')),
        Some(('d', 'D')),
        Some(('i', 'I')),
      ],
    ),
    (
      'G',
      [
        Some(('f', 'F')),
        Some(('7', '&')),
        Some(('8', '*')),
        Some(('c', 'C')),
        Some(('h', 'H')),
        Some(('d', 'D')),
      ],
    ),
    (
      'H',
      [
        Some(('d', 'D')),
        Some(('g', 'G')),
        Some(('c', 'C')),
        Some(('t', 'T')),
        Some(('m', 'M')),
        Some(('b', 'B')),
      ],
    ),
    (
      'I',
      [
        Some(('u', 'U')),
        Some(('y', 'Y')),
        Some(('f', 'F')),
        Some(('d', 'D')),
        Some(('x', 'X')),
        Some(('k', 'K')),
      ],
    ),
    (
      'J',
      [
        Some(('q', 'Q')),
        Some(('e', 'E')),
        Some(('u', 'U')),
        Some(('k', 'K')),
        None,
        None,
      ],
    ),
    (
      'K',
      [
        Some(('j', 'J')),
        Some(('u', 'U')),
        Some(('i', 'I')),
        Some(('x', 'X')),
        None,
        None,
      ],
    ),
    (
      'L',
      [
        Some(('r', 'R')),
        Some(('0', ')')),
        Some(('[', '{')),
        Some(('/', '?')),
        Some(('s', 'S')),
        Some(('n', 'N')),
      ],
    ),
    (
      'M',
      [
        Some(('b', 'B')),
        Some(('h', 'H')),
        Some(('t', 'T')),
        Some(('w', 'W')),
        None,
        None,
      ],
    ),
    (
      'N',
      [
        Some(('t', 'T')),
        Some(('r', 'R')),
        Some(('l', 'L')),
        Some(('s', 'S')),
        Some(('v', 'V')),
        Some(('w', 'W')),
      ],
    ),
    (
      'O',
      [
        Some(('a', 'A')),
        Some((',', '<')),
        Some(('.', '>')),
        Some(('e', 'E')),
        Some(('q', 'Q')),
        Some((';', ':')),
      ],
    ),
    (
      'P',
      [
        Some(('.', '>')),
        Some(('4', '$')),
        Some(('5', '%')),
        Some(('y', 'Y')),
        Some(('u', 'U')),
        Some(('e', 'E')),
      ],
    ),
    (
      'Q',
      [
        Some((';', ':')),
        Some(('o', 'O')),
        Some(('e', 'E')),
        Some(('j', 'J')),
        None,
        None,
      ],
    ),
    (
      'R',
      [
        Some(('c', 'C')),
        Some(('9', '(')),
        Some(('0', ')')),
        Some(('l', 'L')),
        Some(('n', 'N')),
        Some(('t', 'T')),
      ],
    ),
    (
      'S',
      [
        Some(('n', 'N')),
        Some(('l', 'L')),
        Some(('/', '?')),
        Some(('-', '_')),
        Some(('z', 'Z')),
        Some(('v', 'V')),
      ],
    ),
    (
      'T',
      [
        Some(('h', 'H')),
        Some(('c', 'C')),
        Some(('r', 'R')),
        Some(('n', 'N')),
        Some(('w', 'W')),
        Some(('m', 'M')),
      ],
    ),
    (
      'U',
      [
        Some(('e', 'E')),
        Some(('p', 'P')),
        Some(('y', 'Y')),
        Some(('i', 'I')),
        Some(('k', 'K')),
        Some(('j', 'J')),
      ],
    ),
    (
      'V',
      [
        Some(('w', 'W')),
        Some(('n', 'N')),
        Some(('s', 'S')),
        Some(('z', 'Z')),
        None,
        None,
      ],
    ),
    (
      'W',
      [
        Some(('m', 'M')),
        Some(('t', 'T')),
        Some(('n', 'N')),
        Some(('v', 'V')),
        None,
        None,
      ],
    ),
    (
      'X',
      [
        Some(('k', 'K')),
        Some(('i', 'I')),
        Some(('d', 'D')),
        Some(('b', 'B')),
        None,
        None,
      ],
    ),
    (
      'Y',
      [
        Some(('p', 'P')),
        Some(('5', '%')),
        Some(('6', '^')),
        Some(('f', 'F')),
        Some(('i', 'I')),
        Some(('u', 'U')),
      ],
    ),
    (
      'Z',
      [Some(('v', 'V')), Some(('s', 'S')), Some(('-', '_')), None, None, None],
    ),
    (
      '[',
      [
        Some(('0', ')')),
        None,
        None,
        Some((']', '}')),
        Some(('/', '?')),
        Some(('l', 'L')),
      ],
    ),
    ('\\', [Some(('=', '+')), None, None, None, None, None]),
    (
      ']',
      [Some(('[', '{')), None, None, None, Some(('=', '+')), Some(('/', '?'))],
    ),
    (
      '^',
      [
        Some(('5', '%')),
        None,
        None,
        Some(('7', '&')),
        Some(('f', 'F')),
        Some(('y', 'Y')),
      ],
    ),
    (
      '_',
      [
        Some(('s', 'S')),
        Some(('/', '?')),
        Some(('=', '+')),
        None,
        None,
        Some(('z', 'Z')),
      ],
    ),
    ('`', [None, None, None, Some(('1', '!')), None, None]),
    (
      'a',
      [
        None,
        Some(('\'', '"')),
        Some((',', '<')),
        Some(('o', 'O')),
        Some((';', ':')),
        None,
      ],
    ),
    (
      'b',
      [
        Some(('x', 'X')),
        Some(('d', 'D')),
        Some(('h', 'H')),
        Some(('m', 'M')),
        None,
        None,
      ],
    ),
    (
      'c',
      [
        Some(('g', 'G')),
        Some(('8', '*')),
        Some(('9', '(')),
        Some(('r', 'R')),
        Some(('t', 'T')),
        Some(('h', 'H')),
      ],
    ),
    (
      'd',
      [
        Some(('i', 'I')),
        Some(('f', 'F')),
        Some(('g', 'G')),
        Some(('h', 'H')),
        Some(('b', 'B')),
        Some(('x', 'X')),
      ],
    ),
    (
      'e',
      [
        Some(('o', 'O')),
        Some(('.', '>')),
        Some(('p', 'P')),
        Some(('u', 'U')),
        Some(('j', 'J')),
        Some(('q', 'Q')),
      ],
    ),
    (
      'f',
      [
        Some(('y', 'Y')),
        Some(('6', '^')),
        Some(('7', '&')),
        Some(('g', 'G')),
        Some(('d', 'D')),
        Some(('i', 'I')),
      ],
    ),
    (
      'g',
      [
        Some(('f', 'F')),
        Some(('7', '&')),
        Some(('8', '*')),
        Some(('c', 'C')),
        Some(('h', 'H')),
        Some(('d', 'D')),
      ],
    ),
    (
      'h',
      [
        Some(('d', 'D')),
        Some(('g', 'G')),
        Some(('c', 'C')),
        Some(('t', 'T')),
        Some(('m', 'M')),
        Some(('b', 'B')),
      ],
    ),
    (
      'i',
      [
        Some(('u', 'U')),
        Some(('y', 'Y')),
        Some(('f', 'F')),
        Some(('d', 'D')),
        Some(('x', 'X')),
        Some(('k', 'K')),
      ],
    ),
    (
      'j',
      [
        Some(('q', 'Q')),
        Some(('e', 'E')),
        Some(('u', 'U')),
        Some(('k', 'K')),
        None,
        None,
      ],
    ),
    (
      'k',
      [
        Some(('j', 'J')),
        Some(('u', 'U')),
        Some(('i', 'I')),
        Some(('x', 'X')),
        None,
        None,
      ],
    ),
    (
      'l',
      [
        Some(('r', 'R')),
        Some(('0', ')')),
        Some(('[', '{')),
        Some(('/', '?')),
        Some(('s', 'S')),
        Some(('n', 'N')),
      ],
    ),
    (
      'm',
      [
        Some(('b', 'B')),
        Some(('h', 'H')),
        Some(('t', 'T')),
        Some(('w', 'W')),
        None,
        None,
      ],
    ),
    (
      'n',
      [
        Some(('t', 'T')),
        Some(('r', 'R')),
        Some(('l', 'L')),
        Some(('s', 'S')),
        Some(('v', 'V')),
        Some(('w', 'W')),
      ],
    ),
    (
      'o',
      [
        Some(('a', 'A')),
        Some((',', '<')),
        Some(('.', '>')),
        Some(('e', 'E')),
        Some(('q', 'Q')),
        Some((';', ':')),
      ],
    ),
    (
      'p',
      [
        Some(('.', '>')),
        Some(('4', '$')),
        Some(('5', '%')),
        Some(('y', 'Y')),
        Some(('u', 'U')),
        Some(('e', 'E')),
      ],
    ),
    (
      'q',
      [
        Some((';', ':')),
        Some(('o', 'O')),
        Some(('e', 'E')),
        Some(('j', 'J')),
        None,
        None,
      ],
    ),
    (
      'r',
      [
        Some(('c', 'C')),
        Some(('9', '(')),
        Some(('0', ')')),
        Some(('l', 'L')),
        Some(('n', 'N')),
        Some(('t', 'T')),
      ],
    ),
    (
      's',
      [
        Some(('n', 'N')),
        Some(('l', 'L')),
        Some(('/', '?')),
        Some(('-', '_')),
        Some(('z', 'Z')),
        Some(('v', 'V')),
      ],
    ),
    (
      't',
      [
        Some(('h', 'H')),
        Some(('c', 'C')),
        Some(('r', 'R')),
        Some(('n', 'N')),
        Some(('w', 'W')),
        Some(('m', 'M')),
      ],
    ),
    (
      'u',
      [
        Some(('e', 'E')),
        Some(('p', 'P')),
        Some(('y', 'Y')),
        Some(('i', 'I')),
        Some(('k', 'K')),
        Some(('j', 'J')),
      ],
    ),
    (
      'v',
      [
        Some(('w', 'W')),
        Some(('n', 'N')),
        Some(('s', 'S')),
        Some(('z', 'Z')),
        None,
        None,
      ],
    ),
    (
      'w',
      [
        Some(('m', 'M')),
        Some(('t', 'T')),
        Some(('n', 'N')),
        Some(('v', 'V')),
        None,
        None,
      ],
    ),
    (
      'x',
      [
        Some(('k', 'K')),
        Some(('i', 'I')),
        Some(('d', 'D')),
        Some(('b', 'B')),
        None,
        None,
      ],
    ),
    (
      'y',
      [
        Some(('p', 'P')),
        Some(('5', '%')),
        Some(('6', '^')),
        Some(('f', 'F')),
        Some(('i', 'I')),
        Some(('u', 'U')),
      ],
    ),
    (
      'z',
      [Some(('v', 'V')), Some(('s', 'S')), Some(('-', '_')), None, None, None],
    ),
    (
      '{',
      [
        Some(('0', ')')),
        None,
        None,
        Some((']', '}')),
        Some(('/', '?')),
        Some(('l', 'L')),
      ],
    ),
    ('|', [Some(('=', '+')), None, None, None, None, None]),
    (
      '}',
      [Some(('[', '{')), None, None, None, Some(('=', '+')), Some(('/', '?'))],
    ),
    ('~', [None, None, None, Some(('1', '!')), None, None]),
  ])
})

///|
/// `keypad` 邻接图(上游 `adjacency_graphs.keypad`)。
///
/// 每个条目 `(未移位字符, 移位字符)`;`None` 是该方向没有相邻键。占位的 `None`
/// 必须保留——数组下标即 spatial_match_helper 的 direction 计数。
let keypad : Lazy[Map[Char, Array[(Char, Char)?]]] = Lazy(() => {
  Map([
    (
      '*',
      [
        Some(('/', '/')),
        None,
        None,
        None,
        Some(('-', '-')),
        Some(('+', '+')),
        Some(('9', '9')),
        Some(('8', '8')),
      ],
    ),
    (
      '+',
      [
        Some(('9', '9')),
        Some(('*', '*')),
        Some(('-', '-')),
        None,
        None,
        None,
        None,
        Some(('6', '6')),
      ],
    ),
    (
      '-',
      [
        Some(('*', '*')),
        None,
        None,
        None,
        None,
        None,
        Some(('+', '+')),
        Some(('9', '9')),
      ],
    ),
    (
      '.',
      [
        Some(('0', '0')),
        Some(('2', '2')),
        Some(('3', '3')),
        None,
        None,
        None,
        None,
        None,
      ],
    ),
    (
      '/',
      [
        None,
        None,
        None,
        None,
        Some(('*', '*')),
        Some(('9', '9')),
        Some(('8', '8')),
        Some(('7', '7')),
      ],
    ),
    (
      '0',
      [
        None,
        Some(('1', '1')),
        Some(('2', '2')),
        Some(('3', '3')),
        Some(('.', '.')),
        None,
        None,
        None,
      ],
    ),
    (
      '1',
      [
        None,
        None,
        Some(('4', '4')),
        Some(('5', '5')),
        Some(('2', '2')),
        Some(('0', '0')),
        None,
        None,
      ],
    ),
    (
      '2',
      [
        Some(('1', '1')),
        Some(('4', '4')),
        Some(('5', '5')),
        Some(('6', '6')),
        Some(('3', '3')),
        Some(('.', '.')),
        Some(('0', '0')),
        None,
      ],
    ),
    (
      '3',
      [
        Some(('2', '2')),
        Some(('5', '5')),
        Some(('6', '6')),
        None,
        None,
        None,
        Some(('.', '.')),
        Some(('0', '0')),
      ],
    ),
    (
      '4',
      [
        None,
        None,
        Some(('7', '7')),
        Some(('8', '8')),
        Some(('5', '5')),
        Some(('2', '2')),
        Some(('1', '1')),
        None,
      ],
    ),
    (
      '5',
      [
        Some(('4', '4')),
        Some(('7', '7')),
        Some(('8', '8')),
        Some(('9', '9')),
        Some(('6', '6')),
        Some(('3', '3')),
        Some(('2', '2')),
        Some(('1', '1')),
      ],
    ),
    (
      '6',
      [
        Some(('5', '5')),
        Some(('8', '8')),
        Some(('9', '9')),
        Some(('+', '+')),
        None,
        None,
        Some(('3', '3')),
        Some(('2', '2')),
      ],
    ),
    (
      '7',
      [
        None,
        None,
        None,
        Some(('/', '/')),
        Some(('8', '8')),
        Some(('5', '5')),
        Some(('4', '4')),
        None,
      ],
    ),
    (
      '8',
      [
        Some(('7', '7')),
        None,
        Some(('/', '/')),
        Some(('*', '*')),
        Some(('9', '9')),
        Some(('6', '6')),
        Some(('5', '5')),
        Some(('4', '4')),
      ],
    ),
    (
      '9',
      [
        Some(('8', '8')),
        Some(('/', '/')),
        Some(('*', '*')),
        Some(('-', '-')),
        Some(('+', '+')),
        None,
        Some(('6', '6')),
        Some(('5', '5')),
      ],
    ),
  ])
})

///|
/// `mac_keypad` 邻接图(上游 `adjacency_graphs.mac_keypad`)。
///
/// 每个条目 `(未移位字符, 移位字符)`;`None` 是该方向没有相邻键。占位的 `None`
/// 必须保留——数组下标即 spatial_match_helper 的 direction 计数。
let mac_keypad : Lazy[Map[Char, Array[(Char, Char)?]]] = Lazy(() => {
  Map([
    (
      '*',
      [
        Some(('/', '/')),
        None,
        None,
        None,
        None,
        None,
        Some(('-', '-')),
        Some(('9', '9')),
      ],
    ),
    (
      '+',
      [
        Some(('6', '6')),
        Some(('9', '9')),
        Some(('-', '-')),
        None,
        None,
        None,
        None,
        Some(('3', '3')),
      ],
    ),
    (
      '-',
      [
        Some(('9', '9')),
        Some(('/', '/')),
        Some(('*', '*')),
        None,
        None,
        None,
        Some(('+', '+')),
        Some(('6', '6')),
      ],
    ),
    (
      '.',
      [
        Some(('0', '0')),
        Some(('2', '2')),
        Some(('3', '3')),
        None,
        None,
        None,
        None,
        None,
      ],
    ),
    (
      '/',
      [
        Some(('=', '=')),
        None,
        None,
        None,
        Some(('*', '*')),
        Some(('-', '-')),
        Some(('9', '9')),
        Some(('8', '8')),
      ],
    ),
    (
      '0',
      [
        None,
        Some(('1', '1')),
        Some(('2', '2')),
        Some(('3', '3')),
        Some(('.', '.')),
        None,
        None,
        None,
      ],
    ),
    (
      '1',
      [
        None,
        None,
        Some(('4', '4')),
        Some(('5', '5')),
        Some(('2', '2')),
        Some(('0', '0')),
        None,
        None,
      ],
    ),
    (
      '2',
      [
        Some(('1', '1')),
        Some(('4', '4')),
        Some(('5', '5')),
        Some(('6', '6')),
        Some(('3', '3')),
        Some(('.', '.')),
        Some(('0', '0')),
        None,
      ],
    ),
    (
      '3',
      [
        Some(('2', '2')),
        Some(('5', '5')),
        Some(('6', '6')),
        Some(('+', '+')),
        None,
        None,
        Some(('.', '.')),
        Some(('0', '0')),
      ],
    ),
    (
      '4',
      [
        None,
        None,
        Some(('7', '7')),
        Some(('8', '8')),
        Some(('5', '5')),
        Some(('2', '2')),
        Some(('1', '1')),
        None,
      ],
    ),
    (
      '5',
      [
        Some(('4', '4')),
        Some(('7', '7')),
        Some(('8', '8')),
        Some(('9', '9')),
        Some(('6', '6')),
        Some(('3', '3')),
        Some(('2', '2')),
        Some(('1', '1')),
      ],
    ),
    (
      '6',
      [
        Some(('5', '5')),
        Some(('8', '8')),
        Some(('9', '9')),
        Some(('-', '-')),
        Some(('+', '+')),
        None,
        Some(('3', '3')),
        Some(('2', '2')),
      ],
    ),
    (
      '7',
      [
        None,
        None,
        None,
        Some(('=', '=')),
        Some(('8', '8')),
        Some(('5', '5')),
        Some(('4', '4')),
        None,
      ],
    ),
    (
      '8',
      [
        Some(('7', '7')),
        None,
        Some(('=', '=')),
        Some(('/', '/')),
        Some(('9', '9')),
        Some(('6', '6')),
        Some(('5', '5')),
        Some(('4', '4')),
      ],
    ),
    (
      '9',
      [
        Some(('8', '8')),
        Some(('=', '=')),
        Some(('/', '/')),
        Some(('*', '*')),
        Some(('-', '-')),
        Some(('+', '+')),
        Some(('6', '6')),
        Some(('5', '5')),
      ],
    ),
    (
      '=',
      [
        None,
        None,
        None,
        None,
        Some(('/', '/')),
        Some(('9', '9')),
        Some(('8', '8')),
        Some(('7', '7')),
      ],
    ),
  ])
})

///|
/// 邻接图的平均度数(上游 `calc_average_degree`)。
///
/// 注意:上游 CoffeeScript 用的是**浮点除法**(432/94 = 4.595744680851064),
/// 而 zxcvbn-rs 用 u64 整数除法(= 4)。本项目以 dropbox/zxcvbn 官方向量为基准,
/// 故沿用浮点语义——差分对拍时若要跟 zxcvbn-rs 比,这里必须有意识地记为差异点。
fn calc_average_degree(graph : Map[Char, Array[(Char, Char)?]]) -> Double {
  let mut sum = 0
  for arcs in graph.values() {
    for arc in arcs {
      match arc {
        Some(_) => sum += 1
        None => ()
      }
    }
  }
  sum.to_double() / graph.length().to_double()
}

///|
/// qwerty/dvorak 用的起始键位数。
pub let keyboard_starting_positions : Int = qwerty.force().length()

///|
/// keypad/mac_keypad 用的起始键位数。
pub let keypad_starting_positions : Int = keypad.force().length()

///|
/// qwerty/dvorak 的平均度数。
pub let keyboard_average_degree : Double = calc_average_degree(qwerty.force())

///|
/// keypad/mac_keypad 的平均度数(上游注释:与 qwerty 略有不同,但取近似值)。
pub let keypad_average_degree : Double = calc_average_degree(keypad.force())

///|
/// 按图名取邻接图(spatial_match 的四个图)。图名与上游一致。
pub fn get_graph(name : String) -> Map[Char, Array[(Char, Char)?]] {
  match name {
    "qwerty" => qwerty.force()
    "dvorak" => dvorak.force()
    "keypad" => keypad.force()
    "mac_keypad" => mac_keypad.force()
    _ => abort("unknown adjacency graph: \{name}")
  }
}

///|
/// 四个键盘图的名字(顺序与上游 GRAPHS 一致,决定 omnimatch 的匹配顺序)。
pub let graph_names : Array[String] = [
  "qwerty", "dvorak", "keypad", "mac_keypad",
]