Added authors
This commit is contained in:
parent
f3daa4702a
commit
ba719a01ea
@ -1,5 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @author Alexei Baranov
|
* @author Alexei Baranov [alex022003@mail.ru]
|
||||||
|
* @author blevotish
|
||||||
* @copyright Crown Copyright 2024
|
* @copyright Crown Copyright 2024
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
*/
|
*/
|
||||||
@ -21,13 +22,13 @@ const ALPHABETS = {
|
|||||||
Russian: {
|
Russian: {
|
||||||
alpha: "абвгдеёжзийклмнопрстуфхцчшщъыьэюя",
|
alpha: "абвгдеёжзийклмнопрстуфхцчшщъыьэюя",
|
||||||
vowels: "аеёиоуыэюя",
|
vowels: "аеёиоуыэюя",
|
||||||
sonor: "йлмнрь"
|
sonor: "йлмнрь",
|
||||||
},
|
},
|
||||||
English: {
|
English: {
|
||||||
alpha: "abcdefghijklmnopqrstuvwxyz",
|
alpha: "abcdefghijklmnopqrstuvwxyz",
|
||||||
vowels: "aeiou",
|
vowels: "aeiou",
|
||||||
sonor: "lmnrwy"
|
sonor: "lmnrwy",
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -39,7 +40,8 @@ const ALPHABETS = {
|
|||||||
* @returns {string} "Russian" or "English"
|
* @returns {string} "Russian" or "English"
|
||||||
*/
|
*/
|
||||||
function detectAlphabet(codeWords) {
|
function detectAlphabet(codeWords) {
|
||||||
let ru = 0, en = 0;
|
let ru = 0,
|
||||||
|
en = 0;
|
||||||
for (const ch of codeWords.toLowerCase()) {
|
for (const ch of codeWords.toLowerCase()) {
|
||||||
if (ALPHABETS.Russian.alpha.indexOf(ch) >= 0) ru++;
|
if (ALPHABETS.Russian.alpha.indexOf(ch) >= 0) ru++;
|
||||||
else if (ALPHABETS.English.alpha.indexOf(ch) >= 0) en++;
|
else if (ALPHABETS.English.alpha.indexOf(ch) >= 0) en++;
|
||||||
@ -84,11 +86,11 @@ function letterValue(letter, alpha) {
|
|||||||
function syllables(word, ab) {
|
function syllables(word, ab) {
|
||||||
word = word.toLowerCase();
|
word = word.toLowerCase();
|
||||||
if (word.indexOf("-") >= 0) {
|
if (word.indexOf("-") >= 0) {
|
||||||
return word.split("-").filter(s => s.length > 0);
|
return word.split("-").filter((s) => s.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const isVowel = ch => ab.vowels.indexOf(ch) >= 0;
|
const isVowel = (ch) => ab.vowels.indexOf(ch) >= 0;
|
||||||
const isSonor = ch => ab.sonor.indexOf(ch) >= 0;
|
const isSonor = (ch) => ab.sonor.indexOf(ch) >= 0;
|
||||||
|
|
||||||
const result = [];
|
const result = [];
|
||||||
let cur = "";
|
let cur = "";
|
||||||
@ -138,13 +140,16 @@ function syllables(word, ab) {
|
|||||||
* @returns {Object[][]} rows - array of rows, each an array of {cap}
|
* @returns {Object[][]} rows - array of rows, each an array of {cap}
|
||||||
*/
|
*/
|
||||||
function buildRows(codeWordsStr, ab) {
|
function buildRows(codeWordsStr, ab) {
|
||||||
const words = codeWordsStr.trim().split(/\s+/).filter(w => w.length > 0);
|
const words = codeWordsStr
|
||||||
|
.trim()
|
||||||
|
.split(/\s+/)
|
||||||
|
.filter((w) => w.length > 0);
|
||||||
const rows = [];
|
const rows = [];
|
||||||
for (const word of words) {
|
for (const word of words) {
|
||||||
for (const syllable of syllables(word, ab)) {
|
for (const syllable of syllables(word, ab)) {
|
||||||
const boxes = [];
|
const boxes = [];
|
||||||
for (const letter of syllable) {
|
for (const letter of syllable) {
|
||||||
boxes.push({cap: letterValue(letter, ab.alpha)});
|
boxes.push({ cap: letterValue(letter, ab.alpha) });
|
||||||
}
|
}
|
||||||
if (boxes.length > 0) rows.push(boxes);
|
if (boxes.length > 0) rows.push(boxes);
|
||||||
}
|
}
|
||||||
@ -166,7 +171,7 @@ function layout(n, rows) {
|
|||||||
if (rows.length === 0) return order;
|
if (rows.length === 0) return order;
|
||||||
|
|
||||||
// Per-box fill counters.
|
// Per-box fill counters.
|
||||||
const counts = rows.map(row => row.map(() => 0));
|
const counts = rows.map((row) => row.map(() => 0));
|
||||||
|
|
||||||
// Rows are "born" in order; only born rows participate.
|
// Rows are "born" in order; only born rows participate.
|
||||||
const aliveRows = [];
|
const aliveRows = [];
|
||||||
@ -182,7 +187,7 @@ function layout(n, rows) {
|
|||||||
const aliveBoxes = () => {
|
const aliveBoxes = () => {
|
||||||
const list = [];
|
const list = [];
|
||||||
for (const r of aliveRows) {
|
for (const r of aliveRows) {
|
||||||
for (let b = 0; b < rows[r].length; b++) list.push({r, b});
|
for (let b = 0; b < rows[r].length; b++) list.push({ r, b });
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
@ -195,7 +200,7 @@ function layout(n, rows) {
|
|||||||
let openCount = 0;
|
let openCount = 0;
|
||||||
let onlyOpen = -1;
|
let onlyOpen = -1;
|
||||||
for (let idx = 0; idx < boxes.length; idx++) {
|
for (let idx = 0; idx < boxes.length; idx++) {
|
||||||
const {r, b} = boxes[idx];
|
const { r, b } = boxes[idx];
|
||||||
if (counts[r][b] < rows[r][b].cap) {
|
if (counts[r][b] < rows[r][b].cap) {
|
||||||
openCount++;
|
openCount++;
|
||||||
onlyOpen = idx;
|
onlyOpen = idx;
|
||||||
@ -237,7 +242,7 @@ function layout(n, rows) {
|
|||||||
let found = -1;
|
let found = -1;
|
||||||
for (let step = 1; step <= m; step++) {
|
for (let step = 1; step <= m; step++) {
|
||||||
const idx = (cur + step) % m;
|
const idx = (cur + step) % m;
|
||||||
const {r, b} = boxes[idx];
|
const { r, b } = boxes[idx];
|
||||||
if (counts[r][b] < rows[r][b].cap) {
|
if (counts[r][b] < rows[r][b].cap) {
|
||||||
found = idx;
|
found = idx;
|
||||||
break;
|
break;
|
||||||
@ -272,7 +277,6 @@ function clean(text, alpha) {
|
|||||||
* Kolmar Cipher operation.
|
* Kolmar Cipher operation.
|
||||||
*/
|
*/
|
||||||
class KolmarCipher extends Operation {
|
class KolmarCipher extends Operation {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* KolmarCipher constructor
|
* KolmarCipher constructor
|
||||||
*/
|
*/
|
||||||
@ -281,30 +285,31 @@ class KolmarCipher extends Operation {
|
|||||||
|
|
||||||
this.name = "Kolmar Cipher";
|
this.name = "Kolmar Cipher";
|
||||||
this.module = "Ciphers";
|
this.module = "Ciphers";
|
||||||
this.description = "A custom transposition cipher driven by one or more code words, working on either the Russian or English alphabet.<br><br>Each code word is split into syllables (use a hyphen to divide syllables manually, e.g. <code>коль-мар</code> / <code>ho-tel</code>); every syllable defines a row of boxes and every letter's alphabet position gives the capacity of a box. The cleaned text (letters of the chosen alphabet only, everything else is dropped) is dealt into the boxes row by row, birthing new rows as boxes fill up, and read back out box by box to form the cipher text.<br><br>With <code>Alphabet</code> set to <code>Auto</code> the alphabet is inferred from the code words. Decoding reverses the layout, returning the run-together plaintext (spaces are not restored because they are never encoded).";
|
this.description =
|
||||||
|
"A custom transposition cipher driven by one or more code words, working on either the Russian or English alphabet.<br><br>Each code word is split into syllables (use a hyphen to divide syllables manually, e.g. <code>коль-мар</code> / <code>ho-tel</code>); every syllable defines a row of boxes and every letter's alphabet position gives the capacity of a box. The cleaned text (letters of the chosen alphabet only, everything else is dropped) is dealt into the boxes row by row, birthing new rows as boxes fill up, and read back out box by box to form the cipher text.<br><br>With <code>Alphabet</code> set to <code>Auto</code> the alphabet is inferred from the code words. Decoding reverses the layout, returning the run-together plaintext (spaces are not restored because they are never encoded).";
|
||||||
this.inputType = "string";
|
this.inputType = "string";
|
||||||
this.outputType = "string";
|
this.outputType = "string";
|
||||||
this.args = [
|
this.args = [
|
||||||
{
|
{
|
||||||
name: "Code words",
|
name: "Code words",
|
||||||
type: "string",
|
type: "string",
|
||||||
value: ""
|
value: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Alphabet",
|
name: "Alphabet",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["Auto", "Russian", "English"]
|
value: ["Auto", "Russian", "English"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Mode",
|
name: "Mode",
|
||||||
type: "option",
|
type: "option",
|
||||||
value: ["Encode", "Decode"]
|
value: ["Encode", "Decode"],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Squash to single line",
|
name: "Squash to single line",
|
||||||
type: "boolean",
|
type: "boolean",
|
||||||
value: false
|
value: false,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +321,8 @@ class KolmarCipher extends Operation {
|
|||||||
run(input, args) {
|
run(input, args) {
|
||||||
const [codeWords, alphabetArg, mode, squash] = args;
|
const [codeWords, alphabetArg, mode, squash] = args;
|
||||||
|
|
||||||
const lang = alphabetArg === "Auto" ? detectAlphabet(codeWords) : alphabetArg;
|
const lang =
|
||||||
|
alphabetArg === "Auto" ? detectAlphabet(codeWords) : alphabetArg;
|
||||||
const ab = ALPHABETS[lang] || ALPHABETS.Russian;
|
const ab = ALPHABETS[lang] || ALPHABETS.Russian;
|
||||||
|
|
||||||
const rows = buildRows(codeWords, ab);
|
const rows = buildRows(codeWords, ab);
|
||||||
@ -344,7 +350,7 @@ class KolmarCipher extends Operation {
|
|||||||
const order = layout(cleaned.length, rows);
|
const order = layout(cleaned.length, rows);
|
||||||
|
|
||||||
// Deal the letters into their boxes, preserving insertion order.
|
// Deal the letters into their boxes, preserving insertion order.
|
||||||
const boxLetters = rows.map(row => row.map(() => []));
|
const boxLetters = rows.map((row) => row.map(() => []));
|
||||||
for (let k = 0; k < order.length; k++) {
|
for (let k = 0; k < order.length; k++) {
|
||||||
boxLetters[order[k].r][order[k].b].push(cleaned[k]);
|
boxLetters[order[k].r][order[k].b].push(cleaned[k]);
|
||||||
}
|
}
|
||||||
@ -354,7 +360,8 @@ class KolmarCipher extends Operation {
|
|||||||
for (let r = 0; r < rows.length; r++) {
|
for (let r = 0; r < rows.length; r++) {
|
||||||
const parts = [];
|
const parts = [];
|
||||||
for (let b = 0; b < rows[r].length; b++) {
|
for (let b = 0; b < rows[r].length; b++) {
|
||||||
if (boxLetters[r][b].length > 0) parts.push(boxLetters[r][b].join(""));
|
if (boxLetters[r][b].length > 0)
|
||||||
|
parts.push(boxLetters[r][b].join(""));
|
||||||
}
|
}
|
||||||
if (parts.length > 0) lines.push(parts.join(" "));
|
if (parts.length > 0) lines.push(parts.join(" "));
|
||||||
}
|
}
|
||||||
@ -377,7 +384,7 @@ class KolmarCipher extends Operation {
|
|||||||
const order = layout(cipher.length, rows);
|
const order = layout(cipher.length, rows);
|
||||||
|
|
||||||
// For each box, the original indices of the letters it holds, in order.
|
// For each box, the original indices of the letters it holds, in order.
|
||||||
const boxPositions = rows.map(row => row.map(() => []));
|
const boxPositions = rows.map((row) => row.map(() => []));
|
||||||
for (let k = 0; k < order.length; k++) {
|
for (let k = 0; k < order.length; k++) {
|
||||||
boxPositions[order[k].r][order[k].b].push(k);
|
boxPositions[order[k].r][order[k].b].push(k);
|
||||||
}
|
}
|
||||||
@ -396,7 +403,6 @@ class KolmarCipher extends Operation {
|
|||||||
|
|
||||||
return plain.join("");
|
return plain.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default KolmarCipher;
|
export default KolmarCipher;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Kolmar Cipher tests.
|
* Kolmar Cipher tests.
|
||||||
*
|
*
|
||||||
* @author Alexei Baranov
|
* @author Alexei Baranov [alex022003@mail.ru]
|
||||||
*
|
*
|
||||||
* @copyright Crown Copyright 2024
|
* @copyright Crown Copyright 2024
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
@ -123,7 +123,8 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "Kolmar Cipher: encode full demo (3 code words, multiline)",
|
name: "Kolmar Cipher: encode full demo (3 code words, multiline)",
|
||||||
input: "не стоит называть кольмара кальмаром ведь он обидится и умрет от печали",
|
input: "не стоит называть кольмара кальмаром ведь он обидится и умрет от печали",
|
||||||
expectedOutput: "но еиавкь стз тны\nаомр т ьлаалмрм\nкьаов а\nен дои ьбдсим о\nия т\nуроеаи\nет тпчл",
|
expectedOutput:
|
||||||
|
"но еиавкь стз тны\nаомр т ьлаалмрм\nкьаов а\nен дои ьбдсим о\nия т\nуроеаи\nет тпчл",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Kolmar Cipher",
|
op: "Kolmar Cipher",
|
||||||
@ -134,7 +135,8 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "Kolmar Cipher: decode full demo (3 code words, multiline)",
|
name: "Kolmar Cipher: decode full demo (3 code words, multiline)",
|
||||||
input: "но еиавкь стз тны\nаомр т ьлаалмрм\nкьаов а\nен дои ьбдсим о\nия т\nуроеаи\nет тпчл",
|
input: "но еиавкь стз тны\nаомр т ьлаалмрм\nкьаов а\nен дои ьбдсим о\nия т\nуроеаи\nет тпчл",
|
||||||
expectedOutput: "нестоитназыватькольмаракальмаромведьонобидитсяиумретотпечали",
|
expectedOutput:
|
||||||
|
"нестоитназыватькольмаракальмаромведьонобидитсяиумретотпечали",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Kolmar Cipher",
|
op: "Kolmar Cipher",
|
||||||
@ -254,7 +256,8 @@ TestRegister.addTests([
|
|||||||
{
|
{
|
||||||
name: "Kolmar Cipher: round-trip Russian full demo",
|
name: "Kolmar Cipher: round-trip Russian full demo",
|
||||||
input: "не стоит называть кольмара кальмаром ведь он обидится и умрет от печали",
|
input: "не стоит называть кольмара кальмаром ведь он обидится и умрет от печали",
|
||||||
expectedOutput: "нестоитназыватькольмаракальмаромведьонобидитсяиумретотпечали",
|
expectedOutput:
|
||||||
|
"нестоитназыватькольмаракальмаромведьонобидитсяиумретотпечали",
|
||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Kolmar Cipher",
|
op: "Kolmar Cipher",
|
||||||
@ -273,11 +276,21 @@ TestRegister.addTests([
|
|||||||
recipeConfig: [
|
recipeConfig: [
|
||||||
{
|
{
|
||||||
op: "Kolmar Cipher",
|
op: "Kolmar Cipher",
|
||||||
args: ["programming languages are wonderful", "English", "Encode", true],
|
args: [
|
||||||
|
"programming languages are wonderful",
|
||||||
|
"English",
|
||||||
|
"Encode",
|
||||||
|
true,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
op: "Kolmar Cipher",
|
op: "Kolmar Cipher",
|
||||||
args: ["programming languages are wonderful", "English", "Decode", false],
|
args: [
|
||||||
|
"programming languages are wonderful",
|
||||||
|
"English",
|
||||||
|
"Decode",
|
||||||
|
false,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user