Skip to content

Commit 21ec87f

Browse files
committed
feat: add modulus operation
1 parent 2a1294f commit 21ec87f

File tree

5 files changed

+296
-0
lines changed

5 files changed

+296
-0
lines changed

src/core/config/Categories.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,7 @@
220220
"Subtract",
221221
"Multiply",
222222
"Divide",
223+
"MOD",
223224
"Mean",
224225
"Median",
225226
"Standard Deviation",

src/core/lib/Arithmetic.mjs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,19 @@ export function median(data) {
121121
}
122122

123123

124+
/**
125+
* Computes modulo of two numbers and returns the value.
126+
*
127+
* @param {BigNumber[]} data
128+
* @returns {BigNumber}
129+
*/
130+
export function mod(data) {
131+
if (data.length > 0) {
132+
return data.reduce((acc, curr) => acc.mod(curr));
133+
}
134+
}
135+
136+
124137
/**
125138
* Computes standard deviation of a number array and returns the value.
126139
*

src/core/operations/MOD.mjs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/**
2+
* @license Apache-2.0
3+
*/
4+
5+
import BigNumber from "bignumber.js";
6+
import Operation from "../Operation.mjs";
7+
import { createNumArray } from "../lib/Arithmetic.mjs";
8+
import { ARITHMETIC_DELIM_OPTIONS } from "../lib/Delim.mjs";
9+
10+
11+
/**
12+
* MOD operation
13+
*/
14+
class MOD extends Operation {
15+
16+
/**
17+
* MOD constructor
18+
*/
19+
constructor() {
20+
super();
21+
22+
this.name = "MOD";
23+
this.module = "Default";
24+
this.description = "Computes the modulo of each number in a list with a given modulus value. Numbers are extracted from the input based on the delimiter, and non-numeric values are ignored.<br><br>e.g. <code>15 4 7</code> with modulus <code>3</code> becomes <code>0 1 1</code>";
25+
this.inputType = "string";
26+
this.outputType = "string";
27+
this.args = [
28+
{
29+
"name": "Modulus",
30+
"type": "number",
31+
"value": 2
32+
},
33+
{
34+
"name": "Delimiter",
35+
"type": "option",
36+
"value": ARITHMETIC_DELIM_OPTIONS,
37+
}
38+
];
39+
}
40+
41+
/**
42+
* @param {string} input
43+
* @param {Object[]} args
44+
* @returns {string}
45+
*/
46+
run(input, args) {
47+
const modulus = new BigNumber(args[0]);
48+
const delimiter = args[1];
49+
50+
if (modulus.isZero()) {
51+
throw new Error("Modulus cannot be zero");
52+
}
53+
54+
const numbers = createNumArray(input, delimiter);
55+
const results = numbers.map(num => num.mod(modulus));
56+
57+
return results.join(" ");
58+
}
59+
60+
}
61+
62+
export default MOD;

tests/operations/index.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import "./tests/Magic.mjs";
109109
import "./tests/Media.mjs";
110110
import "./tests/MIMEDecoding.mjs";
111111
import "./tests/Modhex.mjs";
112+
import "./tests/MOD.mjs";
112113
import "./tests/MorseCode.mjs";
113114
import "./tests/MS.mjs";
114115
import "./tests/MultipleBombe.mjs";

tests/operations/tests/MOD.mjs

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/**
2+
* MOD tests
3+
*
4+
* @license Apache-2.0
5+
*/
6+
import TestRegister from "../../lib/TestRegister.mjs";
7+
8+
TestRegister.addTests([
9+
{
10+
name: "MOD: Basic modulo operation",
11+
input: "15 4 7",
12+
expectedOutput: "0 1 1",
13+
recipeConfig: [
14+
{
15+
"op": "MOD",
16+
"args": [3, "Space"]
17+
}
18+
],
19+
},
20+
{
21+
name: "MOD: Single number",
22+
input: "10",
23+
expectedOutput: "1",
24+
recipeConfig: [
25+
{
26+
"op": "MOD",
27+
"args": [3, "Space"]
28+
}
29+
],
30+
},
31+
{
32+
name: "MOD: Comma-separated numbers",
33+
input: "15,8,23,16,5",
34+
expectedOutput: "1 1 2 2 5",
35+
recipeConfig: [
36+
{
37+
"op": "MOD",
38+
"args": [7, "Comma"]
39+
}
40+
],
41+
},
42+
{
43+
name: "MOD: Line feed separated numbers",
44+
input: "25\n13\n44\n7",
45+
expectedOutput: "0 3 4 2",
46+
recipeConfig: [
47+
{
48+
"op": "MOD",
49+
"args": [5, "Line feed"]
50+
}
51+
],
52+
},
53+
{
54+
name: "MOD: Tab-separated numbers",
55+
input: "20\t14\t8\t35",
56+
expectedOutput: "2 2 2 5",
57+
recipeConfig: [
58+
{
59+
"op": "MOD",
60+
"args": [6, "Tab"]
61+
}
62+
],
63+
},
64+
{
65+
name: "MOD: Large numbers",
66+
input: "123456789012345 987654321098765",
67+
expectedOutput: "123456789012345 987654321098765",
68+
recipeConfig: [
69+
{
70+
"op": "MOD",
71+
"args": [1234567890123456, "Space"]
72+
}
73+
],
74+
},
75+
{
76+
name: "MOD: Mixed with non-numeric values",
77+
input: "15 abc 4 def 7 xyz 23",
78+
expectedOutput: "0 1 1 2",
79+
recipeConfig: [
80+
{
81+
"op": "MOD",
82+
"args": [3, "Space"]
83+
}
84+
],
85+
},
86+
{
87+
name: "MOD: Decimal numbers",
88+
input: "10.5 15.7 8.2",
89+
expectedOutput: "1.5 0.7 2.2",
90+
recipeConfig: [
91+
{
92+
"op": "MOD",
93+
"args": [3, "Space"]
94+
}
95+
],
96+
},
97+
{
98+
name: "MOD: Negative numbers",
99+
input: "-15 -8 25 -10",
100+
expectedOutput: "0 -2 1 -1",
101+
recipeConfig: [
102+
{
103+
"op": "MOD",
104+
"args": [3, "Space"]
105+
}
106+
],
107+
},
108+
{
109+
name: "MOD: Zero in input",
110+
input: "0 5 10 15 20",
111+
expectedOutput: "0 2 1 0 2",
112+
recipeConfig: [
113+
{
114+
"op": "MOD",
115+
"args": [3, "Space"]
116+
}
117+
],
118+
},
119+
{
120+
name: "MOD: Modulus of 2 (even/odd check)",
121+
input: "1 2 3 4 5 6 7 8 9 10",
122+
expectedOutput: "1 0 1 0 1 0 1 0 1 0",
123+
recipeConfig: [
124+
{
125+
"op": "MOD",
126+
"args": [2, "Space"]
127+
}
128+
],
129+
},
130+
{
131+
name: "MOD: Numbers with extra whitespace",
132+
input: " 15 4 7 ",
133+
expectedOutput: "0 1 1",
134+
recipeConfig: [
135+
{
136+
"op": "MOD",
137+
"args": [3, "Space"]
138+
}
139+
],
140+
},
141+
{
142+
name: "MOD: Empty input",
143+
input: "",
144+
expectedOutput: "",
145+
recipeConfig: [
146+
{
147+
"op": "MOD",
148+
"args": [3, "Space"]
149+
}
150+
],
151+
},
152+
{
153+
name: "MOD: Scientific notation",
154+
input: "1e3 2e2 5e1",
155+
expectedOutput: "1 2 2",
156+
recipeConfig: [
157+
{
158+
"op": "MOD",
159+
"args": [3, "Space"]
160+
}
161+
],
162+
},
163+
{
164+
name: "MOD: Floating point precision",
165+
input: "10.123456789 20.987654321",
166+
expectedOutput: "1.123456789 2.987654321",
167+
recipeConfig: [
168+
{
169+
"op": "MOD",
170+
"args": [3, "Space"]
171+
}
172+
],
173+
},
174+
{
175+
name: "MOD: Zero modulus error",
176+
input: "15 4 7",
177+
expectedError: true,
178+
expectedOutput: "MOD - Modulus cannot be zero",
179+
recipeConfig: [
180+
{
181+
"op": "MOD",
182+
"args": [0, "Space"]
183+
}
184+
],
185+
},
186+
{
187+
name: "MOD: Semi-colon separated numbers",
188+
input: "17;5;8;13",
189+
expectedOutput: "2 0 3 3",
190+
recipeConfig: [
191+
{
192+
"op": "MOD",
193+
"args": [5, "Semi-colon"]
194+
}
195+
],
196+
},
197+
{
198+
name: "MOD: Colon separated numbers",
199+
input: "25:9:14:7",
200+
expectedOutput: "1 1 2 3",
201+
recipeConfig: [
202+
{
203+
"op": "MOD",
204+
"args": [4, "Colon"]
205+
}
206+
],
207+
},
208+
{
209+
name: "MOD: CRLF separated numbers",
210+
input: "30\r\n18\r\n22\r\n11",
211+
expectedOutput: "0 0 4 5",
212+
recipeConfig: [
213+
{
214+
"op": "MOD",
215+
"args": [6, "CRLF"]
216+
}
217+
],
218+
},
219+
]);

0 commit comments

Comments
 (0)