mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-16 06:18:42 +02:00
CONTRACTS: Clean up helpers, expand documentation, Hamming wording again (#2296)
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
|
||||
## Gang.getOtherGangInformation() method
|
||||
|
||||
Get information about the other gangs.
|
||||
Get information about all gangs.
|
||||
|
||||
**Signature:**
|
||||
|
||||
@@ -15,7 +15,7 @@ getOtherGangInformation(): Record<string, GangOtherInfoObject>;
|
||||
|
||||
Record<string, [GangOtherInfoObject](./bitburner.gangotherinfoobject.md)<!-- -->>
|
||||
|
||||
Object containing territory and power information about all gangs.
|
||||
Object containing territory and power information about all gangs, including the player's gang, if any.
|
||||
|
||||
## Remarks
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ If you are not in BitNode-2, then you must have Source-File 2 in order to use th
|
||||
| [getInstallResult(memberName)](./bitburner.gang.getinstallresult.md) | Get the effect of an install on ascension multipliers without installing. |
|
||||
| [getMemberInformation(name)](./bitburner.gang.getmemberinformation.md) | Get information about a specific gang member. |
|
||||
| [getMemberNames()](./bitburner.gang.getmembernames.md) | List all gang members. |
|
||||
| [getOtherGangInformation()](./bitburner.gang.getotherganginformation.md) | Get information about the other gangs. |
|
||||
| [getOtherGangInformation()](./bitburner.gang.getotherganginformation.md) | Get information about all gangs. |
|
||||
| [getRecruitsAvailable()](./bitburner.gang.getrecruitsavailable.md) | Check how many gang members you can currently recruit. |
|
||||
| [getTaskNames()](./bitburner.gang.gettasknames.md) | List member task names. |
|
||||
| [getTaskStats(name)](./bitburner.gang.gettaskstats.md) | Get stats of a task. |
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { comprGenChar, comprLZDecode, comprLZEncode, comprLZGenerate } from "../../utils/CompressionContracts";
|
||||
import { CodingContractTypes } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
|
||||
@@ -143,3 +142,204 @@ export const compression: Pick<
|
||||
validateAnswer: (ans): ans is string => typeof ans === "string",
|
||||
},
|
||||
};
|
||||
|
||||
// choose random characters for generating plaintext to compress
|
||||
function comprGenChar(): string {
|
||||
const r = Math.random();
|
||||
if (r < 0.4) {
|
||||
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(26 * Math.random())];
|
||||
} else if (r < 0.8) {
|
||||
return "abcdefghijklmnopqrstuvwxyz"[Math.floor(26 * Math.random())];
|
||||
} else {
|
||||
return "01234567689"[Math.floor(10 * Math.random())];
|
||||
}
|
||||
}
|
||||
|
||||
// generate plaintext which is amenable to LZ encoding
|
||||
function comprLZGenerate(): string {
|
||||
const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
|
||||
let plain = "";
|
||||
|
||||
while (plain.length < length) {
|
||||
if (Math.random() < 0.8) {
|
||||
plain += comprGenChar();
|
||||
} else {
|
||||
const length = 1 + Math.floor(9 * Math.random());
|
||||
const offset = 1 + Math.floor(9 * Math.random());
|
||||
if (offset > plain.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
plain += plain[plain.length - offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plain.substring(0, length);
|
||||
}
|
||||
|
||||
// compress plaintext string
|
||||
function comprLZEncode(plain: string): string {
|
||||
// for state[i][j]:
|
||||
// if i is 0, we're adding a literal of length j
|
||||
// else, we're adding a backreference of offset i and length j
|
||||
let cur_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10).fill(null));
|
||||
let new_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10));
|
||||
|
||||
function set(state: (string | null)[][], i: number, j: number, str: string): void {
|
||||
const current = state[i][j];
|
||||
if (current == null || str.length < current.length) {
|
||||
state[i][j] = str;
|
||||
} else if (str.length === current.length && Math.random() < 0.5) {
|
||||
// if two strings are the same length, pick randomly so that
|
||||
// we generate more possible inputs to Compression II
|
||||
state[i][j] = str;
|
||||
}
|
||||
}
|
||||
|
||||
// initial state is a literal of length 1
|
||||
cur_state[0][1] = "";
|
||||
|
||||
for (let i = 1; i < plain.length; ++i) {
|
||||
for (const row of new_state) {
|
||||
row.fill(null);
|
||||
}
|
||||
const c = plain[i];
|
||||
|
||||
// handle literals
|
||||
for (let length = 1; length <= 9; ++length) {
|
||||
const string = cur_state[0][length];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length < 9) {
|
||||
// extend current literal
|
||||
set(new_state, 0, length + 1, string);
|
||||
} else {
|
||||
// start new literal
|
||||
set(new_state, 0, 1, string + "9" + plain.substring(i - 9, i) + "0");
|
||||
}
|
||||
|
||||
for (let offset = 1; offset <= Math.min(9, i); ++offset) {
|
||||
if (plain[i - offset] === c) {
|
||||
// start new backreference
|
||||
set(new_state, offset, 1, string + String(length) + plain.substring(i - length, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle backreferences
|
||||
for (let offset = 1; offset <= 9; ++offset) {
|
||||
for (let length = 1; length <= 9; ++length) {
|
||||
const string = cur_state[offset][length];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plain[i - offset] === c) {
|
||||
if (length < 9) {
|
||||
// extend current backreference
|
||||
set(new_state, offset, length + 1, string);
|
||||
} else {
|
||||
// start new backreference
|
||||
set(new_state, offset, 1, string + "9" + String(offset) + "0");
|
||||
}
|
||||
}
|
||||
|
||||
// start new literal
|
||||
set(new_state, 0, 1, string + String(length) + String(offset));
|
||||
|
||||
// end current backreference and start new backreference
|
||||
for (let new_offset = 1; new_offset <= Math.min(9, i); ++new_offset) {
|
||||
if (plain[i - new_offset] === c) {
|
||||
set(new_state, new_offset, 1, string + String(length) + String(offset) + "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tmp_state = new_state;
|
||||
new_state = cur_state;
|
||||
cur_state = tmp_state;
|
||||
}
|
||||
|
||||
let result = null;
|
||||
|
||||
for (let len = 1; len <= 9; ++len) {
|
||||
let string = cur_state[0][len];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string += String(len) + plain.substring(plain.length - len, plain.length);
|
||||
if (result == null || string.length < result.length) {
|
||||
result = string;
|
||||
} else if (string.length == result.length && Math.random() < 0.5) {
|
||||
result = string;
|
||||
}
|
||||
}
|
||||
|
||||
for (let offset = 1; offset <= 9; ++offset) {
|
||||
for (let len = 1; len <= 9; ++len) {
|
||||
let string = cur_state[offset][len];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string += String(len) + "" + String(offset);
|
||||
if (result == null || string.length < result.length) {
|
||||
result = string;
|
||||
} else if (string.length == result.length && Math.random() < 0.5) {
|
||||
result = string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? "";
|
||||
}
|
||||
|
||||
// decompress LZ-compressed string, or return null if input is invalid
|
||||
function comprLZDecode(compr: string): string | null {
|
||||
let plain = "";
|
||||
|
||||
for (let i = 0; i < compr.length; ) {
|
||||
const literal_length = compr.charCodeAt(i) - 0x30;
|
||||
|
||||
if (literal_length < 0 || literal_length > 9 || i + 1 + literal_length > compr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
plain += compr.substring(i + 1, i + 1 + literal_length);
|
||||
i += 1 + literal_length;
|
||||
|
||||
if (i >= compr.length) {
|
||||
break;
|
||||
}
|
||||
const backref_length = compr.charCodeAt(i) - 0x30;
|
||||
|
||||
if (backref_length < 0 || backref_length > 9) {
|
||||
return null;
|
||||
} else if (backref_length === 0) {
|
||||
++i;
|
||||
} else {
|
||||
if (i + 1 >= compr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const backref_offset = compr.charCodeAt(i + 1) - 0x30;
|
||||
if ((backref_length > 0 && (backref_offset < 1 || backref_offset > 9)) || backref_offset > plain.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let j = 0; j < backref_length; ++j) {
|
||||
plain += plain[plain.length - backref_offset];
|
||||
}
|
||||
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return plain;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { HammingDecode, HammingEncode, HammingEncodeProperly } from "../../utils/HammingCodeTools";
|
||||
import { getRandomIntInclusive } from "../../utils/helpers/getRandomIntInclusive";
|
||||
import { CodingContractTypes } from "../ContractTypes";
|
||||
import { CodingContractName } from "@enums";
|
||||
@@ -18,8 +17,9 @@ export const hammingCode: Pick<
|
||||
"An 'extended Hamming code' has an additional parity bit to enhance error detection.\n",
|
||||
"A parity bit is inserted at every position N where N is a power of 2, with the additional parity bit at position 0.\n",
|
||||
"Parity bits are used to make the total number of '1' bits in a given set of data even.\n",
|
||||
"Each parity bit at position 2^N alternately considers N bits then ignores N bits, starting at position 2^N.\n",
|
||||
"Each parity bit at position N alternately considers N bits then ignores N bits, starting at and including position N.\n",
|
||||
"The additional parity bit at position 0 considers all bits including parity bits.\n",
|
||||
"For example, the parity bit at position 2 considers bits 2 to 3 and 6 to 7. The parity bit at position 1 considers bits 1, 3, 5 and 7.\n",
|
||||
"The endianness of the parity bits is reversed compared to the endianness of the data bits:\n",
|
||||
"Data bits are encoded most significant bit first and the parity bits encoded least significant bit first.\n",
|
||||
"The additional parity bit at position 0 is set last.\n\n",
|
||||
@@ -53,8 +53,9 @@ export const hammingCode: Pick<
|
||||
"An 'extended Hamming code' has an additional parity bit to enhance error detection.\n",
|
||||
"A parity bit is inserted at every position N where N is a power of 2, with the additional parity bit at position 0.\n",
|
||||
"Parity bits are used to make the total number of '1' bits in a given set of data even.\n",
|
||||
"Each parity bit at position 2^N alternately considers 2^N bits then ignores 2^N bits, starting at position 2^N.\n",
|
||||
"Each parity bit at position N alternately considers N bits then ignores N bits, starting at and including position N.\n",
|
||||
"The additional parity bit at position 0 considers all bits including parity bits.\n",
|
||||
"For example, the parity bit at position 2 considers bits 2 to 3 and 6 to 7. The parity bit at position 1 considers bits 1, 3, 5 and 7.\n",
|
||||
"The endianness of the parity bits is reversed compared to the endianness of the data bits:\n",
|
||||
"Data bits are encoded most significant bit first and the parity bits encoded least significant bit first.\n",
|
||||
"The additional parity bit at position 0 is set last.\n",
|
||||
@@ -89,3 +90,162 @@ export const hammingCode: Pick<
|
||||
validateAnswer: (ans): ans is number => typeof ans === "number",
|
||||
},
|
||||
};
|
||||
|
||||
function HammingEncode(data: number): string {
|
||||
const enc: number[] = [0];
|
||||
const data_bits: number[] = data
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
let k = data_bits.length;
|
||||
|
||||
/* NOTE: writing the data like this flips the endianness, this is what the
|
||||
* original implementation by Hedrauta did so I'm keeping it like it was. */
|
||||
for (let i = 1; k > 0; i++) {
|
||||
if ((i & (i - 1)) != 0) {
|
||||
enc[i] = data_bits[--k];
|
||||
} else {
|
||||
enc[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let parityNumber = 0;
|
||||
|
||||
/* Figure out the subsection parities */
|
||||
for (let i = 0; i < enc.length; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber ^= i;
|
||||
}
|
||||
}
|
||||
|
||||
const parityArray = parityNumber
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
/* Set the parity bits accordingly */
|
||||
for (let i = 0; i < parityArray.length; i++) {
|
||||
enc[2 ** i] = parityArray[i] ? 1 : 0;
|
||||
}
|
||||
|
||||
parityNumber = 0;
|
||||
/* Figure out the overall parity for the entire block */
|
||||
for (let i = 0; i < enc.length; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally set the overall parity bit */
|
||||
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
|
||||
|
||||
return enc.join("");
|
||||
}
|
||||
|
||||
function HammingEncodeProperly(data: number): string {
|
||||
/* How many bits do we need?
|
||||
* n = 2^m
|
||||
* k = 2^m - m - 1
|
||||
* where k is the number of data bits, m the number
|
||||
* of parity bits and n the number of total bits. */
|
||||
|
||||
let m = 1;
|
||||
|
||||
while (2 ** (2 ** m - m - 1) - 1 < data) {
|
||||
m++;
|
||||
}
|
||||
|
||||
const n: number = 2 ** m;
|
||||
const k: number = 2 ** m - m - 1;
|
||||
|
||||
const enc: number[] = [0];
|
||||
const data_bits: number[] = data
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
/* Flip endianness as in the original implementation by Hedrauta
|
||||
* and write the data back to front
|
||||
* XXX why do we do this? */
|
||||
for (let i = 1, j = k; i < n; i++) {
|
||||
if ((i & (i - 1)) != 0) {
|
||||
enc[i] = data_bits[--j] ? data_bits[j] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
let parityNumber = 0;
|
||||
|
||||
/* Figure out the subsection parities */
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber ^= i;
|
||||
}
|
||||
}
|
||||
|
||||
const parityArray = parityNumber
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
/* Set the parity bits accordingly */
|
||||
for (let i = 0; i < m; i++) {
|
||||
enc[2 ** i] = parityArray[i] ? 1 : 0;
|
||||
}
|
||||
|
||||
parityNumber = 0;
|
||||
/* Figure out the overall parity for the entire block */
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally set the overall parity bit */
|
||||
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
|
||||
|
||||
return enc.join("");
|
||||
}
|
||||
|
||||
function HammingDecode(data: string): number {
|
||||
let err = 0;
|
||||
const bits: number[] = [];
|
||||
|
||||
/* TODO why not just work with an array of digits from the start? */
|
||||
const bitStringArray = data.split("");
|
||||
for (let i = 0; i < bitStringArray.length; ++i) {
|
||||
const bit = parseInt(bitStringArray[i]);
|
||||
bits[i] = bit;
|
||||
|
||||
if (bit) {
|
||||
err ^= +i;
|
||||
}
|
||||
}
|
||||
|
||||
/* If err != 0 then it spells out the index of the bit that was flipped */
|
||||
if (err) {
|
||||
/* Flip to correct */
|
||||
bits[err] = bits[err] ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Now we have to read the message, bit 0 is unused (it's the overall parity bit
|
||||
* which we don't care about). Each bit at an index that is a power of 2 is
|
||||
* a parity bit and not part of the actual message. */
|
||||
|
||||
let ans = "";
|
||||
|
||||
for (let i = 1; i < bits.length; i++) {
|
||||
/* i is not a power of two so it's not a parity bit */
|
||||
if ((i & (i - 1)) != 0) {
|
||||
ans += bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO to avoid ambiguity about endianness why not let the player return the extracted (and corrected)
|
||||
* data bits, rather than guessing at how to convert it to a decimal string? */
|
||||
return parseInt(ans, 2);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ Consider using the [API](https://github.com/bitburner-official/bitburner-src/blo
|
||||
For example, some contracts have long solutions while others have even longer solutions.
|
||||
You might want to use the [API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) to automate the process of submitting your solution rather than copy and paste a long solution into an answer box.
|
||||
The [Coding Contract API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) can also be used to find out useful information about a contract including the number of attempts you have left, the type of contract and its difficulty.
|
||||
It can also be used to test your algorithm for a specific contract type by [spawning dummy contracts](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.createdummycontract.md).
|
||||
|
||||
However, using the [API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) comes at a cost.
|
||||
Like most functions in other APIs, almost all of the functions in the [Coding Contract API](https://github.com/bitburner-official/bitburner-src/blob/stable/markdown/bitburner.codingcontract.md) have a RAM cost.
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
// choose random characters for generating plaintext to compress
|
||||
export function comprGenChar(): string {
|
||||
const r = Math.random();
|
||||
if (r < 0.4) {
|
||||
return "ABCDEFGHIJKLMNOPQRSTUVWXYZ"[Math.floor(26 * Math.random())];
|
||||
} else if (r < 0.8) {
|
||||
return "abcdefghijklmnopqrstuvwxyz"[Math.floor(26 * Math.random())];
|
||||
} else {
|
||||
return "01234567689"[Math.floor(10 * Math.random())];
|
||||
}
|
||||
}
|
||||
|
||||
// generate plaintext which is amenable to LZ encoding
|
||||
export function comprLZGenerate(): string {
|
||||
const length = 50 + Math.floor(25 * (Math.random() + Math.random()));
|
||||
let plain = "";
|
||||
|
||||
while (plain.length < length) {
|
||||
if (Math.random() < 0.8) {
|
||||
plain += comprGenChar();
|
||||
} else {
|
||||
const length = 1 + Math.floor(9 * Math.random());
|
||||
const offset = 1 + Math.floor(9 * Math.random());
|
||||
if (offset > plain.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let i = 0; i < length; ++i) {
|
||||
plain += plain[plain.length - offset];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return plain.substring(0, length);
|
||||
}
|
||||
|
||||
// compress plaintext string
|
||||
export function comprLZEncode(plain: string): string {
|
||||
// for state[i][j]:
|
||||
// if i is 0, we're adding a literal of length j
|
||||
// else, we're adding a backreference of offset i and length j
|
||||
let cur_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10).fill(null));
|
||||
let new_state: (string | null)[][] = Array.from(Array(10), () => Array<string | null>(10));
|
||||
|
||||
function set(state: (string | null)[][], i: number, j: number, str: string): void {
|
||||
const current = state[i][j];
|
||||
if (current == null || str.length < current.length) {
|
||||
state[i][j] = str;
|
||||
} else if (str.length === current.length && Math.random() < 0.5) {
|
||||
// if two strings are the same length, pick randomly so that
|
||||
// we generate more possible inputs to Compression II
|
||||
state[i][j] = str;
|
||||
}
|
||||
}
|
||||
|
||||
// initial state is a literal of length 1
|
||||
cur_state[0][1] = "";
|
||||
|
||||
for (let i = 1; i < plain.length; ++i) {
|
||||
for (const row of new_state) {
|
||||
row.fill(null);
|
||||
}
|
||||
const c = plain[i];
|
||||
|
||||
// handle literals
|
||||
for (let length = 1; length <= 9; ++length) {
|
||||
const string = cur_state[0][length];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (length < 9) {
|
||||
// extend current literal
|
||||
set(new_state, 0, length + 1, string);
|
||||
} else {
|
||||
// start new literal
|
||||
set(new_state, 0, 1, string + "9" + plain.substring(i - 9, i) + "0");
|
||||
}
|
||||
|
||||
for (let offset = 1; offset <= Math.min(9, i); ++offset) {
|
||||
if (plain[i - offset] === c) {
|
||||
// start new backreference
|
||||
set(new_state, offset, 1, string + String(length) + plain.substring(i - length, i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle backreferences
|
||||
for (let offset = 1; offset <= 9; ++offset) {
|
||||
for (let length = 1; length <= 9; ++length) {
|
||||
const string = cur_state[offset][length];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (plain[i - offset] === c) {
|
||||
if (length < 9) {
|
||||
// extend current backreference
|
||||
set(new_state, offset, length + 1, string);
|
||||
} else {
|
||||
// start new backreference
|
||||
set(new_state, offset, 1, string + "9" + String(offset) + "0");
|
||||
}
|
||||
}
|
||||
|
||||
// start new literal
|
||||
set(new_state, 0, 1, string + String(length) + String(offset));
|
||||
|
||||
// end current backreference and start new backreference
|
||||
for (let new_offset = 1; new_offset <= Math.min(9, i); ++new_offset) {
|
||||
if (plain[i - new_offset] === c) {
|
||||
set(new_state, new_offset, 1, string + String(length) + String(offset) + "0");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const tmp_state = new_state;
|
||||
new_state = cur_state;
|
||||
cur_state = tmp_state;
|
||||
}
|
||||
|
||||
let result = null;
|
||||
|
||||
for (let len = 1; len <= 9; ++len) {
|
||||
let string = cur_state[0][len];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string += String(len) + plain.substring(plain.length - len, plain.length);
|
||||
if (result == null || string.length < result.length) {
|
||||
result = string;
|
||||
} else if (string.length == result.length && Math.random() < 0.5) {
|
||||
result = string;
|
||||
}
|
||||
}
|
||||
|
||||
for (let offset = 1; offset <= 9; ++offset) {
|
||||
for (let len = 1; len <= 9; ++len) {
|
||||
let string = cur_state[offset][len];
|
||||
if (string == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string += String(len) + "" + String(offset);
|
||||
if (result == null || string.length < result.length) {
|
||||
result = string;
|
||||
} else if (string.length == result.length && Math.random() < 0.5) {
|
||||
result = string;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result ?? "";
|
||||
}
|
||||
|
||||
// decompress LZ-compressed string, or return null if input is invalid
|
||||
export function comprLZDecode(compr: string): string | null {
|
||||
let plain = "";
|
||||
|
||||
for (let i = 0; i < compr.length; ) {
|
||||
const literal_length = compr.charCodeAt(i) - 0x30;
|
||||
|
||||
if (literal_length < 0 || literal_length > 9 || i + 1 + literal_length > compr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
plain += compr.substring(i + 1, i + 1 + literal_length);
|
||||
i += 1 + literal_length;
|
||||
|
||||
if (i >= compr.length) {
|
||||
break;
|
||||
}
|
||||
const backref_length = compr.charCodeAt(i) - 0x30;
|
||||
|
||||
if (backref_length < 0 || backref_length > 9) {
|
||||
return null;
|
||||
} else if (backref_length === 0) {
|
||||
++i;
|
||||
} else {
|
||||
if (i + 1 >= compr.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const backref_offset = compr.charCodeAt(i + 1) - 0x30;
|
||||
if ((backref_length > 0 && (backref_offset < 1 || backref_offset > 9)) || backref_offset > plain.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let j = 0; j < backref_length; ++j) {
|
||||
plain += plain[plain.length - backref_offset];
|
||||
}
|
||||
|
||||
i += 2;
|
||||
}
|
||||
}
|
||||
|
||||
return plain;
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
export function HammingEncode(data: number): string {
|
||||
const enc: number[] = [0];
|
||||
const data_bits: number[] = data
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
let k = data_bits.length;
|
||||
|
||||
/* NOTE: writing the data like this flips the endianness, this is what the
|
||||
* original implementation by Hedrauta did so I'm keeping it like it was. */
|
||||
for (let i = 1; k > 0; i++) {
|
||||
if ((i & (i - 1)) != 0) {
|
||||
enc[i] = data_bits[--k];
|
||||
} else {
|
||||
enc[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
let parityNumber = 0;
|
||||
|
||||
/* Figure out the subsection parities */
|
||||
for (let i = 0; i < enc.length; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber ^= i;
|
||||
}
|
||||
}
|
||||
|
||||
const parityArray = parityNumber
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
/* Set the parity bits accordingly */
|
||||
for (let i = 0; i < parityArray.length; i++) {
|
||||
enc[2 ** i] = parityArray[i] ? 1 : 0;
|
||||
}
|
||||
|
||||
parityNumber = 0;
|
||||
/* Figure out the overall parity for the entire block */
|
||||
for (let i = 0; i < enc.length; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally set the overall parity bit */
|
||||
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
|
||||
|
||||
return enc.join("");
|
||||
}
|
||||
|
||||
export function HammingEncodeProperly(data: number): string {
|
||||
/* How many bits do we need?
|
||||
* n = 2^m
|
||||
* k = 2^m - m - 1
|
||||
* where k is the number of data bits, m the number
|
||||
* of parity bits and n the number of total bits. */
|
||||
|
||||
let m = 1;
|
||||
|
||||
while (2 ** (2 ** m - m - 1) - 1 < data) {
|
||||
m++;
|
||||
}
|
||||
|
||||
const n: number = 2 ** m;
|
||||
const k: number = 2 ** m - m - 1;
|
||||
|
||||
const enc: number[] = [0];
|
||||
const data_bits: number[] = data
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
/* Flip endianness as in the original implementation by Hedrauta
|
||||
* and write the data back to front
|
||||
* XXX why do we do this? */
|
||||
for (let i = 1, j = k; i < n; i++) {
|
||||
if ((i & (i - 1)) != 0) {
|
||||
enc[i] = data_bits[--j] ? data_bits[j] : 0;
|
||||
}
|
||||
}
|
||||
|
||||
let parityNumber = 0;
|
||||
|
||||
/* Figure out the subsection parities */
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber ^= i;
|
||||
}
|
||||
}
|
||||
|
||||
const parityArray = parityNumber
|
||||
.toString(2)
|
||||
.split("")
|
||||
.reverse()
|
||||
.map((value) => parseInt(value));
|
||||
|
||||
/* Set the parity bits accordingly */
|
||||
for (let i = 0; i < m; i++) {
|
||||
enc[2 ** i] = parityArray[i] ? 1 : 0;
|
||||
}
|
||||
|
||||
parityNumber = 0;
|
||||
/* Figure out the overall parity for the entire block */
|
||||
for (let i = 0; i < n; i++) {
|
||||
if (enc[i]) {
|
||||
parityNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Finally set the overall parity bit */
|
||||
enc[0] = parityNumber % 2 == 0 ? 0 : 1;
|
||||
|
||||
return enc.join("");
|
||||
}
|
||||
|
||||
export function HammingDecode(data: string): number {
|
||||
let err = 0;
|
||||
const bits: number[] = [];
|
||||
|
||||
/* TODO why not just work with an array of digits from the start? */
|
||||
const bitStringArray = data.split("");
|
||||
for (let i = 0; i < bitStringArray.length; ++i) {
|
||||
const bit = parseInt(bitStringArray[i]);
|
||||
bits[i] = bit;
|
||||
|
||||
if (bit) {
|
||||
err ^= +i;
|
||||
}
|
||||
}
|
||||
|
||||
/* If err != 0 then it spells out the index of the bit that was flipped */
|
||||
if (err) {
|
||||
/* Flip to correct */
|
||||
bits[err] = bits[err] ? 0 : 1;
|
||||
}
|
||||
|
||||
/* Now we have to read the message, bit 0 is unused (it's the overall parity bit
|
||||
* which we don't care about). Each bit at an index that is a power of 2 is
|
||||
* a parity bit and not part of the actual message. */
|
||||
|
||||
let ans = "";
|
||||
|
||||
for (let i = 1; i < bits.length; i++) {
|
||||
/* i is not a power of two so it's not a parity bit */
|
||||
if ((i & (i - 1)) != 0) {
|
||||
ans += bits[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* TODO to avoid ambiguity about endianness why not let the player return the extracted (and corrected)
|
||||
* data bits, rather than guessing at how to convert it to a decimal string? */
|
||||
return parseInt(ans, 2);
|
||||
}
|
||||
Reference in New Issue
Block a user