From e286ee1de5b6666677749c1a6567d8f4525998b6 Mon Sep 17 00:00:00 2001 From: Hedrauta Date: Thu, 7 Apr 2022 06:16:14 +0200 Subject: [PATCH] 2 new Coding Contracts also run format and lint on it this time :wink: --- src/data/codingcontracttypes.ts | 59 ++++++++++++++++++++ src/utils/HammingCodeTools.ts | 97 +++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+) create mode 100644 src/utils/HammingCodeTools.ts diff --git a/src/data/codingcontracttypes.ts b/src/data/codingcontracttypes.ts index 23235fa9a..e40c2dafd 100644 --- a/src/data/codingcontracttypes.ts +++ b/src/data/codingcontracttypes.ts @@ -1,5 +1,6 @@ import { getRandomInt } from "../utils/helpers/getRandomInt"; +import { HammingEncode, HammingDecode } from "../utils/HammingCodeTools"; /* tslint:disable:completed-docs no-magic-numbers arrow-return-shorthand */ /* Function that generates a valid 'data' for a contract type */ @@ -1008,4 +1009,62 @@ export const codingContractTypesMetadata: ICodingContractTypeMetadata[] = [ return true; }, }, + { + name: "HammingCodes: Integer to encoded Binary", + numTries: 10, + difficulty: 5, + desc: (n: number): string => { + return [ + "You are given the following decimal Value: \n", + `${n} \n`, + "Convert it into a binary string and encode it as a 'Hamming-Code'. eg:\n ", + "Value 8 will result into binary '1000', which will be encoded", + "with the pattern 'pppdpddd', where p is a paritybit and d a databit,\n", + "or '10101' (Value 21) will result into (pppdpdddpd) '1111101011'.\n\n", + "NOTE: You need an parity Bit on Index 0 as an 'overall'-paritybit. \n", + "NOTE 2: You should watch the HammingCode-video from 3Blue1Brown, which explains the 'rule' of encoding,", + "including the first Index parity-bit mentioned on the first note.\n\n", + "Now the only one rule for this encoding:\n", + " It's not allowed to add additional leading '0's to the binary value\n", + "That means, the binary value has to be encoded as it is", + ].join(" "); + }, + gen: (): number => { + return getRandomInt(Math.pow(2, 4), Math.pow(2, getRandomInt(1, 57))); + }, + solver: (data: number, ans: string): boolean => { + return ans === HammingEncode(data); + }, + }, + { + name: "HammingCodes: Encoded Binary to Integer", + difficulty: 8, + numTries: 10, + desc: (n: string): string => { + return [ + "You are given the following encoded binary String: \n", + `'${n}' \n`, + "Treat it as a Hammingcode with 1 'possible' error on an random Index.\n", + "Find the 'possible' wrong bit, fix it and extract the decimal value, which is hidden inside the string.\n\n", + "Note: The length of the binary string is dynamic, but it's encoding/decoding is following Hammings 'rule'\n", + "Note 2: Index 0 is an 'overall' parity bit. Watch the Hammingcode-video from 3Blue1Brown for more information\n", + "Note 3: There's a ~55% chance for an altered Bit. So... MAYBE there is an altered Bit 😉\n", + "Extranote for automation: return the decimal value as a string", + ].join(" "); + }, + gen: (): string => { + const _alteredBit = Math.round(Math.random()); + const _buildArray: Array = HammingEncode( + getRandomInt(Math.pow(2, 4), Math.pow(2, getRandomInt(1, 57))), + ).split(""); + if (_alteredBit) { + const _randomIndex: number = getRandomInt(0, _buildArray.length - 1); + _buildArray[_randomIndex] = _buildArray[_randomIndex] == "0" ? "1" : "0"; + } + return _buildArray.join(""); + }, + solver: (data: string, ans: string): boolean => { + return parseInt(ans, 10) === HammingDecode(data); + }, + }, ]; diff --git a/src/utils/HammingCodeTools.ts b/src/utils/HammingCodeTools.ts new file mode 100644 index 000000000..6fea2ee13 --- /dev/null +++ b/src/utils/HammingCodeTools.ts @@ -0,0 +1,97 @@ +// by Discord: H3draut3r#6722, feel free to ask me any questions. i probably don't know the answer 🤣 +export function HammingEncode(value: number): string { + // encoding following Hammings rule + function HammingSumOfParity(_lengthOfDBits: number): number { + // will calculate the needed amount of parityBits 'without' the "overall"-Parity (that math took me 4 Days to get it working) + return _lengthOfDBits < 3 || _lengthOfDBits == 0 // oh and of course using ternary operators, it's a pretty neat function + ? _lengthOfDBits == 0 + ? 0 + : _lengthOfDBits + 1 + : // the following math will only work, if the length is greater equal 3, otherwise it's "kind of" broken :D + Math.ceil(Math.log2(_lengthOfDBits * 2)) <= + Math.ceil(Math.log2(1 + _lengthOfDBits + Math.ceil(Math.log2(_lengthOfDBits)))) + ? Math.ceil(Math.log2(_lengthOfDBits) + 1) + : Math.ceil(Math.log2(_lengthOfDBits)); + } + const _data = value.toString(2).split(""); // first, change into binary string, then create array with 1 bit per index + const _sumParity: number = HammingSumOfParity(_data.length); // get the sum of needed parity bits (for later use in encoding) + const count = (arr: Array, val: string): number => + arr.reduce((a: number, v: string) => (v === val ? a + 1 : a), 0); + // function count for specific entries in the array, for later use + + const _build = ["x", "x", ..._data.splice(0, 1)]; // init the "pre-build" + for (let i = 2; i < _sumParity; i++) { + // add new paritybits and the corresponding data bits (pre-building array) + _build.push("x", ..._data.splice(0, Math.pow(2, i) - 1)); + } + // now the "calculation"... get the paritybits ('x') working + for (const index of _build.reduce(function (a: Array, e: string, i: number) { + if (e == "x") a.push(i); + return a; + }, [])) { + // that reduce will result in an array of index numbers where the "x" is placed + const _tempcount = index + 1; // set the "stepsize" for the parityBit + const _temparray = []; // temporary array to store the extracted bits + const _tempdata = [..._build]; // only work with a copy of the _build + while (_tempdata[index] !== undefined) { + // as long as there are bits on the starting index, do "cut" + const _temp: Array = _tempdata.splice(index, _tempcount * 2); // cut stepsize*2 bits, then... + _temparray.push(..._temp.splice(0, _tempcount)); // ... cut the result again and keep the first half + } + _temparray.splice(0, 1); // remove first bit, which is the parity one + _build[index] = (count(_temparray, "1") % 2).toString(); // count with remainder of 2 and"toString" to store the parityBit + } // parity done, now the "overall"-parity is set + _build.unshift((count(_build, "1") % 2).toString()); // has to be done as last element + return _build.join(""); // return the _build as string +} + +export function HammingDecode(_data: string): number { + //check for altered bit and decode + const _build = _data.split(""); // ye, an array for working, again + const _testArray = []; //for the "truthtable". if any is false, the data has an altered bit, will check for and fix it + const _sumParity = Math.ceil(Math.log2(_data.length)); // sum of parity for later use + const count = (arr: Array, val: string): number => + arr.reduce((a: number, v: string) => (v === val ? a + 1 : a), 0); + // the count.... again ;) + + let _overallParity = _build.splice(0, 1).join(""); // store first index, for checking in next step and fix the _build properly later on + _testArray.push(_overallParity == (count(_build, "1") % 2).toString() ? true : false); // first check with the overall parity bit + for (let i = 0; i < _sumParity; i++) { + // for the rest of the remaining parity bits we also "check" + const _tempIndex = Math.pow(2, i) - 1; // get the parityBits Index + const _tempStep = _tempIndex + 1; // set the stepsize + const _tempData = [..._build]; // get a "copy" of the build-data for working + const _tempArray = []; // init empty array for "testing" + while (_tempData[_tempIndex] != undefined) { + // extract from the copied data until the "starting" index is undefined + const _temp = [..._tempData.splice(_tempIndex, _tempStep * 2)]; // extract 2*stepsize + _tempArray.push(..._temp.splice(0, _tempStep)); // and cut again for keeping first half + } + const _tempParity = _tempArray.shift(); // and again save the first index separated for checking with the rest of the data + _testArray.push(_tempParity == (count(_tempArray, "1") % 2).toString() ? true : false); + // is the _tempParity the calculated data? push answer into the 'truthtable' + } + let _fixIndex = 0; // init the "fixing" index and start with 0 + for (let i = 1; i < _sumParity + 1; i++) { + // simple binary adding for every boolean in the _testArray, starting from 2nd index of it + _fixIndex += _testArray[i] ? 0 : Math.pow(2, i) / 2; + } + _build.unshift(_overallParity); // now we need the "overall" parity back in it's place + // try fix the actual encoded binary string if there is an error + if (_fixIndex > 0 && _testArray[0] == false) { + // if the overall is false and the sum of calculated values is greater equal 0, fix the corresponding hamming-bit + _build[_fixIndex] = _build[_fixIndex] == "0" ? "1" : "0"; + } else if (_testArray[0] == false) { + // otherwise, if the the overall_parity is the only wrong, fix that one + _overallParity = _overallParity == "0" ? "1" : "0"; + } else if (_testArray[0] == true && _testArray.some((truth) => truth == false)) { + return 0; // uhm, there's some strange going on... 2 bits are altered? How? This should not happen 👀 + } + // oof.. halfway through... we fixed an possible altered bit, now "extract" the parity-bits from the _build + for (let i = _sumParity; i >= 0; i--) { + // start from the last parity down the 2nd index one + _build.splice(Math.pow(2, i), 1); + } + _build.splice(0, 1); // remove the overall parity bit and we have our binary value + return parseInt(_build.join(""), 2); // parse the integer with redux 2 and we're done! +}