///|
/// Padded alist reader. Whitespace separates nonnegative decimal integers.
/// Both row and column adjacency sections are required and cross-validated.
pub fn parse_alist(text : String) -> ParityCheck raise CodeError {
if text.length() > 16777216 {
raise ResourceLimit("alist input characters")
}
let tokens : Array[Int32] = []
let mut active = false
let mut number = 0
let mut line = 1
for c in text {
if c >= '0' && c <= '9' {
let digit = c.to_int() - '0'.to_int()
if number > (1000000 - digit) / 10 {
raise InvalidFormat(line, "integer exceeds 1000000")
}
number = number * 10 + digit
active = true
} else if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
if active {
if tokens.length() >= 2200000 {
raise ResourceLimit("alist tokens")
}
tokens.push(number)
active = false
number = 0
}
if c == '\n' {
line += 1
}
} else {
raise InvalidFormat(
line, "expected nonnegative decimal integer or whitespace",
)
}
}
if active {
tokens.push(number)
}
if tokens.length() < 4 {
raise InvalidFormat(line, "missing alist header")
}
let n = tokens[0]
let m = tokens[1]
let max_column = tokens[2]
let max_row = tokens[3]
if n <= 0 || n > 65536 || m <= 0 || m > 65536 {
raise InvalidDimensions("alist axes 1..65536")
}
if max_column > m || max_row > n {
raise InvalidFormat(1, "degree maximum exceeds axis")
}
let expected = 4L +
n.to_int64() +
m.to_int64() +
n.to_int64() * max_column.to_int64() +
m.to_int64() * max_row.to_int64()
if expected > 2200000L {
raise ResourceLimit("padded alist cells")
}
if expected != tokens.length().to_int64() {
raise InvalidFormat(line, "incorrect padded alist token count")
}
let column_degrees = Array::makei(n, fn(v) { tokens[4 + v] })
let row_degrees = Array::makei(m, fn(c) { tokens[4 + n + c] })
if column_degrees.any(fn(d) { d > max_column }) ||
row_degrees.any(fn(d) { d > max_row }) {
raise InvalidFormat(2, "degree exceeds declared maximum")
}
if column_degrees.fold(init=0, fn(a, b) { a + b }) > 1000000 {
raise ResourceLimit("alist edges")
}
let rows : Array[Array[Int32]] = Array::makei(m, fn(_) { [] })
let mut cursor = 4 + n + m
for v in 0.. m {
raise InvalidFormat(0, "column neighbor out of range")
}
if seen.contains(value) {
raise DuplicateEntry(value - 1, v)
}
seen[value] = true
rows[value - 1].push(v)
} else if value != 0 {
raise InvalidFormat(0, "column padding must be zero")
}
}
}
let code = ParityCheck::new(n, rows)
for c in 0.. n {
raise InvalidFormat(0, "row neighbor out of range")
}
listed.push(value - 1)
} else if value != 0 {
raise InvalidFormat(0, "row padding must be zero")
}
}
listed.sort()
if listed != code.rows[c] {
raise InvalidFormat(0, "row and column adjacency disagree")
}
}
code
}
///|
pub fn ParityCheck::to_alist(self : ParityCheck) -> String raise CodeError {
let max_column = self.columns.fold(init=0, fn(a, row) { a.max(row.length()) })
let max_row = self.rows.fold(init=0, fn(a, row) { a.max(row.length()) })
let cells = self.n.to_int64() * max_column.to_int64() +
self.checks().to_int64() * max_row.to_int64()
if cells > 2000000L {
raise ResourceLimit("alist padded output cells")
}
let out = StringBuilder()
out.write_string("\{self.n} \{self.checks()}\n\{max_column} \{max_row}\n")
for v in 0.. 0 {
out.write_char(' ')
}
out.write_string(self.columns[v].length().to_string())
}
out.write_char('\n')
for c in 0.. 0 {
out.write_char(' ')
}
out.write_string(self.rows[c].length().to_string())
}
out.write_char('\n')
for column in self.columns {
for k in 0.. 0 {
out.write_char(' ')
}
out.write_string(
if k < column.length() {
(column[k] + 1).to_string()
} else {
"0"
},
)
}
out.write_char('\n')
}
for row in self.rows {
for k in 0.. 0 {
out.write_char(' ')
}
out.write_string(
if k < row.length() {
(row[k] + 1).to_string()
} else {
"0"
},
)
}
out.write_char('\n')
}
out.to_string()
}
///|
/// Base cells -1 are zero blocks; 0..lift-1 denote cyclic permutation blocks.
/// Row r of a block with shift s has its one in column (r+s) mod lift.
pub fn lift_qc(
base : Array[Array[Int32]],
lift : Int32,
) -> ParityCheck raise CodeError {
if base.is_empty() || base[0].is_empty() || lift <= 0 || lift > 4096 {
raise InvalidDimensions("nonempty QC base and lift 1..4096")
}
let columns = base[0].length()
if columns > 65536 / lift || base.length() > 65536 / lift {
raise ResourceLimit("QC lifted axes")
}
let mut blocks = 0
for c, row in base {
if row.length() != columns {
raise InvalidDimensions("ragged QC base")
}
for shift in row {
if shift < -1 || shift >= lift {
raise InvalidIndex(c, shift)
}
if shift >= 0 {
blocks += 1
}
}
}
if blocks > 1000000 / lift {
raise ResourceLimit("QC lifted edges")
}
let rows : Array[Array[Int32]] = []
for base_row in base {
for r in 0..= 0 {
row.push(column * lift + (r + shift) % lift)
}
}
rows.push(row)
}
}
ParityCheck::new(columns * lift, rows)
}