diff --git a/markdown/bitburner.gang.getotherganginformation.md b/markdown/bitburner.gang.getotherganginformation.md index ca884d935..281bbdd7d 100644 --- a/markdown/bitburner.gang.getotherganginformation.md +++ b/markdown/bitburner.gang.getotherganginformation.md @@ -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; 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 diff --git a/markdown/bitburner.gang.md b/markdown/bitburner.gang.md index bcaed2659..6996d7989 100644 --- a/markdown/bitburner.gang.md +++ b/markdown/bitburner.gang.md @@ -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. | diff --git a/src/CodingContract/contracts/Compression.ts b/src/CodingContract/contracts/Compression.ts index d20569f30..1f2588895 100644 --- a/src/CodingContract/contracts/Compression.ts +++ b/src/CodingContract/contracts/Compression.ts @@ -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(10).fill(null)); + let new_state: (string | null)[][] = Array.from(Array(10), () => Array(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; +} diff --git a/src/CodingContract/contracts/HammingCode.ts b/src/CodingContract/contracts/HammingCode.ts index e09831e09..1e04fb2e8 100644 --- a/src/CodingContract/contracts/HammingCode.ts +++ b/src/CodingContract/contracts/HammingCode.ts @@ -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); +} diff --git a/src/Documentation/doc/en/basic/codingcontracts.md b/src/Documentation/doc/en/basic/codingcontracts.md index 9446b4ec5..667365295 100644 --- a/src/Documentation/doc/en/basic/codingcontracts.md +++ b/src/Documentation/doc/en/basic/codingcontracts.md @@ -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. diff --git a/src/utils/CompressionContracts.ts b/src/utils/CompressionContracts.ts deleted file mode 100644 index 8c19c2df0..000000000 --- a/src/utils/CompressionContracts.ts +++ /dev/null @@ -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(10).fill(null)); - let new_state: (string | null)[][] = Array.from(Array(10), () => Array(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; -} diff --git a/src/utils/HammingCodeTools.ts b/src/utils/HammingCodeTools.ts deleted file mode 100644 index d55ed7544..000000000 --- a/src/utils/HammingCodeTools.ts +++ /dev/null @@ -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); -}