+1
.gitignore
+1
.gitignore
···
1
+
node_modules
+11
.zed/settings.json
+11
.zed/settings.json
···
1
+
// Folder-specific settings
2
+
//
3
+
// For a full list of overridable settings, and general information on folder-specific settings,
4
+
// see the documentation: https://zed.dev/docs/configuring-zed#settings-files
5
+
{
6
+
"languages": {
7
+
"TypeScript": {
8
+
"tab_size": 4
9
+
}
10
+
}
11
+
}
+62
career focus/cf_questions.md
+62
career focus/cf_questions.md
···
1
+
# Interests, Skills, Values
2
+
3
+
1. What led you to pursue a career in tech?
4
+
- I've always been interested in tinkering with computers and what they can do.
5
+
2. What aspects of tech excite you (e.g., frontend development, cloud computing, cybersecurity, AI)
6
+
- Software development in general is a fun subject for me, I've done frontend, backend, game dev, and other things.
7
+
3. What types of projects have you enjoyed the most in your education/work experience?
8
+
- The notification server I've been writing in Rust.
9
+
4. What technical and soft skills do you excel at?
10
+
- Git/VCS, SQL, Rust, JS/TS
11
+
5. Which aspects of problem-solving excite you the most?
12
+
- Any parts. I love the full process from learning about the problem to charting a valid solution
13
+
6. Do you prefer frontend, backend, full-stack, or data-related roles?
14
+
- On System (App developemnt) or Full-Stack
15
+
7. What are your strengths based on past feedback from instructors or peers?
16
+
- Great problem solver, fast learner
17
+
8. Are you more inclined toward individual contributor, team lead, project management roles?
18
+
- Individual contributor or team lead. I've done work as both for open source projects and I don't generally have an opinion on either.
19
+
9. Do you enjoy working independently, in small teams, or large teams?
20
+
- Independently or Small Teams
21
+
10. What kind of impact do you want my work to have?
22
+
- I'm unsure
23
+
11. What values are important for you as you consider your tech career? (e.g., compensation, diversity, sustainability, transparency)
24
+
- transparency. I'm a big advocate of Open Source and Source Available software.
25
+
26
+
27
+
# Work Environment & Company Preferences
28
+
29
+
12. Do you prefer working in a startup, a large corporation, or a mid-sized company?
30
+
- I don't love corporate environments. I'd rather something mid size or smaller
31
+
13. Do you want to work remotely, in-office, or in a hybrid setup?
32
+
- Remotely or Hybrid
33
+
14. What kind of work culture and values align with you?
34
+
-
35
+
15. Do you prefer structured environments or fast-paced, adaptable settings?
36
+
- Either, I may lean closer to fast-pace and adaptable settings however.
37
+
16. What industries interest you the most (e.g., finance, healthcare, entertainment, education)?
38
+
- Not much opinion, I just generally love software development
39
+
17. How important is mentorship and professional development in your job search?
40
+
-
41
+
18. What type of work-life balance do you need?
42
+
- I thoroughly enjoy programming. I wouldn't mind doing it for work and for fun
43
+
19. Are you open to relocating for the right job?
44
+
- To a certain extent.
45
+
46
+
47
+
# Career Goals & Market Demand
48
+
49
+
20. What are your short-term and long-term career goals?
50
+
-
51
+
21. What job roles currently have the most demand in the industry?
52
+
- I most commonly see webdev roles
53
+
22. Which companies or industries are hiring for your current/future skill set?
54
+
-
55
+
23. What skills or certifications will make you more competitive?
56
+
-
57
+
24. What salary range aligns with your skills and experience level?
58
+
- ~70-90k (?) I'm currently pretty knowledgable but not really sure how to translate that into a salary range.
59
+
25. What kind of career growth and advancement opportunities are you looking for?
60
+
-
61
+
26. How can you position yourself uniquely among other job seekers?
62
+
- Showcase all my work on FOSS
+1
html/README.md
+1
html/README.md
···
1
+
# A Basic HTML Page
+42
html/css/dino.css
+42
html/css/dino.css
···
1
+
body {
2
+
margin: 0;
3
+
padding: 0;
4
+
display: flex;
5
+
justify-content: center;
6
+
align-items: center;
7
+
min-height: 100vh;
8
+
background-color: #f7f7f7;
9
+
font-family: Arial, sans-serif;
10
+
}
11
+
12
+
.game-container {
13
+
position: relative;
14
+
}
15
+
16
+
#gameCanvas {
17
+
border: 2px solid #535353;
18
+
background-color: #fff;
19
+
display: block;
20
+
}
21
+
22
+
#score {
23
+
position: absolute;
24
+
top: 10px;
25
+
right: 10px;
26
+
font-size: 20px;
27
+
font-weight: bold;
28
+
color: #535353;
29
+
}
30
+
31
+
#gameOver {
32
+
position: absolute;
33
+
width: 100%;
34
+
text-align: center;
35
+
top: 50%;
36
+
left: 50%;
37
+
transform: translate(-50%, -50%);
38
+
font-size: 24px;
39
+
font-weight: bold;
40
+
color: #535353;
41
+
display: none;
42
+
}
+85
html/css/form.css
+85
html/css/form.css
···
1
+
.container {
2
+
display: flex;
3
+
flex-direction: column;
4
+
align-items: center;
5
+
justify-content: center;
6
+
}
7
+
8
+
form {
9
+
display: flex;
10
+
flex-direction: column;
11
+
align-items: center;
12
+
justify-content: center;
13
+
gap: 0.5rem;
14
+
width: 80%;
15
+
}
16
+
17
+
form > * {
18
+
width: 100%;
19
+
}
20
+
21
+
form :nth-child(even) {
22
+
margin-bottom: 1rem;
23
+
}
24
+
25
+
form > button {
26
+
width: 100%;
27
+
padding: 0.25rem;
28
+
margin: 0.5rem;
29
+
}
30
+
31
+
@media (min-width: 769px) {
32
+
form {
33
+
gap: 1rem;
34
+
width: 100%;
35
+
max-width: 48rem;
36
+
display: grid;
37
+
column-gap: 1rem;
38
+
grid-template-columns: repeat(2, minmax(0, 1fr));
39
+
}
40
+
41
+
form > button {
42
+
width: 100%;
43
+
grid-column: span 2;
44
+
padding: 0.25rem;
45
+
margin: 0.5rem;
46
+
}
47
+
}
48
+
49
+
nav {
50
+
display: flex;
51
+
padding: 0.5rem 0rem;
52
+
background-color: #8aacdf;
53
+
position: sticky;
54
+
top: 0;
55
+
left: 0;
56
+
right: 0;
57
+
margin-left: -0.5rem;
58
+
margin-top: -0.5rem;
59
+
justify-content: space-around;
60
+
align-items: center;
61
+
width: 100vw;
62
+
margin-bottom: 4rem;
63
+
}
64
+
65
+
.navitem {
66
+
padding: 0.5rem 4rem;
67
+
border: 1px solid #000;
68
+
border-radius: 0.5rem;
69
+
}
70
+
71
+
#footer {
72
+
width: 100vw;
73
+
height: 6rem;
74
+
position: absolute;
75
+
bottom: 0;
76
+
left: 0;
77
+
justify-content: space-around;
78
+
align-items: center;
79
+
background-color: #000;
80
+
color: #fff;
81
+
text-align: center;
82
+
display: flex;
83
+
vertical-align: center;
84
+
font-size: 2rem;
85
+
}
+70
html/css/styles.css
+70
html/css/styles.css
···
1
+
h1 {
2
+
font-size: 48px;
3
+
margin: 0;
4
+
color: #8aadf4;
5
+
}
6
+
7
+
h2 {
8
+
font-size: 36px;
9
+
margin: 0;
10
+
}
11
+
12
+
.heading {
13
+
color: #a5adcb;
14
+
}
15
+
16
+
* {
17
+
font-family:
18
+
system-ui,
19
+
-apple-system,
20
+
BlinkMacSystemFont,
21
+
"Segoe UI",
22
+
Roboto,
23
+
Oxygen,
24
+
Ubuntu,
25
+
Cantarell,
26
+
"Open Sans",
27
+
"Helvetica Neue",
28
+
sans-serif;
29
+
color: #cad3f5;
30
+
}
31
+
32
+
body {
33
+
background-color: #24273a;
34
+
}
35
+
36
+
main {
37
+
margin: none;
38
+
display: flex;
39
+
flex-direction: column;
40
+
gap: 5rem;
41
+
align-items: center;
42
+
}
43
+
44
+
.hr {
45
+
width: 80rem;
46
+
border-top: 2px solid #6e738d;
47
+
}
48
+
49
+
.container {
50
+
display: flex;
51
+
width: 160rem;
52
+
flex-direction: column;
53
+
gap: 0.1rem;
54
+
align-items: center;
55
+
}
56
+
57
+
.card {
58
+
width: 15rem;
59
+
height: 20rem;
60
+
background-color: #363950;
61
+
border-radius: 0.5rem;
62
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
63
+
transition: transform 0.15s ease-in-out;
64
+
cursor: pointer;
65
+
}
66
+
67
+
.card:hover {
68
+
transform: scale(1.05);
69
+
transition: transform 0.15s ease-in-out;
70
+
}
+19
html/dino.html
+19
html/dino.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
<title>Dino Game</title>
7
+
<link rel="stylesheet" href="css/dino.css" />
8
+
</head>
9
+
<body>
10
+
<div class="game-container">
11
+
<canvas id="gameCanvas" width="800" height="600"></canvas>
12
+
<div id="score">Score: 0</div>
13
+
<div id="gameOver">
14
+
Game Over! Press <kbd>Space</kbd> to Restart
15
+
</div>
16
+
</div>
17
+
<script src="js/dino.js"></script>
18
+
</body>
19
+
</html>
+28
html/form.html
+28
html/form.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+
<title>Form</title>
7
+
<link rel="stylesheet" href="css/form.css" />
8
+
<script src="js/form.js"></script>
9
+
</head>
10
+
<body>
11
+
<nav>
12
+
<a class="navitem" href="/">Home</a>
13
+
<a class="navitem" href="/dino.html">Dino</a>
14
+
</nav>
15
+
<div class="container">
16
+
<form>
17
+
<label for="name">Name:</label>
18
+
<input type="text" id="name" name="name" required />
19
+
<label for="email">Email:</label>
20
+
<input type="email" id="email" name="email" required />
21
+
<label for="message">Message:</label>
22
+
<textarea id="message" name="message" required></textarea>
23
+
<button type="submit">Submit</button>
24
+
</form>
25
+
</div>
26
+
<div id="footer">Totally good footer text</div>
27
+
</body>
28
+
</html>
+40
html/index.html
+40
html/index.html
···
1
+
<html>
2
+
<head>
3
+
<title>HTML Page</title>
4
+
<link rel="stylesheet" href="css/styles.css" />
5
+
<script src="js/script.js"></script>
6
+
</head>
7
+
<body style="margin: 0; padding: 8px">
8
+
<main>
9
+
<h1>Samuel Shuert</h1>
10
+
<div class="container">
11
+
<h2 class="heading">Projects</h2>
12
+
<div class="hr"></div>
13
+
<div
14
+
style="
15
+
display: flex;
16
+
flex-direction: row;
17
+
justify-content: center;
18
+
padding-top: 2rem;
19
+
gap: 1rem;
20
+
"
21
+
>
22
+
<div class="card" onclick="openProject('dino')">Dino</div>
23
+
<div class="card" onclick="openProject('temp')"></div>
24
+
<div class="card" onclick="openProject('temp')"></div>
25
+
</div>
26
+
</div>
27
+
<div class="container">
28
+
<div class="hr" style="width: 20rem; margin-bottom: 2rem"></div>
29
+
<h2 class="heading">Education</h2>
30
+
<div class="hr"></div>
31
+
</div>
32
+
<div class="container">
33
+
<div class="hr" style="width: 20rem; margin-bottom: 2rem"></div>
34
+
<h2 class="heading">Skills</h2>
35
+
<div class="hr"></div>
36
+
</div>
37
+
<div class="hr" style="width: 20rem"></div>
38
+
</main>
39
+
</body>
40
+
</html>
+246
html/js/dino.js
+246
html/js/dino.js
···
1
+
const canvas = document.getElementById("gameCanvas");
2
+
const ctx = canvas.getContext("2d");
3
+
const scoreElement = document.getElementById("score");
4
+
const gameOverElement = document.getElementById("gameOver");
5
+
6
+
let score = 0;
7
+
let gameSpeed = 3;
8
+
let isGameOver = false;
9
+
let frameCount = 0;
10
+
let nextObstacleFrame = 10;
11
+
12
+
const dino = {
13
+
x: 50,
14
+
y: 150,
15
+
width: 40,
16
+
height: 50,
17
+
dy: 0,
18
+
gravity: 0.3,
19
+
jumpPower: -8,
20
+
isJumping: false,
21
+
isDucking: false,
22
+
23
+
draw() {
24
+
ctx.fillStyle = "#8aacdf";
25
+
ctx.fillRect(this.x, this.y, this.width, this.height);
26
+
},
27
+
28
+
update() {
29
+
if (this.isDucking) {
30
+
this.y = 170;
31
+
this.height = 30;
32
+
} else {
33
+
this.height = 50;
34
+
}
35
+
36
+
if (this.isJumping) {
37
+
this.dy += this.gravity;
38
+
this.y += this.dy;
39
+
40
+
if (this.y >= 150) {
41
+
this.y = 150;
42
+
this.isJumping = false;
43
+
this.dy = 0;
44
+
}
45
+
}
46
+
},
47
+
48
+
duck() {
49
+
if (!this.isJumping && !isGameOver) {
50
+
this.isDucking = true;
51
+
} else if (this.isJumping && !isGameOver) {
52
+
this.dy += this.gravity * 10;
53
+
this.y += this.dy;
54
+
}
55
+
},
56
+
57
+
jump() {
58
+
if (!this.isJumping && !isGameOver) {
59
+
this.isJumping = true;
60
+
this.dy = this.jumpPower;
61
+
}
62
+
},
63
+
};
64
+
65
+
const obstacles = [];
66
+
67
+
class Obstacle {
68
+
constructor() {
69
+
this.x = canvas.width;
70
+
const type = Math.random();
71
+
if (type < 0.5) {
72
+
this.width = 20;
73
+
this.height = 40;
74
+
this.y = 160;
75
+
} else if (type < 0.8) {
76
+
this.width = 20;
77
+
this.height = 25;
78
+
this.y = 175;
79
+
} else {
80
+
this.width = 40;
81
+
this.height = 32;
82
+
this.y = 168;
83
+
}
84
+
}
85
+
86
+
draw() {
87
+
ctx.fillStyle = "#40a02b";
88
+
ctx.fillRect(this.x, this.y, this.width, this.height);
89
+
}
90
+
91
+
update() {
92
+
this.x -= gameSpeed;
93
+
}
94
+
}
95
+
96
+
class BirdObstacle {
97
+
constructor() {
98
+
this.x = canvas.width;
99
+
this.width = 30;
100
+
this.height = 20;
101
+
const heightType = Math.floor(Math.random() * 3);
102
+
if (heightType === 0) {
103
+
this.y = 170;
104
+
} else if (heightType === 1) {
105
+
this.y = 150;
106
+
} else {
107
+
this.y = 130;
108
+
}
109
+
}
110
+
111
+
draw() {
112
+
ctx.fillStyle = "#dd7878";
113
+
ctx.fillRect(this.x, this.y, this.width, this.height);
114
+
}
115
+
116
+
update() {
117
+
this.x -= gameSpeed;
118
+
}
119
+
}
120
+
121
+
function checkCollision(dino, obstacle) {
122
+
return (
123
+
dino.x < obstacle.x + obstacle.width &&
124
+
dino.x + dino.width > obstacle.x &&
125
+
dino.y < obstacle.y + obstacle.height &&
126
+
dino.y + dino.height > obstacle.y
127
+
);
128
+
}
129
+
130
+
function spawnObstacle() {
131
+
if (frameCount >= nextObstacleFrame) {
132
+
const type = Math.random();
133
+
if (type < 0.9) {
134
+
obstacles.push(new Obstacle());
135
+
} else {
136
+
obstacles.push(new BirdObstacle());
137
+
}
138
+
139
+
const baseMin = 80;
140
+
const baseMax = 140;
141
+
142
+
const speedFactor = 3 / gameSpeed;
143
+
144
+
const minInterval = Math.floor(baseMin * speedFactor);
145
+
const maxInterval = Math.floor(baseMax * speedFactor);
146
+
nextObstacleFrame =
147
+
frameCount +
148
+
Math.floor(Math.random() * (maxInterval - minInterval) + minInterval);
149
+
}
150
+
}
151
+
152
+
function updateScore() {
153
+
if (!isGameOver) {
154
+
score++;
155
+
scoreElement.textContent = `Score: ${Math.floor(score / 10)}`;
156
+
157
+
// Increase difficulty
158
+
if (score % 500 === 0) {
159
+
gameSpeed += 0.5;
160
+
}
161
+
}
162
+
}
163
+
164
+
function gameLoop() {
165
+
if (isGameOver) return;
166
+
167
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
168
+
169
+
ctx.strokeStyle = "#535353";
170
+
ctx.beginPath();
171
+
ctx.moveTo(0, 200);
172
+
ctx.lineTo(canvas.width, 200);
173
+
ctx.stroke();
174
+
175
+
dino.update();
176
+
dino.draw();
177
+
178
+
spawnObstacle();
179
+
180
+
for (let i = obstacles.length - 1; i >= 0; i--) {
181
+
obstacles[i].update();
182
+
obstacles[i].draw();
183
+
184
+
if (checkCollision(dino, obstacles[i])) {
185
+
isGameOver = true;
186
+
gameOverElement.style.display = "block";
187
+
}
188
+
189
+
if (obstacles[i].x + obstacles[i].width < 0) {
190
+
obstacles.splice(i, 1);
191
+
}
192
+
}
193
+
194
+
updateScore();
195
+
frameCount++;
196
+
197
+
requestAnimationFrame(gameLoop);
198
+
}
199
+
200
+
function resetGame() {
201
+
score = 0;
202
+
gameSpeed = 3;
203
+
isGameOver = false;
204
+
frameCount = 0;
205
+
obstacles.length = 0;
206
+
dino.y = 150;
207
+
dino.dy = 0;
208
+
dino.isJumping = false;
209
+
gameOverElement.style.display = "none";
210
+
nextObstacleFrame = 10;
211
+
gameLoop();
212
+
}
213
+
214
+
document.addEventListener("keydown", (e) => {
215
+
e.preventDefault();
216
+
switch (e.code) {
217
+
case "ArrowUp":
218
+
case "Space": {
219
+
if (isGameOver) {
220
+
resetGame();
221
+
} else {
222
+
dino.jump();
223
+
}
224
+
break;
225
+
}
226
+
case "ArrowDown": {
227
+
dino.duck();
228
+
break;
229
+
}
230
+
default:
231
+
}
232
+
});
233
+
234
+
document.addEventListener("keyup", (e) => {
235
+
switch (e.code) {
236
+
case "ArrowDown": {
237
+
e.preventDefault();
238
+
dino.isDucking = false;
239
+
if (!dino.isJumping) {
240
+
dino.y = 150;
241
+
}
242
+
}
243
+
}
244
+
});
245
+
246
+
gameLoop();
+11
html/js/script.js
+11
html/js/script.js
+112
-3
nilla.nix
+112
-3
nilla.nix
···
8
8
nilla = import pins.nilla;
9
9
in
10
10
nilla.create (
11
-
{ config, lib }:
11
+
{ config }:
12
12
{
13
13
config = {
14
14
inputs = {
···
16
16
src = pins.nixpkgs;
17
17
};
18
18
};
19
-
shells.default = {
19
+
packages.cmu-graphics = {
20
+
systems = [ "x86_64-linux" ];
21
+
package =
22
+
{
23
+
pkgs,
24
+
python313,
25
+
fetchPypi,
26
+
}:
27
+
let
28
+
pname = "cmu_graphics";
29
+
version = "1.1.43";
30
+
in
31
+
python313.pkgs.buildPythonPackage {
32
+
inherit pname version;
33
+
34
+
src = fetchPypi {
35
+
inherit pname version;
36
+
hash = "sha256-IU6z+4xB7Uz/SsIrFkFRfLL10ZmpQTPyK+yDWDq89Xs=";
37
+
};
38
+
39
+
doCheck = false;
40
+
checkPhase = ''
41
+
true
42
+
'';
43
+
dontCheckPythonPackages = true;
44
+
pyproject = true;
45
+
46
+
dependencies = [
47
+
python313.pkgs.pycairo
48
+
python313.pkgs.pygame
49
+
];
50
+
51
+
build-system = [
52
+
python313.pkgs.setuptools
53
+
python313.pkgs.wheel
54
+
pkgs.pre-commit
55
+
];
56
+
57
+
};
58
+
};
59
+
shells.default = config.shells.html;
60
+
shells.python = {
20
61
# Declare what systems the shell can be used on.
21
62
systems = [ "x86_64-linux" ];
22
63
···
27
68
mkShell,
28
69
...
29
70
}:
71
+
let
72
+
python3 = pkgs.python313.override {
73
+
packageOverrides = pyfinal: pyprev: {
74
+
cmu_graphics = config.packages.cmu-graphics.result.x86_64-linux;
75
+
};
76
+
};
77
+
in
30
78
mkShell {
79
+
shellHook = ''
80
+
[ "$(hostname)" = "shorthair" ] && export ZED_PREDICT_EDITS_URL=http://localhost:9000/predict_edits
81
+
'';
31
82
packages = [
32
-
pkgs.python314
83
+
(python3.withPackages (ppkgs: [
84
+
ppkgs.pandas
85
+
ppkgs.pandas-stubs
86
+
ppkgs.matplotlib
87
+
ppkgs.seaborn
88
+
ppkgs.numpy
89
+
ppkgs.requests
90
+
ppkgs.geopy
91
+
ppkgs.cmu_graphics
92
+
ppkgs.pycairo
93
+
ppkgs.pygame
94
+
ppkgs.pillow
95
+
ppkgs.numpy
96
+
]))
97
+
pkgs.black
98
+
];
99
+
};
100
+
};
101
+
shells.ts = {
102
+
systems = [ "x86_64-linux" ];
103
+
104
+
shell =
105
+
{
106
+
pkgs,
107
+
mkShell,
108
+
}:
109
+
mkShell {
110
+
packages = [
111
+
pkgs.bun
112
+
pkgs.eslint_d
113
+
pkgs.eslint
114
+
pkgs.typescript
115
+
pkgs.typescript-language-server
116
+
pkgs.package-version-server
117
+
pkgs.nixd
118
+
pkgs.nil
119
+
];
120
+
};
121
+
};
122
+
shells.html = {
123
+
systems = [ "x86_64-linux" ];
124
+
125
+
shell =
126
+
{
127
+
pkgs,
128
+
mkShell,
129
+
}:
130
+
mkShell {
131
+
shellHook = ''
132
+
serve() {
133
+
live-server /home/coded/Programming/CMU/html --port 5000
134
+
}
135
+
export -f serve
136
+
'';
137
+
packages = [
138
+
pkgs.emmet-language-server
139
+
pkgs.nixd
140
+
pkgs.nil
141
+
pkgs.nodePackages.live-server
33
142
];
34
143
};
35
144
};
-15
python/assignments/sep30/level1/numberOfBricks.py
-15
python/assignments/sep30/level1/numberOfBricks.py
···
1
-
from math import ceil
2
-
3
-
4
-
def numberOfBricks(steps: int) -> int:
5
-
return ceil((steps * (steps + 1)) / 2)
6
-
7
-
8
-
print("Testing numberOfBricks()...", end="")
9
-
assert numberOfBricks(0) == 0
10
-
assert numberOfBricks(1) == 1
11
-
assert numberOfBricks(2) == 3
12
-
assert numberOfBricks(3) == 6
13
-
assert numberOfBricks(4) == 10
14
-
assert numberOfBricks(10) == 55
15
-
print("Passed!")
-32
python/assignments/sep30/level1/pizzas.py
-32
python/assignments/sep30/level1/pizzas.py
···
1
-
def howManyPizzas(students: int, slicesPerStudent: int) -> int:
2
-
neededSlices = students * slicesPerStudent
3
-
return (neededSlices + 7) // 8
4
-
5
-
6
-
def leftoverPizzaSlices(students: int, slicesPerStudent: int) -> int:
7
-
neededSlices = students * slicesPerStudent
8
-
totalSlices = howManyPizzas(students, slicesPerStudent) * 8
9
-
return totalSlices - neededSlices
10
-
11
-
12
-
print("Testing howManyPizzas()...", end="")
13
-
assert howManyPizzas(8, 1) == 1
14
-
assert howManyPizzas(9, 1) == 2
15
-
assert howManyPizzas(5, 4) == 3
16
-
assert howManyPizzas(10, 2) == 3
17
-
assert howManyPizzas(0, 0) == 0
18
-
assert howManyPizzas(0, 3) == 0
19
-
assert howManyPizzas(10, 0) == 0
20
-
assert howManyPizzas(3, 4) == 2
21
-
print("Passed!")
22
-
23
-
print("Testing leftoverPizzaSlices()...", end="")
24
-
assert leftoverPizzaSlices(8, 1) == 0
25
-
assert leftoverPizzaSlices(9, 1) == 7
26
-
assert leftoverPizzaSlices(5, 4) == 4
27
-
assert leftoverPizzaSlices(10, 2) == 4
28
-
assert leftoverPizzaSlices(0, 0) == 0
29
-
assert leftoverPizzaSlices(0, 3) == 0
30
-
assert leftoverPizzaSlices(10, 0) == 0
31
-
assert leftoverPizzaSlices(3, 4) == 4
32
-
print("Passed!")
-20
python/assignments/sep30/level1/pythagoreanTriple.py
-20
python/assignments/sep30/level1/pythagoreanTriple.py
···
1
-
def isPythagoreanTriple(a: float, b: float, c: float) -> bool:
2
-
return (
3
-
(a < b and b < c) and ((a**2 + b**2) == c**2) and not (a < 0 or b < 0 or c < 0)
4
-
)
5
-
6
-
7
-
print("Testing isPythagoreanTriple()...", end="")
8
-
assert isPythagoreanTriple(3, 4, 5) == True # 3**2 + 4**2 == 5**2
9
-
assert isPythagoreanTriple(5, 4, 3) == False # wrong order
10
-
assert isPythagoreanTriple(4, 5, 3) == False # wrong order
11
-
12
-
assert isPythagoreanTriple(5, 12, 13) == True # 5**2 + 12**2 == 13**2
13
-
assert isPythagoreanTriple(13, 12, 5) == False # wrong order
14
-
assert isPythagoreanTriple(12, 13, 5) == False # wrong order
15
-
16
-
assert isPythagoreanTriple(-3, 4, 5) == False # no negatives
17
-
assert isPythagoreanTriple(0, 5, 5) == False # no 0's
18
-
assert isPythagoreanTriple(1, 1, 1) == False # 1**2 + 1**2 != 1**2
19
-
assert isPythagoreanTriple(3, 4, 6) == False # 3**2 + 4**2 != 6**2
20
-
print("Passed!")
-26
python/assignments/sep30/level1/triangleArea.py
-26
python/assignments/sep30/level1/triangleArea.py
···
1
-
from math import sqrt
2
-
3
-
4
-
def almostEqual(x: float, y: float) -> bool:
5
-
return abs(x - y) < 10**-9
6
-
7
-
8
-
def triangleArea(a: float, b: float, c: float) -> float:
9
-
semiperimeter = 0.5 * (a + b + c)
10
-
return sqrt(
11
-
semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c)
12
-
)
13
-
14
-
15
-
print("Testing triangleArea()...", end="")
16
-
assert almostEqual(triangleArea(3, 4, 5), 6)
17
-
assert almostEqual(triangleArea(15, 9, 12), 54)
18
-
assert almostEqual(triangleArea(7, 25, 24), 84)
19
-
assert almostEqual(triangleArea(8, 15, 17), 60)
20
-
assert almostEqual(triangleArea(0, 0, 0), 0)
21
-
assert almostEqual(triangleArea(1, 1, 1), sqrt(3) / 4)
22
-
assert almostEqual(triangleArea(5, 5, 5), 25 * sqrt(3) / 4)
23
-
assert almostEqual(triangleArea(12, 12, 12), 144 * sqrt(3) / 4)
24
-
assert almostEqual(triangleArea(7, 12, 18), sqrt(11063) / 4)
25
-
assert almostEqual(triangleArea(9.1, 11.7, 3), 7 * sqrt(3026) / 50)
26
-
print("Passed!")
-23
python/assignments/sep30/level2/dotsOverlap.py
-23
python/assignments/sep30/level2/dotsOverlap.py
···
1
-
from math import sqrt
2
-
3
-
4
-
def distance(a: tuple[float, float], b: tuple[float, float]) -> float:
5
-
return sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)
6
-
7
-
8
-
def dotsOverlap(
9
-
x1: float, y1: float, r1: float, x2: float, y2: float, r2: float
10
-
) -> bool:
11
-
d = distance((x1, y1), (x2, y2))
12
-
return d <= (r1 + r2)
13
-
14
-
15
-
print("Testing dotsOverlap()...")
16
-
assert dotsOverlap(0, 0, 2, 3, 0, 2) == True
17
-
assert dotsOverlap(0, 0, 2, 5, 0, 2) == False
18
-
assert dotsOverlap(0, 0, 2, 4, 0, 2) == True
19
-
assert dotsOverlap(-4, 5, 2, -3, 5, 5) == True
20
-
assert dotsOverlap(3, 3, 3, 3, -3, 2.99) == False
21
-
assert dotsOverlap(3, 3, 3, 3, -3, 3) == True
22
-
assert dotsOverlap(5, 3, 0, 5, 3, 0) == True
23
-
print("Passed!")
-16
python/assignments/sep30/level2/getGreen.py
-16
python/assignments/sep30/level2/getGreen.py
···
1
-
def fullRGB(rgb: int) -> str:
2
-
return str(rgb).zfill(9)
3
-
4
-
5
-
def getGreen(rgb: int) -> int:
6
-
return int(fullRGB(rgb)[3:6])
7
-
8
-
9
-
print("Testing getGreen()...", end="")
10
-
assert getGreen(218112214) == 112
11
-
assert getGreen(134134134) == 134
12
-
assert getGreen(111019213) == 19
13
-
assert getGreen(221000000) == 0
14
-
assert getGreen(32175) == 32
15
-
assert getGreen(0) == 0
16
-
print("Passed!")
-18
python/assignments/sep30/level2/isGray.py
-18
python/assignments/sep30/level2/isGray.py
···
1
-
def fullRGB(rgb: int) -> str:
2
-
return str(rgb).zfill(9)
3
-
4
-
5
-
def isGray(rgb: int) -> bool:
6
-
rgbStr = fullRGB(rgb)
7
-
return (rgbStr[0:3] == rgbStr[3:6]) and (rgbStr[3:6] == rgbStr[6:9])
8
-
9
-
10
-
print("Testing isGray()...", end="")
11
-
assert isGray(112112112) == True
12
-
assert isGray(112112113) == False
13
-
assert isGray(123195060) == False
14
-
assert isGray(255255255) == True
15
-
assert isGray(0) == True
16
-
assert isGray(19019019) == True
17
-
assert isGray(175112) == False
18
-
print("Passed!")
-21
python/assignments/sep30/level2/nthFibonacciNumber.py
-21
python/assignments/sep30/level2/nthFibonacciNumber.py
···
1
-
from math import sqrt
2
-
3
-
4
-
def almostEqual(x: float, y: float) -> bool:
5
-
return abs(x - y) < 10**-9
6
-
7
-
8
-
def nthFibonacciNumber(n: int) -> float:
9
-
n += 1
10
-
return ((1 + sqrt(5)) ** n - (1 - sqrt(5)) ** n) / (sqrt(5) * 2**n)
11
-
12
-
13
-
print("Testing nthFibonacciNumber()...", end="")
14
-
assert almostEqual(nthFibonacciNumber(0), 1)
15
-
assert almostEqual(nthFibonacciNumber(1), 1)
16
-
assert almostEqual(nthFibonacciNumber(2), 2)
17
-
assert almostEqual(nthFibonacciNumber(3), 3)
18
-
assert almostEqual(nthFibonacciNumber(4), 5)
19
-
assert almostEqual(nthFibonacciNumber(5), 8)
20
-
21
-
print("Passed!")
-20
python/assignments/sep30/level3/numberOfSteps.py
-20
python/assignments/sep30/level3/numberOfSteps.py
···
1
-
from math import sqrt, ceil
2
-
3
-
4
-
def numberOfSteps(bricks: int) -> int:
5
-
return ceil((-1 + sqrt(1 + 8 * bricks)) / 2)
6
-
7
-
8
-
print("Testing numberOfSteps()...", end="")
9
-
assert numberOfSteps(0) == 0
10
-
assert numberOfSteps(1) == 1
11
-
assert numberOfSteps(2) == 2
12
-
assert numberOfSteps(3) == 2
13
-
assert numberOfSteps(4) == 3
14
-
assert numberOfSteps(6) == 3
15
-
assert numberOfSteps(7) == 4
16
-
assert numberOfSteps(10) == 4
17
-
assert numberOfSteps(11) == 5
18
-
assert numberOfSteps(55) == 10
19
-
assert numberOfSteps(56) == 11
20
-
print("Passed!")
-28
python/assignments/sep30/level3/rectanglesOverlap.py
-28
python/assignments/sep30/level3/rectanglesOverlap.py
···
1
-
def rectanglesOverlap(
2
-
ax1: int, ay1: int, aw: float, ah: float, bx1: int, by1: int, bw: float, bh: float
3
-
) -> bool:
4
-
ax2 = ax1 + aw
5
-
ay2 = ay1 + ah
6
-
bx2 = bx1 + bw
7
-
by2 = by1 + bh
8
-
return (ax1 <= bx2 and ax2 >= bx1) and (ay1 <= by2 and ay2 >= by1)
9
-
10
-
11
-
print("Testing rectanglesOverlap()...", end="")
12
-
# Intersect at right of rectangle 1
13
-
assert rectanglesOverlap(1, 1, 5, 1, 6, 1, 2, 2) == True
14
-
# Intersect at top of rectangle 1
15
-
assert rectanglesOverlap(1, 4, 5, 3, 1, 5, 8, 3) == True
16
-
# Intersect at left of rectangle 1
17
-
assert rectanglesOverlap(1, 5, 6, 6, -4, 7, 5, 3) == True
18
-
# Intersect at bottom of rectanagle 1
19
-
assert rectanglesOverlap(10, 10, 3, 3, 9, 7, 3, 3) == True
20
-
# Partially overlapping rectangles
21
-
assert rectanglesOverlap(1, 7, 3, 6, 3, 4, 2, 5) == True
22
-
# Don't intersect
23
-
assert rectanglesOverlap(1, 4, 3, 3, 10, 10, 5, 5) == False
24
-
# Don't intersect, but x-coordinates overlap
25
-
assert rectanglesOverlap(1, 4, 30, 3, 10, 10, 5, 5) == False
26
-
# Don't intersect, but y-coordinates overlap
27
-
assert rectanglesOverlap(1, 4, 3, 15, 10, 10, 5, 5) == False
28
-
print("Passed!")
-34
python/assignments/sep30/level3/triangeAreaByCoordinates.py
-34
python/assignments/sep30/level3/triangeAreaByCoordinates.py
···
1
-
from math import sqrt
2
-
3
-
4
-
def almostEqual(x: float, y: float) -> bool:
5
-
return abs(x - y) < 10**-9
6
-
7
-
8
-
def distance(a: tuple[float, float], b: tuple[float, float]) -> float:
9
-
return sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)
10
-
11
-
12
-
def triangleArea(a: float, b: float, c: float) -> float:
13
-
semiperimeter = 0.5 * (a + b + c)
14
-
return sqrt(
15
-
semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c)
16
-
)
17
-
18
-
19
-
def triangleAreaByCoordinates(
20
-
x1: float, y1: float, x2: float, y2: float, x3: float, y3: float
21
-
) -> float:
22
-
A = distance((x1, y1), (x2, y2))
23
-
B = distance((x2, y2), (x3, y3))
24
-
C = distance((x3, y3), (x1, y1))
25
-
return triangleArea(A, B, C)
26
-
27
-
28
-
print("Testing triangleAreaByCoordinates()...", end="")
29
-
assert almostEqual(triangleAreaByCoordinates(10, 11, 14, 11, 12, 13), 4)
30
-
assert isinstance(triangleAreaByCoordinates(10, 11, 14, 11, 12, 13), float)
31
-
assert almostEqual(triangleAreaByCoordinates(2, 4.0, 2, 7, 6.0, 7), 6)
32
-
assert almostEqual(triangleAreaByCoordinates(0, 0, 12.0, 0, 12, 5), 30)
33
-
assert almostEqual(triangleAreaByCoordinates(0, 0, 0, 1, 1, 1), 0.5)
34
-
print("Passed!")
-19
python/assignments/sep30/level4/blendColors.py
-19
python/assignments/sep30/level4/blendColors.py
···
1
-
def halfway(a: int, b: int) -> int:
2
-
return 0
3
-
4
-
5
-
def blendColors(rgb1: int, rgb2: int) -> int:
6
-
(r1, g1, b1) = [int(str(rgb1).zfill(9)[i : i + 3]) for i in range(0, 9, 3)]
7
-
(r2, g2, b2) = [int(str(rgb2).zfill(9)[i : i + 3]) for i in range(0, 9, 3)]
8
-
(r3, g3, b3) = [round((r1 + r2) / 2), round((g1 + g2) / 2), round((b1 + b2) / 2)]
9
-
(final_r, final_g, final_b) = [str(x).zfill(3) for x in (r3, g3, b3)]
10
-
return int(final_r + final_g + final_b)
11
-
12
-
13
-
print("Testing blendColors()...", end="")
14
-
assert blendColors(204153050, 104000152) == 154076101
15
-
assert blendColors(220153102, 151189051) == 186171076
16
-
assert blendColors(153051153, 51204) == 76051178
17
-
assert blendColors(123456789, 123456789) == 123456789
18
-
assert blendColors(0, 0) == 0
19
-
print("Passed!")
+20
python/oct1/level1/areMultiples.py
+20
python/oct1/level1/areMultiples.py
···
1
+
def areMultiples(x: int, y: int) -> bool:
2
+
"""Return True if x and y are multiples of each other, False otherwise."""
3
+
if x == 0 or y == 0:
4
+
return True
5
+
return (not y % x) or (not x % y)
6
+
7
+
8
+
print("Testing areMultiples()...", end="")
9
+
assert areMultiples(2, 4) == True # 4 is a multiple of 2
10
+
assert areMultiples(400, 200) == True # 400 is a multiple of 200
11
+
assert areMultiples(0, 100) == True # 0 is a multiple of 100
12
+
assert areMultiples(123, 0) == True # 0 is a multiple of 123
13
+
assert areMultiples(888, 888) == True # 888 is a multiple of itself
14
+
assert areMultiples(-50, 100) == True # 100 is a multiple of -50
15
+
assert areMultiples(50, -100) == True # -100 is a multiple of 50
16
+
assert areMultiples(-50, -100) == True # -100 is a multiple of -50
17
+
assert areMultiples(400, 250) == False
18
+
assert areMultiples(3, 100) == False
19
+
assert areMultiples(-3, 100) == False
20
+
print("Passed!")
+30
python/oct1/level1/fizzBuzz.py
+30
python/oct1/level1/fizzBuzz.py
···
1
+
def fizzBuzz(n: int) -> str | int:
2
+
"""Return the fizzbuzz string for n."""
3
+
str = ""
4
+
if n % 3 == 0:
5
+
str += "fizz"
6
+
if n % 5 == 0:
7
+
b = ""
8
+
if len(str):
9
+
b = "B"
10
+
else:
11
+
b = "b"
12
+
str += f"{b}uzz"
13
+
return str or n
14
+
15
+
16
+
print("Testing fizzBuzz()...", end="")
17
+
assert fizzBuzz(21) == "fizz"
18
+
assert fizzBuzz(25) == "buzz"
19
+
assert fizzBuzz(30) == "fizzBuzz"
20
+
assert fizzBuzz(31) == 31
21
+
22
+
# should work for negatives:
23
+
assert fizzBuzz(-240) == "fizzBuzz"
24
+
assert fizzBuzz(-245) == "buzz"
25
+
assert fizzBuzz(-246) == "fizz"
26
+
assert fizzBuzz(-247) == -247
27
+
28
+
# and 0 (which is a multiple of 3 and a multiple of 5):
29
+
assert fizzBuzz(0) == "fizzBuzz"
30
+
print("Passed!")
+17
python/oct1/level1/isLeapYear.py
+17
python/oct1/level1/isLeapYear.py
···
1
+
def isLeapYear(year: int) -> bool:
2
+
"""Return True if year is a leap year, False otherwise."""
3
+
if year <= 0:
4
+
return False
5
+
# 4 years v 400 years v not 100 years v
6
+
return not (year % 4 or (year % 400 and not year % 100))
7
+
8
+
9
+
print("Testing isLeapYear()...", end="")
10
+
assert isLeapYear(2024) == True
11
+
assert isLeapYear(2023) == False
12
+
assert isLeapYear(2020) == True
13
+
assert isLeapYear(1900) == False # divisible by 100 but not by 400
14
+
assert isLeapYear(2000) == True # divisible by 100 but also by 400
15
+
assert isLeapYear(-2024) == False # non-positive years are not leap years
16
+
assert isLeapYear(0) == False # non-positive years are not leap years
17
+
print("Passed!")
+15
python/oct1/level1/isLegalTriangle.py
+15
python/oct1/level1/isLegalTriangle.py
···
1
+
def isLegalTriangle(s1: float, s2: float, s3: float) -> bool:
2
+
"""Return True if s1, s2, and s3 can form a legal triangle, False otherwise."""
3
+
return s1 + s2 > s3 and s2 + s3 > s1 and s3 + s1 > s2
4
+
5
+
6
+
print("Testing isLegalTriangle()...", end="")
7
+
assert isLegalTriangle(8, 6, 7) == True
8
+
assert isLegalTriangle(3.0, 4.0, 5) == True
9
+
assert isLegalTriangle(4, 2, 3) == True
10
+
assert isLegalTriangle(2, 4, 3) == True
11
+
assert isLegalTriangle(2, 3, 5) == False
12
+
assert isLegalTriangle(0, 1, 2) == False
13
+
assert isLegalTriangle(-1, -2.0, -5) == False
14
+
assert isLegalTriangle(2, 2, 9) == False
15
+
print("Passed!")
+14
python/oct1/level1/isOdd.py
+14
python/oct1/level1/isOdd.py
···
1
+
def isOdd(n: int) -> bool:
2
+
"""Return True if n is odd, False otherwise."""
3
+
return not n % 2 == 0
4
+
5
+
6
+
print("Testing isOdd()...", end="")
7
+
assert isOdd(123) == True
8
+
assert isOdd(124) == False
9
+
assert isOdd(1) == True
10
+
assert isOdd(0) == False
11
+
assert isOdd(-123) == True
12
+
assert isOdd(-124) == False
13
+
assert isOdd(-1) == True
14
+
print("Passed!")
+18
python/oct1/level2/chicagoHour.py
+18
python/oct1/level2/chicagoHour.py
···
1
+
def chicagoHour(parisHour: int) -> int:
2
+
"""Return the Chicago hour (12h time) given the Paris hour (24h time)."""
3
+
chicagoHour = parisHour + 5
4
+
twelveHour = chicagoHour % 12
5
+
return 12 if twelveHour == 0 else twelveHour
6
+
7
+
8
+
print("Testing chicagoHour()...", end="")
9
+
assert chicagoHour(0) == 5
10
+
assert chicagoHour(3) == 8
11
+
assert chicagoHour(7) == 12
12
+
assert chicagoHour(8) == 1
13
+
assert chicagoHour(12) == 5
14
+
assert chicagoHour(15) == 8
15
+
assert chicagoHour(19) == 12
16
+
assert chicagoHour(20) == 1
17
+
assert chicagoHour(23) == 4
18
+
print("Passed!")
+30
python/oct1/level2/getInRange.py
+30
python/oct1/level2/getInRange.py
···
1
+
def almostEqual(x, y):
2
+
return abs(x - y) < 10**-9
3
+
4
+
5
+
def getInRange(x: float, bound1: float, bound2: float) -> float:
6
+
"""Return x if it is between bound1 and bound2, inclusive.
7
+
Otherwise, return the nearest bound."""
8
+
lowBound = bound1
9
+
highBound = bound2
10
+
if bound2 < bound1:
11
+
lowBound = bound2
12
+
highBound = bound1
13
+
if x < lowBound:
14
+
return lowBound
15
+
elif x > highBound:
16
+
return highBound
17
+
else:
18
+
return x
19
+
20
+
21
+
print("Testing getInRange()...", end="")
22
+
assert getInRange(1, 3, 5) == 3
23
+
assert getInRange(4, 3, 5) == 4
24
+
assert getInRange(6, 5, 3) == 5
25
+
assert getInRange(5, 5, 3) == 5
26
+
assert getInRange(-1, -3, -5) == -3
27
+
assert getInRange(-4, -5, -3) == -4
28
+
assert getInRange(-6, -6, -3) == -6
29
+
assert almostEqual(getInRange(6.2, 6.3, 6.4), 6.3)
30
+
print("Passed!")
+31
python/oct1/level2/getNumberDescription.py
+31
python/oct1/level2/getNumberDescription.py
···
1
+
def getNumberDescription(n) -> str:
2
+
"""Return a description of the number n."""
3
+
description = ""
4
+
if isinstance(n, float):
5
+
if n < 0:
6
+
description += "negative "
7
+
else:
8
+
description += "non-negative "
9
+
description += "float"
10
+
elif isinstance(n, int):
11
+
if n % 2 == 0:
12
+
description += "even "
13
+
else:
14
+
description += "odd "
15
+
description += "int"
16
+
else:
17
+
description += "not a number"
18
+
return description
19
+
20
+
21
+
print("Testing getNumberDescription()...", end="")
22
+
assert getNumberDescription(124) == "even int"
23
+
assert getNumberDescription(123) == "odd int"
24
+
assert getNumberDescription(-124) == "even int"
25
+
assert getNumberDescription(-123) == "odd int"
26
+
assert getNumberDescription(-1.2) == "negative float"
27
+
assert getNumberDescription(1.2) == "non-negative float"
28
+
assert getNumberDescription(0.0) == "non-negative float"
29
+
assert getNumberDescription(0) == "even int"
30
+
assert getNumberDescription("yes") == "not a number"
31
+
print("Passed!")
+26
python/oct1/level2/getTriangeType.py
+26
python/oct1/level2/getTriangeType.py
···
1
+
def isLegalTriangle(s1: float, s2: float, s3: float) -> bool:
2
+
"""Return True if s1, s2, and s3 can form a legal triangle, False otherwise."""
3
+
return s1 + s2 > s3 and s2 + s3 > s1 and s3 + s1 > s2
4
+
5
+
6
+
def getTriangleType(a: float, b: float, c: float) -> str:
7
+
"""Return the type of triangle formed by sides a, b, and c."""
8
+
if not isLegalTriangle(a, b, c):
9
+
return "illegal"
10
+
if a == b and b == c:
11
+
return "equilateral"
12
+
elif a == b or b == c or c == a:
13
+
return "isosceles"
14
+
else:
15
+
return "scalene"
16
+
17
+
18
+
print("Testing getTriangleType()...", end="")
19
+
assert getTriangleType(5, 6, 7) == "scalene"
20
+
assert getTriangleType(7, 5, 6) == "scalene"
21
+
assert getTriangleType(6, 5, 5) == "isosceles"
22
+
assert getTriangleType(5, 6, 5) == "isosceles"
23
+
assert getTriangleType(5, 5, 6) == "isosceles"
24
+
assert getTriangleType(7, 7, 7) == "equilateral"
25
+
assert getTriangleType(1, 2, 3) == "illegal"
26
+
print("Passed!")
+52
python/oct1/level3/areaWithinThreeLines.py
+52
python/oct1/level3/areaWithinThreeLines.py
···
1
+
from math import sqrt
2
+
3
+
4
+
def almostEqual(x, y):
5
+
return abs(x - y) < 10**-9
6
+
7
+
8
+
def distance(a: tuple[float, float], b: tuple[float, float]) -> float:
9
+
"""Calculate the distance between two points using the Euclidean distance formula."""
10
+
return sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)
11
+
12
+
13
+
def triangleArea(a: float, b: float, c: float) -> float:
14
+
"""Use herons formula: `sp = (a+b+c)/2` & `sqrt(sp(sp-a)(sp-b)(sp-c))` to calculate the area of a triangle using it's side lengths."""
15
+
semiperimeter = 0.5 * (a + b + c)
16
+
return sqrt(
17
+
semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c)
18
+
)
19
+
20
+
21
+
def intersect(m1: float, b1: float, m2: float, b2: float) -> tuple[float, float]:
22
+
"""Calculate the intersection point of two lines."""
23
+
x = (b2 - b1) / (m1 - m2)
24
+
y = m1 * x + b1
25
+
return x, y
26
+
27
+
28
+
def areaWithinThreeLines(
29
+
m1: float, b1: float, m2: float, b2: float, m3: float, b3: float
30
+
) -> float | None:
31
+
"""Calculate the area of a triangle formed by three lines."""
32
+
if m1 == m2 or m2 == m3 or m3 == m1:
33
+
return None
34
+
a = intersect(m1, b1, m2, b2)
35
+
b = intersect(m2, b2, m3, b3)
36
+
c = intersect(m3, b3, m1, b1)
37
+
return triangleArea(distance(a, b), distance(b, c), distance(c, a))
38
+
39
+
40
+
print("Testing areaWithinThreeLines()...", end="")
41
+
assert almostEqual(areaWithinThreeLines(0, 7, 1, 0, -1, 2), 36)
42
+
assert almostEqual(areaWithinThreeLines(1, -5, 0, -2, 2, 2), 25)
43
+
assert almostEqual(areaWithinThreeLines(0, -9.75, -6, 2.25, 1, -4.75), 21)
44
+
assert almostEqual(areaWithinThreeLines(1, -5, 0, -2, 2, 25), 272.25)
45
+
assert almostEqual(areaWithinThreeLines(1, 2, 3, 4, 5, 6), 0)
46
+
47
+
# The first two lines are parallel:
48
+
assert areaWithinThreeLines(1, 2, 1, 4, 0, 7) == None
49
+
50
+
# Here, the second and third lines are parallel
51
+
assert areaWithinThreeLines(5, 2, 1, 4, 1, 6) == None
52
+
print("Passed!")
+24
python/oct1/level3/dayOfWeek.py
+24
python/oct1/level3/dayOfWeek.py
···
1
+
def dayOfWeek(month: int, day: int, year: int) -> int:
2
+
"""Return the day of the week for a given date."""
3
+
m = month if month > 2 else month + 12
4
+
y = year - 1 if month <= 2 else year
5
+
n = (day + 2 * m + (3 * (m + 1)) // 5 + y + y // 4 - y // 100 + y // 400 + 2) % 7
6
+
return n if n != 0 else 7
7
+
8
+
9
+
print("Testing dayOfWeek()...")
10
+
# On 6/15/1215, the Magna Carta was signed on a Monday!
11
+
assert dayOfWeek(6, 15, 1215) == 2
12
+
# On 3/11/1952, the author Douglas Adams was born on a Tuesday!
13
+
assert dayOfWeek(3, 11, 1952) == 3
14
+
# on 4/12/1961, Yuri Gagarin became the first man in space, on a Wednesday!
15
+
assert dayOfWeek(4, 12, 1961) == 4
16
+
# On 7/4/1776, the Declaration of Independence was signed on a Thursday!
17
+
assert dayOfWeek(7, 4, 1776) == 5
18
+
# on 1/2/1920, Isaac Asimov was born on a Friday!
19
+
assert dayOfWeek(1, 2, 1920) == 6
20
+
# on 10/11/1975, Saturday Night Live debuted on a Saturday (of course)!
21
+
assert dayOfWeek(10, 11, 1975) == 7
22
+
# On 2/5/2006, the Steelers won Super Bowl XL on a Sunday!
23
+
assert dayOfWeek(2, 5, 2006) == 1
24
+
print("Passed!")
+92
python/oct1/level4/playThreeDiceYahtzee.py
+92
python/oct1/level4/playThreeDiceYahtzee.py
···
1
+
def handToDice(hand: int) -> tuple[int, int, int]:
2
+
"""Converts a hand to a tuple of dice."""
3
+
return hand // 100, (hand // 10) % 10, hand % 10
4
+
5
+
6
+
def diceToOrderedHand(a: int, b: int, c: int) -> int:
7
+
"""Converts a tuple of dice to an ordered hand."""
8
+
ordered = sorted([a, b, c])
9
+
return ordered[2] * 100 + ordered[1] * 10 + ordered[0]
10
+
11
+
12
+
def playStep2(hand: int, dice: int) -> tuple[int, int]:
13
+
"""
14
+
If you don't have 3 matching dice:
15
+
If you have a pair: keep the pair and reroll the third
16
+
Else: Roll all dice again
17
+
"""
18
+
(a, b, c) = handToDice(hand)
19
+
if a == b and b == c:
20
+
return hand, dice
21
+
if a == b:
22
+
c = dice % 10
23
+
dice //= 10
24
+
elif b == c:
25
+
a = dice % 10
26
+
dice //= 10
27
+
elif a == c:
28
+
b = dice % 10
29
+
dice //= 10
30
+
else:
31
+
b = dice % 10
32
+
dice //= 10
33
+
c = dice % 10
34
+
dice //= 10
35
+
return diceToOrderedHand(a, b, c), dice
36
+
37
+
38
+
def score(hand: int) -> int:
39
+
"""
40
+
Calculate the score of a hand
41
+
3 dice match: 20 + highest value * 3
42
+
2 dice match: 10 + highest value * 2
43
+
1 dice match: highest value
44
+
"""
45
+
(a, b, c) = handToDice(hand)
46
+
if a == b and b == c:
47
+
return 20 + a * 3
48
+
elif a == b:
49
+
return 10 + a * 2
50
+
elif b == c:
51
+
return 10 + b * 2
52
+
elif a == c:
53
+
return 10 + c * 2
54
+
else:
55
+
return a
56
+
57
+
58
+
def playThreeDiceYahtzee(dice: int) -> tuple[int, int]:
59
+
"""Play a game of Three Dice Yahtzee"""
60
+
a = dice % 10
61
+
dice //= 10
62
+
b = dice % 10
63
+
dice //= 10
64
+
c = dice % 10
65
+
dice //= 10
66
+
hand = diceToOrderedHand(a, b, c)
67
+
hand, dice = playStep2(hand, dice)
68
+
hand, dice = playStep2(hand, dice)
69
+
return hand, score(hand)
70
+
71
+
72
+
print("Testing playThreeDiceYahtzee()...", end="")
73
+
assert handToDice(123) == (1, 2, 3)
74
+
assert handToDice(214) == (2, 1, 4)
75
+
assert handToDice(422) == (4, 2, 2)
76
+
77
+
assert diceToOrderedHand(1, 2, 3) == 321
78
+
assert diceToOrderedHand(1, 4, 2) == 421
79
+
80
+
assert playStep2(413, 2312) == (421, 23)
81
+
assert playStep2(544, 23) == (443, 2)
82
+
assert playStep2(544, 456) == (644, 45)
83
+
84
+
assert score(432) == 4
85
+
assert score(443) == 10 + 4 + 4
86
+
assert score(633) == 10 + 3 + 3
87
+
assert score(555) == 20 + 5 + 5 + 5
88
+
89
+
assert playThreeDiceYahtzee(2312413) == (432, 4)
90
+
assert playThreeDiceYahtzee(2633413) == (633, 16)
91
+
assert playThreeDiceYahtzee(2333555) == (555, 35)
92
+
print("Passed!")
+204
python/oct10/Rooms/main.py
+204
python/oct10/Rooms/main.py
···
1
+
from typing import override
2
+
3
+
4
+
class Room:
5
+
name: str
6
+
length: float
7
+
width: float
8
+
9
+
def __init__(self, name: str, length: float, width: float):
10
+
self.name = name
11
+
self.length = length
12
+
self.width = width
13
+
14
+
@override
15
+
def __repr__(self) -> str:
16
+
return f"Room({self.length}'x{self.width}' {self.name})"
17
+
18
+
def getArea(self) -> float:
19
+
return self.length * self.width
20
+
21
+
22
+
class Floor:
23
+
name: str
24
+
room_map: dict[str, Room]
25
+
26
+
def __init__(self, name: str):
27
+
self.name = name
28
+
self.room_map = dict()
29
+
30
+
@override
31
+
def __repr__(self) -> str:
32
+
return f"Floor({self.name} with {len(self.room_map)} room(s))"
33
+
34
+
def addRoom(self, room: Room) -> None:
35
+
if room.name in self.room_map:
36
+
raise LookupError(f"{room.name} already exists on this floor")
37
+
else:
38
+
self.room_map[room.name] = room
39
+
40
+
def getRoomCount(self) -> int:
41
+
return len(self.room_map)
42
+
43
+
def getRoom(self, name: str) -> Room | None:
44
+
return self.room_map.get(name, None)
45
+
46
+
def getRoomNames(self) -> list[str]:
47
+
return sorted(self.room_map.keys())
48
+
49
+
def getArea(self) -> float:
50
+
return sum([r.getArea() for r in self.room_map.values()])
51
+
52
+
53
+
class House:
54
+
floors: dict[str, Floor]
55
+
56
+
def __init__(self) -> None:
57
+
self.floors = dict()
58
+
59
+
@override
60
+
def __repr__(self) -> str:
61
+
return f"House({self.getFloorCount()} floor(s), {self.getRoomCount()} room(s), {self.getArea()} sq feet)"
62
+
63
+
def addFloor(self, floor: Floor) -> None:
64
+
if floor.name in self.floors:
65
+
raise LookupError(f"{floor.name} already exists on this floor")
66
+
else:
67
+
self.floors[floor.name] = floor
68
+
69
+
def getFloorCount(self) -> int:
70
+
return len(self.floors)
71
+
72
+
def getRoomCount(self) -> int:
73
+
return sum([f.getRoomCount() for f in self.floors.values()])
74
+
75
+
def getArea(self) -> float:
76
+
return sum([f.getArea() for f in self.floors.values()])
77
+
78
+
def getRoomFloor(self, room_name: str) -> Floor | None:
79
+
for floor in self.floors.values():
80
+
if room_name in floor.getRoomNames():
81
+
return floor
82
+
else:
83
+
return None
84
+
85
+
def getRoom(self, room_name: str) -> Room | None:
86
+
floor = self.getRoomFloor(room_name)
87
+
if floor == None:
88
+
return None
89
+
else:
90
+
return floor.getRoom(room_name)
91
+
92
+
93
+
def testLevel1():
94
+
print("Testing Level 1 (Core) material...", end="")
95
+
96
+
# Note: a Room represents a room, on a Floor, in a House.
97
+
98
+
room1 = Room("Living Room", 15, 15) # name, length, width
99
+
assert str(room1) == "Room(15'x15' Living Room)"
100
+
assert str([room1]) == "[Room(15'x15' Living Room)]"
101
+
assert room1.getArea() == 225 # 15 * 15
102
+
103
+
room2 = Room("Kitchen", 10, 20)
104
+
assert str(room2) == "Room(10'x20' Kitchen)"
105
+
assert room2.getArea() == 200 # 10 * 20
106
+
107
+
# Notes:
108
+
# * Each Floor can contain 0 or more rooms.
109
+
# * Store the rooms in floor.roomMap, which is a dictionary
110
+
# mapping the room name to the room instance.
111
+
# * Do not store a floor's roomCount or roomNames separately.
112
+
# Instead, compute these based on the keys in floor.roomMap.
113
+
114
+
floor1 = Floor("1st Floor")
115
+
assert str(floor1) == "Floor(1st Floor with 0 room(s))"
116
+
assert floor1.getRoomCount() == 0
117
+
assert floor1.getRoomNames() == []
118
+
assert floor1.getArea() == 0
119
+
120
+
floor1.addRoom(room1)
121
+
assert str(floor1) == "Floor(1st Floor with 1 room(s))"
122
+
assert floor1.getRoomCount() == 1
123
+
assert floor1.getRoomNames() == ["Living Room"]
124
+
assert floor1.getArea() == 225
125
+
126
+
floor1.addRoom(room2)
127
+
assert str(floor1) == "Floor(1st Floor with 2 room(s))"
128
+
assert floor1.getRoomCount() == 2
129
+
130
+
# Note that getRoomNames returns a *sorted* list of room names:
131
+
assert floor1.getRoomNames() == ["Kitchen", "Living Room"]
132
+
assert floor1.getArea() == 425
133
+
134
+
# We will try to add the Kitchen room again.
135
+
# We are not allowed to add the same room twice,
136
+
# nor even two rooms with the same name on any floor.
137
+
error = None
138
+
try:
139
+
floor1.addRoom(room2)
140
+
except Exception as e:
141
+
error = str(e)
142
+
assert error == "Kitchen already exists on this floor"
143
+
144
+
assert floor1.getRoom("Living Room") == room1
145
+
assert floor1.getRoom("Kitchen") == room2
146
+
assert floor1.getRoom("Study") == None
147
+
148
+
room3 = Room("Attic", 20, 30)
149
+
assert str(room3) == "Room(20'x30' Attic)"
150
+
assert room3.getArea() == 600 # 20 * 30
151
+
152
+
floor2 = Floor("2nd Floor")
153
+
floor2.addRoom(room3)
154
+
assert str(floor2) == "Floor(2nd Floor with 1 room(s))"
155
+
assert floor2.getArea() == 600 # 20 * 30
156
+
print("Passed!")
157
+
158
+
159
+
def testLevel2():
160
+
print("Testing Level 2+ (Not Core) material...", end="")
161
+
room1 = Room("Living Room", 15, 15)
162
+
room2 = Room("Kitchen", 10, 20)
163
+
floor1 = Floor("1st Floor")
164
+
floor1.addRoom(room1)
165
+
floor1.addRoom(room2)
166
+
room3 = Room("Attic", 20, 30)
167
+
floor2 = Floor("2nd Floor")
168
+
floor2.addRoom(room3)
169
+
170
+
house = House()
171
+
assert house.getFloorCount() == 0
172
+
assert house.getRoomCount() == 0
173
+
assert house.getArea() == 0
174
+
assert str(house) == "House(0 floor(s), 0 room(s), 0 sq feet)"
175
+
176
+
house.addFloor(floor1)
177
+
assert house.getFloorCount() == 1
178
+
assert house.getRoomCount() == 2
179
+
assert house.getArea() == 425
180
+
assert str(house) == "House(1 floor(s), 2 room(s), 425 sq feet)"
181
+
182
+
house.addFloor(floor2)
183
+
assert str(house) == "House(2 floor(s), 3 room(s), 1025 sq feet)"
184
+
185
+
assert house.getFloorCount() == 2
186
+
assert house.getRoomCount() == 3
187
+
assert house.getArea() == 1025
188
+
189
+
assert house.getRoomFloor("Kitchen") == floor1
190
+
assert house.getRoomFloor("Attic") == floor2
191
+
assert house.getRoomFloor("Study") == None
192
+
193
+
assert house.getRoom("Kitchen") == room2
194
+
assert house.getRoom("Attic") == room3
195
+
assert house.getRoom("Study") == None
196
+
print("Passed!")
197
+
198
+
199
+
def main():
200
+
testLevel1()
201
+
testLevel2()
202
+
203
+
204
+
main()
+255
python/oct10/Tables/main.py
+255
python/oct10/Tables/main.py
···
1
+
possibleData = str | float | None
2
+
3
+
4
+
class DataList:
5
+
data: list[possibleData]
6
+
7
+
def __init__(self, L: list[possibleData]) -> None:
8
+
self.data = L
9
+
10
+
def __getitem__(self, index: int) -> possibleData:
11
+
return self.data[index]
12
+
13
+
def __len__(self) -> int:
14
+
return len(self.data)
15
+
16
+
def numericValues(self) -> list[float | int]:
17
+
items = [item for item in self.data if isinstance(item, float | int)]
18
+
return items
19
+
20
+
def min(self) -> float:
21
+
return sorted(self.numericValues())[0]
22
+
23
+
def average(self) -> float:
24
+
nvs = self.numericValues()
25
+
return sum(nvs) / len(nvs)
26
+
27
+
def median(self) -> float | None:
28
+
nvs = self.numericValues()
29
+
if len(nvs) == 0:
30
+
return None
31
+
snvs = list(sorted(nvs))
32
+
middle = len(nvs) // 2
33
+
if len(nvs) % 2 == 1:
34
+
return snvs[middle]
35
+
else:
36
+
sl = snvs[middle - 1 : middle + 1]
37
+
return sum(sl) / 2
38
+
39
+
def max(self) -> float:
40
+
return list(reversed(sorted(self.numericValues())))[0]
41
+
42
+
def range(self) -> tuple[float, float]:
43
+
return (self.min(), self.max())
44
+
45
+
@property
46
+
def values(self) -> list[float | int | None]:
47
+
values = [item for item in self.data if (isinstance(item, float | int | None))]
48
+
return values
49
+
50
+
51
+
class Table:
52
+
data: list[DataList]
53
+
54
+
def __init__(self, data: list[list[possibleData]]) -> None:
55
+
self.data = []
56
+
m, n = len(data), len(data[0])
57
+
result: list[list[possibleData]] = []
58
+
for col in range(n):
59
+
result.append([])
60
+
for row in range(m):
61
+
result[col].append(data[row][col])
62
+
for row in result:
63
+
dl = DataList(row)
64
+
self.data.append(dl)
65
+
66
+
def getHeaders(self) -> list[str]:
67
+
return [str(r[0]) for r in self.data]
68
+
69
+
def _getColInt(self, index: int) -> DataList:
70
+
if index >= len(self.data):
71
+
raise IndexError(f"Col {index} is out of range")
72
+
return self.data[index]
73
+
74
+
def _getColStr(self, index: str) -> DataList:
75
+
hi = self.getHeaderIndex(index)
76
+
return self._getColInt(hi)
77
+
78
+
def getCol(self, index: int | str) -> DataList:
79
+
if isinstance(index, int):
80
+
return self._getColInt(index)
81
+
else:
82
+
return self._getColStr(index)
83
+
84
+
def getRow(self, index: int) -> list[possibleData]:
85
+
if index + 1 >= len(self.data[0]):
86
+
raise IndexError(f"Row {index} is out of range")
87
+
else:
88
+
row = [c[index + 1] for c in self.data]
89
+
return row
90
+
91
+
def getHeaderIndex(self, header: str) -> int:
92
+
headers = [c[0] for c in self.data]
93
+
for i, h in enumerate(headers):
94
+
if h == header:
95
+
return i
96
+
else:
97
+
raise KeyError(f"No such header: {header}")
98
+
99
+
100
+
def almostEqual(x: float | None, y: float | None) -> bool:
101
+
if x == None or y == None:
102
+
return False
103
+
epsilon = 10**-9
104
+
return abs(x - y) < epsilon
105
+
106
+
107
+
def testLevel1():
108
+
print("Testing Level 1 (Core) material...", end="")
109
+
# First test DataList:
110
+
dl = DataList([5, 2, None, 1.1, "yikes", 3.9])
111
+
assert type(dl) == DataList
112
+
assert dl.numericValues() == [5, 2, 1.1, 3.9]
113
+
assert dl.min() == 1.1
114
+
assert dl.max() == 5
115
+
116
+
# Now test Table and TableRow
117
+
weatherData: list[list[possibleData]] = [
118
+
["Date", "Low", "High"],
119
+
["1-Aug", 63, 81],
120
+
["2-Aug", 67, 85],
121
+
["3-Aug", 64, 86],
122
+
["4-Aug", 61, None],
123
+
["5-Aug", None, None],
124
+
["6-Aug", 59, 71],
125
+
["7-Aug", 63, 77],
126
+
["8-Aug", 68, 88],
127
+
["9-Aug", 75, 91],
128
+
["10-Aug", 74, 93],
129
+
]
130
+
table = Table(weatherData)
131
+
assert table.getHeaders() == ["Date", "Low", "High"]
132
+
133
+
lows = table.getCol(1)
134
+
assert type(lows) == DataList
135
+
assert lows.values == [63, 67, 64, 61, None, 59, 63, 68, 75, 74]
136
+
assert lows.numericValues() == [63, 67, 64, 61, 59, 63, 68, 75, 74]
137
+
assert lows.min() == 59
138
+
assert lows.max() == 75
139
+
140
+
highs = table.getCol(2)
141
+
assert type(highs) == DataList
142
+
assert highs.values == [81, 85, 86, None, None, 71, 77, 88, 91, 93]
143
+
assert highs.numericValues() == [81, 85, 86, 71, 77, 88, 91, 93]
144
+
assert highs.min() == 71
145
+
assert highs.max() == 93
146
+
147
+
# We will try to get col 3, but that is out of range,
148
+
# so it will raise a custom exception.
149
+
error = None
150
+
try:
151
+
_col = table.getCol(3)
152
+
except Exception as e:
153
+
error = str(e)
154
+
assert error == "Col 3 is out of range"
155
+
print("Passed!")
156
+
157
+
158
+
def testLevel2():
159
+
print("Testing Level 2+ (Not Core) material...", end="")
160
+
# First test DataList:
161
+
dl = DataList([5, 2, None, 1.1, "yikes", 3.9])
162
+
assert dl.range() == (1.1, 5)
163
+
assert almostEqual(dl.average(), 3)
164
+
165
+
# Now test Table and TableRow
166
+
weatherData: list[list[possibleData]] = [
167
+
["Date", "Low", "High"],
168
+
["1-Aug", 63, 81],
169
+
["2-Aug", 67, 85],
170
+
["3-Aug", 64, 86],
171
+
["4-Aug", 61, None],
172
+
["5-Aug", None, None],
173
+
["6-Aug", 59, 71],
174
+
["7-Aug", 63, 77],
175
+
["8-Aug", 68, 88],
176
+
["9-Aug", 75, 91],
177
+
["10-Aug", 74, 93],
178
+
]
179
+
table = Table(weatherData)
180
+
assert table.getHeaders() == ["Date", "Low", "High"]
181
+
182
+
assert table.getRow(0) == ["1-Aug", 63, 81]
183
+
assert table.getRow(9) == ["10-Aug", 74, 93]
184
+
185
+
# We will try to get row 10, but that is out of range,
186
+
# so it will raise a custom exception.
187
+
error = None
188
+
try:
189
+
_row = table.getRow(10)
190
+
except Exception as e:
191
+
error = str(e)
192
+
assert error == "Row 10 is out of range"
193
+
194
+
assert table.getHeaderIndex("Date") == 0
195
+
assert table.getHeaderIndex("Low") == 1
196
+
assert table.getHeaderIndex("High") == 2
197
+
198
+
# We will try to find the header index of 'Missing', but
199
+
# there is no such header, so it will raise a custom exception.
200
+
error = None
201
+
try:
202
+
_i = table.getHeaderIndex("Missing")
203
+
except Exception as e:
204
+
error = str(e)
205
+
# assert(error == 'No such header: Missing')
206
+
207
+
lows = table.getCol("Low") # hint: use getHeaderIndex!
208
+
assert type(lows) == DataList
209
+
assert lows.values == [63, 67, 64, 61, None, 59, 63, 68, 75, 74]
210
+
assert lows.range() == (59, 75)
211
+
assert almostEqual(lows.average(), 66)
212
+
213
+
highs = table.getCol("High")
214
+
assert type(highs) == DataList
215
+
assert highs.values == [81, 85, 86, None, None, 71, 77, 88, 91, 93]
216
+
assert highs.range() == (71, 93)
217
+
assert almostEqual(highs.average(), 84)
218
+
219
+
# And for one last challenge:
220
+
# We define the MEDIAN of a list like so:
221
+
# * If the list has odd length, then the median is
222
+
# the middle value of the sorted list.
223
+
# * If the list has even length, then the median is
224
+
# the average of the two middle values of the sorted list.
225
+
# For example:
226
+
# median([11, 19, 7, 14, 3])
227
+
# * This list has length 5, which is odd, so the median is the
228
+
# middle value of the sorted list, [3, 7, 11, 14, 19],
229
+
# which is 11.
230
+
# median([11, 19, 7, 14])
231
+
# * This list has length 4, which is even, so the median is the
232
+
# average of the two middle value of the sorted list,
233
+
# [7, 11, 14, 19], which is (11 + 14)/2 = 25/2 = 12.5.
234
+
#
235
+
# With this in mind, pass these additional tests;
236
+
dl = DataList([11, 19, 7, 14, 3])
237
+
assert almostEqual(dl.median(), 11)
238
+
239
+
dl = DataList([11, 19, 7, 14])
240
+
assert almostEqual(dl.median(), 12.5)
241
+
242
+
dl = DataList([])
243
+
assert dl.median() == None # No values, so no median
244
+
245
+
assert almostEqual(lows.median(), 64)
246
+
assert almostEqual(highs.median(), 85.5)
247
+
print("Passed!")
248
+
249
+
250
+
def main():
251
+
testLevel1()
252
+
testLevel2()
253
+
254
+
255
+
main()
+175
python/oct10/level0/odometer.py
+175
python/oct10/level0/odometer.py
···
1
+
from typing import List, Optional
2
+
3
+
4
+
class Odometer:
5
+
"""An odometer where all digits must be in ascending order"""
6
+
7
+
miles: int
8
+
9
+
@staticmethod
10
+
def _is_error(miles: int) -> bool:
11
+
"""Return True if the given miles are not in ascending order or contain a zero."""
12
+
try:
13
+
Odometer(miles)
14
+
except ValueError:
15
+
return True
16
+
return False
17
+
18
+
@staticmethod
19
+
def possible_values(odo_len: int) -> List[int]:
20
+
"""Return a list of possible values for the given miles."""
21
+
min = Odometer._get_minimum(odo_len)
22
+
max = Odometer._get_maximum(odo_len)
23
+
return [i for i in range(min, max + 1) if not Odometer._is_error(i)]
24
+
25
+
@staticmethod
26
+
def _ascending(digits: List[int]) -> bool:
27
+
return all(digits[i] < digits[i + 1] for i in range(len(digits) - 1))
28
+
29
+
@staticmethod
30
+
def _get_digits(miles: int) -> List[int]:
31
+
"""Return a list of digits from the given miles."""
32
+
return list(map(int, str(miles)))
33
+
34
+
@staticmethod
35
+
def _verify_miles(miles: int) -> None:
36
+
"""Verify that the given miles are in ascending order and do not contain a zero. Throws ValueError if not."""
37
+
digits = Odometer._get_digits(miles)
38
+
if len(digits) > 9:
39
+
raise ValueError("Miles cannot be greater than 9 digits")
40
+
if not Odometer._ascending(digits):
41
+
raise ValueError("Miles must be in ascending order")
42
+
if any(d == 0 for d in digits):
43
+
raise ValueError("Miles cannot contain a zero")
44
+
45
+
@staticmethod
46
+
def _get_maximum(odo_len: int) -> int:
47
+
"""Return the maximum possible miles given the current odometer length."""
48
+
return int("".join([str(i) for i in range(10 - odo_len, 10)]))
49
+
50
+
@staticmethod
51
+
def _get_minimum(odo_len: int) -> int:
52
+
"""Return the minimum possible miles given the current odometer length."""
53
+
return int("".join([str(i) for i in range(1, odo_len + 1)]))
54
+
55
+
def __init__(self, starting_miles: int = 1) -> None:
56
+
"""Initialize an odometer with the given starting miles."""
57
+
if starting_miles < 0:
58
+
raise ValueError("Starting miles cannot be negative")
59
+
Odometer._verify_miles(starting_miles)
60
+
self.miles = starting_miles
61
+
62
+
63
+
def verify_miles(self) -> bool:
64
+
"""Verify that the odometer's miles are in ascending order and do not contain a zero."""
65
+
try:
66
+
Odometer._verify_miles(self.miles)
67
+
except ValueError:
68
+
return False
69
+
return True
70
+
71
+
def get_digits(self) -> List[int]:
72
+
"""Return the odometer's miles as a list of digits."""
73
+
return list(map(int, str(self.miles)))
74
+
75
+
def get_minimum(self) -> int:
76
+
"""Return the minimum valid odometer reading."""
77
+
return Odometer._get_minimum(len(self.get_digits()))
78
+
79
+
def get_maximum(self) -> int:
80
+
"""Return the maximum valid odometer reading."""
81
+
return Odometer._get_maximum(len(self.get_digits()))
82
+
83
+
def next_reading(self) -> Optional[int]:
84
+
"""Return the next valid odometer reading."""
85
+
first_run = True
86
+
if self.miles == self.get_maximum():
87
+
return None
88
+
while not self.verify_miles() or first_run:
89
+
first_run = False
90
+
self.miles += 1
91
+
return self.miles
92
+
93
+
def previous_reading(self) -> Optional[int]:
94
+
"""Return the previous valid odometer reading."""
95
+
first_run = True
96
+
if self.miles == self.get_minimum():
97
+
return None
98
+
while not self.verify_miles() or first_run:
99
+
first_run = False
100
+
self.miles -= 1
101
+
return self.miles
102
+
103
+
def nth_reading_after(self, n: int) -> Optional[int]:
104
+
"""Return the nth valid odometer reading."""
105
+
first_run = True
106
+
i = 0
107
+
while not self.verify_miles() or first_run or i < n:
108
+
if self.miles == self.get_maximum():
109
+
return None
110
+
first_run = False
111
+
self.miles += 1
112
+
if self.verify_miles():
113
+
i += 1
114
+
return self.miles
115
+
116
+
def nth_reading_before(self, n: int) -> Optional[int]:
117
+
"""Return the nth valid odometer reading before the current one."""
118
+
first_run = True
119
+
i = 0
120
+
while not self.verify_miles() or first_run or i < n:
121
+
if self.miles == self.get_minimum():
122
+
return None
123
+
first_run = False
124
+
self.miles -= 1
125
+
if self.verify_miles():
126
+
i += 1
127
+
return self.miles
128
+
129
+
@staticmethod
130
+
def print(start: Optional[int], end) -> None:
131
+
"""Print the start of the odometer with some other value."""
132
+
print(f"{start} -> {end}")
133
+
134
+
@staticmethod
135
+
def _distance(start: int, end: int) -> int:
136
+
"""Calculate the distance between two odometer readings."""
137
+
odo_len = len(Odometer._get_digits(start))
138
+
if odo_len != len(Odometer._get_digits(end)):
139
+
raise ValueError("Odometer readings must have the same number of digits")
140
+
n = 0
141
+
while start != end:
142
+
try:
143
+
Odometer._verify_miles(start)
144
+
n += 1
145
+
if start == Odometer._get_maximum(odo_len):
146
+
start = Odometer._get_minimum(odo_len)
147
+
else:
148
+
start += 1
149
+
except:
150
+
start += 1
151
+
return n
152
+
153
+
def distance(self, end: int) -> Optional[int]:
154
+
"""Calculate the distance between the current odometer reading and another."""
155
+
try:
156
+
return Odometer._distance(self.miles, end)
157
+
except:
158
+
return None
159
+
160
+
odometer = Odometer(2467)
161
+
Odometer.print(odometer.miles, odometer.get_minimum())
162
+
Odometer.print(odometer.miles, odometer.get_maximum())
163
+
Odometer.print(odometer.miles, odometer.nth_reading_after(6))
164
+
Odometer.print(odometer.miles, odometer.nth_reading_before(6))
165
+
Odometer.print(odometer.miles, odometer.next_reading())
166
+
Odometer.print(odometer.miles, odometer.previous_reading())
167
+
Odometer.print(odometer.miles, f"1234: {odometer.distance(1234)}")
168
+
169
+
odo2 = Odometer(123)
170
+
Odometer.print(odo2.miles, odo2.get_minimum())
171
+
Odometer.print(odo2.miles, odo2.get_maximum())
172
+
Odometer.print(odo2.miles, odo2.nth_reading_after(83))
173
+
Odometer.print(odo2.miles, f"123: {odo2.distance(123)}")
174
+
175
+
print(Odometer.possible_values(8))
+25
python/oct14/level1/maxDigit.py
+25
python/oct14/level1/maxDigit.py
···
1
+
def maxDigit(n: int) -> int:
2
+
n = abs(n)
3
+
if n == 0:
4
+
return 0
5
+
rest = n // 10
6
+
ones = n % 10
7
+
next = maxDigit(rest)
8
+
return max(ones, next)
9
+
10
+
def testMaxDigit():
11
+
print('Testing maxDigit()...', end='')
12
+
assert(maxDigit(0) == 0)
13
+
assert(maxDigit(1) == 1)
14
+
assert(maxDigit(-1) == 1)
15
+
assert(maxDigit(12321) == 3)
16
+
assert(maxDigit(-12321) == 3)
17
+
assert(maxDigit(102030405060708090) == 9)
18
+
assert(maxDigit(22222222222) == 2)
19
+
assert(maxDigit(22222322222) == 3)
20
+
print('Passed!')
21
+
22
+
def main():
23
+
testMaxDigit()
24
+
25
+
main()
+18
python/oct14/level1/oddCount.py
+18
python/oct14/level1/oddCount.py
···
1
+
def oddCount(L: list[int]) -> int:
2
+
return len([n for n in L if n % 2])
3
+
4
+
def testOddCount():
5
+
print('Testing oddCount()...', end='')
6
+
assert(oddCount([]) == 0)
7
+
assert(oddCount([1]) == 1)
8
+
assert(oddCount([2]) == 0)
9
+
assert(oddCount([1,2,3,4,5,4,3]) == 4)
10
+
assert(oddCount([1,2,3,4,5,4,3,2]) == 4)
11
+
assert(oddCount([2,4,6,8,10,12,14]) == 0)
12
+
assert(oddCount([1,1,1,1,1]) == 5)
13
+
print('Passed!')
14
+
15
+
def main():
16
+
testOddCount()
17
+
18
+
main()
+24
python/oct14/level1/oddSum.py
+24
python/oct14/level1/oddSum.py
···
1
+
def oddSum(L: list[int], sum: int = 0) -> int:
2
+
if len(L) == 0:
3
+
return sum
4
+
val = L[0]
5
+
rest = L[1:]
6
+
if val % 2 == 1:
7
+
sum += val
8
+
return oddSum(rest, sum)
9
+
10
+
def testOddSum():
11
+
print('Testing oddSum()...', end='')
12
+
assert(oddSum([]) == 0)
13
+
assert(oddSum([1]) == 1)
14
+
assert(oddSum([2]) == 0)
15
+
assert(oddSum([1,2,3,4,5,4,3]) == 12) # 1+3+5+3
16
+
assert(oddSum([1,2,3,4,5,4,3,2]) == 12) # 1+3+5+3
17
+
assert(oddSum([2,4,6,8,10,12,14]) == 0)
18
+
assert(oddSum([1,1,1,1,1]) == 5) # 1+1+1+1+1
19
+
print('Passed!')
20
+
21
+
def main():
22
+
testOddSum()
23
+
24
+
main()
+17
python/oct14/level1/reversedEvens.py
+17
python/oct14/level1/reversedEvens.py
···
1
+
def reversedEvens(L: list[int]) -> list[int]:
2
+
return list(reversed([n for n in L if not (n % 2)]))
3
+
4
+
def testReversedEvens():
5
+
print('Testing reversedEvens()...', end='')
6
+
assert(reversedEvens([]) == [])
7
+
assert(reversedEvens([1]) == [])
8
+
assert(reversedEvens([1,2]) == [2])
9
+
assert(reversedEvens([1,2,3]) == [2])
10
+
assert(reversedEvens([1,2,3,4]) == [4,2])
11
+
assert(reversedEvens([1,2,3,4,5,6,5,4,5,6,7]) == [6,4,6,4,2])
12
+
print('Passed!')
13
+
14
+
def main():
15
+
testReversedEvens()
16
+
17
+
main()
+29
python/oct14/level1/twoHeadsInFiveFlips.py
+29
python/oct14/level1/twoHeadsInFiveFlips.py
···
1
+
from random import choice
2
+
3
+
def flipCoin() -> str:
4
+
return choice("HT")
5
+
6
+
def estimateProbability(trials: int) -> float:
7
+
heads = 0
8
+
for _ in range(trials):
9
+
tosses: list[str] = []
10
+
for _ in range(5):
11
+
tosses.append(flipCoin())
12
+
if len([h for h in tosses if h == "H"]) == 2:
13
+
heads += 1
14
+
else:
15
+
return heads / trials
16
+
17
+
18
+
def testTwoHeadsInFiveFlipsProblem():
19
+
print('Testing the twoHeadsInFiveFlips Problem...', end='')
20
+
trials = 10**5
21
+
p = estimateProbability(trials)
22
+
print(p)
23
+
assert(0.30 <= p <= 0.32) # actual answer is about 0.313
24
+
print('Passed!')
25
+
26
+
def main():
27
+
testTwoHeadsInFiveFlipsProblem()
28
+
29
+
main()
python/oct15/cats/Figure_1.png
python/oct15/cats/Figure_1.png
This is a binary file and will not be displayed.
+6
python/oct15/cats/README.txt
+6
python/oct15/cats/README.txt
+1001
python/oct15/cats/cats_dataset.csv
+1001
python/oct15/cats/cats_dataset.csv
···
1
+
Breed,Age (Years),Weight (kg),Color,Gender
2
+
Russian Blue,19,7,Tortoiseshell,Female
3
+
Norwegian Forest,19,9,Tortoiseshell,Female
4
+
Chartreux,3,3,Brown,Female
5
+
Persian,13,6,Sable,Female
6
+
Ragdoll,10,8,Tabby,Male
7
+
Ocicat,9,8,Blue,Female
8
+
Ragdoll,6,5,Calico,Female
9
+
Abyssinian,12,3,Tabby,Male
10
+
Oriental,2,7,White,Male
11
+
Egyptian Mau,12,3,White,Male
12
+
Chartreux,16,4,White,Male
13
+
American Shorthair,13,5,Black,Male
14
+
Bengal,16,6,White,Male
15
+
Cornish Rex,13,6,Pointed,Male
16
+
British Shorthair,19,3,Red,Female
17
+
Burmese,14,5,Pointed,Male
18
+
Burmese,13,3,Pointed,Male
19
+
Russian Blue,10,7,Black,Male
20
+
Singapura,7,3,Blue,Female
21
+
Maine Coon,5,6,Tricolor,Male
22
+
British Shorthair,17,6,Cream,Female
23
+
Abyssinian,9,3,Tabby,Male
24
+
Cornish Rex,6,9,Pointed,Male
25
+
Turkish Angora,16,6,Sable,Female
26
+
Burmese,6,7,Calico,Female
27
+
Himalayan,6,6,Tortoiseshell,Male
28
+
Sphynx,18,4,Cream,Male
29
+
Manx,1,4,Blue,Male
30
+
Siberian,17,2,Calico,Male
31
+
Birman,17,7,Pointed,Female
32
+
Oriental,1,2,Tortoiseshell,Female
33
+
Balinese,11,9,White,Male
34
+
Egyptian Mau,13,3,Pointed,Male
35
+
Devon Rex,1,3,Orange,Male
36
+
Egyptian Mau,8,3,Cream,Male
37
+
Sphynx,6,3,Tortoiseshell,Female
38
+
Norwegian Forest,5,9,Sable,Female
39
+
Norwegian Forest,5,6,Tabby,Male
40
+
Persian,6,2,Sable,Male
41
+
American Shorthair,13,8,Black,Male
42
+
Ragdoll,18,3,Cream,Male
43
+
Burmese,13,4,Blue,Female
44
+
Himalayan,7,9,Tabby,Female
45
+
Egyptian Mau,8,6,White,Male
46
+
Exotic Shorthair,17,2,White,Male
47
+
Egyptian Mau,1,7,Gray,Female
48
+
Egyptian Mau,16,6,White,Male
49
+
Turkish Angora,11,5,Bicolor,Male
50
+
Abyssinian,4,9,Blue,Female
51
+
Persian,4,3,Bicolor,Male
52
+
Maine Coon,17,4,Black,Male
53
+
Oriental,17,7,Red,Female
54
+
Manx,10,9,Tricolor,Female
55
+
Persian,6,2,Blue,Male
56
+
Scottish Fold,17,6,Tricolor,Male
57
+
Balinese,18,2,Orange,Male
58
+
Cornish Rex,10,5,Tabby,Male
59
+
Ragdoll,13,4,Brown,Male
60
+
Savannah,16,7,Gray,Male
61
+
American Shorthair,7,8,Bicolor,Female
62
+
Cornish Rex,15,6,Blue,Male
63
+
Munchkin,2,7,Blue,Male
64
+
Siamese,5,9,Brown,Female
65
+
Persian,17,5,White,Male
66
+
Persian,5,4,Bicolor,Male
67
+
Bengal,8,3,Red,Female
68
+
Chartreux,15,2,Calico,Male
69
+
Sphynx,8,8,Calico,Male
70
+
British Shorthair,3,5,Tricolor,Male
71
+
Birman,17,7,Pointed,Female
72
+
Balinese,16,7,Tricolor,Female
73
+
Himalayan,16,9,Orange,Male
74
+
Norwegian Forest,19,2,Brown,Female
75
+
Bengal,12,2,Tricolor,Male
76
+
Oriental,15,5,Tabby,Female
77
+
Scottish Fold,8,5,Tricolor,Male
78
+
Singapura,3,6,Calico,Male
79
+
Burmese,18,4,Bicolor,Female
80
+
Maine Coon,4,8,Black,Female
81
+
Maine Coon,8,7,Tricolor,Female
82
+
Abyssinian,14,3,Pointed,Female
83
+
Oriental,17,4,Gray,Female
84
+
Manx,6,7,Black,Male
85
+
Ragdoll,5,8,Cream,Female
86
+
British Shorthair,14,7,Red,Female
87
+
Cornish Rex,6,8,Red,Female
88
+
Savannah,12,3,Sable,Female
89
+
Turkish Angora,9,3,Orange,Male
90
+
American Shorthair,1,8,Tricolor,Male
91
+
Ocicat,18,6,Sable,Female
92
+
Persian,6,8,Calico,Female
93
+
Turkish Angora,5,4,Brown,Male
94
+
Ragdoll,2,5,Tricolor,Male
95
+
Chartreux,6,2,Tabby,Male
96
+
Russian Blue,17,7,Cream,Male
97
+
Manx,7,6,Sable,Male
98
+
Scottish Fold,9,6,Sable,Male
99
+
Balinese,5,6,White,Female
100
+
Savannah,11,9,Red,Female
101
+
Bengal,11,5,Tortoiseshell,Female
102
+
British Shorthair,4,7,Red,Male
103
+
Bengal,6,8,White,Female
104
+
Norwegian Forest,10,2,Cream,Female
105
+
Balinese,9,3,Black,Female
106
+
Ragdoll,18,7,Red,Male
107
+
Russian Blue,9,5,Tortoiseshell,Male
108
+
Bengal,11,4,Calico,Male
109
+
Balinese,2,4,Orange,Female
110
+
Birman,15,5,Cream,Male
111
+
Turkish Angora,6,6,Brown,Female
112
+
Norwegian Forest,17,5,Black,Female
113
+
Balinese,4,7,Tortoiseshell,Female
114
+
Ragdoll,8,9,Calico,Male
115
+
Singapura,2,8,Blue,Male
116
+
Cornish Rex,9,2,Tricolor,Female
117
+
Norwegian Forest,1,7,Cream,Female
118
+
Siberian,6,3,Gray,Female
119
+
Chartreux,16,8,Calico,Female
120
+
Chartreux,7,9,Bicolor,Male
121
+
Devon Rex,16,3,Pointed,Male
122
+
Himalayan,6,9,Sable,Female
123
+
Sphynx,10,3,Tabby,Male
124
+
Oriental,18,8,Tortoiseshell,Female
125
+
Ragdoll,17,7,Black,Male
126
+
Persian,17,3,Bicolor,Female
127
+
Sphynx,12,6,Red,Female
128
+
Persian,15,7,Pointed,Female
129
+
Himalayan,6,8,Brown,Female
130
+
American Shorthair,16,7,Bicolor,Male
131
+
Bengal,14,4,Calico,Male
132
+
Siamese,12,9,Red,Female
133
+
Devon Rex,12,4,Blue,Male
134
+
Exotic Shorthair,2,5,Black,Female
135
+
Ragdoll,8,3,Cream,Male
136
+
Siamese,17,4,Tortoiseshell,Female
137
+
Scottish Fold,8,2,Brown,Male
138
+
Siberian,16,8,Bicolor,Female
139
+
Devon Rex,1,5,Blue,Female
140
+
Abyssinian,5,9,Tortoiseshell,Female
141
+
Chartreux,19,6,Calico,Female
142
+
Oriental,3,4,Orange,Female
143
+
Persian,9,3,Black,Male
144
+
Scottish Fold,11,8,Gray,Male
145
+
American Shorthair,6,8,Bicolor,Female
146
+
Savannah,7,8,Orange,Female
147
+
Singapura,7,6,Pointed,Male
148
+
Cornish Rex,5,2,White,Female
149
+
Ocicat,2,2,Calico,Female
150
+
Siamese,5,2,Brown,Female
151
+
Manx,6,6,Calico,Female
152
+
Siamese,6,7,Tabby,Female
153
+
Ragdoll,7,4,Calico,Male
154
+
Ocicat,4,4,Red,Female
155
+
Ragdoll,18,7,Sable,Male
156
+
American Shorthair,16,7,Tabby,Female
157
+
Turkish Angora,19,3,Gray,Male
158
+
Ragdoll,10,7,Blue,Female
159
+
Balinese,5,6,Gray,Male
160
+
Himalayan,18,4,Red,Male
161
+
American Shorthair,3,7,Pointed,Male
162
+
Ocicat,4,5,Red,Female
163
+
Turkish Angora,14,3,Brown,Male
164
+
Oriental,12,4,Red,Male
165
+
Singapura,14,5,Tricolor,Male
166
+
Maine Coon,5,3,Calico,Male
167
+
Bengal,18,2,Orange,Male
168
+
Scottish Fold,7,2,Gray,Male
169
+
Tonkinese,16,5,Bicolor,Female
170
+
Savannah,3,2,Pointed,Male
171
+
Birman,11,5,Black,Male
172
+
Savannah,18,3,Brown,Female
173
+
Siamese,6,4,Bicolor,Male
174
+
Egyptian Mau,5,9,Sable,Female
175
+
Devon Rex,5,3,Cream,Male
176
+
Persian,16,5,Orange,Female
177
+
Tonkinese,3,8,Brown,Male
178
+
Persian,12,6,Red,Male
179
+
British Shorthair,3,6,Bicolor,Female
180
+
Egyptian Mau,2,9,Gray,Male
181
+
Turkish Angora,15,8,Red,Male
182
+
Scottish Fold,17,7,Gray,Female
183
+
Egyptian Mau,12,7,White,Male
184
+
Burmese,2,7,Tortoiseshell,Male
185
+
Munchkin,2,5,Orange,Male
186
+
Birman,14,2,Cream,Male
187
+
Siberian,7,5,Gray,Male
188
+
Burmese,14,9,Pointed,Female
189
+
Exotic Shorthair,7,9,Brown,Female
190
+
Siamese,17,7,Tricolor,Female
191
+
Ragdoll,4,4,Pointed,Female
192
+
Siamese,15,9,Pointed,Female
193
+
Savannah,3,3,White,Female
194
+
Siberian,6,5,Tricolor,Female
195
+
Devon Rex,8,9,Pointed,Male
196
+
Birman,16,7,Bicolor,Female
197
+
Birman,15,8,Pointed,Female
198
+
Egyptian Mau,4,4,Tricolor,Male
199
+
Birman,8,6,Gray,Female
200
+
Singapura,8,5,Orange,Female
201
+
Siamese,7,2,Orange,Male
202
+
Burmese,18,7,Blue,Male
203
+
Balinese,12,5,Tabby,Female
204
+
Ragdoll,4,7,Tabby,Female
205
+
Russian Blue,14,6,Gray,Male
206
+
Exotic Shorthair,5,7,Red,Male
207
+
Turkish Angora,7,7,Sable,Male
208
+
Persian,12,4,Orange,Male
209
+
Bengal,15,5,Tricolor,Male
210
+
Ragdoll,19,9,Tortoiseshell,Male
211
+
Siberian,13,5,Sable,Male
212
+
Savannah,4,7,Black,Male
213
+
Munchkin,6,8,Bicolor,Male
214
+
Tonkinese,8,2,Tortoiseshell,Female
215
+
Siberian,15,5,Pointed,Female
216
+
Manx,12,3,Black,Male
217
+
Siberian,10,8,Black,Female
218
+
Savannah,12,8,Black,Male
219
+
Burmese,17,5,Orange,Male
220
+
Devon Rex,8,4,Tricolor,Male
221
+
Burmese,2,8,Tortoiseshell,Female
222
+
Exotic Shorthair,19,3,Cream,Male
223
+
Chartreux,1,2,Cream,Male
224
+
Turkish Angora,10,4,Tabby,Female
225
+
Manx,9,2,Pointed,Female
226
+
Devon Rex,19,8,Brown,Female
227
+
Munchkin,12,8,Bicolor,Female
228
+
Manx,18,8,Black,Male
229
+
Tonkinese,16,5,Red,Female
230
+
Chartreux,4,5,White,Male
231
+
Exotic Shorthair,11,5,Tricolor,Male
232
+
Egyptian Mau,15,6,Tabby,Female
233
+
Maine Coon,16,6,Tabby,Female
234
+
Birman,2,9,Bicolor,Male
235
+
Persian,13,6,Sable,Female
236
+
Bengal,12,3,Black,Female
237
+
Ocicat,8,6,Red,Male
238
+
Ocicat,12,7,Sable,Female
239
+
Manx,14,9,Blue,Female
240
+
British Shorthair,3,7,Tortoiseshell,Male
241
+
Chartreux,2,5,Red,Male
242
+
Siberian,17,9,Bicolor,Female
243
+
Abyssinian,2,5,Tabby,Female
244
+
Burmese,17,2,Black,Male
245
+
Burmese,2,4,Tricolor,Female
246
+
Norwegian Forest,2,4,Tricolor,Male
247
+
Oriental,13,3,Tabby,Female
248
+
American Shorthair,7,4,Orange,Female
249
+
Birman,9,9,Sable,Male
250
+
Tonkinese,9,8,Calico,Male
251
+
Norwegian Forest,14,3,Cream,Female
252
+
Savannah,11,9,Brown,Female
253
+
British Shorthair,19,2,Sable,Female
254
+
Norwegian Forest,1,5,Tricolor,Female
255
+
Exotic Shorthair,5,5,Tricolor,Male
256
+
Manx,1,3,Tricolor,Male
257
+
Maine Coon,6,6,Cream,Female
258
+
Tonkinese,16,9,Orange,Female
259
+
Russian Blue,16,2,White,Male
260
+
Burmese,13,9,Pointed,Male
261
+
Exotic Shorthair,14,9,Sable,Female
262
+
Burmese,11,7,Sable,Female
263
+
Ragdoll,10,5,Black,Male
264
+
American Shorthair,17,5,Brown,Male
265
+
Norwegian Forest,7,9,Cream,Male
266
+
Ragdoll,4,8,Orange,Male
267
+
Scottish Fold,17,2,Sable,Male
268
+
Russian Blue,1,7,Brown,Female
269
+
British Shorthair,2,7,Red,Female
270
+
Ragdoll,19,3,Gray,Female
271
+
Tonkinese,1,9,Cream,Female
272
+
Exotic Shorthair,12,6,Bicolor,Female
273
+
Sphynx,19,8,Blue,Male
274
+
Cornish Rex,19,8,Orange,Male
275
+
Savannah,16,3,Tabby,Male
276
+
Persian,16,2,Calico,Male
277
+
Savannah,8,4,Black,Female
278
+
Birman,13,3,Bicolor,Female
279
+
Balinese,14,4,Bicolor,Male
280
+
Scottish Fold,19,8,Tricolor,Male
281
+
Tonkinese,15,2,Black,Female
282
+
Savannah,15,7,Brown,Female
283
+
Sphynx,8,4,Blue,Male
284
+
Exotic Shorthair,13,5,Tortoiseshell,Female
285
+
Munchkin,12,9,Sable,Female
286
+
Norwegian Forest,13,4,Sable,Female
287
+
Birman,16,6,White,Female
288
+
Siamese,12,6,Pointed,Female
289
+
Ocicat,14,8,Cream,Male
290
+
Egyptian Mau,15,2,Tabby,Male
291
+
Manx,19,6,Cream,Female
292
+
Cornish Rex,5,9,Brown,Female
293
+
Chartreux,2,7,Gray,Male
294
+
Egyptian Mau,18,7,Tricolor,Female
295
+
Turkish Angora,18,8,Gray,Male
296
+
Balinese,17,4,Tortoiseshell,Male
297
+
Chartreux,10,8,Gray,Male
298
+
Exotic Shorthair,17,9,Calico,Male
299
+
Ragdoll,3,5,Pointed,Female
300
+
Ragdoll,16,2,Cream,Male
301
+
Abyssinian,18,9,Tortoiseshell,Male
302
+
Oriental,7,3,Tricolor,Female
303
+
Oriental,1,5,Tabby,Male
304
+
Oriental,7,8,Cream,Male
305
+
Cornish Rex,13,3,White,Female
306
+
Ragdoll,16,2,Tortoiseshell,Male
307
+
Turkish Angora,11,3,Calico,Female
308
+
Singapura,3,5,Bicolor,Female
309
+
American Shorthair,9,9,Sable,Female
310
+
Russian Blue,7,6,Red,Female
311
+
Ragdoll,3,8,Red,Male
312
+
Oriental,1,2,White,Male
313
+
Scottish Fold,11,6,Tabby,Male
314
+
Ocicat,13,6,Tortoiseshell,Male
315
+
Siberian,4,6,Black,Male
316
+
Abyssinian,10,5,Tabby,Female
317
+
Abyssinian,8,8,Orange,Female
318
+
Exotic Shorthair,17,7,Blue,Female
319
+
Chartreux,11,8,Black,Female
320
+
Maine Coon,5,4,Tricolor,Male
321
+
Russian Blue,18,6,Gray,Female
322
+
Siamese,4,4,Gray,Female
323
+
Siamese,6,2,Orange,Male
324
+
Burmese,7,3,Bicolor,Male
325
+
Maine Coon,12,6,White,Male
326
+
Sphynx,3,7,Sable,Male
327
+
Birman,8,4,Bicolor,Female
328
+
Bengal,10,2,Black,Male
329
+
Persian,1,5,Orange,Female
330
+
Savannah,17,7,Blue,Male
331
+
Siamese,18,8,Calico,Female
332
+
Sphynx,9,7,Brown,Female
333
+
Chartreux,16,2,Sable,Male
334
+
Devon Rex,17,5,White,Male
335
+
Birman,18,3,Brown,Female
336
+
Exotic Shorthair,11,3,White,Female
337
+
Maine Coon,18,7,Black,Male
338
+
Himalayan,4,4,White,Female
339
+
Devon Rex,8,9,Blue,Female
340
+
Ragdoll,10,5,Orange,Female
341
+
Burmese,4,5,Pointed,Female
342
+
Ocicat,8,7,Orange,Male
343
+
Birman,17,3,Tricolor,Male
344
+
Balinese,3,7,Cream,Female
345
+
Bengal,5,7,Tabby,Female
346
+
Devon Rex,5,2,Gray,Male
347
+
Manx,7,2,Blue,Male
348
+
Ragdoll,1,4,Bicolor,Female
349
+
Abyssinian,18,3,Black,Female
350
+
Munchkin,6,8,Brown,Female
351
+
Ragdoll,15,9,Pointed,Male
352
+
American Shorthair,9,3,Tortoiseshell,Male
353
+
Exotic Shorthair,3,6,Pointed,Female
354
+
Himalayan,18,7,Pointed,Male
355
+
Maine Coon,16,9,Black,Female
356
+
American Shorthair,17,4,Red,Male
357
+
Himalayan,9,3,Red,Female
358
+
Exotic Shorthair,13,6,Tortoiseshell,Female
359
+
Balinese,8,5,White,Female
360
+
Exotic Shorthair,13,9,Orange,Male
361
+
Siamese,1,8,Tabby,Female
362
+
Sphynx,12,7,Calico,Female
363
+
Sphynx,12,6,Tricolor,Female
364
+
Russian Blue,9,3,Black,Male
365
+
Russian Blue,1,2,Gray,Male
366
+
Chartreux,14,4,Bicolor,Female
367
+
Exotic Shorthair,14,5,Pointed,Male
368
+
Siamese,6,8,Orange,Male
369
+
Norwegian Forest,16,9,Bicolor,Female
370
+
Ocicat,8,7,Blue,Female
371
+
Chartreux,19,9,White,Female
372
+
Abyssinian,5,2,Tortoiseshell,Male
373
+
Abyssinian,15,4,Gray,Female
374
+
Savannah,13,5,Gray,Male
375
+
Tonkinese,6,5,Brown,Female
376
+
Himalayan,17,7,Orange,Female
377
+
British Shorthair,5,7,Orange,Male
378
+
Abyssinian,10,3,Bicolor,Male
379
+
American Shorthair,5,6,Pointed,Female
380
+
Munchkin,17,7,Orange,Female
381
+
Savannah,19,9,Tabby,Female
382
+
Egyptian Mau,6,2,Cream,Female
383
+
Himalayan,15,7,Calico,Male
384
+
Oriental,4,9,Gray,Female
385
+
Exotic Shorthair,17,6,Blue,Female
386
+
Turkish Angora,1,9,Cream,Male
387
+
Chartreux,18,3,Gray,Male
388
+
Oriental,10,3,Gray,Female
389
+
Manx,19,2,Red,Male
390
+
American Shorthair,2,8,Black,Male
391
+
Persian,17,5,Pointed,Male
392
+
Ocicat,18,9,Pointed,Female
393
+
Oriental,13,4,Tabby,Female
394
+
Munchkin,7,8,Red,Female
395
+
Savannah,5,5,Bicolor,Male
396
+
Himalayan,4,6,Sable,Female
397
+
Oriental,7,8,Orange,Male
398
+
Singapura,5,7,Black,Female
399
+
Persian,19,2,Tricolor,Female
400
+
Tonkinese,7,8,Sable,Female
401
+
Devon Rex,13,3,Bicolor,Male
402
+
Scottish Fold,4,7,Sable,Male
403
+
Bengal,15,5,Tricolor,Female
404
+
Burmese,18,2,Cream,Female
405
+
Balinese,4,4,Pointed,Female
406
+
Ragdoll,4,7,Bicolor,Male
407
+
Egyptian Mau,2,9,Gray,Male
408
+
Birman,5,8,Tricolor,Female
409
+
Burmese,8,3,Blue,Female
410
+
Ocicat,11,5,Red,Female
411
+
Exotic Shorthair,11,3,Black,Female
412
+
Ocicat,18,9,Gray,Female
413
+
Turkish Angora,19,9,Brown,Male
414
+
Cornish Rex,17,6,Black,Female
415
+
Persian,1,4,Red,Male
416
+
Egyptian Mau,5,9,Black,Male
417
+
Birman,3,7,Red,Female
418
+
Abyssinian,14,4,Pointed,Female
419
+
Cornish Rex,2,4,Brown,Female
420
+
Siberian,3,8,Black,Female
421
+
Singapura,3,6,Brown,Female
422
+
Himalayan,13,5,Blue,Male
423
+
Burmese,8,9,Sable,Male
424
+
Balinese,14,9,Black,Female
425
+
Cornish Rex,18,9,Pointed,Male
426
+
Persian,13,7,Sable,Male
427
+
Savannah,18,5,Calico,Female
428
+
Munchkin,1,8,Gray,Male
429
+
Bengal,2,5,Tortoiseshell,Male
430
+
Manx,5,5,Pointed,Male
431
+
Persian,11,9,Bicolor,Male
432
+
Scottish Fold,11,5,Gray,Female
433
+
Turkish Angora,1,2,Blue,Female
434
+
Tonkinese,3,8,Blue,Female
435
+
Munchkin,2,6,Blue,Female
436
+
Balinese,14,4,Orange,Female
437
+
American Shorthair,16,5,Bicolor,Male
438
+
Bengal,18,5,Brown,Female
439
+
Tonkinese,5,4,Cream,Male
440
+
Ragdoll,8,4,Orange,Male
441
+
Singapura,7,2,Bicolor,Female
442
+
Egyptian Mau,10,6,Pointed,Female
443
+
Abyssinian,16,3,Brown,Female
444
+
Birman,19,7,Pointed,Female
445
+
Exotic Shorthair,11,2,Black,Female
446
+
Siberian,19,3,Tabby,Male
447
+
Sphynx,3,2,Tricolor,Male
448
+
Persian,18,6,White,Male
449
+
Birman,4,6,Bicolor,Female
450
+
Norwegian Forest,17,3,Cream,Male
451
+
Norwegian Forest,13,8,Tortoiseshell,Female
452
+
Chartreux,4,5,Tabby,Male
453
+
Savannah,14,7,Tortoiseshell,Female
454
+
Bengal,3,5,Cream,Male
455
+
Abyssinian,4,5,Calico,Female
456
+
Munchkin,18,8,Orange,Female
457
+
Siberian,13,3,Sable,Male
458
+
Singapura,7,5,Tabby,Male
459
+
Scottish Fold,11,4,Black,Female
460
+
Balinese,11,4,Tortoiseshell,Male
461
+
Turkish Angora,6,6,Tortoiseshell,Male
462
+
Himalayan,11,3,Sable,Male
463
+
Ocicat,9,5,Blue,Male
464
+
Egyptian Mau,16,4,White,Female
465
+
Ragdoll,5,5,White,Male
466
+
Oriental,6,7,Gray,Female
467
+
American Shorthair,4,2,Black,Male
468
+
Chartreux,8,5,Gray,Female
469
+
Siamese,16,6,White,Male
470
+
Egyptian Mau,16,8,Gray,Male
471
+
Chartreux,11,2,Tortoiseshell,Female
472
+
Singapura,14,9,Tricolor,Male
473
+
Savannah,8,3,White,Male
474
+
Cornish Rex,6,5,Orange,Female
475
+
Munchkin,8,5,Red,Female
476
+
Munchkin,19,2,Bicolor,Female
477
+
American Shorthair,8,6,Blue,Female
478
+
Siberian,1,9,Orange,Female
479
+
Cornish Rex,13,6,Cream,Female
480
+
Maine Coon,8,6,Blue,Male
481
+
British Shorthair,9,7,Black,Male
482
+
Scottish Fold,16,3,Orange,Male
483
+
Chartreux,5,8,Tortoiseshell,Male
484
+
Devon Rex,11,9,Tabby,Male
485
+
Ocicat,17,2,Gray,Male
486
+
Devon Rex,9,9,Red,Male
487
+
Singapura,17,8,Black,Female
488
+
Munchkin,15,4,Black,Male
489
+
Ragdoll,1,3,Calico,Female
490
+
Singapura,16,6,Gray,Male
491
+
Himalayan,13,6,Blue,Female
492
+
Scottish Fold,3,5,Cream,Male
493
+
Oriental,14,7,Sable,Male
494
+
Russian Blue,3,7,Red,Male
495
+
Siberian,18,3,Gray,Female
496
+
Manx,14,2,Tortoiseshell,Male
497
+
British Shorthair,17,8,Brown,Male
498
+
British Shorthair,6,8,Calico,Female
499
+
Abyssinian,8,6,White,Male
500
+
Birman,2,4,Gray,Male
501
+
Siberian,18,5,Pointed,Male
502
+
Burmese,3,2,Calico,Male
503
+
Abyssinian,2,2,Tricolor,Male
504
+
Devon Rex,5,7,Tortoiseshell,Female
505
+
Burmese,9,2,Gray,Male
506
+
Tonkinese,19,9,Sable,Male
507
+
Burmese,2,7,Tricolor,Male
508
+
Norwegian Forest,4,7,Cream,Male
509
+
Manx,17,8,White,Female
510
+
Birman,4,7,Brown,Female
511
+
Ragdoll,6,9,Bicolor,Female
512
+
British Shorthair,2,5,Tricolor,Male
513
+
Himalayan,4,4,Tricolor,Female
514
+
Abyssinian,8,6,Blue,Female
515
+
Balinese,2,5,Pointed,Male
516
+
Manx,2,5,Gray,Male
517
+
Siamese,8,4,Brown,Female
518
+
Munchkin,5,8,Tortoiseshell,Male
519
+
Devon Rex,14,8,Pointed,Female
520
+
Burmese,13,2,Cream,Female
521
+
American Shorthair,19,6,Gray,Female
522
+
Siberian,1,3,Cream,Female
523
+
Cornish Rex,12,9,Sable,Male
524
+
Burmese,3,2,Tricolor,Male
525
+
Bengal,14,7,White,Male
526
+
Bengal,5,2,Tabby,Male
527
+
Russian Blue,17,4,Bicolor,Female
528
+
Himalayan,17,3,White,Female
529
+
Cornish Rex,11,6,Bicolor,Female
530
+
Tonkinese,12,6,Cream,Female
531
+
Manx,4,9,Tabby,Female
532
+
Balinese,9,6,Bicolor,Female
533
+
Maine Coon,15,9,Calico,Male
534
+
Sphynx,1,4,Tabby,Female
535
+
Manx,1,7,Tortoiseshell,Female
536
+
Cornish Rex,3,3,Gray,Male
537
+
Ocicat,12,3,Brown,Male
538
+
Exotic Shorthair,6,2,Brown,Female
539
+
Maine Coon,3,7,Orange,Male
540
+
Egyptian Mau,8,5,Red,Female
541
+
Chartreux,13,3,Calico,Male
542
+
Cornish Rex,12,6,Calico,Male
543
+
Egyptian Mau,2,6,Cream,Male
544
+
Tonkinese,17,5,Cream,Male
545
+
Tonkinese,19,5,Pointed,Male
546
+
Abyssinian,19,2,Gray,Male
547
+
Oriental,15,2,Bicolor,Male
548
+
Himalayan,16,8,Red,Male
549
+
Oriental,16,4,Blue,Male
550
+
Tonkinese,6,7,Black,Female
551
+
Persian,2,8,Calico,Female
552
+
Egyptian Mau,11,6,Sable,Female
553
+
Burmese,3,4,Cream,Male
554
+
Bengal,9,6,Tricolor,Female
555
+
Himalayan,16,4,Cream,Male
556
+
Balinese,8,9,Gray,Male
557
+
Chartreux,13,2,Pointed,Male
558
+
Devon Rex,4,3,Calico,Male
559
+
Ragdoll,2,7,Tabby,Female
560
+
Scottish Fold,17,8,Sable,Male
561
+
Maine Coon,18,5,Tabby,Male
562
+
Balinese,17,6,Red,Female
563
+
Bengal,6,7,Orange,Male
564
+
Bengal,11,2,Red,Female
565
+
Devon Rex,18,8,White,Male
566
+
Devon Rex,14,2,Gray,Male
567
+
Norwegian Forest,1,2,Tortoiseshell,Male
568
+
Scottish Fold,2,4,Tortoiseshell,Male
569
+
Siberian,7,7,Cream,Male
570
+
Birman,10,9,Bicolor,Female
571
+
Ocicat,6,9,Tricolor,Male
572
+
Savannah,13,5,Calico,Female
573
+
Balinese,11,8,Tabby,Female
574
+
British Shorthair,17,2,Cream,Female
575
+
Oriental,18,4,Calico,Female
576
+
Munchkin,5,8,Red,Female
577
+
Ocicat,15,2,Gray,Female
578
+
Norwegian Forest,14,9,Cream,Male
579
+
Ragdoll,18,4,Tabby,Female
580
+
Egyptian Mau,7,9,Sable,Male
581
+
Russian Blue,11,7,Blue,Male
582
+
American Shorthair,6,3,Bicolor,Male
583
+
Himalayan,5,9,Cream,Female
584
+
Scottish Fold,5,4,Tricolor,Male
585
+
Siamese,19,6,Gray,Male
586
+
Maine Coon,19,9,Brown,Female
587
+
Savannah,11,5,Gray,Female
588
+
Ragdoll,12,4,Pointed,Female
589
+
Russian Blue,15,5,Tabby,Male
590
+
Bengal,17,9,White,Female
591
+
Singapura,2,4,Tricolor,Female
592
+
Maine Coon,9,5,Blue,Female
593
+
Himalayan,8,9,Tabby,Male
594
+
Persian,6,9,Gray,Male
595
+
Abyssinian,16,7,Red,Female
596
+
Burmese,10,3,Red,Female
597
+
Singapura,1,6,Cream,Male
598
+
Bengal,15,6,Tricolor,Male
599
+
Ragdoll,3,6,Pointed,Female
600
+
Sphynx,5,8,Sable,Male
601
+
British Shorthair,1,8,Pointed,Male
602
+
Ragdoll,6,4,Pointed,Female
603
+
Siamese,18,9,Brown,Female
604
+
Manx,5,8,White,Male
605
+
Birman,1,6,Bicolor,Male
606
+
Munchkin,18,2,Red,Female
607
+
Maine Coon,18,2,Cream,Female
608
+
Munchkin,9,8,Sable,Male
609
+
Bengal,10,8,Pointed,Female
610
+
Singapura,5,6,Black,Female
611
+
Persian,10,7,Gray,Male
612
+
Burmese,13,6,Red,Female
613
+
Siberian,17,4,Pointed,Female
614
+
Egyptian Mau,4,8,White,Female
615
+
Oriental,1,6,Black,Male
616
+
Chartreux,15,5,Pointed,Female
617
+
Sphynx,1,7,Calico,Female
618
+
Maine Coon,12,3,White,Female
619
+
Abyssinian,2,4,Red,Female
620
+
Exotic Shorthair,19,4,Tortoiseshell,Female
621
+
Birman,15,8,Red,Female
622
+
Tonkinese,13,2,Pointed,Female
623
+
Balinese,9,4,Blue,Female
624
+
Abyssinian,6,4,Black,Male
625
+
Ragdoll,3,8,Cream,Female
626
+
Chartreux,18,3,Cream,Female
627
+
Ocicat,16,7,Blue,Female
628
+
Russian Blue,4,9,Tricolor,Female
629
+
Ragdoll,17,5,Cream,Female
630
+
British Shorthair,19,8,Tortoiseshell,Female
631
+
Balinese,6,3,Gray,Male
632
+
Abyssinian,19,7,Tortoiseshell,Male
633
+
Egyptian Mau,8,7,Gray,Male
634
+
Persian,18,7,Sable,Female
635
+
Sphynx,5,5,Calico,Male
636
+
Ocicat,14,2,Gray,Female
637
+
Savannah,16,7,Sable,Female
638
+
Oriental,15,3,Tricolor,Male
639
+
Egyptian Mau,19,3,Tricolor,Female
640
+
Abyssinian,1,3,Tabby,Male
641
+
Chartreux,5,6,Tricolor,Male
642
+
Sphynx,7,6,Gray,Male
643
+
Munchkin,13,8,Tricolor,Male
644
+
Birman,4,9,Tricolor,Male
645
+
Savannah,19,7,Gray,Male
646
+
Turkish Angora,14,3,Blue,Male
647
+
Persian,18,4,Bicolor,Female
648
+
Birman,2,2,Bicolor,Female
649
+
Egyptian Mau,19,4,Orange,Male
650
+
British Shorthair,13,2,Tortoiseshell,Male
651
+
Manx,16,9,Sable,Female
652
+
Scottish Fold,8,8,Sable,Male
653
+
Egyptian Mau,3,8,Brown,Male
654
+
Siamese,8,7,Pointed,Female
655
+
Ocicat,14,8,Cream,Male
656
+
Cornish Rex,17,2,Sable,Female
657
+
Persian,2,6,White,Female
658
+
Egyptian Mau,17,8,Calico,Female
659
+
Singapura,14,2,Tabby,Female
660
+
Turkish Angora,6,8,Calico,Male
661
+
Persian,19,9,Cream,Male
662
+
Egyptian Mau,17,3,Calico,Male
663
+
British Shorthair,13,9,Gray,Female
664
+
Maine Coon,5,9,Sable,Female
665
+
Russian Blue,17,3,Pointed,Female
666
+
Ocicat,15,7,Black,Female
667
+
Manx,9,7,Blue,Male
668
+
American Shorthair,15,8,Gray,Male
669
+
Siberian,18,2,White,Male
670
+
Oriental,11,8,Tricolor,Male
671
+
Bengal,15,6,Red,Female
672
+
Manx,7,4,Pointed,Female
673
+
Exotic Shorthair,14,6,Calico,Male
674
+
Ocicat,5,6,Pointed,Female
675
+
Himalayan,16,9,Gray,Male
676
+
Ragdoll,15,4,Red,Female
677
+
Siamese,11,3,Tricolor,Female
678
+
Balinese,12,8,Blue,Female
679
+
Siberian,15,7,Red,Female
680
+
Manx,12,8,Tabby,Female
681
+
Norwegian Forest,3,5,Blue,Male
682
+
Cornish Rex,1,6,Tabby,Female
683
+
Bengal,15,3,Tortoiseshell,Female
684
+
Oriental,7,2,Blue,Male
685
+
Bengal,19,6,Red,Male
686
+
Himalayan,15,6,Calico,Male
687
+
American Shorthair,14,9,Tortoiseshell,Female
688
+
Siberian,13,8,Pointed,Female
689
+
Siberian,6,2,Cream,Male
690
+
Cornish Rex,9,9,Blue,Male
691
+
Siamese,9,5,Gray,Female
692
+
Ragdoll,16,5,Red,Female
693
+
Oriental,11,4,Brown,Female
694
+
Singapura,11,7,Tricolor,Female
695
+
Cornish Rex,18,2,Red,Female
696
+
Singapura,1,9,Calico,Male
697
+
Tonkinese,8,3,Brown,Male
698
+
Tonkinese,7,3,Tortoiseshell,Male
699
+
Russian Blue,3,5,Gray,Female
700
+
Norwegian Forest,2,4,White,Female
701
+
Siamese,17,7,Calico,Female
702
+
Singapura,14,4,Tricolor,Female
703
+
Egyptian Mau,18,8,White,Female
704
+
British Shorthair,8,3,Black,Male
705
+
Turkish Angora,2,7,Pointed,Male
706
+
Devon Rex,3,7,Bicolor,Female
707
+
Exotic Shorthair,1,8,Bicolor,Female
708
+
Munchkin,19,4,Black,Male
709
+
Norwegian Forest,7,7,Cream,Male
710
+
Singapura,7,5,White,Female
711
+
Birman,4,5,Bicolor,Male
712
+
Persian,5,4,Cream,Female
713
+
Scottish Fold,19,5,Black,Female
714
+
Savannah,7,7,Gray,Male
715
+
Siberian,15,8,Calico,Female
716
+
Abyssinian,16,5,Black,Female
717
+
British Shorthair,18,4,Calico,Female
718
+
Siberian,2,5,Tabby,Male
719
+
Chartreux,10,9,Orange,Male
720
+
Norwegian Forest,18,2,Tortoiseshell,Female
721
+
Oriental,12,5,Blue,Male
722
+
British Shorthair,1,6,Tricolor,Female
723
+
Chartreux,4,3,Tabby,Female
724
+
Savannah,5,2,Black,Male
725
+
American Shorthair,16,4,Tricolor,Female
726
+
Abyssinian,6,3,White,Male
727
+
Munchkin,5,6,Red,Female
728
+
Maine Coon,10,3,Brown,Female
729
+
Oriental,17,5,Blue,Female
730
+
Siamese,17,3,Gray,Female
731
+
American Shorthair,6,9,Pointed,Male
732
+
Munchkin,1,4,Tortoiseshell,Female
733
+
Turkish Angora,4,5,Orange,Male
734
+
Sphynx,9,9,Orange,Male
735
+
Ocicat,10,5,Tricolor,Male
736
+
Tonkinese,11,6,Tricolor,Male
737
+
Bengal,7,8,Cream,Female
738
+
Exotic Shorthair,13,7,Black,Female
739
+
American Shorthair,12,9,Black,Male
740
+
Munchkin,3,3,Tortoiseshell,Male
741
+
Turkish Angora,18,7,Pointed,Male
742
+
Exotic Shorthair,8,9,Calico,Male
743
+
American Shorthair,8,6,Tricolor,Male
744
+
Maine Coon,7,8,Blue,Female
745
+
Russian Blue,6,5,Sable,Male
746
+
Persian,10,5,Blue,Male
747
+
Oriental,13,8,Bicolor,Female
748
+
Devon Rex,9,6,Orange,Female
749
+
Siamese,2,8,Calico,Male
750
+
Birman,15,5,Tabby,Male
751
+
Egyptian Mau,14,8,Sable,Male
752
+
Exotic Shorthair,16,7,Black,Female
753
+
Scottish Fold,5,7,Red,Female
754
+
Devon Rex,3,3,Cream,Male
755
+
Singapura,8,2,Blue,Female
756
+
Ocicat,14,7,Gray,Male
757
+
Burmese,9,8,Tortoiseshell,Female
758
+
Burmese,6,6,Bicolor,Female
759
+
Maine Coon,8,4,Tabby,Male
760
+
Persian,16,2,Sable,Male
761
+
Sphynx,16,4,Black,Female
762
+
Burmese,10,5,Pointed,Male
763
+
Siamese,10,2,Orange,Female
764
+
Norwegian Forest,12,5,Brown,Female
765
+
Ragdoll,4,9,White,Male
766
+
Munchkin,8,5,Sable,Female
767
+
Turkish Angora,19,9,Bicolor,Female
768
+
Sphynx,9,9,Red,Female
769
+
Savannah,2,9,White,Male
770
+
Egyptian Mau,4,8,Sable,Female
771
+
Burmese,19,7,Black,Male
772
+
British Shorthair,18,6,Brown,Male
773
+
Balinese,15,8,Red,Female
774
+
British Shorthair,11,8,Calico,Male
775
+
Munchkin,11,4,Bicolor,Male
776
+
Egyptian Mau,12,8,Calico,Male
777
+
British Shorthair,14,7,Tortoiseshell,Female
778
+
Siamese,15,3,Black,Male
779
+
Munchkin,6,6,Pointed,Male
780
+
Munchkin,11,6,Blue,Female
781
+
British Shorthair,8,4,Pointed,Male
782
+
Sphynx,18,6,Cream,Male
783
+
Ocicat,6,9,Orange,Male
784
+
Sphynx,7,4,Red,Female
785
+
Siberian,12,6,Orange,Male
786
+
Oriental,12,2,Black,Male
787
+
Bengal,18,9,Blue,Male
788
+
Tonkinese,19,4,Bicolor,Female
789
+
Siberian,6,3,Orange,Female
790
+
Chartreux,18,8,Blue,Female
791
+
Ocicat,9,7,Orange,Male
792
+
Himalayan,18,6,Black,Female
793
+
Ragdoll,11,7,Brown,Male
794
+
Siberian,8,8,Calico,Male
795
+
Egyptian Mau,12,2,Tricolor,Female
796
+
Singapura,15,7,Pointed,Male
797
+
Birman,1,3,Red,Male
798
+
Manx,2,8,Calico,Male
799
+
Chartreux,2,6,Pointed,Male
800
+
Siberian,5,6,Bicolor,Female
801
+
Sphynx,13,7,Blue,Male
802
+
Tonkinese,9,7,Orange,Male
803
+
Devon Rex,19,7,Orange,Male
804
+
Manx,13,5,Tabby,Male
805
+
Turkish Angora,4,6,Calico,Male
806
+
Cornish Rex,11,3,Orange,Male
807
+
Sphynx,8,3,Tricolor,Female
808
+
Maine Coon,15,2,Sable,Male
809
+
Sphynx,9,5,Cream,Male
810
+
British Shorthair,15,2,Tortoiseshell,Male
811
+
Russian Blue,4,9,Gray,Male
812
+
Singapura,4,8,Orange,Female
813
+
Scottish Fold,18,2,Sable,Female
814
+
Ocicat,13,6,Bicolor,Female
815
+
Tonkinese,14,8,Sable,Male
816
+
Turkish Angora,8,2,Tricolor,Female
817
+
Birman,16,4,Brown,Male
818
+
Abyssinian,15,5,Sable,Male
819
+
Siberian,8,6,Brown,Female
820
+
Sphynx,11,4,Tortoiseshell,Male
821
+
Norwegian Forest,14,3,Red,Male
822
+
Ocicat,19,5,Tortoiseshell,Male
823
+
Scottish Fold,2,9,Sable,Female
824
+
Ragdoll,12,3,Tabby,Male
825
+
Scottish Fold,19,7,Cream,Male
826
+
American Shorthair,15,8,Brown,Male
827
+
Manx,6,3,Tricolor,Female
828
+
Ocicat,9,6,White,Male
829
+
Manx,19,4,Orange,Female
830
+
Ragdoll,3,5,Orange,Female
831
+
Exotic Shorthair,12,6,White,Male
832
+
Turkish Angora,13,2,Calico,Female
833
+
American Shorthair,12,5,Tabby,Female
834
+
Sphynx,9,6,Tricolor,Female
835
+
Singapura,5,9,Sable,Male
836
+
Bengal,18,5,Brown,Female
837
+
Himalayan,13,9,Cream,Male
838
+
Sphynx,18,5,Calico,Female
839
+
Burmese,11,8,Brown,Female
840
+
Ragdoll,17,6,Tabby,Male
841
+
Sphynx,11,3,Orange,Female
842
+
Birman,12,8,Tricolor,Female
843
+
Bengal,3,3,Tabby,Male
844
+
Singapura,11,8,Blue,Female
845
+
Himalayan,13,6,White,Female
846
+
Cornish Rex,18,5,Bicolor,Female
847
+
Burmese,8,3,Brown,Male
848
+
Maine Coon,17,4,Sable,Female
849
+
American Shorthair,11,2,Blue,Male
850
+
Maine Coon,3,2,Brown,Male
851
+
Turkish Angora,16,4,Black,Female
852
+
Himalayan,12,9,Pointed,Female
853
+
Abyssinian,15,8,Sable,Female
854
+
Chartreux,10,4,Tricolor,Female
855
+
Savannah,4,4,Black,Female
856
+
Turkish Angora,17,5,Blue,Male
857
+
Devon Rex,1,3,Tabby,Female
858
+
Balinese,14,4,Blue,Female
859
+
Maine Coon,7,6,Tortoiseshell,Male
860
+
Himalayan,5,7,Brown,Female
861
+
Exotic Shorthair,4,2,Gray,Male
862
+
Russian Blue,18,2,Sable,Female
863
+
Tonkinese,17,4,Brown,Male
864
+
Turkish Angora,8,3,Black,Male
865
+
Tonkinese,12,4,Tabby,Male
866
+
Persian,3,6,Blue,Female
867
+
Siamese,9,4,White,Female
868
+
Egyptian Mau,9,5,Bicolor,Female
869
+
Cornish Rex,7,9,Orange,Male
870
+
Munchkin,2,8,Orange,Male
871
+
Manx,5,8,Brown,Male
872
+
Turkish Angora,12,8,Cream,Male
873
+
American Shorthair,6,8,Pointed,Male
874
+
Savannah,8,4,Bicolor,Male
875
+
Balinese,11,5,Brown,Female
876
+
Oriental,10,3,Tortoiseshell,Male
877
+
Turkish Angora,19,8,Gray,Male
878
+
Persian,6,7,Brown,Female
879
+
Russian Blue,14,8,Gray,Female
880
+
Tonkinese,16,3,Calico,Female
881
+
Oriental,13,9,Bicolor,Female
882
+
American Shorthair,12,6,Brown,Female
883
+
Bengal,1,3,Orange,Female
884
+
Abyssinian,15,5,Red,Male
885
+
Ragdoll,8,3,Bicolor,Female
886
+
Devon Rex,1,5,Tortoiseshell,Female
887
+
Russian Blue,2,6,Gray,Male
888
+
Sphynx,16,5,Pointed,Male
889
+
Balinese,14,4,Tortoiseshell,Female
890
+
Siberian,12,8,Gray,Male
891
+
Devon Rex,16,3,Bicolor,Male
892
+
Siberian,5,8,Gray,Female
893
+
American Shorthair,11,8,Blue,Male
894
+
Egyptian Mau,8,2,Brown,Male
895
+
Himalayan,11,8,Cream,Female
896
+
Abyssinian,12,4,Calico,Female
897
+
Singapura,1,7,Red,Female
898
+
Himalayan,18,8,Tricolor,Female
899
+
Turkish Angora,13,8,Gray,Male
900
+
Exotic Shorthair,12,5,Gray,Male
901
+
Ragdoll,16,9,Red,Female
902
+
Scottish Fold,3,7,Tricolor,Male
903
+
Balinese,3,7,Brown,Male
904
+
Scottish Fold,11,7,Brown,Male
905
+
Maine Coon,5,4,Pointed,Female
906
+
Cornish Rex,5,4,Black,Female
907
+
Balinese,13,4,Red,Male
908
+
Devon Rex,18,8,Bicolor,Male
909
+
Scottish Fold,17,8,Tortoiseshell,Male
910
+
American Shorthair,16,4,Tricolor,Male
911
+
Munchkin,7,5,Black,Female
912
+
Singapura,7,3,Tabby,Male
913
+
Cornish Rex,17,3,Cream,Male
914
+
Balinese,10,4,Orange,Female
915
+
Devon Rex,14,2,Calico,Male
916
+
Siamese,10,9,Blue,Male
917
+
British Shorthair,3,3,Orange,Male
918
+
Oriental,10,5,Tricolor,Male
919
+
Siamese,15,8,White,Female
920
+
Balinese,5,5,Blue,Male
921
+
Russian Blue,1,3,Sable,Female
922
+
Oriental,11,5,Cream,Male
923
+
Manx,14,5,Calico,Female
924
+
Munchkin,12,6,Bicolor,Male
925
+
Chartreux,3,5,Red,Female
926
+
Bengal,6,3,Tortoiseshell,Male
927
+
Singapura,8,3,Orange,Male
928
+
Bengal,18,7,Brown,Male
929
+
Oriental,4,7,Sable,Male
930
+
Scottish Fold,16,8,Tricolor,Male
931
+
Russian Blue,3,2,Brown,Female
932
+
Birman,17,4,Tabby,Female
933
+
Maine Coon,19,3,Tortoiseshell,Female
934
+
Sphynx,19,5,Black,Female
935
+
Abyssinian,11,7,White,Male
936
+
Persian,19,7,Sable,Male
937
+
Singapura,4,2,Orange,Female
938
+
Maine Coon,18,6,Tabby,Male
939
+
Singapura,8,9,Orange,Female
940
+
Ragdoll,11,8,Gray,Male
941
+
Manx,15,7,Black,Male
942
+
American Shorthair,6,2,Cream,Female
943
+
Burmese,12,5,Tricolor,Male
944
+
American Shorthair,11,3,Bicolor,Male
945
+
Balinese,11,8,Calico,Male
946
+
Munchkin,10,8,Calico,Female
947
+
Birman,4,2,Sable,Male
948
+
American Shorthair,12,8,Tortoiseshell,Female
949
+
Manx,1,5,Red,Female
950
+
Burmese,7,6,Brown,Female
951
+
Abyssinian,2,7,Cream,Female
952
+
Scottish Fold,13,2,White,Female
953
+
Ocicat,10,9,Cream,Male
954
+
Munchkin,12,5,Brown,Male
955
+
Burmese,4,4,White,Male
956
+
American Shorthair,10,3,Tortoiseshell,Male
957
+
Ragdoll,9,5,Tortoiseshell,Male
958
+
Russian Blue,9,4,Brown,Female
959
+
Egyptian Mau,10,3,Pointed,Male
960
+
Siberian,14,7,Bicolor,Male
961
+
Tonkinese,16,9,Pointed,Female
962
+
Persian,16,3,Tabby,Male
963
+
Scottish Fold,5,3,Blue,Female
964
+
Ragdoll,2,4,Tricolor,Female
965
+
British Shorthair,6,2,Red,Male
966
+
Ocicat,11,7,Sable,Male
967
+
Ragdoll,1,4,Tortoiseshell,Male
968
+
Manx,7,8,Orange,Female
969
+
British Shorthair,4,4,Blue,Female
970
+
Egyptian Mau,17,5,Sable,Female
971
+
Siamese,4,5,White,Male
972
+
American Shorthair,7,6,Cream,Female
973
+
American Shorthair,10,9,Red,Female
974
+
British Shorthair,6,4,Cream,Male
975
+
Balinese,12,4,Orange,Male
976
+
Savannah,18,6,Tortoiseshell,Female
977
+
Tonkinese,12,4,Tortoiseshell,Male
978
+
Persian,4,9,Calico,Female
979
+
Bengal,15,4,Brown,Female
980
+
Siberian,15,6,Tabby,Female
981
+
Oriental,17,6,Blue,Male
982
+
Siamese,4,6,Pointed,Male
983
+
Maine Coon,7,3,White,Female
984
+
Russian Blue,12,5,Calico,Female
985
+
Norwegian Forest,7,6,Bicolor,Female
986
+
Tonkinese,3,3,Red,Female
987
+
Maine Coon,1,9,Sable,Male
988
+
Cornish Rex,2,7,Tabby,Male
989
+
Russian Blue,5,5,White,Male
990
+
Sphynx,14,9,White,Male
991
+
British Shorthair,9,9,White,Male
992
+
Maine Coon,8,6,Tabby,Female
993
+
Scottish Fold,18,9,Red,Male
994
+
British Shorthair,9,7,Tabby,Female
995
+
Exotic Shorthair,6,4,Calico,Female
996
+
British Shorthair,2,4,Tabby,Female
997
+
British Shorthair,19,5,Gray,Female
998
+
British Shorthair,11,2,Bicolor,Female
999
+
Savannah,12,5,Bicolor,Female
1000
+
American Shorthair,8,3,Tortoiseshell,Female
1001
+
Chartreux,11,4,Sable,Female
+55
python/oct15/cats/main.py
+55
python/oct15/cats/main.py
···
1
+
import numpy
2
+
import pandas as pd
3
+
import matplotlib.pyplot as plt
4
+
import seaborn as sns
5
+
6
+
7
+
def loadCatsDatabase():
8
+
df = pd.read_csv('./cats/cats_dataset.csv')
9
+
return df
10
+
11
+
12
+
def avg_age(df: pd.DataFrame, breed: str|None = None) -> float:
13
+
new_df = df
14
+
if breed:
15
+
new_df = df[df["Breed"] == breed]
16
+
return numpy.round(new_df['Age (Years)'].astype(int).sum()/len(new_df['Age (Years)']), 2)
17
+
18
+
def high_age_breed(df: pd.DataFrame) -> tuple[str, float]:
19
+
vals: list[tuple[str, float]] = []
20
+
for breed in df["Breed"].unique():
21
+
vals.append((breed, avg_age(df, str(breed))))
22
+
highest = vals[0]
23
+
for (b, v) in vals[1:]:
24
+
if v > highest[1]:
25
+
highest = (b, v)
26
+
return highest
27
+
28
+
def weight_avg(df: pd.DataFrame) -> float:
29
+
return numpy.round(df["Weight (kg)"].astype(float).sum()/len(df["Weight (kg)"]), 2)
30
+
31
+
def gender_heavier(df: pd.DataFrame) -> str:
32
+
male_avg = weight_avg(df[df["Gender"] == "Male"])
33
+
female_avg = weight_avg(df[df["Gender"] == "Female"])
34
+
return "male" if male_avg > female_avg else "female"
35
+
36
+
def correlation(df: pd.DataFrame) -> bool:
37
+
corr = df["Age (Years)"].corr(df["Weight (kg)"])
38
+
return abs(corr) > 0.9
39
+
40
+
def cat_age_histogram(df: pd.DataFrame) -> None:
41
+
ages = df["Age (Years)"].astype(int)
42
+
_ = sns.histplot(data=ages)
43
+
plt.show()
44
+
45
+
print("Running tests...", end="")
46
+
try:
47
+
df = loadCatsDatabase()
48
+
assert(avg_age(df) == 10.21) # Q1
49
+
assert(high_age_breed(df) == ("Himalayan", 11.67)) # Q2
50
+
assert(gender_heavier(df) == "female") # Q3
51
+
assert(not correlation(df)) # Q4
52
+
cat_age_histogram(df)
53
+
print("Passed!")
54
+
except:
55
+
print("Failed :(")
python/oct15/fast-food-nutrition/Figure_1.png
python/oct15/fast-food-nutrition/Figure_1.png
This is a binary file and will not be displayed.
python/oct15/fast-food-nutrition/Figure_2.png
python/oct15/fast-food-nutrition/Figure_2.png
This is a binary file and will not be displayed.
+6
python/oct15/fast-food-nutrition/README.txt
+6
python/oct15/fast-food-nutrition/README.txt
+516
python/oct15/fast-food-nutrition/fastfood.csv
+516
python/oct15/fast-food-nutrition/fastfood.csv
···
1
+
restaurant,item,calories,cal_fat,total_fat,sat_fat,trans_fat,cholesterol,sodium,total_carb,fiber,sugar,protein,vit_a,vit_c,calcium,salad
2
+
Mcdonalds,Artisan Grilled Chicken Sandwich,380,60,7,2,0,95,1110,44,3,11,37,4,20,20,Other
3
+
Mcdonalds,Single Bacon Smokehouse Burger,840,410,45,17,1.5,130,1580,62,2,18,46,6,20,20,Other
4
+
Mcdonalds,Double Bacon Smokehouse Burger,1130,600,67,27,3,220,1920,63,3,18,70,10,20,50,Other
5
+
Mcdonalds,Grilled Bacon Smokehouse Chicken Sandwich,750,280,31,10,0.5,155,1940,62,2,18,55,6,25,20,Other
6
+
Mcdonalds,Crispy Bacon Smokehouse Chicken Sandwich,920,410,45,12,0.5,120,1980,81,4,18,46,6,20,20,Other
7
+
Mcdonalds,Big Mac,540,250,28,10,1,80,950,46,3,9,25,10,2,15,Other
8
+
Mcdonalds,Cheeseburger,300,100,12,5,0.5,40,680,33,2,7,15,10,2,10,Other
9
+
Mcdonalds,Classic Chicken Sandwich,510,210,24,4,0,65,1040,49,3,6,25,0,4,2,Other
10
+
Mcdonalds,Double Cheeseburger,430,190,21,11,1,85,1040,35,2,7,25,20,4,15,Other
11
+
Mcdonalds,Double Quarter Pounderยฎ with Cheese,770,400,45,21,2.5,175,1290,42,3,10,51,20,6,20,Other
12
+
Mcdonalds,Filet-O-Fishยฎ,380,170,18,4,0,40,640,38,2,5,15,2,0,15,Other
13
+
Mcdonalds,Garlic White Cheddar Burger,620,300,34,13,1.5,95,790,48,3,11,32,10,10,35,Other
14
+
Mcdonalds,Grilled Garlic White Cheddar Chicken Sandwich,530,180,20,7,0,125,1150,48,3,11,42,10,20,35,Other
15
+
Mcdonalds,Crispy Garlic White Cheddar Chicken Sandwich,700,300,34,9,0,85,1190,67,5,11,33,10,15,35,Other
16
+
Mcdonalds,Hamburger,250,70,8,3,0,30,480,31,2,6,13,2,2,4,Other
17
+
Mcdonalds,Lobster Roll,290,50,5,1.5,0,65,630,35,2,3,24,4,6,15,Other
18
+
Mcdonalds,Maple Bacon Dijon 1/4 lb Burger,640,330,36,14,1.5,110,1260,40,3,10,37,6,15,15,Other
19
+
Mcdonalds,Grilled Maple Bacon Dijon Chicken Sandwich,580,190,21,8,0,135,1890,50,3,14,48,4,30,30,Other
20
+
Mcdonalds,Crispy Maple Bacon Dijon Chicken Sandwich,740,310,35,9,0.5,95,1780,69,5,14,39,4,20,290,Other
21
+
Mcdonalds,McChicken,350,130,15,3.5,0,40,600,40,2,5,15,2,2,4,Other
22
+
Mcdonalds,McDouble,380,160,18,8,1,70,840,34,2,7,23,10,2,10,Other
23
+
Mcdonalds,McRib,480,200,22,7,0,80,870,45,2,12,25,2,2,6,Other
24
+
Mcdonalds,Pico Guacamole 1/4 lb Burger,580,300,33,12,1.5,95,920,41,4,7,29,8,15,15,Other
25
+
Mcdonalds,Grilled Pico Guacamole Chicken Sandwich,520,160,18,6,0,115,1540,50,4,12,40,8,25,30,Other
26
+
Mcdonalds,Crispy Pico Guacamole Chicken Sandwich,680,280,32,7,0,80,1430,69,6,12,31,8,15,30,Other
27
+
Mcdonalds,Premium Buttermilk Crispy Chicken Deluxe Sandwich,570,200,23,5,0,60,1050,64,4,11,28,4,10,20,Other
28
+
Mcdonalds,Premium Crispy Chicken Deluxe Sandwich,530,200,22,4,0,45,1000,59,3,13,25,6,10,20,Other
29
+
Mcdonalds,Quarter Pounderยฎ with Cheese,530,240,27,13,1.5,100,1090,41,3,10,31,20,6,15,Other
30
+
Mcdonalds,Signature Sriracha Burger,670,320,35,12,1.5,95,1010,56,4,13,32,20,15,30,Other
31
+
Mcdonalds,Grilled Signature Sriracha Chicken Sandwich,560,180,20,5,0,115,1550,56,4,14,41,20,25,30,Other
32
+
Mcdonalds,Crispy Signature Sriracha Chicken Sandwich,730,300,33,7,0,80,1430,75,5,13,32,20,20,30,Other
33
+
Mcdonalds,Sweet BBQ Bacon 1/4 lb Burger,690,340,37,14,1.5,110,1310,51,3,14,38,6,15,15,Other
34
+
Mcdonalds,Grilled Sweet BBQ Bacon Chicken Sandwich,630,200,22,7,0,135,1930,61,4,18,48,4,30,25,Other
35
+
Mcdonalds,Crispy Sweet BBQ Bacon Chicken Sandwich,800,320,36,9,0.5,95,1820,80,5,18,39,4,20,30,Other
36
+
Mcdonalds,3 piece Buttermilk Crispy Chicken Tenders,370,190,21,3.5,0,70,910,16,0,0,28,0,0,2,Other
37
+
Mcdonalds,4 piece Buttermilk Crispy Chicken Tenders,480,250,28,4.5,0,95,1290,21,0,1,38,0,0,2,Other
38
+
Mcdonalds,6 piece Buttermilk Crispy Chicken Tenders,760,390,44,8,0.5,145,1890,32,1,1,58,0,0,2,Other
39
+
Mcdonalds,10 piece Buttermilk Crispy Chicken Tenders,1210,630,70,12,1,240,3230,52,1,4,94,0,0,4,Other
40
+
Mcdonalds,12 piece Buttermilk Crispy Chicken Tenders,1510,790,88,15,1,295,3770,64,1,2,115,0,2,6,Other
41
+
Mcdonalds,20 piece Buttermilk Crispy Chicken Tenders,2430,1270,141,24,2,475,6080,103,2,3,186,0,2,8,Other
42
+
Mcdonalds,4 Piece Chicken McNuggets,180,100,11,2,0,30,340,11,1,0,10,0,2,0,Other
43
+
Mcdonalds,6 Piece Chicken McNuggets,270,140,16,2.5,0,45,510,16,1,0,15,0,2,0,Other
44
+
Mcdonalds,10 Piece Chicken McNuggets,440,240,27,4.5,0,75,840,26,2,0,24,0,4,2,Other
45
+
Mcdonalds,20 Piece Chicken McNuggets,890,480,53,9,0,145,1680,53,4,0,49,0,8,4,Other
46
+
Mcdonalds,40 piece Chicken McNuggets,1770,960,107,18,0.5,295,3370,105,7,1,98,0,15,6,Other
47
+
Mcdonalds,4 piece Sweet N' Spicy Honey BBQ Glazed Tenders,640,240,27,4,0,105,1780,63,2,35,39,4,15,4,Other
48
+
Mcdonalds,6 piece Sweet N' Spicy Honey BBQ Glazed Tenders,960,360,40,6,0,160,2670,94,3,52,58,4,25,8,Other
49
+
Mcdonalds,10 piece Sweet N' Spicy Honey BBQ Glazed Tenders,1600,600,66,10,0,265,4450,156,5,87,97,8,40,10,Other
50
+
Mcdonalds,Premium Asian Salad w/o Chicken,140,70,7,0.5,0,0,20,13,5,7,7,180,45,10,Other
51
+
Mcdonalds,Premium Asian Salad w/ Grilled Chicken,270,80,9,1,0,80,740,18,5,10,31,180,70,10,Other
52
+
Mcdonalds,Premium Asian Salad w/ Crispy Chicken,490,250,28,8,0,95,1120,28,4,4,33,180,60,15,Other
53
+
Mcdonalds,Premium Bacon Ranch Salad w/o Chicken,190,110,12,5,0,40,660,9,3,3,14,180,50,15,Other
54
+
Mcdonalds,Premium Bacon Ranch Salad w/ Grilled Chicken,320,120,14,6,0,45,1230,9,3,4,42,180,60,15,Other
55
+
Mcdonalds,Premium Bacon Ranch Salad w/ Crispy Chicken,490,250,28,8,0,95,1120,28,4,4,33,180,60,15,Other
56
+
Mcdonalds,Premium Southwest Salad w/o Chicken,220,90,10,3.5,0,15,500,26,6,9,8,180,40,20,Other
57
+
Mcdonalds,Premium Southwest Salad w/ Grilled Chicken,350,100,12,4.5,0,110,1070,27,6,9,37,180,50,20,Other
58
+
Mcdonalds,Premium Southwest Salad w/ Crispy Chicken,520,230,25,6,0,75,960,46,8,9,28,180,40,20,Other
59
+
Chick Fil-A,Chargrilled Chicken Club Sandwich,430,144,16,8,0,85,1120,37,3,7,37,30,40,25,Other
60
+
Chick Fil-A,Chargrilled Chicken Sandwich,310,54,6,2,0,55,820,36,3,7,29,25,40,10,Other
61
+
Chick Fil-A,Chick-n-Slider,270,99,11,2.5,0,45,800,26,1,4,16,NA,0,2,Other
62
+
Chick Fil-A,1 Piece Chick-n-Strips,120,54,6,3,0,25,320,6,0,1,11,0,0,2,Other
63
+
Chick Fil-A,2 Piece Chick-n-Strips,230,108,12,3,0,55,630,13,1,1,22,0,2,4,Other
64
+
Chick Fil-A,3 Piece Chick-n-Strips,350,153,17,3,0,70,940,22,1,3,28,2,2,6,Other
65
+
Chick Fil-A,4 piece Chick-n-Strips,470,207,23,3,0,90,1250,29,1,4,37,2,4,8,Other
66
+
Chick Fil-A,Chicken Deluxe,500,207,23,7,0,75,1590,42,3,6,31,30,10,20,Other
67
+
Chick Fil-A,4 piece Chicken Nuggets,130,54,6,1.5,0,40,490,5,1,0,14,0,2,2,Other
68
+
Chick Fil-A,6 piece Chicken Nuggets,190,81,9,1.5,0,55,730,7,1,0,21,0,4,2,Other
69
+
Chick Fil-A,8 piece Chicken Nuggets,260,110,12,3,0,70,990,9,1,1,28,0,2,4,Other
70
+
Chick Fil-A,12 piece Chicken Nuggets,390,162,18,1.5,0,115,1460,14,2,1,41,0,8,4,Other
71
+
Chick Fil-A,30 piece Chicken Nuggets,970,414,46,2.5,0,285,3660,35,4,1,103,NA,20,10,Other
72
+
Chick Fil-A,Chicken Salad Sandwich,490,170,19,3,0,80,1130,55,5,12,28,35,8,15,Other
73
+
Chick Fil-A,Chicken Sandwich,440,171,19,4,0,60,1350,40,2,5,28,2,4,15,Other
74
+
Chick Fil-A,4 Piece Grilled Chicken Nuggets,70,18,2,1,0,35,220,1,0,0,13,0,6,0,Other
75
+
Chick Fil-A,6 Piece Grilled Chicken Nuggets,110,27,3,1,0,50,330,2,0,0,19,0,8,0,Other
76
+
Chick Fil-A,8 piece Grilled Chicken Nuggets,140,36,4,1,0,70,440,2,0,0,25,0,10,2,Other
77
+
Chick Fil-A,12 Piece Grilled Chicken Nuggets,210,45,5,1,0,100,670,3,0,1,38,0,20,2,Other
78
+
Chick Fil-A,Spicy Grilled Chicken Sub Sandwich,430,108,12,4.5,0,85,1310,47,5,9,33,NA,25,25,Other
79
+
Chick Fil-A,Regular Grilled Chicken Sub Sandwich,450,117,13,6,0,75,1000,48,4,10,34,NA,50,25,Other
80
+
Chick Fil-A,Smokehouse BBQ Bacon Sandwich,500,162,18,0,0,95,1200,46,2,10,33,45,40,20,Other
81
+
Chick Fil-A,Spicy Chicken Sandwich,450,171,19,4,0,60,1620,41,1,5,29,4,2,15,Other
82
+
Chick Fil-A,Spicy Deluxe,540,225,25,8,0,80,1760,43,2,6,34,30,10,30,Other
83
+
Chick Fil-A,Chargrilled Chicken Cool Wrap,350,126,14,5,0,60,960,29,15,3,37,60,35,35,Other
84
+
Chick Fil-A,Chicken Enchiladas Meal Kit,860,423,47,16,1,100,2520,70,NA,8,39,NA,NA,NA,Other
85
+
Chick Fil-A,Chicken Parmesan Meal Kit,720,279,31,15,0,120,1780,65,NA,7,48,NA,NA,NA,Other
86
+
Sonic,Hatch Green Chile Cheeseburger,710,380,43,17,2,120,1120,44,2,7,35,10,25,30,Other
87
+
Sonic,Jalapeno Burger,640,330,37,14,2,100,930,42,2,6,31,4,2,20,Other
88
+
Sonic,Jr. Burger,340,150,17,6,1,35,640,34,1,6,15,2,4,6,Other
89
+
Sonic,Jr. Chili Cheeseburger,410,220,24,9,0.5,55,730,32,1,4,20,7,1,15,Other
90
+
Sonic,Jr. Deluxe Burger,380,200,23,6,1,40,470,32,1,4,15,2,4,6,Other
91
+
Sonic,Jr. Deluxe Cheeseburger,450,250,28,9,1,60,800,33,1,4,19,6,4,15,Other
92
+
Sonic,Jr. Double Cheeseburger,600,350,38,16,2,110,1350,35,1,7,31,15,4,25,Other
93
+
Sonic,Sonic Bacon Cheeseburger (w/mayo),870,530,59,20,2,140,1350,45,2,7,39,10,8,30,Other
94
+
Sonic,Sonic Burger W/ Mustard,640,330,37,14,2,100,790,43,2,7,31,6,8,20,Other
95
+
Sonic,Sonic Burger W/ Ketchup,650,340,37,14,2,100,860,46,2,10,32,8,10,20,Other
96
+
Sonic,Sonic Burger W/ Mayonnaise,740,430,48,15,2,110,760,44,2,7,31,6,8,20,Other
97
+
Sonic,Sonic Cheeseburger W/ Mustard,710,380,43,17,2,120,1120,43,2,7,35,10,8,30,Other
98
+
Sonic,Sonic Cheeseburger W/ Ketchup,720,380,43,17,2,120,1190,47,2,10,35,15,10,30,Other
99
+
Sonic,Sonic Cheeseburger W/ Mayonnaise,800,480,54,18,2,130,1090,44,2,7,35,10,8,30,Other
100
+
Sonic,Super Sonic Bacon Double Cheeseburger (w/mayo),1280,830,92,36,4,260,1630,44,2,7,67,15,6,40,Other
101
+
Sonic,Super Sonic Double Cheeseburger W/ Mustard,1120,680,76,32,4,235,1550,44,2,8,63,15,8,40,Other
102
+
Sonic,Super Sonic Double Cheeseburger W/ Ketchup,1130,680,76,32,4,235,1620,47,2,11,63,20,10,40,Other
103
+
Sonic,Super Sonic Double Cheeseburger W/ Mayo,1220,780,87,34,4,245,1520,45,2,8,63,15,8,40,Other
104
+
Sonic,Super Sonic Jalapeno Double Cheeseburger,1120,680,76,32,4,235,1690,43,2,7,63,15,2,40,Other
105
+
Sonic,Veggie Burger W/ Ketchup,450,130,14,4,0,10,1410,67,5,11,15,6,8,25,Other
106
+
Sonic,Veggie Burger With Mustard,450,130,14,4,0,10,1350,64,5,8,15,6,8,27,Other
107
+
Sonic,Veggie Burger W/ Mustard,450,130,14,4,0,10,1300,64,5,8,15,6,8,25,Other
108
+
Sonic,Grilled Asiago Caesar Chicken Club Sandwich,610,270,30,7,0,110,1570,44,3,8,40,11,20,16,Other
109
+
Sonic,Crispy Asiago Caesar Chicken Club Sandwich,680,350,39,9,0,80,1120,53,4,7,31,11,7,16,Other
110
+
Sonic,Grilled Chicken Sandwich,430,180,20,4,0,80,940,33,2,6,28,6,8,10,Other
111
+
Sonic,Crispy Chicken Sandwich,570,300,33,5,0,45,1060,47,4,6,23,6,8,10,Other
112
+
Sonic,Chicken Strip Sandwich,450,220,24,4,0,35,740,43,1,4,19,0,0,4,Other
113
+
Sonic,3 Piece Crispy Chicken Tender Dinner,280,130,14,2.5,0,0,800,16,0,0,22,NA,NA,NA,Other
114
+
Sonic,5 Piece Crispy Chicken Tender Dinner,470,220,24,4.5,0,0,1340,26,0,0,37,NA,NA,NA,Other
115
+
Sonic,Deluxe Ultimate Chicken Sandwich,740,350,39,8,0,90,1550,63,4,12,33,10,8,15,Other
116
+
Sonic,Buffalo Dunked Ultimate Chicken Sandwich,1000,550,61,12,0.5,125,4520,70,5,12,23,NA,NA,NA,Other
117
+
Sonic,Garlic Parmesan Dunked Ultimate Chicken Sandwich,1350,900,100,17,0,190,2180,69,4,10,23,NA,NA,NA,Other
118
+
Sonic,Small Jumbo Popcorn Chicken,380,190,22,4,0,45,1250,27,3,1,18,0,0,2,Other
119
+
Sonic,Large Jumbo Popcorn Chicken,560,290,32,6,1,65,1890,41,5,2,27,0,0,4,Other
120
+
Sonic,Small Spicy Jumbo Popcorn Chicken,350,150,17,3,0,45,860,30,2,0,21,10,0,2,Other
121
+
Sonic,Large Spicy Jumbo Popcorn Chicken,610,270,30,5,0,80,1500,51,3,0,36,17,0,3,Other
122
+
Sonic,3 Piece Super Crunch Chicken Strip Dinner,970,410,46,8,1,55,2160,109,7,9,30,1,6,13,Other
123
+
Sonic,4 Piece Super Crunch Chicken Strip Dinner,1080,460,51,9,1,75,2390,118,8,9,37,1,7,13,Other
124
+
Sonic,5 Piece Super Crunch Chicken Strip Dinner,1190,510,57,10,1,90,2610,126,8,9,44,2,8,14,Other
125
+
Sonic,3 Piece Super Crunch Chicken Strips,330,140,16,3,0,55,670,25,2,0,22,1,2,1,Other
126
+
Sonic,4 Piece Super Crunch Chicken Strips,440,190,21,4,0,70,900,34,2,1,29,1,2,1,Other
127
+
Sonic,5 Piece Super Crunch Chicken Strips,550,240,26,5,0,90,1120,42,3,1,36,1,3,2,Other
128
+
Sonic,Traditional Ultimate Chicken Sandwich,730,350,39,8,0,90,1540,62,3,11,32,4,2,15,Other
129
+
Sonic,Ultimate Chicken Club,100,580,64,15,0.5,100,2070,65,4,12,39,15,8,30,Other
130
+
Sonic,"All Beef All-american Style Dog โ 6""",370,160,18,7,0,40,1180,40,1,15,12,2,4,8,Other
131
+
Sonic,"All Beef Chicago Dog โ 6""",430,180,20,7,0,40,2310,49,1,17,14,4,6,10,Other
132
+
Sonic,"All Beef Chili Cheese Coney โ 6""",410,230,26,11,0,65,1140,30,2,4,17,10,2,20,Other
133
+
Sonic,"All Beef New York Dog โ 6""",340,170,19,7,0,40,1250,30,3,4,13,2,10,8,Other
134
+
Sonic,"All Beef Regular Hot Dog โ 6""",320,160,18,7,0,40,870,27,1,3,11,0,2,8,Other
135
+
Sonic,Cheesy Bacon Pretzel Dog - 6 In.,500,240,26,10,0,50,1410,46,2,7,15,1,3,8,Other
136
+
Sonic,Corn Dog,210,100,11,4,0,20,530,23,2,4,6,0,0,4,Other
137
+
Sonic,Footlong Quarter Pound Coney,830,490,54,22,1,85,1940,54,3,9,30,15,4,30,Other
138
+
Sonic,The Original Pretzel Dog,320,160,18,7,0,35,910,27,1,2,11,0,0,4,Other
139
+
Arbys,Arby's Melt,330,100,11,4,0,30,920,40,2,5,18,2,0,8,Other
140
+
Arbys,Arby-Q Sandwich,400,90,10,3,0,30,1230,58,3,23,18,4,10,10,Other
141
+
Arbys,Beef 'n Cheddar Classic,450,180,20,6,1,50,1280,45,2,9,23,2,2,15,Other
142
+
Arbys,Beef 'n Cheddar Mid,630,290,32,11,1.5,100,2100,48,2,9,39,2,2,15,Other
143
+
Arbys,Bourbon BBQ Brisket Sandwich,650,300,33,12,1,105,1460,51,2,15,38,NA,NA,NA,Other
144
+
Arbys,Bourbon BBQ Chicken Sandwich,690,280,31,9,0,90,1990,66,3,16,38,NA,NA,NA,Other
145
+
Arbys,Bourbon BBQ Steak Sandwich,690,280,31,9,0,90,1990,66,3,16,38,NA,NA,NA,Other
146
+
Arbys,Buttermilk Buffalo Chicken Sandwich,540,220,24,4.5,0,60,2110,53,2,6,29,NA,NA,NA,Other
147
+
Arbys,Buttermilk Chicken Bacon & Swiss,650,280,31,9,0,90,1750,56,2,9,39,NA,NA,NA,Other
148
+
Arbys,Buttermilk Chicken Cordon Bleu Sandwich,690,310,35,10,0,110,2000,53,1,7,41,NA,NA,NA,Other
149
+
Arbys,Buttermilk Crispy Chicken Sandwich,550,230,26,4.5,0,60,1480,52,2,6,29,NA,NA,NA,Other
150
+
Arbys,Classic French Dip & Swiss/Au Jus,540,210,23,11,1,85,2500,50,2,3,35,2,8,15,Other
151
+
Arbys,Classic Roast Beef,360,120,14,5,0.5,50,970,37,2,5,23,0,0,6,Other
152
+
Arbys,Double Roast Beef,510,210,24,9,1.5,95,1610,38,2,5,38,0,0,6,Other
153
+
Arbys,Fire-Roasted Philly Steak,640,290,32,11,0.5,105,1950,46,3,4,42,NA,NA,NA,Other
154
+
Arbys,Grand Turkey Club,480,220,24,7,0,65,1610,37,2,9,30,15,10,15,Other
155
+
Arbys,Greek Gyro,710,390,44,13,0,75,1360,55,4,6,23,NA,NA,NA,Other
156
+
Arbys,Half Pound Beef 'n Cheddar Sandwich,740,350,39,14,2,130,2530,48,2,9,49,NA,NA,NA,Other
157
+
Arbys,Half Pound French Dip & Swiss,750,330,36,17,2,150,3350,51,2,3,55,NA,NA,NA,Other
158
+
Arbys,Half Pound Roast Beef Sandwich,610,270,30,12,2,130,2040,38,2,5,48,NA,NA,NA,Other
159
+
Arbys,Ham & Swiss Melt,300,80,9,4,0,35,1030,37,2,6,18,2,0,15,Other
160
+
Arbys,Loaded Italian Sandwich,680,360,40,14,0.5,100,2270,49,3,7,32,NA,NA,NA,Other
161
+
Arbys,Pecan Chicken Salad Flatbread,710,410,46,7,0.5,65,980,53,4,9,22,NA,NA,NA,Other
162
+
Arbys,Pecan Chicken Salad Sandwich,840,400,44,6,0.5,75,1210,81,6,20,33,10,8,25,Other
163
+
Arbys,2 piece Prime-Cut Chicken Tenders,240,100,11,1.5,0,30,640,19,1,0,16,0,0,2,Other
164
+
Arbys,3 piece Prime-Cut Chicken Tenders,360,150,17,2.5,0,45,950,28,2,0,23,0,4,2,Other
165
+
Arbys,5 piece Prime-Cut Chicken Tenders,600,250,28,4,0,75,1590,47,3,0,39,0,8,2,Other
166
+
Arbys,Reuben Sandwich,680,280,31,8,0.5,80,2420,62,4,5,37,6,20,35,Other
167
+
Arbys,Roast Beef Gyro,550,260,29,7,1,60,1290,48,3,5,24,10,15,10,Other
168
+
Arbys,Roast Turkey & Swiss Sandwich,710,260,28,7,0,65,1930,79,5,15,38,20,10,45,Other
169
+
Arbys,Roast Turkey & Swiss Wrap,520,240,27,9,0,65,1640,39,4,6,30,20,10,35,Other
170
+
Arbys,"Roast Turkey, Ranch & Bacon Sandwich",800,310,34,10,0.5,80,2420,79,5,16,45,20,10,45,Other
171
+
Arbys,"Roast Turkey, Ranch & Bacon Wrap",620,310,34,11,0.5,85,2130,39,4,6,37,20,10,30,Other
172
+
Arbys,Smoke Mountain w/ Beef Short Rib,740,320,35,13,1,125,2050,62,4,17,43,NA,NA,NA,Other
173
+
Arbys,Smokehouse Beef Short Rib Sandwich,590,250,59,10,1,75,1510,59,4,14,26,NA,NA,NA,Other
174
+
Arbys,Smokehouse Brisket,600,310,35,12,1,110,1240,42,2,7,33,4,8,20,Other
175
+
Arbys,Super Roast Beef,430,160,17,5,1,45,1060,45,3,11,23,10,10,8,Other
176
+
Arbys,Three Cheese Steak Sandwich,650,320,36,15,1,115,1760,44,2,9,30,NA,NA,NA,Other
177
+
Arbys,Triple Decker Sandwich,1030,459,51,17,1,155,2940,83,5,19,62,NA,NA,NA,Other
178
+
Arbys,Turkey Avocado Club,730,252,28,6,0,65,2140,80,6,16,41,NA,NA,NA,Other
179
+
Arbys,Turkey Gyro,470,180,20,3.5,0,45,1520,48,3,5,25,10,15,10,Other
180
+
Arbys,Ultimate BLT,980,495,55,14,0,85,2130,80,6,19,43,NA,NA,NA,Other
181
+
Arbys,Buffalo Chicken Slider,290,120,13,2,0,20,860,31,2,2,12,NA,NA,NA,Other
182
+
Arbys,Chicken Tender 'n Cheese Slider,290,110,12,3.5,0,25,720,30,1,1,15,NA,NA,NA,Other
183
+
Arbys,Corned Beef 'n Cheese Slider,220,80,9,3.5,0,30,890,21,1,1,14,NA,NA,NA,Other
184
+
Arbys,Ham 'n Cheese Slider,210,70,8,3,0,25,780,21,1,2,13,NA,NA,NA,Other
185
+
Arbys,Jalapeno Roast Beef 'n Cheese Slider,240,90,11,4.5,0,30,670,21,1,1,14,NA,NA,NA,Other
186
+
Arbys,Pizza Slider,300,150,17,6,0,35,930,23,1,2,13,NA,NA,NA,Other
187
+
Arbys,Roast Beef 'n Cheese Slider,240,90,11,4.5,0,30,670,21,1,1,14,NA,NA,NA,Other
188
+
Arbys,Turkey 'n Cheese Slider,200,60,7,2.5,0,25,760,21,1,2,14,NA,NA,NA,Other
189
+
Arbys,Chopped Side Salad,70,45,5,2.5,0,15,100,4,1,2,5,35,10,10,Other
190
+
Arbys,Crispy Chicken Farmhouse Salad,430,220,24,8,0,65,1000,26,4,4,28,60,20,25,Other
191
+
Arbys,Greek Gyro Salad,420,340,37,9,0,55,700,11,2,4,10,NA,NA,NA,Other
192
+
Arbys,Roast Turkey Farmhouse Salad,230,120,13,7,0,55,870,8,2,5,22,60,20,25,Other
193
+
Arbys,Super Greek Salad,720,480,53,15,0,85,1310,39,5,7,22,NA,NA,NA,Other
194
+
Burger King,American Brewhouse King,1550,1134,126,47,8,805,1820,21,3,7,134,NA,NA,NA,Other
195
+
Burger King,Bacon & Swiss Sourdough King,1000,585,65,24,3,200,1320,48,2,8,56,NA,NA,NA,Other
196
+
Burger King,Bacon Cheeseburger,330,140,16,7,0,55,830,32,1,7,18,NA,NA,NA,Other
197
+
Burger King,Bacon Cheeseburger Deluxe,290,120,14,6,0.5,40,720,28,1,7,12,NA,NA,NA,Other
198
+
Burger King,Bacon King,1040,630,48,28,2.5,220,1900,48,1,10,57,NA,NA,NA,Other
199
+
Burger King,Bacon King Jr,730,351,39,9,0,90,1930,63,0,16,32,NA,NA,NA,Other
200
+
Burger King,BBQ Bacon King,1100,675,75,29,3,220,1850,51,NA,13,57,NA,NA,NA,Other
201
+
Burger King,Cheeseburger,300,130,14,6,0,45,710,28,1,6,16,NA,NA,NA,Other
202
+
Burger King,Double Bacon Cheeseburger,520,280,31,14,1,105,1180,33,1,8,31,NA,NA,NA,Other
203
+
Burger King,Double Cheeseburger,450,230,26,12,1,95,960,29,1,6,26,NA,NA,NA,Other
204
+
Burger King,Double Hamburger,360,160,18,8,0,70,520,28,1,6,22,NA,NA,NA,Other
205
+
Burger King,Double Quarter Pound King,900,486,54,25,3,210,1740,50,2,11,56,NA,NA,NA,Other
206
+
Burger King,Extra Long Cheeseburger,580,300,33,13,1.5,85,1030,45,2,9,26,NA,NA,NA,Other
207
+
Burger King,Farmhouse King,1220,720,80,28,3,335,2050,62,NA,15,NA,NA,NA,NA,Other
208
+
Burger King,Hamburger,260,90,10,4,0,35,490,28,1,6,13,NA,NA,NA,Other
209
+
Burger King,Homestyle Cheeseburger,550,250,27,12,1.5,95,1140,48,2,10,30,NA,NA,NA,Other
210
+
Burger King,Jalapeno King Sandwich,990,585,65,24,3,205,1550,46,2,7,55,NA,NA,NA,Other
211
+
Burger King,Mushroom & Swiss King,940,567,63,21,2.5,175,1380,45,NA,8,49,NA,NA,NA,Other
212
+
Burger King,Rodeo Burger,310,110,13,4,0.5,25,450,38,1,9,9,NA,NA,NA,Other
213
+
Burger King,Rodeo King,1250,738,82,31,3.5,230,2270,69,3,14,60,NA,NA,NA,Other
214
+
Burger King,Sourdough King Single,730,387,43,16,1.5,125,1570,52,2,12,35,NA,NA,NA,Other
215
+
Burger King,Sourdough King Double,970,549,61,24,3,205,1640,52,2,12,55,NA,NA,NA,Other
216
+
Burger King,Steakhouse King,1100,666,74,24,1,180,1620,59,NA,13,50,NA,NA,NA,Other
217
+
Burger King,Bacon & Cheese Whopper,770,432,48,16,2,95,1360,47,2,9,29,NA,NA,NA,Other
218
+
Burger King,DOUBLE WHOPPER w/o Cheese,900,510,57,19,2,140,1050,51,3,11,47,NA,NA,NA,Other
219
+
Burger King,DOUBLE WHOPPER w/ Cheese,990,580,65,24,2,160,1480,53,3,11,52,NA,NA,NA,Other
220
+
Burger King,WHOPPER w/o Cheese,660,360,40,12,1.5,90,980,49,2,11,28,NA,NA,NA,Other
221
+
Burger King,WHOPPER w/ Cheese,760,430,47,16,1,100,1410,53,3,11,33,NA,NA,NA,Other
222
+
Burger King,WHOPPER JR. w/o Cheese,340,170,19,5,0,40,510,28,2,6,14,NA,NA,NA,Other
223
+
Burger King,WHOPPER JR. w/ Cheese,380,210,23,8,1,55,730,29,2,6,16,NA,NA,NA,Other
224
+
Burger King,Bacon Cheddar Ranch Chicken Salad w/ grilled Chicken & Dressing,590,360,40,12,0,150,1540,18,3,6,42,NA,NA,NA,Other
225
+
Burger King,Bacon Cheddar Ranch Chicken Salad w/ crispy Chicken & Dressing,720,450,50,13,0,120,1960,32,5,7,36,NA,NA,NA,Other
226
+
Burger King,Chicken BLT Salad w/ Grilled Chicken,550,330,37,10,0,115,1640,17,3,5,36,NA,NA,NA,Other
227
+
Burger King,Chicken BLT Salad w/ Crispy Chicken,690,430,48,12,1,100,1750,31,4,8,35,NA,NA,NA,Other
228
+
Burger King,Chicken Caesar Salad w/ Grilled Chicken,530,290,32,5,0,95,1640,26,3,6,35,NA,NA,NA,Other
229
+
Burger King,Chicken Caesar Salad w/ Crispy Chicken,670,380,43,7,0,80,1760,40,5,8,34,NA,NA,NA,Other
230
+
Burger King,"Chicken, Apple & Cranberry Salad w/ Grilled Chicken",560,270,30,7,0,90,980,40,4,34,29,NA,NA,NA,Other
231
+
Burger King,"Chicken, Apple & Cranberry Salad w/ Crispy Chicken",700,370,41,9,0,80,1090,54,5,37,28,NA,NA,NA,Other
232
+
Burger King,"Garden Grilled Chicken Salad w/ Grilled Chicken, no dressing",320,120,14,6,0,115,650,16,2,4,36,NA,NA,NA,Other
233
+
Burger King,"Garden Grilled Chicken Salad w/ Crispy Chicken, no dressing",450,220,24,7,0,85,1070,30,5,6,29,NA,NA,NA,Other
234
+
Burger King,Side Caesar Salad with dressing,220,180,20,4,0,10,540,7,2,3,6,NA,NA,NA,Other
235
+
Burger King,Side Garden Salad and Avocado Ranch Dressing,230,190,21,5,0,30,520,7,2,3,5,NA,NA,NA,Other
236
+
Burger King,Bacon Cheddar Ranch Crispy Chicken Sandwich,830,468,52,14,0.5,110,2100,57,NA,9,34,NA,NA,NA,Other
237
+
Burger King,BBQ Bacon Crispy Chicken Sandwich,440,243,27,4.5,0,15,630,44,NA,13,7,NA,NA,NA,Other
238
+
Burger King,Big Fish Sandwich,530,250,27,4.5,0,30,1360,54,2,7,17,NA,NA,NA,Other
239
+
Burger King,BK VEGGIE Burger,410,150,16,3,0,5,1030,44,7,8,22,NA,NA,NA,Other
240
+
Burger King,Chicken Burger,480,220,25,2.5,0,5,1160,42,2,10,22,NA,NA,NA,Other
241
+
Burger King,Chicken Cordon Bleu Sandwich,730,351,39,9,0,90,1930,63,NA,16,32,NA,NA,NA,Other
242
+
Burger King,Chicken Fries,290,150,17,3,1.5,40,780,18,1,1,16,NA,NA,NA,Other
243
+
Burger King,4 Piece Chicken Nuggets,190,100,11,2,0,25,310,10,1,0,10,NA,NA,NA,Other
244
+
Burger King,6 Piece Chicken Nuggets,290,150,17,3,0,40,460,15,1,0,15,NA,NA,NA,Other
245
+
Burger King,20 Piece Chicken Nuggets,950,500,55,11,0,130,1530,50,5,0,51,NA,NA,NA,Other
246
+
Burger King,Chicken Nuggets (10pc),470,260,29,5,0,50,890,34,5,0,21,NA,NA,NA,Other
247
+
Burger King,Chicken Parmesan Sandwich,570,225,25,8,0,70,1340,57,NA,9,32,NA,NA,NA,Other
248
+
Burger King,Crispy Buffalo Chicken Melt,580,252,28,8,0.5,70,2310,56,NA,8,30,NA,NA,NA,Other
249
+
Burger King,Crispy Chicken Jr.,430,250,28,4.5,0,30,760,34,2,4,12,NA,NA,NA,Other
250
+
Burger King,Crispy Chicken Sandwich,670,370,41,7,0.5,60,1070,54,2,7,23,NA,NA,NA,Other
251
+
Burger King,Grilled Chicken Sandwich,470,170,19,3.5,0,85,850,39,2,6,37,NA,NA,NA,Other
252
+
Burger King,Grilled Chili Cheese Dog,330,170,19,8,1,40,980,28,2,5,14,NA,NA,NA,Other
253
+
Burger King,Grilled Hot Dog,310,140,16,6,1,30,960,32,2,10,11,NA,NA,NA,Other
254
+
Burger King,Jalapeno Chicken Fries,300,160,18,3,0,40,950,19,1,1,15,NA,NA,NA,Other
255
+
Burger King,Original Chicken Sandwich,630,350,39,7,1,65,1390,46,3,4,24,NA,NA,NA,Other
256
+
Burger King,Pretzel Chicken Fries,340,189,21,3.5,0,45,1200,21,1,1,16,NA,NA,NA,Other
257
+
Burger King,Rodeo Crispy Chicken Sandwich,410,150,17,3,0,20,870,53,2,14,12,NA,NA,NA,Other
258
+
Burger King,Sourdough Chicken Club,840,459,51,12,1,95,1760,62,3,7,32,NA,NA,NA,Other
259
+
Burger King,4 Piece Spicy Chicken Nuggets,210,135,15,3,0,20,570,11,2,0,8,NA,NA,NA,Other
260
+
Burger King,Spicy Chicken Nuggets,530,333,37,7,0,55,1420,28,NA,0,20,NA,NA,NA,Other
261
+
Burger King,Spicy Crispy Chicken Jr.,410,220,25,4.5,0,35,850,35,2,5,12,NA,NA,NA,Other
262
+
Burger King,Spicy Crispy Chicken Sandwich,700,378,42,7,0,65,1140,57,3,8,25,NA,NA,NA,Other
263
+
Burger King,Spicy Crispy Jalapeno Chicken Sandwich,760,405,45,11,0,95,1720,58,3,8,32,NA,NA,NA,Other
264
+
Dairy Queen,1/2 lb. FlameThrowerยฎ GrillBurger,1000,660,74,26,2,170,1610,40,2,9,46,25,8,30,Other
265
+
Dairy Queen,1/2 lb. GrillBurger with Cheese,800,460,51,20,2,135,1280,44,3,13,40,25,6,35,Other
266
+
Dairy Queen,1/4 lb. Bacon Cheese GrillBurger,630,330,37,13,1,95,1250,44,2,13,30,20,6,25,Other
267
+
Dairy Queen,1/4 lb. GrillBurger with Cheese,540,270,30,11,1,70,1020,44,3,13,23,20,6,25,Other
268
+
Dairy Queen,1/4 lb. Mushroom Swiss GrillBurger,570,310,35,11,1,75,820,39,2,8,24,2,0,25,Other
269
+
Dairy Queen,Original Cheeseburger,400,160,18,9,1,65,930,34,1,8,19,10,0,10,Other
270
+
Dairy Queen,Original Double Cheeseburger,630,310,34,18,2,125,1240,34,1,9,34,15,0,20,Other
271
+
Dairy Queen,4 Piece Chicken Strip Basket w/ Country Gravy,1030,480,53,9,1,80,2780,105,9,4,35,2,0,10,Other
272
+
Dairy Queen,6 Piece Chicken Strip Basket w/ Country Gravy,1260,590,66,11,1,120,3500,121,12,4,49,2,0,10,Other
273
+
Dairy Queen,Bacon Cheese Dog,420,240,26,11,1,60,1140,26,1,3,19,NA,NA,NA,Other
274
+
Dairy Queen,Cheese Dog,390,220,24,11,1,50,1000,26,1,3,16,NA,NA,NA,Other
275
+
Dairy Queen,Chili Cheese Dog,380,220,24,11,1,55,900,23,1,3,16,10,0,15,Other
276
+
Dairy Queen,Chili Dog,330,180,20,8,1,40,1050,24,1,5,13,8,0,6,Other
277
+
Dairy Queen,Hot Dog,290,160,17,7,1,35,900,22,1,4,11,4,0,6,Other
278
+
Dairy Queen,Relish Dog,350,180,20,8,1,35,1000,30,1,6,13,NA,NA,NA,Other
279
+
Dairy Queen,Barbecue Pork Sandwich,310,80,9,3,0,50,830,41,2,9,17,10,4,4,Other
280
+
Dairy Queen,Breaded Mushrooms,250,80,9,1,0,0,500,36,2,1,7,0,2,2,Other
281
+
Dairy Queen,Regular Cheese Curds,550,410,45,25,0,150,900,0,0,0,35,30,0,100,Other
282
+
Dairy Queen,Large Cheese Curds,1050,670,75,43,1,180,2210,52,0,30,43,NA,NA,NA,Other
283
+
Dairy Queen,Chili Cheese Mega Dog,760,440,49,21,2,100,1570,48,2,6,32,NA,NA,NA,Other
284
+
Dairy Queen,Corn Dog,260,140,15,4,0,20,450,26,1,7,6,0,4,0,Other
285
+
Dairy Queen,Crispy Fish Sandwich,470,200,22,3,0,20,1210,53,2,7,17,10,2,6,Other
286
+
Dairy Queen,Deluxe Cheeseburger,400,160,18,9,1,65,930,35,1,9,20,20,6,10,Other
287
+
Dairy Queen,Deluxe Double Cheeseburger,640,310,34,18,2,125,1240,35,1,9,34,25,6,20,Other
288
+
Dairy Queen,Deluxe Double Hamburger,540,240,26,13,1,100,750,34,1,9,29,15,6,4,Other
289
+
Dairy Queen,Deluxe Hamburger,350,130,14,7,1,50,680,34,1,9,17,15,6,4,Other
290
+
Dairy Queen,DQ Ultimateยฎ Burger,780,430,48,22,2,150,1390,34,1,7,41,20,6,20,Other
291
+
Dairy Queen,Pork Tenderloin Sandwich,580,310,34,7,0,45,910,48,2,6,19,NA,NA,NA,Other
292
+
Dairy Queen,Steak Finger Basket,910,430,48,13,0.5,45,2210,95,5,2,23,NA,NA,NA,Other
293
+
Dairy Queen,3 chicken strips Chicken Strips,350,180,20,3,0,60,960,22,10,0,22,NA,NA,NA,Other
294
+
Dairy Queen,Chicken Bacon Ranch Sandwich,500,180,20,8,0,65,1190,45,3,3,33,NA,NA,NA,Other
295
+
Dairy Queen,Chicken Mozzarella Sandwich,640,220,25,8,0,60,1530,68,4,3,34,NA,NA,NA,Other
296
+
Dairy Queen,Crispy Chicken BLT Salad,520,280,31,10,0,100,1470,25,9,6,37,NA,NA,NA,Other
297
+
Dairy Queen,Crispy Chicken Garden Greens Salad,280,120,13,2,0,40,670,24,9,6,17,NA,NA,NA,Other
298
+
Dairy Queen,Crispy Chicken Sandwich,600,270,30,5,0,55,1250,59,7,8,24,10,6,15,Other
299
+
Dairy Queen,Crispy Chicken Wrap,350,190,21,5,0,35,820,30,2,1,12,10,2,10,Other
300
+
Dairy Queen,Grilled Chicken BLT Salad,380,170,19,9,0,100,1540,11,3,6,42,NA,NA,NA,Other
301
+
Dairy Queen,Grilled Chicken Garden Greens Salad,150,20,2,0.5,0,40,730,10,3,6,23,NA,NA,NA,Other
302
+
Dairy Queen,Grilled Chicken Sandwich,360,140,15,3,0,50,1040,32,1,5,25,10,8,6,Other
303
+
Dairy Queen,Grilled Chicken Wrap,280,130,15,4,0,30,800,22,1,1,15,10,4,10,Other
304
+
Dairy Queen,Side Salad,20,0,0,0,0,0,15,5,2,3,1,50,30,15,Other
305
+
Dairy Queen,Turkey BLT Sandwich,550,240,26,8,0,60,1420,45,3,3,30,NA,NA,NA,Other
306
+
Subway,"6"" B.L.T.",320,80,9,4,0,20,680,43,5,6,15,8,8,30,Other
307
+
Subway,Footlong B.L.T.,640,160,18,8,0,40,1360,86,10,12,30,16,16,60,Other
308
+
Subway,"6"" BBQ Rib Sandwich",430,160,18,6,0,50,590,47,5,8,19,8,20,30,Other
309
+
Subway,Footlong BBQ Rib Sandwich,860,320,36,12,0,100,1180,94,10,16,38,16,40,60,Other
310
+
Subway,"6"" Big Hot Pastrami",580,310,31,11,0,85,1470,47,5,7,29,10,45,40,Other
311
+
Subway,Footlong Big Hot Pastrami,1160,620,62,22,0,170,2940,94,10,14,58,20,90,80,Other
312
+
Subway,"6"" Big Philly Cheesesteak",500,150,17,9,1,85,1310,51,6,8,38,15,20,50,Other
313
+
Subway,Footlong Big Philly Cheesesteak,1000,300,34,18,2,170,2620,102,12,16,76,30,40,100,Other
314
+
Subway,Kids Mini Sub Black Forest Ham,180,20,3,0.5,0,10,450,30,3,5,10,6,15,20,Other
315
+
Subway,"6"" Black Forest Ham",290,40,5,1,0,20,830,46,5,8,18,8,20,30,Other
316
+
Subway,Footlong Black Forest Ham,580,80,10,2,0,40,1660,92,10,16,36,16,40,60,Other
317
+
Subway,"6"" Carved Turkey",330,45,5,1,0,45,890,45,5,7,25,8,20,30,Other
318
+
Subway,Footlong Carved Turkey,660,90,10,2,0,90,1780,90,10,14,50,16,40,60,Other
319
+
Subway,"6"" Carved Turkey & Bacon w/ Cheese",570,230,26,7,0,70,1600,46,5,8,33,10,20,40,Other
320
+
Subway,Footlong Carved Turkey & Bacon w/ Cheese,1140,460,52,14,0,140,3200,92,10,16,66,20,40,80,Other
321
+
Subway,"6"" Chicken & Bacon Ranch Melt",570,250,28,10,1,95,1080,47,5,8,35,15,25,50,Other
322
+
Subway,Footlong Chicken & Bacon Ranch Melt,1140,500,56,20,2,190,2160,94,10,16,70,30,50,100,Other
323
+
Subway,"6"" Chicken Pizziola Melt",460,140,16,6,0,80,1140,49,6,9,32,15,30,45,Other
324
+
Subway,Footlong Chicken Pizziola Melt,920,280,32,12,0,160,2280,98,12,18,64,30,60,90,Other
325
+
Subway,"6"" Cold Cut Combo",370,120,13,4,0,50,1140,46,5,7,18,10,20,35,Other
326
+
Subway,Footlong Cold Cut Combo,740,240,26,8,0,100,2280,92,10,14,36,20,40,70,Other
327
+
Subway,"6"" Corned Beef Reuben",470,130,15,4.5,0,85,1770,45,7,12,39,10,35,20,Other
328
+
Subway,Footlong Corned Beef Reuben,940,260,30,9,0,170,3540,90,14,24,78,20,70,40,Other
329
+
Subway,"6"" Italian B.M.T.",410,150,16,6,0,45,1300,46,5,8,20,8,20,30,Other
330
+
Subway,Footlong Italian B.M.T.,820,300,32,12,0,90,2600,92,10,16,40,16,40,60,Other
331
+
Subway,"6"" Italian Hero",550,260,29,10,0,75,1470,47,5,9,26,10,20,40,Other
332
+
Subway,Footlong Italian Hero,1100,520,58,20,0,150,2940,94,10,18,52,20,40,80,Other
333
+
Subway,"6"" Meatball Marinara",480,160,18,7,1,30,950,59,8,12,21,25,35,35,Other
334
+
Subway,Footlong Meatball Marinara,960,320,36,14,2,60,1900,118,16,24,42,50,70,70,Other
335
+
Subway,"6"" Oven Roasted Chicken",320,40,5,2,0,25,640,47,5,8,23,8,30,30,Other
336
+
Subway,Footlong Oven Roasted Chicken,640,80,10,4,0,50,1280,44,10,16,46,16,60,60,Other
337
+
Subway,Kids Mini Sub Roast Beef,200,25,3,1,0,25,390,30,4,5,14,6,15,20,Other
338
+
Subway,"6"" Roast Beef",320,40,5,2,0,40,700,45,5,7,24,8,20,30,Other
339
+
Subway,Footlong Roast Beef,640,80,10,4,0,80,1400,90,10,14,48,16,40,60,Other
340
+
Subway,"6"" Rotisserie Style Chicken",350,50,6,1.5,0,50,540,44,5,7,29,8,20,30,Other
341
+
Subway,Footlong Rotisserie Style Chicken,700,100,12,3,0,100,1080,88,10,14,58,16,40,60,Other
342
+
Subway,"6"" Spicy Italian",480,220,24,9,1,50,1520,46,5,8,20,8,20,30,Other
343
+
Subway,Footlong Spicy Italian,960,440,48,18,2,100,3040,92,10,16,40,16,40,60,Other
344
+
Subway,"6"" Steak and Cheese",380,90,10,5,0,50,1060,48,5,8,26,10,20,40,Other
345
+
Subway,Footlong Steak and Cheese,760,180,20,10,0,100,2120,96,10,16,52,20,40,80,Other
346
+
Subway,"6"" Subway Club",310,40,5,2,0,40,880,46,5,7,23,8,20,30,Other
347
+
Subway,Footlong Subway Club,620,80,10,4,0,80,1760,92,10,14,46,16,40,60,Other
348
+
Subway,"6"" Subway Melt (includes cheese)",370,100,11,5,0,45,1210,47,5,8,23,10,20,40,Other
349
+
Subway,Footlong Subway Melt (includes cheese),740,200,22,10,0,90,1420,94,10,16,46,20,40,80,Other
350
+
Subway,"6"" Subway Seafood Sensation",420,170,19,3,0,20,690,51,5,8,13,10,20,35,Other
351
+
Subway,Footlong Subway Seafood Sensation,840,340,38,6,0,40,1380,102,10,16,26,20,40,70,Other
352
+
Subway,"6"" Sweet Onion Chicken Teriyaki",380,40,5,1,0,50,900,59,5,18,26,8,30,35,Other
353
+
Subway,Footlong Sweet Onion Chicken Teriyaki,760,80,10,2,0,100,1800,118,10,36,52,16,60,70,Other
354
+
Subway,"6"" Tuna",470,210,24,4,0,30,620,44,5,6,20,8,20,30,Other
355
+
Subway,Footlong Tuna,940,420,48,8,0,60,1240,88,10,12,40,16,40,60,Other
356
+
Subway,"6"" Turkey & Bacon Avocado",390,110,13,3.5,0,30,860,49,8,7,22,10,200,30,Other
357
+
Subway,Footlong Turkey & Bacon Avocado,780,220,26,7,0,60,1720,98,16,14,44,20,400,60,Other
358
+
Subway,Kids Mini Sub Turkey Breast,180,20,2,0.5,0,10,380,30,3,5,10,6,15,20,Other
359
+
Subway,"6"" Turkey Breast",280,30,4,1,0,20,810,46,5,7,18,8,20,30,Other
360
+
Subway,Footlong Turkey Breast,560,60,8,2,0,40,1620,92,10,14,36,16,40,60,Other
361
+
Subway,"6"" Turkey Breast & Ham",280,35,4,1,0,20,820,46,5,8,18,8,20,30,Other
362
+
Subway,Footlong Turkey Breast & Ham,560,70,8,2,0,40,1640,92,10,16,36,16,40,60,Other
363
+
Subway,"6"" Turkey Italiano Melt (with Provolone)",490,210,24,9,1,50,1480,47,5,8,24,10,20,45,Other
364
+
Subway,Footlong Turkey Italiano Melt (with Provolone),980,420,48,18,2,100,2960,94,10,16,48,20,40,90,Other
365
+
Subway,Kids Mini Sub Veggie Delite,150,15,2,0,0,0,190,29,3,4,6,6,15,20,Other
366
+
Subway,"6"" Veggie Delite",230,20,3,1,0,0,310,44,5,6,8,8,20,30,Other
367
+
Subway,Footlong Veggie Delite,460,40,6,2,0,0,620,88,10,12,16,16,40,60,Other
368
+
Subway,"6"" Veggie Patty",390,70,7,1,0,10,800,56,8,8,23,15,20,35,Other
369
+
Subway,Footlong Veggie Patty,780,140,14,2,0,20,1600,112,16,16,46,30,20,70,Other
370
+
Subway,Autumn Carved Turkey Salad,300,80,9,3,0,60,1120,26,3,22,25,40,40,15,Other
371
+
Subway,B.L.T. Salad,150,70,8,4,0,20,420,10,4,5,10,50,50,6,Other
372
+
Subway,Big Hot Pastrami Melt Salad,400,300,29,11,0,85,1250,12,4,4,23,25,70,10,Other
373
+
Subway,Big Philly Cheesesteak Salad,330,140,16,8,1,85,1080,17,5,6,32,60,50,25,Other
374
+
Subway,Black Forest Ham Salad,110,25,3,1,0,20,590,11,4,6,12,25,45,4,Other
375
+
Subway,Buffalo Chicken Salad (with Ranch dressing),360,230,26,4,0,60,1100,13,4,6,20,50,60,8,Other
376
+
Subway,Carved Turkey & Bacon w/ Cheese Salad,280,110,12,4.5,0,65,1320,11,4,5,28,50,50,15,Other
377
+
Subway,Carved Turkey Salad,150,30,4,0,0,45,680,8,3,3,19,40,40,6,Other
378
+
Subway,Chicken & Bacon Ranch Melt Salad (includes Ranch dressing),510,340,38,12,1,100,1040,14,4,7,30,60,60,30,Other
379
+
Subway,Cold Cut Combo Salad,180,95,11,4,0,45,820,12,4,5,12,50,50,10,Other
380
+
Subway,Double Chicken Salad,220,35,5,1.5,0,100,490,10,4,4,36,50,60,8,Other
381
+
Subway,Italian B.M.T.ยฎ Salad,230,135,15,6,0,45,1060,12,4,6,14,50,50,6,Other
382
+
Subway,Italian Hero Salad,230,140,15,5,0,45,1060,13,4,8,14,40,60,4,Other
383
+
Subway,Meatball Marinara Salad,310,150,17,7,1,30,720,25,6,10,16,60,70,10,Other
384
+
Subway,Oven Roasted Chicken Salad,140,25,3,0.5,0,50,280,10,4,4,19,50,60,8,Other
385
+
Subway,Roast Beef Salad,140,30,4,1,0,40,450,10,4,5,18,25,45,4,Other
386
+
Subway,Spicy Italian Salad,310,205,23,9,1,50,1280,11,4,6,15,50,50,8,Other
387
+
Subway,Steak & Cheese Salad,210,75,8,4,0,50,830,14,4,6,20,50,50,15,Other
388
+
Subway,Subway Club Salad,140,30,4,1,0,40,640,11,4,5,17,25,45,6,Other
389
+
Subway,Subway Meltยฎ Salad,200,85,10,5,0,45,910,13,4,6,18,50,50,15,Other
390
+
Subway,Sweet Onion Chicken Teriyaki Salad,200,25,3,1,0,50,660,24,4,16,20,25,50,6,Other
391
+
Subway,Tuna Salad,310,215,24,4,0,40,370,10,4,4,15,50,50,6,Other
392
+
Subway,Turkey Breast & Ham Salad,110,20,3,1,0,25,580,11,4,5,12,25,45,6,Other
393
+
Subway,Turkey Breast Salad,110,20,2,1,0,20,570,11,4,5,12,25,45,6,Other
394
+
Subway,Veggie Delite Salad,50,10,1,0,0,0,65,9,4,4,3,25,45,4,Other
395
+
Subway,Chipotle Southwest Steak & Cheese Wrap,760,330,37,12,1,100,2250,65,4,7,43,15,45,30,Other
396
+
Subway,Rotisserie-Style Chicken Caesar Wrap,730,310,34,10,0.5,135,1900,53,3,4,55,15,8,45,Other
397
+
Subway,"Turkey, Bacon & Guacamole Wrap",810,380,42,13,0.5,75,2970,62,3,6,43,10,30,30,Other
398
+
Subway,Cheese & Veggies Pizza,740,230,25,11,0,50,1270,100,5,9,36,35,30,60,Other
399
+
Subway,Cheese Pizza,680,200,22,9,0,40,1070,96,4,7,32,25,4,45,Other
400
+
Subway,Pepperoni Pizza,790,290,32,13,0,60,1350,96,4,8,38,30,4,60,Other
401
+
Subway,Sausage Pizza,820,310,34,14,0,70,1420,97,4,8,39,30,4,60,Other
402
+
Taco Bell,1/2 lb.* Cheesy Potato Burrito,540,230,26,7,1,45,1360,59,7,4,19,NA,NA,NA,Other
403
+
Taco Bell,1/2 lb.* Combo Burrito,460,170,18,7,1,45,1320,53,9,3,21,NA,NA,NA,Other
404
+
Taco Bell,7-Layer Burrito,510,170,19,7,0,20,1090,68,11,4,16,NA,NA,NA,Other
405
+
Taco Bell,Bean Burrito,370,100,11,4,0,5,960,56,9,3,13,NA,NA,NA,Other
406
+
Taco Bell,Beefy 5-Layer Burrito,550,200,22,8,0,35,1270,68,8,5,19,NA,NA,NA,Other
407
+
Taco Bell,Beefy Fritosยฎ Burrito,440,160,18,5,0,20,1030,55,4,3,13,NA,NA,NA,Other
408
+
Taco Bell,Black Bean Burrito,410,110,12,4,0,10,1100,62,8,3,14,NA,NA,NA,Other
409
+
Taco Bell,Burrito Supremeยฎ โ Beef,420,140,16,7,0,35,1090,53,8,5,16,NA,NA,NA,Other
410
+
Taco Bell,Burrito Supremeยฎ - Chicken,390,110,12,5,0,40,1050,52,7,5,19,NA,NA,NA,Other
411
+
Taco Bell,Burrito Supremeยฎ - Steak,390,120,13,5,0,30,1090,52,7,5,17,NA,NA,NA,Other
412
+
Taco Bell,Cantina Power Burrito - Chicken,760,240,27,6,0,60,1960,96,12,7,32,NA,NA,NA,Other
413
+
Taco Bell,Cantina Power Burrito - Steak,780,250,28,7,0,50,1900,98,13,7,33,NA,NA,NA,Other
414
+
Taco Bell,Cantina Power Burrito - Veggie,740,230,26,5,0,10,1750,107,17,8,20,NA,NA,NA,Other
415
+
Taco Bell,Cheesy Bean and Rice Burrito,420,160,17,3.5,0,0,930,55,6,4,11,NA,NA,NA,Other
416
+
Taco Bell,Chili Cheese Burrito,380,150,17,8,1,35,930,41,5,2,16,NA,NA,NA,Other
417
+
Taco Bell,Chicken Crunchy Cheesy Core Burrito,610,210,24,9,0,55,1510,74,5,5,25,10,4,35,Other
418
+
Taco Bell,Steak Crunchy Cheesy Core Burrito,610,220,24,9,0,50,1520,75,5,5,25,10,4,40,Other
419
+
Taco Bell,Beef Crunchy Cheesy Core Burrito,630,240,26,10,0.5,45,1530,76,7,5,22,15,4,35,Other
420
+
Taco Bell,Loaded Taco Burrito,550,260,29,9,0.5,50,1130,52,7,4,20,15,6,20,Other
421
+
Taco Bell,Chicken Quesarito,620,270,30,10,0,60,1440,64,4,4,24,NA,NA,NA,Other
422
+
Taco Bell,Steak Quesarito,630,280,31,11,0.5,65,1410,64,3,4,25,NA,NA,NA,Other
423
+
Taco Bell,Beef Quesarito,650,300,34,12,0.5,60,1450,65,6,5,22,NA,NA,NA,Other
424
+
Taco Bell,Shredded Chicken Burrito,400,160,18,4.5,0,30,960,45,3,3,16,NA,NA,NA,Other
425
+
Taco Bell,Smothered Burrito - Beef,710,320,35,13,1,75,2260,70,10,4,28,NA,NA,NA,Other
426
+
Taco Bell,Smothered Burrito - Shredded Chicken,650,250,28,10,0,70,2230,67,8,4,34,NA,NA,NA,Other
427
+
Taco Bell,Smothered Burrito - Steak,670,260,29,11,0.5,80,2080,68,7,4,35,NA,NA,NA,Other
428
+
Taco Bell,Chicken Spicy Cheesy Core Burrito,540,180,20,8,0,55,1740,66,5,5,24,15,8,35,Other
429
+
Taco Bell,Steak Spicy Cheesy Core Burrito,550,190,21,9,0,50,1750,66,5,5,24,10,6,35,Other
430
+
Taco Bell,Beef Spicy Cheesy Core Burrito,570,210,23,10,0.5,45,1760,68,7,5,22,15,6,35,Other
431
+
Taco Bell,Triple Melt Burrito,410,140,16,6,0,30,1030,50,4,3,15,6,2,20,Other
432
+
Taco Bell,XXL Grilled Stuft Burrito - Beef,880,380,42,14,1,75,2020,94,12,6,31,NA,NA,NA,Other
433
+
Taco Bell,XXL Grilled Stuft Burrito - Chicken,830,320,35,11,0,85,1940,91,10,6,37,NA,NA,NA,Other
434
+
Taco Bell,XXL Grilled Stuft Burrito - Steak,820,320,36,12,1,70,2020,91,10,7,33,NA,NA,NA,Other
435
+
Taco Bell,Chicken Soft Taco,170,50,6,3,0,30,460,18,1,1,12,NA,NA,NA,Other
436
+
Taco Bell,Cool Ranchยฎ Doritosยฎ Double Deckerยฎ Taco,320,120,14,5,0,25,770,36,6,2,13,NA,NA,NA,Other
437
+
Taco Bell,Cool Ranchยฎ Doritosยฎ Locos Taco,160,90,10,3.5,0,25,350,13,2,1,8,NA,NA,NA,Other
438
+
Taco Bell,Cool Ranchยฎ Doritosยฎ Locos Taco Supreme,200,100,12,4.5,0,35,370,15,3,3,9,NA,NA,NA,Other
439
+
Taco Bell,Crunchy Taco,170,90,10,4,0,25,290,12,3,1,8,NA,NA,NA,Other
440
+
Taco Bell,Crunchy Taco Supremeยฎ,200,110,12,5,0,35,320,15,3,2,9,NA,NA,NA,Other
441
+
Taco Bell,Double Deckerยฎ Taco,320,120,14,5,0,25,640,37,7,2,13,NA,NA,NA,Other
442
+
Taco Bell,DOUBLE DECKERยฎ Taco Supremeยฎ,350,140,16,6,0,35,670,40,7,3,14,NA,NA,NA,Other
443
+
Taco Bell,Spicy Sweet Double Stacked Taco,340,160,18,7,0,35,640,32,4,6,12,10,2,15,Other
444
+
Taco Bell,Cool Ranch Habanero Double Stacked Taco,350,180,20,8,0.5,40,630,30,4,3,13,15,2,20,Other
445
+
Taco Bell,Nacho Crunch Double Stacked Taco,380,170,19,6,0,35,650,39,5,2,13,8,2,20,Other
446
+
Taco Bell,Fiery Doritosยฎ Double Deckerยฎ Taco,320,120,13,5,0,25,770,36,7,2,14,NA,NA,NA,Other
447
+
Taco Bell,Fiery Doritosยฎ Locos Taco,170,90,10,3.5,0,25,370,12,3,1,8,NA,NA,NA,Other
448
+
Taco Bell,Fiery Doritosยฎ Locos Taco Supreme,200,110,12,5,0,30,390,15,3,2,9,NA,NA,NA,Other
449
+
Taco Bell,Grilled Steak Soft Taco,250,130,14,4,0,30,550,19,2,2,11,NA,NA,NA,Other
450
+
Taco Bell,Nacho Cheese Doritosยฎ Double Deckerยฎ Taco,320,120,13,5,0,25,760,36,7,2,14,NA,NA,NA,Other
451
+
Taco Bell,Nacho Cheese Doritosยฎ Locos Tacos,170,80,9,4,0,25,340,13,2,1,8,NA,NA,NA,Other
452
+
Taco Bell,Nacho Cheese Doritosยฎ Locos Tacos Supreme,200,100,11,5,0,35,370,15,3,2,9,NA,NA,NA,Other
453
+
Taco Bell,Soft Taco Supremeยฎ โ Beef,230,100,11,5,0,35,530,22,3,3,10,NA,NA,NA,Other
454
+
Taco Bell,Soft Taco-Beef,200,80,9,4,0,25,510,19,3,1,10,NA,NA,NA,Other
455
+
Taco Bell,Spicy Potato Soft Taco,250,120,13,3,0,10,510,28,3,1,6,NA,NA,NA,Other
456
+
Taco Bell,Chalupa Supremeยฎ - Chicken,340,160,18,4,0,40,530,29,3,4,16,NA,NA,NA,Other
457
+
Taco Bell,Chalupa Supremeยฎ - Steak,340,170,18,4,0,30,570,29,3,4,14,NA,NA,NA,Other
458
+
Taco Bell,Chalupa SupremeยฎโBeef,370,190,21,5,0,30,570,31,4,4,13,NA,NA,NA,Other
459
+
Taco Bell,Double Chalupa,600,310,35,8,0.5,50,1010,50,6,5,21,15,4,15,Other
460
+
Taco Bell,Wild Naked Chicken Chalupa,420,250,28,6,0,65,1070,23,4,2,19,6,4,6,Other
461
+
Taco Bell,Mild Naked Chicken Chalupa,440,270,30,7,0,70,1090,22,3,1,20,6,4,6,Other
462
+
Taco Bell,Spicy Double Chalupa,600,310,35,8,0.5,50,1240,52,7,5,21,15,8,15,Other
463
+
Taco Bell,Fresco Bean Burrito,350,80,9,3,0,0,950,57,9,3,11,NA,NA,NA,Other
464
+
Taco Bell,Fresco Burrito Supremeยฎ โ Chicken,340,80,8,3,0,25,1020,50,7,4,17,NA,NA,NA,Other
465
+
Taco Bell,Fresco Burrito Supremeยฎ โ Steak,340,80,9,3,0,15,1060,50,7,4,15,NA,NA,NA,Other
466
+
Taco Bell,Fresco Chicken Soft Taco,150,35,4,1,0,25,460,18,2,2,11,NA,NA,NA,Other
467
+
Taco Bell,Fresco Crunchy Taco,140,70,8,2,0,20,290,13,3,1,6,NA,NA,NA,Other
468
+
Taco Bell,Fresco Grilled Steak Soft Taco,150,35,4,2,0,15,500,19,2,2,9,NA,NA,NA,Other
469
+
Taco Bell,Fresco Soft Taco,170,60,7,3,0,20,500,20,3,2,8,NA,NA,NA,Other
470
+
Taco Bell,Cheesy Gordita Crunch,490,260,29,10,1,55,810,39,5,6,20,NA,NA,NA,Other
471
+
Taco Bell,Doritosยฎ Cheesy Gordita Crunch - Cool Ranch,490,250,28,10,1,55,890,40,5,5,20,NA,NA,NA,Other
472
+
Taco Bell,Doritosยฎ Cheesy Gordita Crunch - Fiery,490,250,28,10,1,55,890,40,5,4,20,NA,NA,NA,Other
473
+
Taco Bell,Doritosยฎ Cheesy Gordita Crunch - Nacho Cheese,490,250,28,10,1,55,880,40,5,5,20,NA,NA,NA,Other
474
+
Taco Bell,Double Cheesy Gordita Crunch,570,290,32,12,1,70,1110,44,7,5,25,15,2,30,Other
475
+
Taco Bell,Gordita Supremeยฎ โ Beef,300,120,14,5,0,30,550,31,4,6,13,NA,NA,NA,Other
476
+
Taco Bell,Gordita Supremeยฎ - Chicken,270,90,10,4,0,40,510,29,2,6,16,NA,NA,NA,Other
477
+
Taco Bell,Gordita Supremeยฎ - Steak,270,90,11,4,0,30,550,29,2,6,14,NA,NA,NA,Other
478
+
Taco Bell,Nacho Fries Bellgrande,710,360,41,6,0,30,1420,73,10,4,13,10,4,8,Other
479
+
Taco Bell,Nachos BellGrandeยฎ,760,360,39,6,0,30,1100,82,13,5,18,NA,NA,NA,Other
480
+
Taco Bell,Nachos Supreme,430,210,23,5,0,30,690,44,7,3,12,NA,NA,NA,Other
481
+
Taco Bell,Triple Layer Nachos,320,140,15,1.5,0,0,600,41,6,2,7,NA,NA,NA,Other
482
+
Taco Bell,Triple Melt Nachos,260,140,16,4.5,0,30,550,19,3,1,10,6,0,10,Other
483
+
Taco Bell,Beefy Cheddar Crunchwrap Slider,410,170,19,6,0,25,960,46,4,3,14,NA,NA,NA,Other
484
+
Taco Bell,Beefy Mini Quesadilla,210,110,12,4,0,25,560,17,3,1,9,NA,NA,NA,Other
485
+
Taco Bell,Beefy Nacho Griller,420,170,19,4.5,0,20,870,49,5,3,12,NA,NA,NA,Other
486
+
Taco Bell,BLT Crunchwrap Slider,430,210,23,5,0,20,900,43,3,4,12,NA,NA,NA,Other
487
+
Taco Bell,Cantina Power Bowl - Chicken,560,200,22,4,0,60,1520,64,9,4,26,NA,NA,NA,Other
488
+
Taco Bell,Cantina Power Bowl - Steak,580,210,23,4,0,50,1460,66,10,4,27,NA,NA,NA,Other
489
+
Taco Bell,Cantina Power Bowl - Veggie,540,190,21,3,0,10,1310,75,14,4,14,NA,NA,NA,Other
490
+
Taco Bell,Cheese Quesadilla,480,240,27,11,1,50,1000,40,4,3,19,NA,NA,NA,Other
491
+
Taco Bell,Cheese Roll-Up,190,80,9,5,0,20,450,18,2,1,9,NA,NA,NA,Other
492
+
Taco Bell,Chicken Quesadilla,520,250,28,12,1,75,1210,41,4,3,27,NA,NA,NA,Other
493
+
Taco Bell,Chickstar,620,340,37,8,0,50,1290,53,4,4,17,8,6,15,Other
494
+
Taco Bell,Chili Cheese Burrito,380,150,17,8,1,35,930,41,5,2,16,NA,NA,NA,Other
495
+
Taco Bell,Chipotle Crispy Chicken Griller,290,170,18,3,0,25,640,22,1,1,9,NA,NA,NA,Other
496
+
Taco Bell,Crispy Chicken Quesadilla,650,340,37,13,0.5,75,1480,51,5,3,26,10,2,45,Other
497
+
Taco Bell,Crunchwrap Supremeยฎ,540,190,21,6,0,30,1110,71,7,7,16,NA,NA,NA,Other
498
+
Taco Bell,Double Tostada,270,100,11,4,0,15,650,32,8,2,12,NA,NA,NA,Other
499
+
Taco Bell,Express Taco Salad w/ Chips,580,260,29,9,1,60,1270,59,8,7,23,NA,NA,NA,Other
500
+
Taco Bell,Loaded Potato Griller,470,200,22,6,0,25,1120,55,4,5,13,NA,NA,NA,Other
501
+
Taco Bell,Mexican Pizza,540,270,31,8,1,40,860,47,7,2,20,NA,NA,NA,Other
502
+
Taco Bell,MexiMeltยฎ,270,130,14,7,1,40,740,21,3,2,14,NA,NA,NA,Other
503
+
Taco Bell,Steak Quesalupa,440,210,23,10,0.5,60,840,36,3,3,22,15,6,35,Other
504
+
Taco Bell,Chicken Quesalupa,440,200,23,10,0.5,60,840,37,3,3,22,15,8,35,Other
505
+
Taco Bell,Beef Quesalupa,460,240,26,11,1,50,890,38,4,3,19,15,6,35,Other
506
+
Taco Bell,Shredded Chicken Mini Quesadilla,180,70,8,2.5,0,25,540,15,2,1,12,NA,NA,NA,Other
507
+
Taco Bell,Spicy Chicken Crunchwrap Slider,400,180,20,4,0,25,900,42,3,3,15,NA,NA,NA,Other
508
+
Taco Bell,Spicy Tostada,200,90,10,2.5,0,10,440,22,4,1,7,NA,NA,NA,Other
509
+
Taco Bell,Stacker,390,170,18,8,0.5,40,1050,39,4,3,18,8,2,30,Other
510
+
Taco Bell,Steak Quesadilla,520,250,28,12,1,65,1250,41,4,3,25,NA,NA,NA,Other
511
+
Taco Bell,Original Triple Double Crunchwrap,700,270,30,9,0.5,45,1550,85,9,7,23,15,6,25,Other
512
+
Taco Bell,Spicy Triple Double Crunchwrap,780,340,38,10,0.5,50,1850,87,9,8,23,20,10,25,Other
513
+
Taco Bell,Express Taco Salad w/ Chips,580,260,29,9,1,60,1270,59,8,7,23,NA,NA,NA,Other
514
+
Taco Bell,Fiesta Taco Salad-Beef,780,380,42,10,1,60,1340,74,11,7,26,NA,NA,NA,Other
515
+
Taco Bell,Fiesta Taco Salad-Chicken,720,320,35,7,0,70,1260,70,8,8,32,NA,NA,NA,Other
516
+
Taco Bell,Fiesta Taco Salad-Steak,720,320,36,8,1,55,1340,70,8,8,28,NA,NA,NA,Other
+52
python/oct15/fast-food-nutrition/main.py
+52
python/oct15/fast-food-nutrition/main.py
···
1
+
import pandas as pd
2
+
import matplotlib.pyplot as plt
3
+
import seaborn as sns
4
+
5
+
def loadFastFoodDatabase():
6
+
df = pd.read_csv('fast-food-nutrition/fastfood.csv')
7
+
return df
8
+
9
+
def highest_cal_item(df: pd.DataFrame) -> tuple[str, int, str]:
10
+
cals = df["calories"].astype(int)
11
+
item = df.iloc[cals.argmax()]
12
+
return (item["item"], item["calories"], item["restaurant"])
13
+
14
+
def cal_fat_correlation(df: pd.DataFrame) -> float:
15
+
corr = df["calories"].corr(df["total_fat"])
16
+
return corr
17
+
18
+
def scatter_plot(df: pd.DataFrame) -> None:
19
+
sns.scatterplot(x=df["calories"], y=df["total_fat"])
20
+
plt.show()
21
+
pass
22
+
23
+
def high_rest_avg(df: pd.DataFrame) -> tuple[str, float]:
24
+
avgs: list[tuple[str, float]] = []
25
+
for restaurant in df["restaurant"].unique():
26
+
items = df[df["restaurant"] == restaurant]
27
+
avg = items["calories"].astype(int).sum()/len(items)
28
+
avgs.append((restaurant, avg))
29
+
highest = avgs[0]
30
+
for (r, avg) in avgs[1:]:
31
+
if avg > highest[1]:
32
+
highest = (r, avg)
33
+
return highest
34
+
35
+
def percent_500(df: pd.DataFrame) -> float:
36
+
sodium = df["sodium"]
37
+
df = df.assign(above_500=sodium > 500)
38
+
# df["above_500"].value_counts().plot.pie(labels=["Above 500mg", "Below 500mg"])
39
+
# plt.show()
40
+
return len(df[df["above_500"]])/len(sodium)
41
+
42
+
print("Running test cases...", end="")
43
+
try:
44
+
df = loadFastFoodDatabase()
45
+
assert(highest_cal_item(df) == ("20 piece Buttermilk Crispy Chicken Tenders", 2430, "Mcdonalds")) # Q1
46
+
assert(cal_fat_correlation(df) == 0.9004936961298484) # Q2. This means that fats highly contribute to calorie count
47
+
# scatter_plot(df) # Q3
48
+
assert(high_rest_avg(df) == ("Mcdonalds", 640.3508771929825)) # Q4
49
+
assert(percent_500(df) == 0.9145631067961165) # Q5
50
+
print("Passed!")
51
+
except:
52
+
print("Failed :(")
python/oct15/sunrise-sunset/Figure_1.png
python/oct15/sunrise-sunset/Figure_1.png
This is a binary file and will not be displayed.
python/oct15/sunrise-sunset/Figure_2.png
python/oct15/sunrise-sunset/Figure_2.png
This is a binary file and will not be displayed.
+72
python/oct15/sunrise-sunset/main.py
+72
python/oct15/sunrise-sunset/main.py
···
1
+
import pandas as pd
2
+
import requests
3
+
from geopy.geocoders import Nominatim
4
+
from geopy.location import Location
5
+
import datetime
6
+
import seaborn as sns
7
+
import matplotlib.pyplot as plt
8
+
9
+
def sunrise_time(location_name: str, date: str|None|tuple[str,str] = None) -> list[dict[str, str]]:
10
+
nominatim = Nominatim(user_agent="CMU_Bootcamp")
11
+
geo: Location | None = nominatim.geocode(location_name)
12
+
if not geo:
13
+
raise KeyError("Location not found")
14
+
params = {
15
+
"lng": geo.longitude,
16
+
"lat": geo.latitude
17
+
}
18
+
19
+
if isinstance(date, tuple):
20
+
params["date_start"] = date[0],
21
+
params["date_end"] = date[1]
22
+
else:
23
+
params["date"] = date if date else "today"
24
+
25
+
out = requests.get(
26
+
"https://api.sunrisesunset.io/json",
27
+
params
28
+
)
29
+
j = out.json()['results']
30
+
res = j if isinstance(j, list) else [j]
31
+
return [{
32
+
"sunrise": r["sunrise"],
33
+
"sunset": r["sunset"],
34
+
"date": r["date"]
35
+
} for r in res]
36
+
37
+
def dayLength(sunrise: str, sunset: str) -> float:
38
+
sr = datetime.datetime.strptime(sunrise, "%I:%M:%S %p")
39
+
ss = datetime.datetime.strptime(sunset, "%I:%M:%S %p")
40
+
srhr = round(sr.hour + sr.minute/60, 1)
41
+
sshr = round(ss.hour + ss.minute/60, 1)
42
+
length = sshr - srhr
43
+
return round(length, 1)
44
+
45
+
location = "5000 Forbes Ave, Pittsburgh"
46
+
print("Running checks...")
47
+
try:
48
+
compare_date = "2024-08-02"
49
+
a = sunrise_time("Juneau, Alaska", compare_date)
50
+
b = sunrise_time("Miami, Florida", compare_date)
51
+
print(f"Juneau, Alaska {a[0]["sunrise"]}\nMiami, Florida {b[0]["sunrise"]}")
52
+
df = pd.DataFrame(sunrise_time("Seattle, Washington", ("2023-01-01", "2023-12-31")))
53
+
print(f"Latest: {df["sunrise"].max()} | Earliest: {df["sunrise"].min()}")
54
+
df = df.assign(dayLengthInHours=df.apply(lambda row: dayLength(row["sunrise"], row["sunset"]), axis=1))
55
+
print(f"Longest: {df["dayLengthInHours"].max()} | Shortest: {df['dayLengthInHours'].min()}")
56
+
# ax = sns.lineplot(data=df["dayLengthInHours"])
57
+
# ax.set(xlabel="", ylabel="", xticklabels=[])
58
+
# plt.show()
59
+
df = df.assign(month=df.apply(lambda row: datetime.datetime.strptime(row["date"], "%Y-%m-%d").strftime("%B"), axis=1))
60
+
print(df)
61
+
avgs: dict[str, float] = {}
62
+
for month in df["month"].unique():
63
+
this_month = pd.DataFrame(df[df["month"] == month])
64
+
total = this_month["dayLengthInHours"].astype(float).sum()
65
+
avg = round(total / len(this_month), 1)
66
+
avgs[month] = avg
67
+
# ax = sns.barplot(data=avgs)
68
+
# ax.tick_params(axis="x", rotation=45)
69
+
# plt.show()
70
+
print("Passed!")
71
+
except:
72
+
print("Failed :(")
+245
python/oct15/tic-tac-toe/main.py
+245
python/oct15/tic-tac-toe/main.py
···
1
+
# Tic-Tac-Toe
2
+
# by David Kosbie
3
+
4
+
# This implements a basic game of TicTacToe.
5
+
6
+
# This version does not save or load the game (that is left for you to do!)
7
+
8
+
import json
9
+
from cmu_graphics import *
10
+
import math
11
+
import os
12
+
13
+
def onAppStart(app):
14
+
# Set the model values (in the app object) that never change.
15
+
app.rows = 3
16
+
app.cols = 3
17
+
app.boardBounds = (50, 75, 350, 375) # left, top, right, bottom
18
+
app.cellBorderWidth = 2
19
+
resetApp(app)
20
+
21
+
def resetApp(app):
22
+
app.selection = None
23
+
app.board = [[None]*app.cols for row in range(app.rows)]
24
+
app.turn = 'X'
25
+
app.message = "X's turn"
26
+
app.turnCount = 0
27
+
app.gameOver = False
28
+
app.winningCells = None
29
+
30
+
def saveGame(app):
31
+
with open("savedGame.txt", "w") as f:
32
+
json.dump({
33
+
"board": app.board,
34
+
"turn": app.turn,
35
+
"gameOver": app.gameOver,
36
+
"winningCells": app.winningCells
37
+
}, f)
38
+
39
+
def loadGame(app):
40
+
with open("savedGame.txt", "r") as f:
41
+
game = json.load(f)
42
+
app.selection = None
43
+
app.board = game["board"]
44
+
app.turn = game["turn"]
45
+
app.message = f"{app.turn}'s turn"
46
+
flat_list = []
47
+
for r in app.board:
48
+
for c in r:
49
+
if c != None:
50
+
flat_list.append(1)
51
+
app.turn_count = sum(flat_list)
52
+
app.gameOver = game["gameOver"]
53
+
app.winningCells = game["winningCells"]
54
+
55
+
56
+
def onKeyPress(app, key):
57
+
if key == 's':
58
+
saveGame(app)
59
+
elif key == 'l':
60
+
loadGame(app)
61
+
elif (app.gameOver) and (key == 'r'):
62
+
resetApp(app)
63
+
64
+
def onMousePress(app, mouseX, mouseY):
65
+
# Always clear the selection on any mouse press.
66
+
app.selection = None
67
+
# Then, make the move, but only if the game is not over, and the move is legal
68
+
# (that is, it's in an empty cell).
69
+
if not app.gameOver:
70
+
cell = getCell(app, mouseX, mouseY)
71
+
if cell != None:
72
+
row, col = cell
73
+
if app.board[row][col] == None:
74
+
makeMove(app, row, col)
75
+
76
+
def onMouseMove(app, mouseX, mouseY):
77
+
if app.gameOver:
78
+
return
79
+
# Set the cell selection as the mouse is moved, but only
80
+
# if there is a selected cell, and that cell on the board is empty.
81
+
# Otherwise, clear the cell selection.
82
+
selectedCell = getCell(app, mouseX, mouseY)
83
+
if selectedCell == None:
84
+
app.selection = None
85
+
else:
86
+
row, col = selectedCell
87
+
if app.board[row][col] == None:
88
+
app.selection = selectedCell
89
+
else:
90
+
app.selection = None
91
+
92
+
def makeMove(app, row, col):
93
+
# We already know that this is a legal move, so set the board
94
+
# to the current player, add one to the turn count, check if the
95
+
# game is over, and if not, change turns.
96
+
app.board[row][col] = app.turn
97
+
app.turnCount += 1
98
+
checkForGameOver(app)
99
+
if not app.gameOver:
100
+
changeTurns(app)
101
+
102
+
def checkForGameOver(app):
103
+
# Check if the game is over (tie or win), and if so, set app.gameOver to
104
+
# True and set the app.message as appropriate.
105
+
# First check for a tie game. If it is, set
106
+
if app.turnCount == app.rows * app.cols:
107
+
app.gameOver = True
108
+
app.message = 'Tie game!'
109
+
# It's not a tie game, so check if there are 3 in a row on the board,
110
+
# in a search that is similar to wordSearch:
111
+
else:
112
+
directions = [ (0, 1), # right
113
+
(1, 0), # down
114
+
(1, 1), # right-down diagonal
115
+
(1, -1) # right-up diagonal
116
+
]
117
+
for startRow in range(app.rows):
118
+
for startCol in range(app.cols):
119
+
for drow,dcol in directions:
120
+
winner = checkForWin(app, startRow, startCol, drow, dcol)
121
+
if winner != None:
122
+
app.gameOver = True
123
+
app.message = f'{winner} wins!'
124
+
return
125
+
126
+
def checkForWin(app, startRow, startCol, drow, dcol):
127
+
# Check for a winner (3 in a row) starting from (startRow, startCol) and
128
+
# heading in the direction (drow, dcol). Return the winner if there
129
+
# is one, otherwise None. Also, so that we can draw the line through
130
+
# the winning 3-in-a-row run, store the winning cells in the order
131
+
# they appear in app.winningCells.
132
+
player = app.board[startRow][startCol]
133
+
if player == None:
134
+
return None
135
+
winLength = 3
136
+
winningCells = [ ]
137
+
for i in range(winLength):
138
+
row = startRow + i * drow
139
+
col = startCol + i * dcol
140
+
if ((row < 0) or (row >= app.rows) or
141
+
(col < 0) or (col >= app.cols)):
142
+
# we went off the board
143
+
return None
144
+
if app.board[row][col] != player:
145
+
return None
146
+
winningCells.append((row, col))
147
+
app.winningCells = winningCells
148
+
return player
149
+
150
+
def changeTurns(app):
151
+
# Change the turn from 'X' to 'O' or 'O' to 'X',
152
+
# and set the app.message as appropriate.
153
+
app.turn = 'O' if (app.turn == 'X') else 'X'
154
+
app.message = f"{app.turn}'s turn"
155
+
156
+
def redrawAll(app):
157
+
drawLabel('Tic-Tac-Toe', 200, 20, size=16, bold=True)
158
+
drawLabel('Press s to save game, l to load game', 200, 40, size=14)
159
+
drawAppMessage(app)
160
+
drawBoard(app)
161
+
drawWinningLine(app)
162
+
163
+
def drawAppMessage(app):
164
+
# Draw the app.message, and if the game is over, make the message red
165
+
# and add a note to press r to restart.
166
+
if app.gameOver:
167
+
message = app.message + ' (press r to restart)'
168
+
color = 'red'
169
+
else:
170
+
message = app.message
171
+
color = 'black'
172
+
drawLabel(message, 200, 60, size=14, fill=color)
173
+
174
+
def drawBoard(app):
175
+
# first draw each cell (with single-thickness):
176
+
for row in range(app.rows):
177
+
for col in range(app.cols):
178
+
drawCell(app, row, col)
179
+
# then draw the board outline (with double-thickness):
180
+
x0, y0, x1, y1 = app.boardBounds
181
+
drawRect(x0, y0, x1-x0, y1-y0,
182
+
fill=None, border='black',
183
+
borderWidth=2*app.cellBorderWidth)
184
+
185
+
def drawCell(app, row, col):
186
+
x0, y0, x1, y1 = getCellBounds(app, row, col)
187
+
color = 'cyan' if (row, col) == app.selection else None
188
+
drawRect(x0, y0, x1-x0, y1-y0,
189
+
fill=color, border='black', borderWidth=app.cellBorderWidth)
190
+
label = app.board[row][col]
191
+
if label != None:
192
+
cx = x0 + (x1 - x0)/2
193
+
cy = y0 + (y1 - y0)/2
194
+
drawLabel(label, cx, cy, size=24, bold=True)
195
+
196
+
def drawWinningLine(app):
197
+
# If there is a winner, then app.winningCells will contain the
198
+
# cells in order, so draw a line from the center of the first cell
199
+
# to the center of the last cell.
200
+
if app.winningCells != None:
201
+
cx0, cy0 = getCellCenter(app, app.winningCells[0])
202
+
cx1, cy1 = getCellCenter(app, app.winningCells[-1])
203
+
drawLine(cx0, cy0, cx1, cy1)
204
+
205
+
def getCellCenter(app, cell):
206
+
# Return the center of the given cell, a (row, col) tuple.
207
+
row, col = cell
208
+
x0, y0, x1, y1 = getCellBounds(app, row, col)
209
+
cx = (x0 + x1) / 2
210
+
cy = (y0 + y1) / 2
211
+
return cx, cy
212
+
213
+
def getCellBounds(app, row, col):
214
+
boardX0, boardY0, boardX1, boardY1 = app.boardBounds
215
+
cellWidth, cellHeight = getCellSize(app)
216
+
x0 = boardX0 + col * cellWidth
217
+
y0 = boardY0 + row * cellHeight
218
+
x1 = x0 + cellWidth
219
+
y1 = y0 + cellHeight
220
+
return (x0, y0, x1, y1)
221
+
222
+
def getCellSize(app):
223
+
boardX0, boardY0, boardX1, boardY1 = app.boardBounds
224
+
boardWidth = boardX1 - boardX0
225
+
boardHeight = boardY1 - boardY0
226
+
cellWidth = boardWidth / app.cols
227
+
cellHeight = boardHeight / app.rows
228
+
return (cellWidth, cellHeight)
229
+
230
+
def getCell(app, x, y):
231
+
boardX0, boardY0, boardX1, boardY1 = app.boardBounds
232
+
dx = x - boardX0
233
+
dy = y - boardY0
234
+
cellWidth, cellHeight = getCellSize(app)
235
+
row = math.floor(dy / cellHeight)
236
+
col = math.floor(dx / cellWidth)
237
+
if (0 <= row < app.rows) and (0 <= col < app.cols):
238
+
return (row, col)
239
+
else:
240
+
return None
241
+
242
+
def main():
243
+
runApp()
244
+
245
+
main()
+21
python/oct2/level0/euler.py
+21
python/oct2/level0/euler.py
···
1
+
from typing import List
2
+
from typing import Generator
3
+
4
+
5
+
def _multiples(nums: List[int], above: int, below: int) -> List[int]:
6
+
return [num for num in range(above, below) if any(num % n == 0 for n in nums)]
7
+
8
+
9
+
def _multiples_gen(nums: List[int], above: int, below: int) -> Generator[int]:
10
+
for i in range(above, below):
11
+
if any(i % n == 0 for n in nums):
12
+
yield i
13
+
14
+
15
+
def multiples(nums: List[int], below: int) -> List[int]:
16
+
return _multiples(nums, 0, below)
17
+
18
+
19
+
print(sum(multiples([3, 5], 1000)))
20
+
print(sum(_multiples_gen([3, 5], 0, 1000)))
21
+
print(sum(_multiples([3, 5], 0, 1000)))
+20
python/oct2/level1/interleaveStrings.py
+20
python/oct2/level1/interleaveStrings.py
···
1
+
def interleaveStrings(s1: str, s2: str) -> str:
2
+
"""Interleave two strings."""
3
+
s = ""
4
+
for i in range(max(len(s1), len(s2))):
5
+
if i < len(s1):
6
+
s += s1[i]
7
+
if i < len(s2):
8
+
s += s2[i]
9
+
return s
10
+
11
+
12
+
print("Testing interleaveStrings()...", end="")
13
+
assert interleaveStrings("pto", "yhn") == "python"
14
+
assert interleaveStrings("ab", "xyz?") == "axbyz?"
15
+
assert interleaveStrings("a", "b") == "ab"
16
+
assert interleaveStrings("xyz", "a b") == "xay zb"
17
+
assert interleaveStrings("", "cpcs1") == "cpcs1"
18
+
assert interleaveStrings("cpcs1", "") == "cpcs1"
19
+
assert interleaveStrings("", "") == ""
20
+
print("Passed!")
+19
python/oct2/level1/largestNumber.py
+19
python/oct2/level1/largestNumber.py
···
1
+
def largestNumber(text: str) -> int | None:
2
+
"""Find the largest number in a string."""
3
+
number = [int(word) for word in text.split() if word.isdigit()]
4
+
return max(number) if number else None
5
+
6
+
7
+
print("Testing largestNumber()...", end="")
8
+
assert largestNumber("I saw 3 dogs, 17 cats, and 14 cows!") == 17
9
+
assert largestNumber("One person ate two hot dogs!") == None
10
+
assert largestNumber("There are no numbers in this text.") == None
11
+
assert largestNumber("I have 2 cats, but the shelter has 20") == 20
12
+
text = "It is 82 degrees Fahrenheit, which is 28 degree Celsius."
13
+
assert largestNumber(text) == 82
14
+
text = "Not one of my 10 penguins are taller than 2 feet 11 inches."
15
+
assert largestNumber(text) == 11
16
+
assert largestNumber("15-112 is also 112 and is my favorite class.") == 112
17
+
assert largestNumber("0 classifies as an integer") == 0
18
+
assert largestNumber("") == None
19
+
print("Passed!")
+14
python/oct2/level1/rotateString.py
+14
python/oct2/level1/rotateString.py
···
1
+
def rotateString(s: str, k: int) -> str:
2
+
"""Rotate a string by k positions."""
3
+
k %= len(s)
4
+
return s[k:] + s[:k]
5
+
6
+
7
+
print("Testing rotateString()...", end="")
8
+
assert rotateString("abcd", 1) == "bcda"
9
+
assert rotateString("abcd", -1) == "dabc"
10
+
assert rotateString("efgh", 4) == "efgh"
11
+
assert rotateString("coffee", 2) == "ffeeco"
12
+
assert rotateString("112cmu", -3) == "cmu112"
13
+
assert rotateString("water", -9) == "aterw"
14
+
print("Passed!")
+24
python/oct2/level1/sameChars.py
+24
python/oct2/level1/sameChars.py
···
1
+
def sameChars(s1: str, s2: str) -> bool:
2
+
"""Check if two strings have the same characters regardless of order or count."""
3
+
if not (isinstance(s1, str) and isinstance(s2, str)):
4
+
return False
5
+
c1 = list(set(s1))
6
+
c2 = list(set(s2))
7
+
c1.sort()
8
+
c2.sort()
9
+
print(c1, c2)
10
+
return c1 == c2
11
+
12
+
13
+
print("Testing sameChars()...", end="")
14
+
assert sameChars("abc", "bac") == True
15
+
assert sameChars("aaaaabbbbbbccccc", "abc") == True
16
+
assert sameChars("abc", "ba") == False
17
+
assert sameChars("ba", "abc") == False
18
+
assert sameChars("AAAAAAaaaa", "Aa") == True
19
+
assert sameChars("NNN", "n") == False
20
+
assert sameChars("123", "123") == True
21
+
assert sameChars("", "a") == False
22
+
assert sameChars("", "") == True
23
+
# assert sameChars(14, "14") == False # this check passes but if I don't comment it I get an editor warning since I've typed the function
24
+
print("Passed!")
+13
python/oct2/level1/vowelCount.py
+13
python/oct2/level1/vowelCount.py
···
1
+
def vowelCount(s: str) -> int:
2
+
"""Count the number of vowels (a, e, i, o, u) in a string."""
3
+
s = s.lower()
4
+
return s.count("a") + s.count("e") + s.count("i") + s.count("o") + s.count("u")
5
+
6
+
7
+
print("Testing vowelCount()...", end="")
8
+
assert vowelCount("Abc def!!! a? yzyzyz!") == 3
9
+
assert vowelCount("Hi! How are you?") == 6
10
+
assert vowelCount("AAAEEEiOL") == 8
11
+
assert vowelCount("Rhythm") == 0
12
+
assert vowelCount("WHY, TRY SHY FLY RHYTHMS SLYLY.") == 0
13
+
print("Passed!")
+17
python/oct2/level2/addUpcBarcodeCheckDigit.py
+17
python/oct2/level2/addUpcBarcodeCheckDigit.py
···
1
+
def addUpcBarcodeCheckDigit(barcode: str) -> str:
2
+
"""Adds the check digit to a UPC barcode."""
3
+
evenChecks = [int(digit) for i, digit in enumerate(barcode) if i % 2 == 0]
4
+
oddChecks = [int(digit) for i, digit in enumerate(barcode) if i % 2 != 0]
5
+
evenTotal = sum(int(digit) for digit in evenChecks) * 3
6
+
oddTotal = sum(int(digit) for digit in oddChecks)
7
+
checkDigit = 10 - (evenTotal + oddTotal) % 10
8
+
return barcode + (str(checkDigit) if checkDigit != 10 else "0")
9
+
10
+
11
+
print("Testing addUpcBarcodeCheckDigit()...", end="")
12
+
assert addUpcBarcodeCheckDigit("03600029145") == "036000291452"
13
+
assert addUpcBarcodeCheckDigit("11111111111") == "111111111117"
14
+
assert addUpcBarcodeCheckDigit("23232323232") == "232323232329"
15
+
assert addUpcBarcodeCheckDigit("12345678900") == "123456789005"
16
+
assert addUpcBarcodeCheckDigit("00000000000") == "000000000000"
17
+
print("Passed!")
+50
python/oct2/level2/simpleCipher.py
+50
python/oct2/level2/simpleCipher.py
···
1
+
def encodeSimpleCipher(s: str) -> str:
2
+
"""
3
+
Encode a string using this formula.
4
+
- s[i] = 2*i
5
+
- if 2*i > len(s), s[i] = (2*i) % len(s)
6
+
"""
7
+
encoded = list(s)
8
+
for i in range(len(s)):
9
+
j = (2 * i) % len(s)
10
+
cj = encoded[j]
11
+
encoded[j] = encoded[i]
12
+
encoded[i] = cj
13
+
return "".join(encoded)
14
+
15
+
16
+
def decodeSimpleCipher(s: str) -> str:
17
+
"""Decode a string encoded from encodeSimpleCipher"""
18
+
decoded = list(s)
19
+
for i in range(len(s) - 1, -1, -1):
20
+
j = (2 * i) % len(s)
21
+
cj = decoded[j]
22
+
decoded[j] = decoded[i]
23
+
decoded[i] = cj
24
+
return "".join(decoded)
25
+
26
+
27
+
print("Testing encodeSimpleCipher()...", end="")
28
+
assert encodeSimpleCipher("AB") == "BA"
29
+
assert encodeSimpleCipher("ABC") == "ABC"
30
+
assert encodeSimpleCipher("ABCD") == "BCDA"
31
+
assert encodeSimpleCipher("ABCDEFGH") == "BCFGDEHA"
32
+
assert encodeSimpleCipher("SECRET MESSAGE") == "MCE SSTSAEREEG"
33
+
assert (
34
+
encodeSimpleCipher("THIS IS A LONGER SECRET MESSAGE JUST FOR YOU!")
35
+
== "T SAENG SR MGARJ H U!SSTE TFECORIIEYSELUOOS"
36
+
)
37
+
print("Passed!")
38
+
39
+
40
+
print("Testing decodeSimpleCipher()...", end="")
41
+
assert decodeSimpleCipher("BA") == "AB"
42
+
assert decodeSimpleCipher("ABC") == "ABC"
43
+
assert decodeSimpleCipher("BCDA") == "ABCD"
44
+
assert decodeSimpleCipher("BCFGDEHA") == "ABCDEFGH"
45
+
assert decodeSimpleCipher("MCE SSTSAEREEG") == "SECRET MESSAGE"
46
+
assert (
47
+
decodeSimpleCipher("T SAENG SR MGARJ H U!SSTE TFECORIIEYSELUOOS")
48
+
== "THIS IS A LONGER SECRET MESSAGE JUST FOR YOU!"
49
+
)
50
+
print("Passed!")
+45
python/oct2/level2/spellCheck.py
+45
python/oct2/level2/spellCheck.py
···
1
+
def spellCheck(lookupWord: str, words: str) -> str | None:
2
+
"""Spell check a word against a list of words."""
3
+
map = {}
4
+
for word in words.split():
5
+
map[word] = levenshtein(lookupWord, word)
6
+
min_distance = min(map.values())
7
+
if min_distance >= 3:
8
+
return None
9
+
return ",".join([word for word in map.keys() if map[word] == min_distance])
10
+
11
+
12
+
# from: https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance#Python
13
+
def levenshtein(s1: str, s2: str) -> int:
14
+
if len(s1) < len(s2):
15
+
return levenshtein(s2, s1)
16
+
17
+
# len(s1) >= len(s2)
18
+
if len(s2) == 0:
19
+
return len(s1)
20
+
21
+
previous_row = range(len(s2) + 1)
22
+
for i, c1 in enumerate(s1):
23
+
current_row = [i + 1]
24
+
for j, c2 in enumerate(s2):
25
+
insertions = (
26
+
previous_row[j + 1] + 1
27
+
) # j+1 instead of j since previous_row and current_row are one character longer
28
+
deletions = current_row[j] + 1 # than s2
29
+
substitutions = previous_row[j] + (c1 != c2)
30
+
current_row.append(min(insertions, deletions, substitutions))
31
+
previous_row = current_row
32
+
33
+
return previous_row[-1]
34
+
35
+
36
+
print("Testing spellCheck()...", end="")
37
+
words = "cat cow dog frog"
38
+
assert spellCheck("frog", words) == "frog" # frog is correct
39
+
assert spellCheck("cats", words) == "cat" # cat is distance 1
40
+
assert spellCheck("caw", words) == "cat,cow" # cat and cow are distance 1
41
+
assert spellCheck("drog", words) == "dog,frog" # dog and frog are distance 1
42
+
assert spellCheck("kit", words) == "cat" # cat is distance 2
43
+
assert spellCheck("ack", words) == None # cat, cow, and dog are
44
+
# distance 3 (too far)
45
+
print("Passed!")
+52
python/oct2/level2/wrapText.py
+52
python/oct2/level2/wrapText.py
···
1
+
def wrapText(text: str, lineSize: int) -> str:
2
+
"""Wrap text to a specified line size."""
3
+
lines = []
4
+
words = text.split()
5
+
currentLine = ""
6
+
for word in words:
7
+
if len(currentLine) + len(word) > lineSize:
8
+
currentLine = currentLine.rstrip()
9
+
currentLine = currentLine.ljust(lineSize)
10
+
currentLine = f"|{currentLine}|"
11
+
lines.append(currentLine)
12
+
currentLine = f"{word} "
13
+
else:
14
+
currentLine += f"{word} "
15
+
else:
16
+
currentLine = currentLine.rstrip()
17
+
currentLine = currentLine.ljust(lineSize)
18
+
currentLine = f"|{currentLine}|"
19
+
lines.append(currentLine)
20
+
return "\n".join(lines)
21
+
22
+
23
+
print("Testing wrapText()...", end="")
24
+
text = """\
25
+
This is some sample text. It is just sample text.
26
+
Nothing more than sample text. Really, that's it."""
27
+
28
+
textWrappedAt20 = """\
29
+
|This is some sample |
30
+
|text. It is just |
31
+
|sample text. Nothing|
32
+
|more than sample |
33
+
|text. Really, that's|
34
+
|it. |"""
35
+
36
+
assert wrapText(text, 20) == textWrappedAt20
37
+
38
+
textWrappedAt30 = """\
39
+
|This is some sample text. It |
40
+
|is just sample text. Nothing |
41
+
|more than sample text. Really,|
42
+
|that's it. |"""
43
+
44
+
assert wrapText(text, 30) == textWrappedAt30
45
+
46
+
textWrappedAt40 = """\
47
+
|This is some sample text. It is just |
48
+
|sample text. Nothing more than sample |
49
+
|text. Really, that's it. |"""
50
+
51
+
assert wrapText(text, 40) == textWrappedAt40
52
+
print("Passed!")
+35
python/oct2/level3/isWordLadder.py
+35
python/oct2/level3/isWordLadder.py
···
1
+
def isWordLadder(s: str) -> bool:
2
+
"""Check if a string is a word ladder."""
3
+
words = s.split("-")
4
+
if len(words) < 2:
5
+
return False
6
+
if len(words[0]) != len(words[-1]):
7
+
return False
8
+
if len(set(words)) != len(words):
9
+
return False
10
+
for i in range(len(words) - 1):
11
+
if len(words[i]) != len(words[i + 1]):
12
+
return False
13
+
diff = 0
14
+
for j in range(len(words[i])):
15
+
if words[i][j] != words[i + 1][j]:
16
+
diff += 1
17
+
if diff != 1:
18
+
return False
19
+
return True
20
+
21
+
22
+
print("Testing isWordLadder()...", end="")
23
+
assert isWordLadder("dog-cog-cot-cat") == True
24
+
assert isWordLadder("cat-bat") == True
25
+
assert isWordLadder("cold-cord-card-ward-warm") == True
26
+
assert isWordLadder("toggle-goggle-google") == True
27
+
assert isWordLadder("cold-cord-card-warm") == False
28
+
assert isWordLadder("cat-bat-cat") == False # duplicate word
29
+
assert isWordLadder("cat-bat-") == False
30
+
assert isWordLadder("cat-cats") == False
31
+
assert isWordLadder("cat-cabs") == False
32
+
assert isWordLadder("cat") == False # just one word
33
+
assert isWordLadder("") == False # no words
34
+
assert isWordLadder("-") == False # no words
35
+
print("Passed!")
+19
python/oct2/level3/longestSubpalindrome.py
+19
python/oct2/level3/longestSubpalindrome.py
···
1
+
def longestSubpalindrome(s: str) -> str:
2
+
"""Find the longest subpalindrome (doesn't have to be centered) in a string."""
3
+
longest = ""
4
+
for i in range(len(s)):
5
+
for j in range(len(s), i - 1, -1):
6
+
forwards = s[i:j]
7
+
if forwards == forwards[::-1] and len(forwards) >= len(longest):
8
+
longest = s[i:j]
9
+
return longest
10
+
11
+
12
+
print("Testing longestSubpalindrome()...", end="")
13
+
assert longestSubpalindrome("ab-4-be!!!") == "b-4-b"
14
+
assert longestSubpalindrome("abcbce") == "cbc"
15
+
assert longestSubpalindrome("aba") == "aba"
16
+
assert longestSubpalindrome("a") == "a"
17
+
assert longestSubpalindrome("abd") == "d"
18
+
assert longestSubpalindrome("kP:p") == "p"
19
+
print("Passed!")
+57
python/oct2/level3/stripComments.py
+57
python/oct2/level3/stripComments.py
···
1
+
def stripComments(code: str) -> str:
2
+
"""Remove comments from a Python code string."""
3
+
lines = code.split("\n")
4
+
lines = [line.split("#")[0].rstrip() for line in lines]
5
+
lines = [line for line in lines if line]
6
+
lines = [f"{line}\n" for line in lines]
7
+
return "".join(lines)
8
+
9
+
10
+
print("Testing stripComments()...", end="")
11
+
code = """\
12
+
# here's a comment!
13
+
def foo(x):
14
+
return x + 1 # here's another one
15
+
"""
16
+
result = """\
17
+
def foo(x):
18
+
return x + 1
19
+
"""
20
+
assert stripComments(code) == result
21
+
22
+
code = """\
23
+
def g(x):
24
+
# Here are some comments
25
+
# which must be removed
26
+
# by stripComments
27
+
return x * 7
28
+
"""
29
+
result = """\
30
+
def g(x):
31
+
return x * 7
32
+
"""
33
+
assert stripComments(code) == result
34
+
35
+
code = """\
36
+
def doIHaveAnyComments():
37
+
return 'No'
38
+
"""
39
+
result = """\
40
+
def doIHaveAnyComments():
41
+
return 'No'
42
+
"""
43
+
assert stripComments(code) == result
44
+
45
+
code = """\
46
+
def f(x):
47
+
#This function returns x + 5
48
+
return x + 5
49
+
"""
50
+
result = """\
51
+
def f(x):
52
+
return x + 5
53
+
"""
54
+
assert stripComments(code) == result
55
+
56
+
assert stripComments("") == ""
57
+
print("Passed!")
+25
python/oct2/level4/carrylessAdd.py
+25
python/oct2/level4/carrylessAdd.py
···
1
+
from typing import List
2
+
3
+
4
+
def carrylessAdd(x: int, y: int) -> int:
5
+
"""Add two numbers without carrying."""
6
+
pos: List[int] = []
7
+
while x > 0 or y > 0:
8
+
sum = (x % 10 + y % 10) % 10
9
+
pos.append(sum)
10
+
x //= 10
11
+
y //= 10
12
+
num = 0
13
+
for idx, val in enumerate(pos):
14
+
num += val * 10**idx
15
+
return num
16
+
17
+
18
+
print("Testing carrylessAdd()...", end="")
19
+
assert carrylessAdd(8, 7) == 5
20
+
assert carrylessAdd(785, 376) == 51
21
+
assert carrylessAdd(0, 325) == 325
22
+
assert carrylessAdd(30, 873) == 803
23
+
assert carrylessAdd(873, 30) == 803
24
+
assert carrylessAdd(100, 11) == 111
25
+
print("Passed!")
+28
python/oct2/level4/carrylessMultiply.py
+28
python/oct2/level4/carrylessMultiply.py
···
1
+
from typing import List
2
+
3
+
4
+
def carrylessMultiply(x: int, y: int) -> int:
5
+
"""Multiply 2 numbers without carrying"""
6
+
str_a = str(x)[::-1]
7
+
str_b = str(y)[::-1]
8
+
9
+
max_len = len(str_a) + len(str_b)
10
+
pos: List[int] = [0] * max_len
11
+
for i in range(len(str_a)):
12
+
for j in range(len(str_b)):
13
+
pnt = i + j
14
+
digit_a = int(str_a[i])
15
+
digit_b = int(str_b[j])
16
+
pos[pnt] += digit_a * digit_b
17
+
pos[pnt] %= 10
18
+
result_str = "".join(str(digit) for digit in pos[::-1]).lstrip("0") or "0"
19
+
return int(result_str)
20
+
21
+
22
+
print("Testing carrylessMultiply()...", end="")
23
+
assert carrylessMultiply(3, 3) == 9
24
+
assert carrylessMultiply(4, 4) == 6
25
+
assert carrylessMultiply(12345, 0) == 0
26
+
assert carrylessMultiply(643, 59) == 417
27
+
assert carrylessMultiply(6412, 387) == 807234
28
+
print("Passed!")
+51
python/oct2/level4/routeCipher.py
+51
python/oct2/level4/routeCipher.py
···
1
+
def encodeRouteCipher(message: str, rows: int) -> str:
2
+
"""Encode a message using the Route Cipher."""
3
+
cur_char = "z"
4
+
while len(message) % rows != 0:
5
+
message += cur_char
6
+
cur_char = chr(ord(cur_char) - 1)
7
+
str_cols = [message[i : i + rows] for i in range(0, len(message), rows)]
8
+
str_rows = ["".join(col) for col in zip(*str_cols)]
9
+
encoded_message = f"{rows}"
10
+
for idx, row in enumerate(str_rows):
11
+
if idx % 2 == 0:
12
+
encoded_message += row
13
+
else:
14
+
encoded_message += row[::-1]
15
+
return encoded_message
16
+
17
+
18
+
def decodeRouteCipher(encodedMessage: str) -> str:
19
+
"""Decode a message encoded using the Route Cipher."""
20
+
rows = int(encodedMessage[0])
21
+
rows_len = len(encodedMessage[1:]) // rows
22
+
encoded_message = encodedMessage[1:]
23
+
str_rows = [
24
+
encoded_message[i : i + rows_len]
25
+
for i in range(0, len(encoded_message), rows_len)
26
+
]
27
+
str_rows = [row[::-1] if idx % 2 == 1 else row for idx, row in enumerate(str_rows)]
28
+
str_cols = ["".join(row) for row in zip(*str_rows)]
29
+
decoded_string = "".join(str_cols)
30
+
while decoded_string[-1].islower():
31
+
decoded_string = decoded_string[:-1]
32
+
return decoded_string
33
+
34
+
35
+
print("Testing encodeRouteCipher()...", end="")
36
+
assert encodeRouteCipher("ASECRETMESSAGE", 4) == "4AREGESESETSzyAMC"
37
+
assert encodeRouteCipher("ASECRETMESSAGE", 3) == "3ACTSGESMRSEEEAz"
38
+
assert encodeRouteCipher("ASECRETMESSAGE", 5) == "5AESATSEMGEECRSz"
39
+
assert encodeRouteCipher("ANOTHERSECRET", 4) == "4AHETzCENORRyxEST"
40
+
41
+
42
+
print("Testing decodeRouteCipher()...", end="")
43
+
assert decodeRouteCipher("4AREGESESETSzyAMC") == "ASECRETMESSAGE"
44
+
assert decodeRouteCipher("3ACTSGESMRSEEEAz") == "ASECRETMESSAGE"
45
+
assert decodeRouteCipher("5AESATSEMGEECRSz") == "ASECRETMESSAGE"
46
+
assert decodeRouteCipher("4AHETzCENORRyxEST") == "ANOTHERSECRET"
47
+
message = "SECRETSTUFFGOESHERE"
48
+
encodedMessage = encodeRouteCipher(message, 6)
49
+
plaintext = decodeRouteCipher(encodedMessage)
50
+
assert plaintext == message
51
+
print("Passed!")
+63
python/oct2/level4/routeCipher2.py
+63
python/oct2/level4/routeCipher2.py
···
1
+
from math import ceil
2
+
from string import ascii_lowercase
3
+
4
+
5
+
def getIndex(row: int, col: int, rows: int) -> int:
6
+
return col * rows + row
7
+
8
+
9
+
def getRowAndCol(x: int, rows: int) -> tuple[int, int]:
10
+
return x // rows, x % rows
11
+
12
+
13
+
def encodeRouteCipher(message: str, rows: int) -> str:
14
+
row_len = ceil(len(message) / rows)
15
+
missing_chars = row_len * rows - len(message)
16
+
message += "".join(reversed(ascii_lowercase))[:missing_chars]
17
+
encoded_string = ""
18
+
for i in range(len(message)):
19
+
row, col = getRowAndCol(i, row_len)
20
+
encoded_string += message[getIndex(row, col, rows)]
21
+
encoded_message = f"{rows}"
22
+
for i in range(0, rows):
23
+
if i % 2 == 0:
24
+
encoded_message += encoded_string[i * row_len : (i + 1) * row_len]
25
+
else:
26
+
encoded_message += encoded_string[i * row_len : (i + 1) * row_len][::-1]
27
+
return encoded_message
28
+
29
+
30
+
def decodeRouteCipher(encodedMessage: str) -> str:
31
+
decoded_message = ""
32
+
rows = int(encodedMessage[0])
33
+
encoded_string = encodedMessage[1:]
34
+
row_len = ceil(len(encoded_string) / rows)
35
+
decoded_string = ""
36
+
for i in range(rows):
37
+
if i % 2 == 0:
38
+
decoded_string += encoded_string[i * row_len : (i + 1) * row_len]
39
+
else:
40
+
decoded_string += encoded_string[i * row_len : (i + 1) * row_len][::-1]
41
+
for i in range(len(decoded_string)):
42
+
row, col = getRowAndCol(i, rows)
43
+
decoded_message += decoded_string[getIndex(row, col, row_len)]
44
+
return decoded_message.rstrip(ascii_lowercase)
45
+
46
+
47
+
print("Testing encodeRouteCipher()...", end="")
48
+
assert encodeRouteCipher("ASECRETMESSAGE", 4) == "4AREGESESETSzyAMC"
49
+
assert encodeRouteCipher("ASECRETMESSAGE", 3) == "3ACTSGESMRSEEEAz"
50
+
assert encodeRouteCipher("ASECRETMESSAGE", 5) == "5AESATSEMGEECRSz"
51
+
assert encodeRouteCipher("ANOTHERSECRET", 4) == "4AHETzCENORRyxEST"
52
+
53
+
54
+
print("Testing decodeRouteCipher()...", end="")
55
+
assert decodeRouteCipher("4AREGESESETSzyAMC") == "ASECRETMESSAGE"
56
+
assert decodeRouteCipher("3ACTSGESMRSEEEAz") == "ASECRETMESSAGE"
57
+
assert decodeRouteCipher("5AESATSEMGEECRSz") == "ASECRETMESSAGE"
58
+
assert decodeRouteCipher("4AHETzCENORRyxEST") == "ANOTHERSECRET"
59
+
message = "SECRETSTUFFGOESHERE"
60
+
encodedMessage = encodeRouteCipher(message, 6)
61
+
plaintext = decodeRouteCipher(encodedMessage)
62
+
assert plaintext == message
63
+
print("Passed!")
+36
python/oct3/level0/date.py
+36
python/oct3/level0/date.py
···
1
+
def is_leap_year(yyyy: int) -> bool:
2
+
return not (yyyy % 4 or (yyyy % 400 and not yyyy % 100))
3
+
4
+
5
+
def sum_months(mm: int, yyyy: int) -> int:
6
+
months = [
7
+
31,
8
+
29 if is_leap_year(yyyy) else 28,
9
+
31,
10
+
30,
11
+
31,
12
+
30,
13
+
31,
14
+
31,
15
+
30,
16
+
31,
17
+
30,
18
+
31,
19
+
]
20
+
return sum(months[:mm])
21
+
22
+
23
+
def nth_day(s: str) -> int:
24
+
"""Calculate the nth day since the beginning of the year."""
25
+
mm, dd, yyyy = map(int, [s[:2], s[2:4], s[4:]])
26
+
day_since_new_year = dd + sum_months(mm - 1, yyyy)
27
+
return yyyy * 1000 + day_since_new_year
28
+
29
+
30
+
print("we're testing the nth day function...", end="")
31
+
assert nth_day("02072016") == 2016038
32
+
assert nth_day("12201996") == 1996355
33
+
# assert(nth_day("abcdef") == None)
34
+
assert nth_day("12312020") == 2020366
35
+
assert nth_day("12312021") == 2021365
36
+
print("and it passes the example cases")
+28
python/oct3/level1/isPalindromicList.py
+28
python/oct3/level1/isPalindromicList.py
···
1
+
from typing import Sequence
2
+
3
+
4
+
def isPalindromicList(L: Sequence) -> bool:
5
+
"""Check if a list is palindromic."""
6
+
for i in range(len(L) // 2):
7
+
if L[i] != L[-i - 1]:
8
+
return False
9
+
return True
10
+
11
+
12
+
print("Testing isPalindromicList()...", end="")
13
+
assert isPalindromicList([1, 2, 2, 1]) == True
14
+
assert isPalindromicList([1, 2, 3, 1]) == False
15
+
assert isPalindromicList([1]) == True
16
+
assert isPalindromicList([5.0, -1, True, "hey", True, -1, 5.0]) == True
17
+
assert isPalindromicList([0, "hi", False, False, 4.0]) == False
18
+
assert isPalindromicList([]) == True
19
+
20
+
# Verify function is non-mutating
21
+
L = [1, 2, 1]
22
+
assert isPalindromicList(L) == True
23
+
assert L == [1, 2, 1]
24
+
25
+
L = [1, 9, 8]
26
+
assert isPalindromicList(L) == False
27
+
assert L == [1, 9, 8]
28
+
print("Passed!")
+24
python/oct3/level1/isRotation.py
+24
python/oct3/level1/isRotation.py
···
1
+
from typing import Sequence
2
+
3
+
4
+
def isRotation(L: Sequence, M: Sequence) -> bool:
5
+
"""Check if M is a rotation of L."""
6
+
L = list(L)
7
+
M = list(M)
8
+
if not len(L) == len(M):
9
+
return False
10
+
if not L and not M:
11
+
return True
12
+
for i in range(len(L)):
13
+
if L[i:] + L[:i] == M:
14
+
return True
15
+
return False
16
+
17
+
18
+
print("Testing isRotation()...", end="")
19
+
assert isRotation([2, 3, 4, 5, 6], [4, 5, 6, 2, 3]) == True
20
+
assert isRotation([2, 3, 4, 5, 6], [2, 3, 4, 5, 6]) == True
21
+
assert isRotation([2, 3, 4, 5, 6], [2, 4, 3, 5, 6]) == False
22
+
assert isRotation([1], []) == False
23
+
assert isRotation([], []) == True
24
+
print("Passed!")
+34
python/oct3/level1/median.py
+34
python/oct3/level1/median.py
···
1
+
from typing import Sequence
2
+
3
+
4
+
def almostEqual(x, y):
5
+
return abs(x - y) < 10**-9
6
+
7
+
8
+
def median(L: Sequence[float]) -> float | None:
9
+
"""Return the median of a sequence of numbers."""
10
+
L = list(L)
11
+
if not len(L):
12
+
return None
13
+
L.sort()
14
+
mid = len(L) // 2
15
+
if len(L) % 2 == 1:
16
+
return L[mid]
17
+
else:
18
+
return (L[mid - 1] + L[mid]) / 2
19
+
20
+
21
+
print("Testing median()...", end="")
22
+
assert median([42]) == 42
23
+
assert almostEqual(median([1, 2]), 1.5)
24
+
assert median([-1.7, 3, -2, 4.2, 2]) == 2
25
+
assert median([2, 3, 2, 4, 2]) == 2
26
+
assert almostEqual(median([2, 3, 2, 4, 2, 3]), 2.5)
27
+
assert median([]) == None
28
+
29
+
# Verify function is non-mutating
30
+
L = [1, 3, 2]
31
+
assert median(L) == 2
32
+
assert L == [1, 3, 2]
33
+
34
+
print("Passed!")
+46
python/oct3/level1/missingValue.py
+46
python/oct3/level1/missingValue.py
···
1
+
from typing import Sequence, TypeVar
2
+
3
+
4
+
T = TypeVar("T", bound=str | float)
5
+
6
+
7
+
def missingValue(L: Sequence[T], M: Sequence[T]) -> T | None:
8
+
"""Return the missing value from one of the two sorted sequences."""
9
+
if (not L) and M:
10
+
return M[0]
11
+
elif (not M) and L:
12
+
return L[0]
13
+
elif (not L) and (not M):
14
+
return None
15
+
L = sorted(L)
16
+
M = sorted(M)
17
+
larger = L if len(L) > len(M) else M
18
+
smaller = L if len(L) < len(M) else M
19
+
for i in range(len(larger)):
20
+
a = larger[i]
21
+
if i > (len(smaller) - 1):
22
+
return a
23
+
b = smaller[i]
24
+
if a != b:
25
+
return larger[i]
26
+
return None
27
+
28
+
29
+
print("Testing missingValue()...", end="")
30
+
assert missingValue([1, 2, 3], [3, 1]) == 2
31
+
assert missingValue([1, 2, 3], [2, 1]) == 3
32
+
assert missingValue(["A", "B", "A", "C"], ["C", "A", "B"]) == "A"
33
+
assert missingValue(["A", "B", "A", "C"], ["A", "A", "B"]) == "C"
34
+
assert missingValue(["A", "B", "A", "C"], ["A", "C", "A"]) == "B"
35
+
assert missingValue([2, 2], [2]) == 2
36
+
assert missingValue([2, 3], [2]) == 3
37
+
assert missingValue([2, 3], [3]) == 2
38
+
assert missingValue(["amazing"], []) == "amazing"
39
+
40
+
# Make sure the function is non-mutating
41
+
L = [1, 2, 3]
42
+
M = [3, 1]
43
+
assert missingValue(L, M) == 2
44
+
assert L == [1, 2, 3]
45
+
assert M == [3, 1]
46
+
print("Passed!")
+20
python/oct3/level1/sortedEvens.py
+20
python/oct3/level1/sortedEvens.py
···
1
+
from typing import Sequence
2
+
3
+
4
+
def sortedEvens(L: Sequence[int]) -> Sequence[int]:
5
+
"""Return a sorted list of even numbers from the input sequence."""
6
+
return sorted([x for x in L if x % 2 == 0])
7
+
8
+
9
+
print("Testing sortedEvens()...", end="")
10
+
assert sortedEvens([1, 4, 3, 2, 5]) == [2, 4]
11
+
assert sortedEvens([1, 3, 2, 5]) == [2]
12
+
assert sortedEvens([6, 4, -2, 8, 4, 0, 9]) == [-2, 0, 4, 4, 6, 8]
13
+
assert sortedEvens([1, 3, 5]) == []
14
+
assert sortedEvens([]) == []
15
+
16
+
# confirm this is non-mutating:
17
+
L = [1, 4, 3, 2, 5]
18
+
assert sortedEvens([1, 4, 3, 2, 5]) == [2, 4]
19
+
assert L == [1, 4, 3, 2, 5]
20
+
print("Passed!")
+40
python/oct3/level2/isNearlySorted.py
+40
python/oct3/level2/isNearlySorted.py
···
1
+
from typing import Sequence, Literal, Set, Tuple
2
+
from copy import deepcopy
3
+
4
+
5
+
def isNearlySorted(L: Sequence[float]) -> Literal[False] | Sequence[float]:
6
+
"""Returns a tuple of indices that need to be swapped to sort the list, or False if it cannot be sorted with a single swap."""
7
+
if not len(L):
8
+
return False
9
+
sortPath: Set[Tuple[int, int]] = set()
10
+
sortedList = list(deepcopy(L))
11
+
for i in range(len(L) - 1):
12
+
for j in range(len(L) - 1, i, -1):
13
+
a = sortedList[i]
14
+
b = sortedList[j]
15
+
if a > b:
16
+
sortedList[i], sortedList[j] = b, a
17
+
sortPath.add((i, j))
18
+
if len(sortPath) > 1:
19
+
return False
20
+
if len(sortPath) == 1:
21
+
return list(sortPath.pop())
22
+
return False
23
+
24
+
25
+
print("Testing isNearlySorted()...", end="")
26
+
assert isNearlySorted([2, 7, 5, 4]) == [1, 3]
27
+
assert isNearlySorted([2, 3, 1]) == False
28
+
assert isNearlySorted([1, 2, 3]) == False
29
+
assert isNearlySorted([-1, 1, 0, 5]) == [1, 2]
30
+
assert isNearlySorted([5, 6, 9, 8, 8]) == [2, 4]
31
+
assert isNearlySorted([2, 1]) == [0, 1]
32
+
assert isNearlySorted([3, 3]) == False
33
+
assert isNearlySorted([2]) == False
34
+
assert isNearlySorted([]) == False
35
+
36
+
# Verify that the function is non-mutating
37
+
L = [2, 7, 5, 4]
38
+
isNearlySorted(L)
39
+
assert L == [2, 7, 5, 4]
40
+
print("Passed!")
+40
python/oct3/level2/repeatingPattern.py
+40
python/oct3/level2/repeatingPattern.py
···
1
+
from typing import List, Optional, TypeVar
2
+
3
+
4
+
T = TypeVar("T")
5
+
6
+
7
+
def repeatingPattern(L: List[T]) -> Optional[List[T]]:
8
+
"""Returns the smallest repeating pattern of a list or None if no pattern exists."""
9
+
if len(L) < 2:
10
+
return None
11
+
repeat_options = list(
12
+
reversed([i for i in range(2, max(len(L) // 2 + 1, 3)) if len(L) % i == 0])
13
+
)
14
+
M = [L[0]]
15
+
for repeat in repeat_options:
16
+
M = L[: len(L) // repeat]
17
+
if M * repeat == L:
18
+
return M
19
+
if len(M) > len(L):
20
+
return None
21
+
return None
22
+
23
+
24
+
print("Testing repeatingPattern()...", end="")
25
+
assert repeatingPattern([1, 2, 1, 2]) == [1, 2]
26
+
assert repeatingPattern([1, 2, 1, 2, 1, 2, 1, 2]) == [1, 2]
27
+
assert repeatingPattern([1, 2, 3, 1, 2, 3]) == [1, 2, 3]
28
+
assert repeatingPattern([1, 1]) == [1]
29
+
assert repeatingPattern([]) == None
30
+
assert repeatingPattern([42]) == None
31
+
assert repeatingPattern([1, 2, 3, 1, 2]) == None
32
+
assert repeatingPattern([121, 212, 12]) == None
33
+
L = [1, 2, 3, 4]
34
+
assert repeatingPattern(L * 20) == L
35
+
36
+
# Finally, verify that the function is non-mutating
37
+
L = [1, 2, 1, 2]
38
+
repeatingPattern(L)
39
+
assert L == [1, 2, 1, 2]
40
+
print("Passed!")
+37
python/oct3/level2/sortEvens.py
+37
python/oct3/level2/sortEvens.py
···
1
+
from typing import List
2
+
3
+
4
+
def sortEvens(L: List[int]):
5
+
"""Sorts the even elements of a list in ascending order."""
6
+
modifiable_indecies = [i for i, x in enumerate(L) if x % 2 == 0]
7
+
sorted_evens = sorted([L[i] for i in modifiable_indecies])
8
+
9
+
for i, x in enumerate(sorted_evens):
10
+
L[modifiable_indecies[i]] = x
11
+
12
+
13
+
print("Testing sortEvens()...", end="")
14
+
L = [1, 8, 4, 9, -2, 7]
15
+
assert sortEvens(L) == None
16
+
assert L == [1, -2, 4, 9, 8, 7]
17
+
18
+
L = [1, 4, 3, 2, 5]
19
+
assert sortEvens(L) == None
20
+
assert L == [1, 2, 3, 4, 5]
21
+
22
+
L = [1, 3, 2, 5]
23
+
assert sortEvens(L) == None
24
+
assert L == [1, 3, 2, 5]
25
+
26
+
L = [3, 6, 4, -2, 8, 4, 0, 5]
27
+
assert sortEvens(L) == None
28
+
assert L == [3, -2, 0, 4, 4, 6, 8, 5]
29
+
30
+
L = [1, 3, 5]
31
+
assert sortEvens(L) == None
32
+
assert L == [1, 3, 5]
33
+
34
+
L = []
35
+
assert sortEvens(L) == None
36
+
assert L == []
37
+
print("Passed!")
+57
python/oct3/level2/subCipher.py
+57
python/oct3/level2/subCipher.py
···
1
+
from string import ascii_lowercase, ascii_uppercase
2
+
3
+
4
+
def encodeSubstitutionCipher(msg: str, key: str) -> str:
5
+
"""Encode a message using a substitution cipher."""
6
+
unique = set(msg)
7
+
for c in unique:
8
+
if c.isalpha():
9
+
if c.isupper():
10
+
encoded = key[ord(c) - ord("A")]
11
+
else:
12
+
encoded = key[ord(c) - ord("a")]
13
+
encoded = encoded.lower()
14
+
msg = msg.replace(c, encoded)
15
+
return msg
16
+
17
+
18
+
def decodeSubstitutionCipher(encodedMsg: str, key: str) -> str:
19
+
"""Decode a message using a substitution cipher."""
20
+
unique = set(encodedMsg)
21
+
for c in unique:
22
+
if c.isalpha():
23
+
if c.isupper():
24
+
decoded = ascii_uppercase[key.index(c.upper())]
25
+
else:
26
+
decoded = ascii_lowercase[key.index(c.upper())]
27
+
encodedMsg = encodedMsg.replace(c, decoded)
28
+
return encodedMsg
29
+
30
+
31
+
print("Testing encodeSubstitutionCipher()...", end="")
32
+
assert encodeSubstitutionCipher("CAB", "SQGYFEZXLANKJIMPURDCWTHVOB") == "GSQ"
33
+
assert encodeSubstitutionCipher("Cab Z?", "SQGYFEZXLANKJIMPURDCWTHVOB") == "Gsq B?"
34
+
assert (
35
+
encodeSubstitutionCipher("Hello, World!", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
36
+
== "Hello, World!"
37
+
)
38
+
assert (
39
+
encodeSubstitutionCipher("42 - 0 = 42", "XHNSBMTOPQWGZDCEAVLKJYIURF")
40
+
== "42 - 0 = 42"
41
+
)
42
+
assert encodeSubstitutionCipher("", "XHNSBMTOPQWGZDCEAVLKJYIURF") == ""
43
+
44
+
45
+
print("Testing decodeSubstitutionCipher()...", end="")
46
+
assert decodeSubstitutionCipher("GSQ", "SQGYFEZXLANKJIMPURDCWTHVOB") == "CAB"
47
+
assert decodeSubstitutionCipher("Gsq B?", "SQGYFEZXLANKJIMPURDCWTHVOB") == "Cab Z?"
48
+
assert (
49
+
decodeSubstitutionCipher("Hello, World!", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
50
+
== "Hello, World!"
51
+
)
52
+
assert (
53
+
decodeSubstitutionCipher("42 - 0 = 42", "XHNSBMTOPQWGZDCEAVLKJYIURF")
54
+
== "42 - 0 = 42"
55
+
)
56
+
assert decodeSubstitutionCipher("", "XHNSBMTOPQWGZDCEAVLKJYIURF") == ""
57
+
print("Passed!")
+44
python/oct3/level3/bowlingScore.py
+44
python/oct3/level3/bowlingScore.py
···
1
+
from typing import List
2
+
3
+
4
+
def bowlingScore(frames: List[int]) -> int:
5
+
"""Calculate the total score of a bowling game."""
6
+
scores: List[int] = []
7
+
next_frame = 0
8
+
for i in range(10):
9
+
frame_score = 0
10
+
if frames[next_frame] == 10:
11
+
frame_score += 10
12
+
if i < 10:
13
+
frame_score += frames[next_frame + 1]
14
+
if frames[i + 1] == 10:
15
+
frame_score += frames[next_frame + 2]
16
+
else:
17
+
frame_score += frames[next_frame + 2]
18
+
next_frame += 1
19
+
elif frames[next_frame] + frames[next_frame + 1] == 10:
20
+
frame_score += 10
21
+
frame_score += frames[next_frame + 2]
22
+
next_frame += 2
23
+
else:
24
+
frame_score += frames[next_frame] + frames[next_frame + 1]
25
+
next_frame += 2
26
+
scores.append(frame_score)
27
+
return sum(scores)
28
+
29
+
30
+
print("Testing bowlingScore()...", end="")
31
+
assert bowlingScore([10] * 12) == 300
32
+
assert bowlingScore([7, 2, 8, 2, 10, 7, 1, 8, 2, 7, 3, 10, 10, 5, 4, 8, 2, 7]) == 162
33
+
assert bowlingScore([2, 6, 2, 6, 9, 1, 10, 10, 10, 5, 1, 4, 5, 9, 0, 8, 1]) == 140
34
+
assert (
35
+
bowlingScore([6, 4, 2, 7, 8, 1, 2, 4, 6, 3, 10, 6, 2, 1, 9, 6, 4, 10, 10, 10])
36
+
== 137
37
+
)
38
+
assert bowlingScore([8, 1, 5, 3, 4, 3, 0, 8, 9, 0, 8, 1, 3, 6, 1, 8, 5, 4, 7, 1]) == 85
39
+
40
+
# Finally, verify that the function is non-mutating
41
+
L = [7, 2, 8, 2, 10, 7, 1, 8, 2, 7, 3, 10, 10, 5, 4, 8, 2, 7]
42
+
bowlingScore(L)
43
+
assert L == [7, 2, 8, 2, 10, 7, 1, 8, 2, 7, 3, 10, 10, 5, 4, 8, 2, 7]
44
+
print("Passed!")
+45
python/oct3/level3/solvesCryptarithm.py
+45
python/oct3/level3/solvesCryptarithm.py
···
1
+
def solvesCryptarithm(puzzle: str, solution: str) -> bool:
2
+
"""Check if a cryptarithm puzzle is solved correctly.
3
+
4
+
Args:
5
+
puzzle (str): The cryptarithm puzzle.
6
+
solution (str): The solution to the puzzle.
7
+
8
+
Returns:
9
+
bool: True if the puzzle is solved correctly, False otherwise.
10
+
"""
11
+
left = puzzle.split(" + ")[0].strip()
12
+
right = puzzle.split(" + ")[1].split(" = ")[0].strip()
13
+
result = puzzle.split(" + ")[1].split(" = ")[1].strip()
14
+
scores = list(solution)
15
+
for score, letter in enumerate(scores):
16
+
print(score, letter)
17
+
left = left.replace(letter, str(score))
18
+
right = right.replace(letter, str(score))
19
+
result = result.replace(letter, str(score))
20
+
print(left, right, result)
21
+
try:
22
+
return int(left) + int(right) == int(result)
23
+
except ValueError:
24
+
return False
25
+
26
+
27
+
print("Testing solvesCryptarithm()...", end="")
28
+
# 9567 + 1085 = 10652
29
+
assert solvesCryptarithm("SEND + MORE = MONEY", "OMY--ENDRS") == True
30
+
31
+
# 201689 + 201689 = 403378
32
+
assert solvesCryptarithm("NUMBER + NUMBER = PUZZLE", "UMNZP-BLER") == True
33
+
34
+
# 91542 + 3077542 = 3169084
35
+
assert solvesCryptarithm("TILES + PUZZLES = PICTURE", "UISPELCZRT") == True
36
+
37
+
# 8456 + 1074 = 10542 (False)
38
+
assert solvesCryptarithm("SEND + MORE = MONEY", "OMY-ENDRS") == False
39
+
40
+
# 9567 + 1085 = 1062 (False)
41
+
assert solvesCryptarithm("SEND + MORE = MONY", "OMY--ENDRS") == False
42
+
43
+
# No S in solution
44
+
assert solvesCryptarithm("SEND + MORE = MONEY", "OMY--ENDR-") == False
45
+
print("Passed!")
+52
python/oct3/level3/topScorer.py
+52
python/oct3/level3/topScorer.py
···
1
+
from typing import List, Tuple
2
+
3
+
4
+
def topScorer(data: str) -> str | None:
5
+
"""Return the name(s) of the student(s) with the highest total score."""
6
+
lines = [line for line in map(lambda line: line.strip(), data.split("\n")) if line]
7
+
max_score: List[Tuple[str, int]] = []
8
+
for line in lines:
9
+
name, *scores = line.split(",")
10
+
scores = list(map(int, scores))
11
+
if not scores:
12
+
max_score.append((name, 0))
13
+
else:
14
+
max_score.append((name, sum(scores)))
15
+
max_score.sort(key=lambda x: x[1], reverse=True)
16
+
if not max_score:
17
+
return None
18
+
names = [name for name, score in max_score if score == max_score[0][1]]
19
+
return ",".join(names)
20
+
21
+
22
+
print("Testing topScorer()...", end="")
23
+
data = """\
24
+
Joe,10,20,30,40
25
+
Lauren,10,20,30
26
+
Ben,10,20,30,5
27
+
"""
28
+
assert topScorer(data) == "Joe"
29
+
30
+
data = """\
31
+
David,11,20,30
32
+
Austin,10,20,30,1
33
+
Lauren,50
34
+
"""
35
+
assert topScorer(data) == "David,Austin"
36
+
37
+
data = """\
38
+
Ping-Ya,100,80,90
39
+
"""
40
+
assert topScorer(data) == "Ping-Ya"
41
+
42
+
data = """\
43
+
Reyna,20,40,40
44
+
Ema,5,5,5,5,10
45
+
Ketandu,50,20,10,20
46
+
Tanya,80,20
47
+
Kate,70
48
+
"""
49
+
assert topScorer(data) == "Reyna,Ketandu,Tanya"
50
+
51
+
assert topScorer("") == None
52
+
print("Passed!")
+126
python/oct3/level4/getEvalSteps.py
+126
python/oct3/level4/getEvalSteps.py
···
1
+
from typing import List
2
+
3
+
4
+
def getMostImportantOpIndex(parts: List[str]) -> int:
5
+
"""Returns the index of the most important operator in the given list of parts."""
6
+
index = len(parts) + 1
7
+
higher_order_operator = 0
8
+
9
+
if "^" in parts:
10
+
higher_order_operator = 2
11
+
index = parts.index("^")
12
+
if "*" in parts and higher_order_operator < 2 and parts.index("*") < index:
13
+
higher_order_operator = 1
14
+
index = parts.index("*")
15
+
if "/" in parts and higher_order_operator < 2 and parts.index("/") < index:
16
+
higher_order_operator = 1
17
+
index = parts.index("/")
18
+
if "%" in parts and higher_order_operator < 2 and parts.index("%") < index:
19
+
higher_order_operator = 1
20
+
index = parts.index("%")
21
+
if "+" in parts and higher_order_operator < 1 and parts.index("+") < index:
22
+
index = parts.index("+")
23
+
if "-" in parts and higher_order_operator < 1 and parts.index("-") < index:
24
+
index = parts.index("-")
25
+
return index
26
+
27
+
28
+
def runStep(a: str, sign: str, b: str) -> str:
29
+
"""Runs a single step of the evaluation process."""
30
+
if sign == "+":
31
+
return str(int(a) + int(b))
32
+
elif sign == "-":
33
+
return str(int(a) - int(b))
34
+
elif sign == "*":
35
+
return str(int(a) * int(b))
36
+
elif sign == "/":
37
+
return str(int(a) // int(b))
38
+
elif sign == "%":
39
+
return str(int(a) % int(b))
40
+
elif sign == "^":
41
+
return str(int(a) ** int(b))
42
+
else:
43
+
raise ValueError("Invalid operator")
44
+
45
+
46
+
def getEvalSteps(expr: str) -> str:
47
+
"""Returns a string containing the evaluation steps of the given expression."""
48
+
padding = " " * (len(expr) + 1) + "= "
49
+
50
+
expr = expr.strip()
51
+
expr = expr.replace(" ", "")
52
+
expr = expr.replace("**", "^")
53
+
expr = expr.replace("//", "/")
54
+
steps = [expr]
55
+
56
+
expr_parts = []
57
+
cur_piece = ""
58
+
for c in expr:
59
+
if c.isdigit():
60
+
cur_piece += c
61
+
else:
62
+
expr_parts.append(cur_piece)
63
+
expr_parts.append(c)
64
+
cur_piece = ""
65
+
expr_parts.append(cur_piece)
66
+
while len(expr_parts) > 1:
67
+
next_op = getMostImportantOpIndex(expr_parts)
68
+
left = expr_parts.pop(next_op - 1)
69
+
op = expr_parts.pop(next_op - 1)
70
+
right = expr_parts.pop(next_op - 1)
71
+
val = runStep(left, op, right)
72
+
expr_parts.insert(next_op - 1, val)
73
+
steps.append("".join(expr_parts))
74
+
steps[0] = steps[0] + " = " + steps.pop(1)
75
+
steps = [step.replace("^", "**") for step in steps]
76
+
steps = [step.replace("/", "//") for step in steps]
77
+
steps = [padding + step if not i == 0 else step for i, step in enumerate(steps)]
78
+
final = "\n".join(steps) + "\n"
79
+
return final
80
+
81
+
82
+
def testGetEvalSteps():
83
+
print("Testing getEvalSteps()...", end="")
84
+
assert (
85
+
getEvalSteps("2+3*4-8**3%3")
86
+
== """\
87
+
2+3*4-8**3%3 = 2+3*4-512%3
88
+
= 2+12-512%3
89
+
= 2+12-2
90
+
= 14-2
91
+
= 12
92
+
"""
93
+
)
94
+
assert (
95
+
getEvalSteps("2**2**3+4-2//3")
96
+
== """\
97
+
2**2**3+4-2//3 = 4**3+4-2//3
98
+
= 64+4-2//3
99
+
= 64+4-0
100
+
= 68-0
101
+
= 68
102
+
"""
103
+
)
104
+
assert (
105
+
getEvalSteps("4//2**2*8-3+2")
106
+
== """\
107
+
4//2**2*8-3+2 = 4//4*8-3+2
108
+
= 1*8-3+2
109
+
= 8-3+2
110
+
= 5+2
111
+
= 7
112
+
"""
113
+
)
114
+
assert (
115
+
getEvalSteps("2**4//2-3+1")
116
+
== """\
117
+
2**4//2-3+1 = 16//2-3+1
118
+
= 8-3+1
119
+
= 5+1
120
+
= 6
121
+
"""
122
+
)
123
+
print("Passed!")
124
+
125
+
126
+
testGetEvalSteps()
+140
python/oct3/level4/runSimpleProgram.py
+140
python/oct3/level4/runSimpleProgram.py
···
1
+
from typing import List
2
+
3
+
4
+
def findLabel(lines: List[List[str]], label: str) -> int:
5
+
"""Find the line number of a label"""
6
+
for i, line in enumerate(lines):
7
+
if line[0] == label + ":":
8
+
return i
9
+
raise ValueError(f"Label '{label}' not found")
10
+
11
+
12
+
def parse(args: List[int], values: List[int], arg: str) -> int:
13
+
"""Parse an argument string into an integer value"""
14
+
arg_val = 0
15
+
16
+
if arg.isdigit():
17
+
arg_val = int(arg)
18
+
elif arg.startswith("L"):
19
+
arg_val = values[int(arg[1:])]
20
+
elif arg.startswith("A"):
21
+
arg_val = args[int(arg[1:])]
22
+
23
+
return arg_val
24
+
25
+
26
+
def runL(args: List[int], values: List[int], line: List[str]):
27
+
"""Run a Set command"""
28
+
out_var = int(line[0][1:])
29
+
op = line[1]
30
+
31
+
if out_var >= len(values):
32
+
values.extend([0] * (out_var - len(values) + 1))
33
+
34
+
if op.isdigit():
35
+
values[out_var] = int(op)
36
+
else:
37
+
var1, var2 = line[2], line[3]
38
+
var1_val, var2_val = parse(args, values, var1), parse(args, values, var2)
39
+
if op == "+":
40
+
values[out_var] = var1_val + var2_val
41
+
elif op == "-":
42
+
values[out_var] = var1_val - var2_val
43
+
else:
44
+
raise ValueError(f"Invalid Operator: {op}")
45
+
46
+
47
+
def handleTargetError(target: int, lines: List[List[str]]) -> int | None:
48
+
if target < 0 or target >= len(lines):
49
+
raise ValueError(f"Invalid Jump Target: {target}")
50
+
return target
51
+
52
+
53
+
def runJMP(args: List[int], values: List[int], line: List[str], lines) -> int | None:
54
+
"""Return the target line number or None for a JMP command"""
55
+
command = line[0]
56
+
if command == "JMP":
57
+
jump_targ = line[1]
58
+
if jump_targ.isdigit():
59
+
target = int(line[1])
60
+
return handleTargetError(target, lines)
61
+
else:
62
+
target = findLabel(lines, line[1])
63
+
return handleTargetError(target, lines)
64
+
else:
65
+
expr = line[1]
66
+
if command[3] == "+":
67
+
var = int(expr[1])
68
+
if values[var] > 0:
69
+
target = findLabel(lines, line[2])
70
+
return handleTargetError(target, lines)
71
+
return None
72
+
elif command[3] == "0":
73
+
var = int(expr[1])
74
+
if values[var] == 0:
75
+
target = findLabel(lines, line[2])
76
+
return handleTargetError(target, lines)
77
+
return None
78
+
else:
79
+
raise ValueError(f"Invalid Jump Target: {line[0]}")
80
+
81
+
82
+
def runSimpleProgram(program: str, args: List[int]):
83
+
"""Run a simple program with given arguments."""
84
+
lines = program.splitlines()
85
+
lines = [line.strip() for line in lines if line.strip()]
86
+
lines = [line.split() for line in lines if not line.startswith("!")]
87
+
88
+
values: List[int] = []
89
+
i = 0
90
+
return_value = None
91
+
while not return_value:
92
+
line = lines[i]
93
+
i += 1
94
+
# print(f"executing {i}: {line}, L: {values}, A: {args}")
95
+
command = line[0]
96
+
if ":" in line:
97
+
continue
98
+
if command.startswith("L"):
99
+
runL(args, values, line)
100
+
elif command.startswith("JMP"):
101
+
jump_to = runJMP(args, values, line, lines)
102
+
if jump_to != None:
103
+
i = jump_to
104
+
elif command == "RTN":
105
+
val = line[1]
106
+
# print(f"RTN {val} from A: {args}, L: {values}")
107
+
return_value = parse(args, values, val)
108
+
break
109
+
return return_value
110
+
111
+
112
+
def testRunSimpleProgram():
113
+
print("Testing runSimpleProgram()...", end="")
114
+
largest = """! largest: Returns max(A0, A1)
115
+
L0 - A0 A1
116
+
JMP+ L0 a0
117
+
RTN A1
118
+
a0:
119
+
RTN A0"""
120
+
assert runSimpleProgram(largest, [5, 6]) == 6
121
+
assert runSimpleProgram(largest, [6, 5]) == 6
122
+
123
+
sumToN = """! SumToN: Returns 1 + ... + A0
124
+
! L0 is a counter, L1 is the result
125
+
L0 0
126
+
L1 0
127
+
loop:
128
+
L2 - L0 A0
129
+
JMP0 L2 done
130
+
L0 + L0 1
131
+
L1 + L1 L0
132
+
JMP loop
133
+
done:
134
+
RTN L1"""
135
+
assert runSimpleProgram(sumToN, [5]) == 1 + 2 + 3 + 4 + 5
136
+
assert runSimpleProgram(sumToN, [10]) == 10 * 11 // 2
137
+
print("Passed!")
138
+
139
+
140
+
testRunSimpleProgram()
+192
python/oct6/blackjack/main.py
+192
python/oct6/blackjack/main.py
···
1
+
from cmu_graphics.cmu_graphics import AppWrapper
2
+
from cmu_graphics import *
3
+
import random
4
+
from types import SimpleNamespace
5
+
from sys import exit
6
+
from calendar import c
7
+
8
+
9
+
def onAppStart(app_inst: AppWrapper):
10
+
app_inst.status = ""
11
+
app_inst.deck = makeRandomDeck()
12
+
app_inst.playerHand = [app_inst.deck.pop(), app_inst.deck.pop()]
13
+
app_inst.dealerHand = [app_inst.deck.pop(), app_inst.deck.pop()]
14
+
app_inst.dealerHand[0].hidden = True
15
+
app_inst.playerDrawing = True
16
+
17
+
18
+
def getScore(hand):
19
+
score = 0
20
+
aces = 0
21
+
for card in hand:
22
+
if card.rank == "Ace":
23
+
aces += 1
24
+
score += 11
25
+
elif card.rank in ["Jack", "Queen", "King"]:
26
+
score += 10
27
+
else:
28
+
score += int(card.rank)
29
+
while score > 21 and aces > 0:
30
+
score -= 10
31
+
aces -= 1
32
+
return score
33
+
34
+
35
+
def onKeyPress(app_inst: AppWrapper, key):
36
+
if key == "h" and app_inst.playerDrawing:
37
+
app_inst.playerHand.append(app_inst.deck.pop())
38
+
elif key == "s":
39
+
app_inst.playerDrawing = False
40
+
app_inst.dealerHand[0].hidden = False
41
+
app_inst.dealerHand[1].hidden = False
42
+
elif key == "r":
43
+
onAppStart(app_inst)
44
+
return
45
+
elif key == "q":
46
+
exit()
47
+
48
+
dealer_score = getScore(app_inst.dealerHand)
49
+
player_score = getScore(app_inst.playerHand)
50
+
if player_score > 21:
51
+
app_inst.status = "loss"
52
+
app_inst.playerDrawing = False
53
+
return
54
+
55
+
if app_inst.playerDrawing:
56
+
if dealer_score < 17:
57
+
app_inst.dealerHand.append(app_inst.deck.pop())
58
+
else:
59
+
while dealer_score < 17:
60
+
app_inst.dealerHand.append(app_inst.deck.pop())
61
+
if dealer_score > 21:
62
+
app_inst.status = "win"
63
+
app_inst.playerDrawing = False
64
+
return
65
+
if not app_inst.playerDrawing and dealer_score >= 17:
66
+
if player_score > dealer_score:
67
+
app_inst.status = "win"
68
+
elif player_score < dealer_score:
69
+
app_inst.status = "loss"
70
+
else:
71
+
if len(app_inst.dealerHand) < len(app_inst.playerHand):
72
+
app_inst.status = "win"
73
+
else:
74
+
app_inst.status = "loss"
75
+
76
+
77
+
def redrawAll(app_inst: AppWrapper):
78
+
print(app_inst.status, getScore(app_inst.dealerHand), getScore(app_inst.playerHand))
79
+
match app_inst.status:
80
+
case "win":
81
+
app_inst.dealerHand[0].hidden = False
82
+
drawLabel(
83
+
f"Dealer's score: {getScore(app_inst.dealerHand)}",
84
+
app_inst.width // 2,
85
+
30,
86
+
)
87
+
drawRect(
88
+
app_inst.width // 2 - 100,
89
+
app_inst.height // 2 - 50,
90
+
200,
91
+
100,
92
+
fill="lightGreen",
93
+
)
94
+
drawLabel("You Win!", app_inst.width // 2, app_inst.height // 2)
95
+
case "loss":
96
+
app_inst.dealerHand[0].hidden = False
97
+
drawLabel(
98
+
f"Dealer's score: {getScore(app_inst.dealerHand)}",
99
+
app_inst.width // 2,
100
+
30,
101
+
)
102
+
drawRect(
103
+
app_inst.width // 2 - 100,
104
+
app_inst.height // 2 - 50,
105
+
200,
106
+
100,
107
+
fill="pink",
108
+
)
109
+
drawLabel(
110
+
"You Lose!", app_inst.width // 2, app_inst.height // 2, fill="red"
111
+
)
112
+
case _:
113
+
pass
114
+
115
+
dealer_card_count = len(app_inst.dealerHand)
116
+
for i, card in enumerate(app_inst.dealerHand):
117
+
drawCard(
118
+
app_inst,
119
+
card,
120
+
app_inst.width // 2 + 25 * dealer_card_count // 2 - i * 37.5,
121
+
60 + i * 2,
122
+
)
123
+
124
+
drawLabel(
125
+
f"Score: {getScore(app_inst.playerHand)}",
126
+
app_inst.width // 2,
127
+
app_inst.height * 3 // 4 - 20,
128
+
size=18,
129
+
)
130
+
card_count = len(app_inst.playerHand)
131
+
for i, card in enumerate(app_inst.playerHand):
132
+
drawCard(
133
+
app_inst,
134
+
card,
135
+
app_inst.width // 2 + 25 * card_count // 2 - i * 37.5,
136
+
app_inst.height - 60 + i * 2,
137
+
)
138
+
pass
139
+
140
+
141
+
def getSuitLabelAndColor(suit):
142
+
if suit[0] == "C":
143
+
return "โฃ", "black"
144
+
elif suit[0] == "D":
145
+
return "โฆ", "red"
146
+
elif suit[0] == "H":
147
+
return "โฅ", "red"
148
+
else:
149
+
return "โ ", "black"
150
+
151
+
152
+
def makeRandomDeck():
153
+
# first make a sorted deck
154
+
ranks = "Ace,2,3,4,5,6,7,8,9,10,Jack,Queen,King".split(",")
155
+
suits = "Clubs,Diamonds,Hearts,Spades".split(",")
156
+
deck = [makeCard(rank, suit) for rank in ranks for suit in suits]
157
+
# now shuffle and return the deck
158
+
random.shuffle(deck)
159
+
print([card.rank for card in deck])
160
+
return deck
161
+
162
+
163
+
def makeCard(rank, suit):
164
+
card = SimpleNamespace()
165
+
card.rank = rank
166
+
card.suit = suit
167
+
card.hidden = False
168
+
return card
169
+
170
+
171
+
def drawCard(app_inst: AppWrapper, card, x, y):
172
+
label, color = getSuitLabelAndColor(card.suit)
173
+
drawRect(x - 25, y - 37.5, 50, 75, fill="white", border="black")
174
+
if card.hidden:
175
+
drawRect(x - 25, y - 37.5, 50, 75, fill="gray", border="black")
176
+
else:
177
+
drawLabel(f"{label}", x - 15, y - 27.5, fill=color, size=20)
178
+
drawLabel(f"{label}", x + 15, y + 27.5, fill=color, rotateAngle=180, size=20)
179
+
drawLabel(
180
+
f"{card.rank[0] if not card.rank.isdigit() else card.rank}",
181
+
x,
182
+
y,
183
+
fill=color,
184
+
size=20,
185
+
)
186
+
187
+
188
+
def main():
189
+
runApp()
190
+
191
+
192
+
main()
+167
python/oct6/memorygame/main.py
+167
python/oct6/memorygame/main.py
···
1
+
from cmu_graphics.cmu_graphics import AppWrapper, app, runApp
2
+
from cmu_graphics import *
3
+
from types import SimpleNamespace
4
+
from random import randint, choice
5
+
from time import sleep
6
+
from sys import exit
7
+
8
+
9
+
def onAppStart(app_inst: AppWrapper):
10
+
app_inst.difficulty = 1
11
+
pairs = 5 * app_inst.difficulty
12
+
app_inst.dots = []
13
+
for i in range(10 * app_inst.difficulty):
14
+
app_inst.dots.append(makeDot(app_inst, i % pairs))
15
+
app_inst.counter = 0
16
+
app_inst.showing = []
17
+
app_inst.end_game = False
18
+
app_inst.hint = False
19
+
app_inst.hints = 0
20
+
app_inst.warning = 0
21
+
22
+
23
+
def checkOverlap(dot1, dots):
24
+
for dot2 in dots:
25
+
if (dot1.cx - dot2.cx) ** 2 + (dot1.cy - dot2.cy) ** 2 <= (
26
+
dot1.r + dot2.r
27
+
) ** 2:
28
+
return True
29
+
return False
30
+
31
+
32
+
def makeDot(app_inst: AppWrapper, num: int):
33
+
dot = SimpleNamespace()
34
+
dot.num = num
35
+
dot.r = randint(10, 30)
36
+
dot.cx = 0
37
+
dot.cy = 0
38
+
while dot.cx == 0 or dot.cy == 0 or checkOverlap(dot, app_inst.dots):
39
+
dot.cx = randint(20, app_inst.width - 20)
40
+
dot.cy = randint(20, app_inst.height - 20)
41
+
42
+
dot.color = choice(["aqua", "lime", "yellow"])
43
+
return dot
44
+
45
+
46
+
def drawDot(dot: SimpleNamespace):
47
+
drawCircle(dot.cx, dot.cy, dot.r, fill=dot.color)
48
+
49
+
50
+
def collision(pointer, dot2):
51
+
if (pointer.cx - dot2.cx) ** 2 + (pointer.cy - dot2.cy) ** 2 <= dot2.r**2:
52
+
return True
53
+
return False
54
+
55
+
56
+
def onMousePress(app_inst: AppWrapper, mouseX, mouseY):
57
+
pointer = SimpleNamespace()
58
+
pointer.cx = mouseX
59
+
pointer.cy = mouseY
60
+
for dot in app_inst.dots:
61
+
if collision(pointer, dot):
62
+
app_inst.showing.append(dot)
63
+
64
+
65
+
def onStep(app_inst: AppWrapper):
66
+
if app_inst.warning != 0:
67
+
sleep(2)
68
+
app_inst.warning = 0
69
+
if app_inst.hint:
70
+
sleep(2)
71
+
app_inst.hint = False
72
+
if len(app_inst.showing) == 2:
73
+
if app_inst.showing[0].num == app_inst.showing[1].num:
74
+
app_inst.dots.remove(app_inst.showing[0])
75
+
app_inst.dots.remove(app_inst.showing[1])
76
+
sleep(0.5)
77
+
app_inst.counter += 1
78
+
app_inst.showing = []
79
+
if len(app_inst.dots) == 0:
80
+
app_inst.end_game = True
81
+
82
+
83
+
def onKeyPress(app_inst: AppWrapper, key):
84
+
if key == "r":
85
+
app_inst.dots = []
86
+
for i in range(10):
87
+
app_inst.dots.append(makeDot(app_inst, i))
88
+
app_inst.showing = []
89
+
app_inst.counter = 0
90
+
app_inst.end_game = False
91
+
elif key == "q":
92
+
exit()
93
+
elif key == "h":
94
+
app_inst.hint = True
95
+
app_inst.hints += 1
96
+
elif key.isdigit():
97
+
if int(key) < 1 or int(key) > 4:
98
+
app_inst.warning = int(key)
99
+
app_inst.difficulty = max(1, min(int(key), 4))
100
+
pairs = 5 * app_inst.difficulty
101
+
app_inst.dots = []
102
+
for i in range(10 * app_inst.difficulty):
103
+
app_inst.dots.append(makeDot(app_inst, i % pairs))
104
+
app_inst.counter = 0
105
+
app_inst.showing = []
106
+
app_inst.end_game = False
107
+
108
+
109
+
def redrawAll(app_inst: AppWrapper):
110
+
if app_inst.warning:
111
+
drawLabel(
112
+
f"Invalid difficulty: {app_inst.warning}",
113
+
app_inst.width // 2,
114
+
app_inst.height // 2 - 50,
115
+
size=20,
116
+
)
117
+
drawLabel(
118
+
"difficulty must be between 1 and 4",
119
+
app_inst.width // 2,
120
+
app_inst.height // 2 - 25,
121
+
size=20,
122
+
)
123
+
drawLabel(
124
+
f"picking difficulty {app_inst.difficulty}",
125
+
app_inst.width // 2,
126
+
app_inst.height // 2,
127
+
size=20,
128
+
)
129
+
return
130
+
for dot in app_inst.dots:
131
+
drawDot(dot)
132
+
if dot in app_inst.showing:
133
+
drawLabel(str(dot.num), dot.cx, dot.cy, size=24)
134
+
elif app_inst.hint:
135
+
drawLabel(str(dot.num), dot.cx, dot.cy, size=24)
136
+
if app_inst.end_game:
137
+
drawLabel("You win!", app_inst.width // 2, app_inst.height // 2 - 50, size=36)
138
+
drawLabel(
139
+
f"Final score: {app_inst.counter}",
140
+
app_inst.width // 2,
141
+
app_inst.height // 2,
142
+
size=24,
143
+
)
144
+
drawLabel(
145
+
f"Hints used: {app_inst.hints}",
146
+
app_inst.width // 2,
147
+
app_inst.height // 2 + 25,
148
+
size=24,
149
+
)
150
+
drawLabel(
151
+
"Press 'r' to restart",
152
+
app_inst.width // 2,
153
+
app_inst.height // 2 + 75,
154
+
size=24,
155
+
)
156
+
drawLabel(
157
+
"Press 'q' to quit",
158
+
app_inst.width // 2,
159
+
app_inst.height // 2 + 100,
160
+
size=24,
161
+
)
162
+
else:
163
+
drawLabel(f"{app_inst.counter}", 20, 20, size=24, fill="blue")
164
+
drawLabel(f"{app_inst.hints}", app_inst.width - 20, 20, size=24, fill="red")
165
+
166
+
167
+
runApp()
+249
python/oct6/snake/main.py
+249
python/oct6/snake/main.py
···
1
+
from cmu_graphics.cmu_graphics import app, AppWrapper
2
+
from cmu_graphics import *
3
+
from random import randint
4
+
from time import sleep
5
+
from sys import exit
6
+
7
+
8
+
def onAppStart(app_inst: AppWrapper):
9
+
app_inst.rows = 8
10
+
app_inst.cols = 10
11
+
app_inst.boardLeft = 25
12
+
app_inst.boardTop = 85
13
+
app_inst.boardWidth = 350
14
+
app_inst.boardHeight = 280
15
+
app_inst.cellBorderWidth = 1
16
+
app_inst.snake = [(randint(0, app_inst.rows - 1), randint(0, app_inst.cols - 1))]
17
+
app_inst.direction = [0, 0]
18
+
app_inst.fruit = None
19
+
app_inst.gameOver = False
20
+
app_inst.portals = [(-1, -1), (-1, -1)]
21
+
makePortals(app_inst)
22
+
app_inst.poison = None
23
+
24
+
25
+
def makePortals(app_inst: AppWrapper):
26
+
while (
27
+
manhattenDist(app_inst.portals[0], app_inst.portals[1]) < 5
28
+
or distanceToWall(app_inst, app_inst.portals[0]) < 2
29
+
or distanceToWall(app_inst, app_inst.portals[1]) < 2
30
+
):
31
+
app_inst.portals = [
32
+
(randint(0, app_inst.rows - 1), randint(0, app_inst.cols - 1)),
33
+
(randint(0, app_inst.rows - 1), randint(0, app_inst.cols - 1)),
34
+
]
35
+
36
+
37
+
def distanceToWall(app_inst: AppWrapper, pos):
38
+
return min(pos[0], app_inst.rows - pos[0] - 1, pos[1], app_inst.cols - pos[1] - 1)
39
+
40
+
41
+
def manhattenDist(p1, p2):
42
+
return abs(p1[0] - p2[0]) + abs(p1[1] - p2[1])
43
+
44
+
45
+
def onKeyPress(app_inst: AppWrapper, key):
46
+
if key == "up" and not app_inst.direction[0]:
47
+
app_inst.direction = [-1, 0]
48
+
elif key == "down" and not app_inst.direction[0]:
49
+
app_inst.direction = [1, 0]
50
+
elif key == "left" and not app_inst.direction[1]:
51
+
app_inst.direction = [0, -1]
52
+
elif key == "right" and not app_inst.direction[1]:
53
+
app_inst.direction = [0, 1]
54
+
elif key == "r":
55
+
onAppStart(app_inst)
56
+
elif key == "q":
57
+
exit()
58
+
elif key == "space":
59
+
app_inst.gameOver = True
60
+
61
+
62
+
def moveSnake(app_inst: AppWrapper):
63
+
if app_inst.gameOver:
64
+
return
65
+
head = app_inst.snake[0]
66
+
newHead = (
67
+
(head[0] + app_inst.direction[0]) % app_inst.rows,
68
+
(head[1] + app_inst.direction[1]) % app_inst.cols,
69
+
)
70
+
app_inst.snake.insert(0, newHead)
71
+
if newHead in app_inst.portals:
72
+
app_inst.snake[0] = app_inst.portals[1 - app_inst.portals.index(newHead)]
73
+
elif newHead == app_inst.poison:
74
+
app_inst.gameOver = True
75
+
if len(app_inst.snake) > 1:
76
+
app_inst.snake.pop()
77
+
for segment in app_inst.snake[1:]:
78
+
if app_inst.snake[0] == segment:
79
+
app_inst.gameOver = True
80
+
81
+
82
+
def onStep(app_inst: AppWrapper):
83
+
if not app_inst.fruit:
84
+
makeFruit(app_inst)
85
+
makePoison(app_inst)
86
+
head = app_inst.snake[0]
87
+
if head == app_inst.fruit:
88
+
app_inst.fruit = None
89
+
app_inst.snake.append(app_inst.snake[-1])
90
+
moveSnake(app_inst)
91
+
92
+
93
+
def getCell(app_inst: AppWrapper, pos):
94
+
if pos in app_inst.snake:
95
+
return "snake"
96
+
elif pos == app_inst.fruit:
97
+
return "fruit"
98
+
elif pos in app_inst.portals:
99
+
return "portal"
100
+
elif pos == app_inst.poison:
101
+
return "poison"
102
+
elif pos == None:
103
+
return "empty"
104
+
return None
105
+
106
+
107
+
def makeFruit(app_inst: AppWrapper):
108
+
fruit = None
109
+
while getCell(app_inst, fruit) is not None:
110
+
fruit = (randint(0, app_inst.rows - 1), randint(0, app_inst.cols - 1))
111
+
app_inst.fruit = fruit
112
+
break
113
+
114
+
115
+
def makePoison(app_inst: AppWrapper):
116
+
poison = None
117
+
while getCell(app_inst, poison) is not None:
118
+
poison = (randint(0, app_inst.rows - 1), randint(0, app_inst.cols - 1))
119
+
app_inst.poison = poison
120
+
break
121
+
122
+
123
+
def redrawAll(app_inst: AppWrapper):
124
+
drawLabel("Snake!", 200, 30, size=16)
125
+
drawBoard(app_inst)
126
+
drawBoardBorder(app_inst)
127
+
drawFruit(app_inst)
128
+
drawPoison(app_inst)
129
+
drawPortals(app_inst)
130
+
drawSnake(app_inst)
131
+
sleep(0.1875)
132
+
if app_inst.gameOver:
133
+
drawGameOver(app_inst)
134
+
135
+
136
+
def drawPoison(app_inst: AppWrapper):
137
+
if app_inst.poison is not None:
138
+
cellLeft, cellTop = getCellLeftTop(app_inst, *app_inst.poison)
139
+
cellWidth, cellHeight = getCellSize(app_inst)
140
+
drawCircle(
141
+
cellLeft + cellWidth / 2,
142
+
cellTop + cellHeight / 2,
143
+
cellWidth / 2 - 6,
144
+
fill="purple",
145
+
)
146
+
147
+
148
+
def drawPortals(app_inst: AppWrapper):
149
+
for portal in app_inst.portals:
150
+
cellLeft, cellTop = getCellLeftTop(app_inst, portal[0], portal[1])
151
+
cellWidth, cellHeight = getCellSize(app_inst)
152
+
drawCircle(
153
+
cellLeft + cellWidth / 2,
154
+
cellTop + cellHeight / 2,
155
+
cellWidth / 2 - 6,
156
+
fill="black",
157
+
)
158
+
159
+
160
+
def drawGameOver(app_inst: AppWrapper):
161
+
drawRect(app_inst.width / 2 - 150, app_inst.height / 2 - 50, 300, 100, fill="white")
162
+
drawLabel("Game Over!", app_inst.width / 2, app_inst.height / 2 - 20, size=16)
163
+
drawLabel(
164
+
f"Score: {len(app_inst.snake)-1}",
165
+
app_inst.width / 2,
166
+
app_inst.height / 2,
167
+
size=12,
168
+
)
169
+
drawLabel(
170
+
"Press R to restart", app_inst.width / 2, app_inst.height / 2 + 20, size=12
171
+
)
172
+
drawLabel("Press Q to quit", app_inst.width / 2, app_inst.height / 2 + 40, size=12)
173
+
174
+
175
+
def drawFruit(app_inst: AppWrapper):
176
+
if app_inst.fruit is not None:
177
+
cellLeft, cellTop = getCellLeftTop(app_inst, *app_inst.fruit)
178
+
cellWidth, cellHeight = getCellSize(app_inst)
179
+
drawCircle(
180
+
cellLeft + cellWidth / 2,
181
+
cellTop + cellHeight / 2,
182
+
cellWidth / 2 - 8,
183
+
fill="red",
184
+
)
185
+
186
+
187
+
def drawSnake(app_inst: AppWrapper):
188
+
cellWidth, cellHeight = getCellSize(app_inst)
189
+
for i, segment in enumerate(app_inst.snake):
190
+
cellLeft, cellTop = getCellLeftTop(app_inst, segment[0], segment[1])
191
+
color = "green" if i == 0 else "gray"
192
+
drawCircle(
193
+
cellLeft + cellWidth / 2,
194
+
cellTop + cellHeight / 2,
195
+
cellWidth / 2 - 4,
196
+
fill=color,
197
+
)
198
+
199
+
200
+
def drawBoard(app_inst: AppWrapper):
201
+
for row in range(app_inst.rows):
202
+
for col in range(app_inst.cols):
203
+
drawCell(app_inst, row, col)
204
+
205
+
206
+
def drawBoardBorder(app_inst: AppWrapper):
207
+
drawRect(
208
+
app_inst.boardLeft,
209
+
app_inst.boardTop,
210
+
app_inst.boardWidth,
211
+
app_inst.boardHeight,
212
+
fill=None,
213
+
border="black",
214
+
borderWidth=2 * app_inst.cellBorderWidth,
215
+
)
216
+
217
+
218
+
def drawCell(app_inst: AppWrapper, row, col):
219
+
cellLeft, cellTop = getCellLeftTop(app_inst, row, col)
220
+
cellWidth, cellHeight = getCellSize(app_inst)
221
+
drawRect(
222
+
cellLeft,
223
+
cellTop,
224
+
cellWidth,
225
+
cellHeight,
226
+
fill=None,
227
+
border="gray",
228
+
borderWidth=app_inst.cellBorderWidth,
229
+
)
230
+
231
+
232
+
def getCellLeftTop(app_inst: AppWrapper, row, col):
233
+
cellWidth, cellHeight = getCellSize(app_inst)
234
+
cellLeft = app_inst.boardLeft + col * cellWidth
235
+
cellTop = app_inst.boardTop + row * cellHeight
236
+
return (cellLeft, cellTop)
237
+
238
+
239
+
def getCellSize(app_inst: AppWrapper):
240
+
cellWidth = app_inst.boardWidth / app_inst.cols
241
+
cellHeight = app_inst.boardHeight / app_inst.rows
242
+
return (cellWidth, cellHeight)
243
+
244
+
245
+
def main():
246
+
runApp()
247
+
248
+
249
+
main()
+2451
python/oct6/wordle/main.py
+2451
python/oct6/wordle/main.py
···
1
+
from cmu_graphics.cmu_graphics import AppWrapper, app, runApp
2
+
from cmu_graphics import *
3
+
from random import choice
4
+
from typing import List
5
+
6
+
7
+
def onAppStart(app_inst: AppWrapper):
8
+
app_inst.word = getWord()
9
+
app_inst.guesses = [
10
+
["", "", "", "", ""],
11
+
["", "", "", "", ""],
12
+
["", "", "", "", ""],
13
+
["", "", "", "", ""],
14
+
["", "", "", "", ""],
15
+
["", "", "", "", ""],
16
+
]
17
+
app_inst.invalid_word = False
18
+
app_inst.current_guess = 0
19
+
app_inst.game_over = False
20
+
21
+
22
+
def wordHistory(app_inst: AppWrapper):
23
+
return ["".join(guess) for guess in app_inst.guesses[: app_inst.current_guess]]
24
+
25
+
26
+
def onKeyPress(app_inst: AppWrapper, key):
27
+
guess = (
28
+
app_inst.guesses[app_inst.current_guess] if app_inst.current_guess < 6 else [""]
29
+
)
30
+
if key == "backspace":
31
+
app_inst.invalid_word = False
32
+
current_letter = guess.index("") if guess[4] == "" else 5
33
+
app_inst.guesses[app_inst.current_guess][current_letter - 1] = ""
34
+
elif key == "enter":
35
+
if "".join(guess) not in getWords() or "".join(guess) in wordHistory(app_inst):
36
+
app_inst.invalid_word = True
37
+
return
38
+
if app_inst.current_guess <= 6:
39
+
app_inst.current_guess += 1
40
+
if "".join(guess) == app_inst.word or app_inst.current_guess == 6:
41
+
app_inst.game_over = True
42
+
elif key == "r" and app_inst.game_over:
43
+
onAppStart(app_inst)
44
+
elif len(key) == 1 and key.isalpha():
45
+
current_letter = guess.index("") if guess[4] == "" else 4
46
+
app_inst.guesses[app_inst.current_guess][current_letter] = key.lower()
47
+
48
+
49
+
def letterCount(word):
50
+
letter_count = {}
51
+
for letter in word:
52
+
if letter == "":
53
+
continue
54
+
letter_count[letter] = letter_count.get(letter, 0) + 1
55
+
return letter_count
56
+
57
+
58
+
def checkWord(app_inst: AppWrapper, word: List[str], index):
59
+
if not index < app_inst.current_guess:
60
+
if app_inst.invalid_word:
61
+
return ["red", "red", "red", "red", "red"]
62
+
return ["white", "white", "white", "white", "white"]
63
+
letter_colors = ["gray", "gray", "gray", "gray", "gray"]
64
+
correct_letter_count = letterCount(app_inst.word)
65
+
for i, letter in enumerate(word):
66
+
if letter == app_inst.word[i]:
67
+
correct_letter_count[letter] -= 1
68
+
letter_colors[i] = "green"
69
+
for i, letter in enumerate(word):
70
+
if letter in app_inst.word and correct_letter_count[letter] >= 1:
71
+
letter_colors[i] = "yellow"
72
+
correct_letter_count[letter] -= 1
73
+
return letter_colors
74
+
75
+
76
+
def drawCell(app_inst: AppWrapper, x, y, letter, color):
77
+
cell_size = (app_inst.width - 150) / 5
78
+
drawRect(
79
+
x - cell_size / 2,
80
+
y - cell_size / 2,
81
+
cell_size,
82
+
cell_size,
83
+
fill=color,
84
+
border="black",
85
+
borderWidth=1,
86
+
)
87
+
drawLabel(letter.upper(), x, y, size=cell_size * 0.8)
88
+
89
+
90
+
def redrawAll(app_inst: AppWrapper):
91
+
padding = 8
92
+
x_play_area = app_inst.width - 150
93
+
y_play_area = app_inst.height - 100
94
+
x_center = x_play_area / 2
95
+
y_center = y_play_area / 2
96
+
x_border = x_center - (x_play_area / 10) - (padding * 2.5)
97
+
y_border = y_center - (y_play_area / 4) - (padding * 3)
98
+
for i, guess in enumerate(app_inst.guesses):
99
+
if i > app_inst.current_guess:
100
+
continue
101
+
word_colors = checkWord(app_inst, guess, i)
102
+
for j, letter in enumerate(guess):
103
+
x_cell_center = x_border + (x_play_area / 5 + padding) * j
104
+
y_cell_center = y_border + (y_play_area / 6 + padding) * i
105
+
drawCell(app_inst, x_cell_center, y_cell_center, letter, word_colors[j])
106
+
guess = "".join(app_inst.guesses[app_inst.current_guess - 1])
107
+
if app_inst.game_over:
108
+
text = "Game Over :("
109
+
if guess == app_inst.word:
110
+
text = "You Win!"
111
+
drawRect(
112
+
app_inst.width / 2 - 150,
113
+
app_inst.height / 2 - 50,
114
+
300,
115
+
100,
116
+
fill="white",
117
+
border="black",
118
+
borderWidth=1,
119
+
)
120
+
drawLabel(text, app_inst.width / 2, app_inst.height / 2, size=32)
121
+
122
+
123
+
def getWord():
124
+
# Return a random wordle word
125
+
# words from: https://www.wordunscrambler.net/word-list/wordle-word-list
126
+
words = getWords()
127
+
return choice(words)
128
+
129
+
130
+
def getWords():
131
+
# Return a list of all 2309 wordle words
132
+
# words from: https://www.wordunscrambler.net/word-list/wordle-word-list
133
+
words = [
134
+
"aback",
135
+
"abase",
136
+
"abate",
137
+
"abbey",
138
+
"abbot",
139
+
"abhor",
140
+
"abide",
141
+
"abled",
142
+
"abode",
143
+
"abort",
144
+
"about",
145
+
"above",
146
+
"abuse",
147
+
"abyss",
148
+
"acorn",
149
+
"acrid",
150
+
"actor",
151
+
"acute",
152
+
"adage",
153
+
"adapt",
154
+
"adept",
155
+
"admin",
156
+
"admit",
157
+
"adobe",
158
+
"adopt",
159
+
"adore",
160
+
"adorn",
161
+
"adult",
162
+
"affix",
163
+
"afire",
164
+
"afoot",
165
+
"afoul",
166
+
"after",
167
+
"again",
168
+
"agape",
169
+
"agate",
170
+
"agent",
171
+
"agile",
172
+
"aging",
173
+
"aglow",
174
+
"agony",
175
+
"agree",
176
+
"ahead",
177
+
"aider",
178
+
"aisle",
179
+
"alarm",
180
+
"album",
181
+
"alert",
182
+
"algae",
183
+
"alibi",
184
+
"alien",
185
+
"align",
186
+
"alike",
187
+
"alive",
188
+
"allay",
189
+
"alley",
190
+
"allot",
191
+
"allow",
192
+
"alloy",
193
+
"aloft",
194
+
"alone",
195
+
"along",
196
+
"aloof",
197
+
"aloud",
198
+
"alpha",
199
+
"altar",
200
+
"alter",
201
+
"amass",
202
+
"amaze",
203
+
"amber",
204
+
"amble",
205
+
"amend",
206
+
"amiss",
207
+
"amity",
208
+
"among",
209
+
"ample",
210
+
"amply",
211
+
"amuse",
212
+
"angel",
213
+
"anger",
214
+
"angle",
215
+
"angry",
216
+
"angst",
217
+
"anime",
218
+
"ankle",
219
+
"annex",
220
+
"annoy",
221
+
"annul",
222
+
"anode",
223
+
"antic",
224
+
"anvil",
225
+
"aorta",
226
+
"apart",
227
+
"aphid",
228
+
"aping",
229
+
"apnea",
230
+
"apple",
231
+
"apply",
232
+
"apron",
233
+
"aptly",
234
+
"arbor",
235
+
"ardor",
236
+
"arena",
237
+
"argue",
238
+
"arise",
239
+
"armor",
240
+
"aroma",
241
+
"arose",
242
+
"array",
243
+
"arrow",
244
+
"arson",
245
+
"artsy",
246
+
"ascot",
247
+
"ashen",
248
+
"aside",
249
+
"askew",
250
+
"assay",
251
+
"asset",
252
+
"atoll",
253
+
"atone",
254
+
"attic",
255
+
"audio",
256
+
"audit",
257
+
"augur",
258
+
"aunty",
259
+
"avail",
260
+
"avert",
261
+
"avian",
262
+
"avoid",
263
+
"await",
264
+
"awake",
265
+
"award",
266
+
"aware",
267
+
"awash",
268
+
"awful",
269
+
"awoke",
270
+
"axial",
271
+
"axiom",
272
+
"axion",
273
+
"azure",
274
+
"bacon",
275
+
"badge",
276
+
"badly",
277
+
"bagel",
278
+
"baggy",
279
+
"baker",
280
+
"baler",
281
+
"balmy",
282
+
"banal",
283
+
"banjo",
284
+
"barge",
285
+
"baron",
286
+
"basal",
287
+
"basic",
288
+
"basil",
289
+
"basin",
290
+
"basis",
291
+
"baste",
292
+
"batch",
293
+
"bathe",
294
+
"baton",
295
+
"batty",
296
+
"bawdy",
297
+
"bayou",
298
+
"beach",
299
+
"beady",
300
+
"beard",
301
+
"beast",
302
+
"beech",
303
+
"beefy",
304
+
"befit",
305
+
"began",
306
+
"begat",
307
+
"beget",
308
+
"begin",
309
+
"begun",
310
+
"being",
311
+
"belch",
312
+
"belie",
313
+
"belle",
314
+
"belly",
315
+
"below",
316
+
"bench",
317
+
"beret",
318
+
"berry",
319
+
"berth",
320
+
"beset",
321
+
"betel",
322
+
"bevel",
323
+
"bezel",
324
+
"bible",
325
+
"bicep",
326
+
"biddy",
327
+
"bigot",
328
+
"bilge",
329
+
"billy",
330
+
"binge",
331
+
"bingo",
332
+
"biome",
333
+
"birch",
334
+
"birth",
335
+
"bison",
336
+
"bitty",
337
+
"black",
338
+
"blade",
339
+
"blame",
340
+
"bland",
341
+
"blank",
342
+
"blare",
343
+
"blast",
344
+
"blaze",
345
+
"bleak",
346
+
"bleat",
347
+
"bleed",
348
+
"bleep",
349
+
"blend",
350
+
"bless",
351
+
"blimp",
352
+
"blind",
353
+
"blink",
354
+
"bliss",
355
+
"blitz",
356
+
"bloat",
357
+
"block",
358
+
"bloke",
359
+
"blond",
360
+
"blood",
361
+
"bloom",
362
+
"blown",
363
+
"bluer",
364
+
"bluff",
365
+
"blunt",
366
+
"blurb",
367
+
"blurt",
368
+
"blush",
369
+
"board",
370
+
"boast",
371
+
"bobby",
372
+
"boney",
373
+
"bongo",
374
+
"bonus",
375
+
"booby",
376
+
"boost",
377
+
"booth",
378
+
"booty",
379
+
"booze",
380
+
"boozy",
381
+
"borax",
382
+
"borne",
383
+
"bosom",
384
+
"bossy",
385
+
"botch",
386
+
"bough",
387
+
"boule",
388
+
"bound",
389
+
"bowel",
390
+
"boxer",
391
+
"brace",
392
+
"braid",
393
+
"brain",
394
+
"brake",
395
+
"brand",
396
+
"brash",
397
+
"brass",
398
+
"brave",
399
+
"bravo",
400
+
"brawl",
401
+
"brawn",
402
+
"bread",
403
+
"break",
404
+
"breed",
405
+
"briar",
406
+
"bribe",
407
+
"brick",
408
+
"bride",
409
+
"brief",
410
+
"brine",
411
+
"bring",
412
+
"brink",
413
+
"briny",
414
+
"brisk",
415
+
"broad",
416
+
"broil",
417
+
"broke",
418
+
"brood",
419
+
"brook",
420
+
"broom",
421
+
"broth",
422
+
"brown",
423
+
"brunt",
424
+
"brush",
425
+
"brute",
426
+
"buddy",
427
+
"budge",
428
+
"buggy",
429
+
"bugle",
430
+
"build",
431
+
"built",
432
+
"bulge",
433
+
"bulky",
434
+
"bully",
435
+
"bunch",
436
+
"bunny",
437
+
"burly",
438
+
"burnt",
439
+
"burst",
440
+
"bused",
441
+
"bushy",
442
+
"butch",
443
+
"butte",
444
+
"buxom",
445
+
"buyer",
446
+
"bylaw",
447
+
"cabal",
448
+
"cabby",
449
+
"cabin",
450
+
"cable",
451
+
"cacao",
452
+
"cache",
453
+
"cacti",
454
+
"caddy",
455
+
"cadet",
456
+
"cagey",
457
+
"cairn",
458
+
"camel",
459
+
"cameo",
460
+
"canal",
461
+
"candy",
462
+
"canny",
463
+
"canoe",
464
+
"canon",
465
+
"caper",
466
+
"caput",
467
+
"carat",
468
+
"cargo",
469
+
"carol",
470
+
"carry",
471
+
"carve",
472
+
"caste",
473
+
"catch",
474
+
"cater",
475
+
"catty",
476
+
"caulk",
477
+
"cause",
478
+
"cavil",
479
+
"cease",
480
+
"cedar",
481
+
"cello",
482
+
"chafe",
483
+
"chaff",
484
+
"chain",
485
+
"chair",
486
+
"chalk",
487
+
"champ",
488
+
"chant",
489
+
"chaos",
490
+
"chard",
491
+
"charm",
492
+
"chart",
493
+
"chase",
494
+
"chasm",
495
+
"cheap",
496
+
"cheat",
497
+
"check",
498
+
"cheek",
499
+
"cheer",
500
+
"chess",
501
+
"chest",
502
+
"chick",
503
+
"chide",
504
+
"chief",
505
+
"child",
506
+
"chili",
507
+
"chill",
508
+
"chime",
509
+
"china",
510
+
"chirp",
511
+
"chock",
512
+
"choir",
513
+
"choke",
514
+
"chord",
515
+
"chore",
516
+
"chose",
517
+
"chuck",
518
+
"chump",
519
+
"chunk",
520
+
"churn",
521
+
"chute",
522
+
"cider",
523
+
"cigar",
524
+
"cinch",
525
+
"circa",
526
+
"civic",
527
+
"civil",
528
+
"clack",
529
+
"claim",
530
+
"clamp",
531
+
"clang",
532
+
"clank",
533
+
"clash",
534
+
"clasp",
535
+
"class",
536
+
"clean",
537
+
"clear",
538
+
"cleat",
539
+
"cleft",
540
+
"clerk",
541
+
"click",
542
+
"cliff",
543
+
"climb",
544
+
"cling",
545
+
"clink",
546
+
"cloak",
547
+
"clock",
548
+
"clone",
549
+
"close",
550
+
"cloth",
551
+
"cloud",
552
+
"clout",
553
+
"clove",
554
+
"clown",
555
+
"cluck",
556
+
"clued",
557
+
"clump",
558
+
"clung",
559
+
"coach",
560
+
"coast",
561
+
"cobra",
562
+
"cocoa",
563
+
"colon",
564
+
"color",
565
+
"comet",
566
+
"comfy",
567
+
"comic",
568
+
"comma",
569
+
"conch",
570
+
"condo",
571
+
"conic",
572
+
"copse",
573
+
"coral",
574
+
"corer",
575
+
"corny",
576
+
"couch",
577
+
"cough",
578
+
"could",
579
+
"count",
580
+
"coupe",
581
+
"court",
582
+
"coven",
583
+
"cover",
584
+
"covet",
585
+
"covey",
586
+
"cower",
587
+
"coyly",
588
+
"crack",
589
+
"craft",
590
+
"cramp",
591
+
"crane",
592
+
"crank",
593
+
"crash",
594
+
"crass",
595
+
"crate",
596
+
"crave",
597
+
"crawl",
598
+
"craze",
599
+
"crazy",
600
+
"creak",
601
+
"cream",
602
+
"credo",
603
+
"creed",
604
+
"creek",
605
+
"creep",
606
+
"creme",
607
+
"crepe",
608
+
"crept",
609
+
"cress",
610
+
"crest",
611
+
"crick",
612
+
"cried",
613
+
"crier",
614
+
"crime",
615
+
"crimp",
616
+
"crisp",
617
+
"croak",
618
+
"crock",
619
+
"crone",
620
+
"crony",
621
+
"crook",
622
+
"cross",
623
+
"croup",
624
+
"crowd",
625
+
"crown",
626
+
"crude",
627
+
"cruel",
628
+
"crumb",
629
+
"crump",
630
+
"crush",
631
+
"crust",
632
+
"crypt",
633
+
"cubic",
634
+
"cumin",
635
+
"curio",
636
+
"curly",
637
+
"curry",
638
+
"curse",
639
+
"curve",
640
+
"curvy",
641
+
"cutie",
642
+
"cyber",
643
+
"cycle",
644
+
"cynic",
645
+
"daddy",
646
+
"daily",
647
+
"dairy",
648
+
"daisy",
649
+
"dally",
650
+
"dance",
651
+
"dandy",
652
+
"datum",
653
+
"daunt",
654
+
"dealt",
655
+
"death",
656
+
"debar",
657
+
"debit",
658
+
"debug",
659
+
"debut",
660
+
"decal",
661
+
"decay",
662
+
"decor",
663
+
"decoy",
664
+
"decry",
665
+
"defer",
666
+
"deign",
667
+
"deity",
668
+
"delay",
669
+
"delta",
670
+
"delve",
671
+
"demon",
672
+
"demur",
673
+
"denim",
674
+
"dense",
675
+
"depot",
676
+
"depth",
677
+
"derby",
678
+
"deter",
679
+
"detox",
680
+
"deuce",
681
+
"devil",
682
+
"diary",
683
+
"dicey",
684
+
"digit",
685
+
"dilly",
686
+
"dimly",
687
+
"diner",
688
+
"dingo",
689
+
"dingy",
690
+
"diode",
691
+
"dirge",
692
+
"dirty",
693
+
"disco",
694
+
"ditch",
695
+
"ditto",
696
+
"ditty",
697
+
"diver",
698
+
"dizzy",
699
+
"dodge",
700
+
"dodgy",
701
+
"dogma",
702
+
"doing",
703
+
"dolly",
704
+
"donor",
705
+
"donut",
706
+
"dopey",
707
+
"doubt",
708
+
"dough",
709
+
"dowdy",
710
+
"dowel",
711
+
"downy",
712
+
"dowry",
713
+
"dozen",
714
+
"draft",
715
+
"drain",
716
+
"drake",
717
+
"drama",
718
+
"drank",
719
+
"drape",
720
+
"drawl",
721
+
"drawn",
722
+
"dread",
723
+
"dream",
724
+
"dress",
725
+
"dried",
726
+
"drier",
727
+
"drift",
728
+
"drill",
729
+
"drink",
730
+
"drive",
731
+
"droit",
732
+
"droll",
733
+
"drone",
734
+
"drool",
735
+
"droop",
736
+
"dross",
737
+
"drove",
738
+
"drown",
739
+
"druid",
740
+
"drunk",
741
+
"dryer",
742
+
"dryly",
743
+
"duchy",
744
+
"dully",
745
+
"dummy",
746
+
"dumpy",
747
+
"dunce",
748
+
"dusky",
749
+
"dusty",
750
+
"dutch",
751
+
"duvet",
752
+
"dwarf",
753
+
"dwell",
754
+
"dwelt",
755
+
"dying",
756
+
"eager",
757
+
"eagle",
758
+
"early",
759
+
"earth",
760
+
"easel",
761
+
"eaten",
762
+
"eater",
763
+
"ebony",
764
+
"eclat",
765
+
"edict",
766
+
"edify",
767
+
"eerie",
768
+
"egret",
769
+
"eight",
770
+
"eject",
771
+
"eking",
772
+
"elate",
773
+
"elbow",
774
+
"elder",
775
+
"elect",
776
+
"elegy",
777
+
"elfin",
778
+
"elide",
779
+
"elite",
780
+
"elope",
781
+
"elude",
782
+
"email",
783
+
"embed",
784
+
"ember",
785
+
"emcee",
786
+
"empty",
787
+
"enact",
788
+
"endow",
789
+
"enema",
790
+
"enemy",
791
+
"enjoy",
792
+
"ennui",
793
+
"ensue",
794
+
"enter",
795
+
"entry",
796
+
"envoy",
797
+
"epoch",
798
+
"epoxy",
799
+
"equal",
800
+
"equip",
801
+
"erase",
802
+
"erect",
803
+
"erode",
804
+
"error",
805
+
"erupt",
806
+
"essay",
807
+
"ester",
808
+
"ether",
809
+
"ethic",
810
+
"ethos",
811
+
"etude",
812
+
"evade",
813
+
"event",
814
+
"every",
815
+
"evict",
816
+
"evoke",
817
+
"exact",
818
+
"exalt",
819
+
"excel",
820
+
"exert",
821
+
"exile",
822
+
"exist",
823
+
"expel",
824
+
"extol",
825
+
"extra",
826
+
"exult",
827
+
"eying",
828
+
"fable",
829
+
"facet",
830
+
"faint",
831
+
"fairy",
832
+
"faith",
833
+
"false",
834
+
"fancy",
835
+
"fanny",
836
+
"farce",
837
+
"fatal",
838
+
"fatty",
839
+
"fault",
840
+
"fauna",
841
+
"favor",
842
+
"feast",
843
+
"fecal",
844
+
"feign",
845
+
"fella",
846
+
"felon",
847
+
"femme",
848
+
"femur",
849
+
"fence",
850
+
"feral",
851
+
"ferry",
852
+
"fetal",
853
+
"fetch",
854
+
"fetid",
855
+
"fetus",
856
+
"fever",
857
+
"fewer",
858
+
"fiber",
859
+
"ficus",
860
+
"field",
861
+
"fiend",
862
+
"fiery",
863
+
"fifth",
864
+
"fifty",
865
+
"fight",
866
+
"filer",
867
+
"filet",
868
+
"filly",
869
+
"filmy",
870
+
"filth",
871
+
"final",
872
+
"finch",
873
+
"finer",
874
+
"first",
875
+
"fishy",
876
+
"fixer",
877
+
"fizzy",
878
+
"fjord",
879
+
"flack",
880
+
"flail",
881
+
"flair",
882
+
"flake",
883
+
"flaky",
884
+
"flame",
885
+
"flank",
886
+
"flare",
887
+
"flash",
888
+
"flask",
889
+
"fleck",
890
+
"fleet",
891
+
"flesh",
892
+
"flick",
893
+
"flier",
894
+
"fling",
895
+
"flint",
896
+
"flirt",
897
+
"float",
898
+
"flock",
899
+
"flood",
900
+
"floor",
901
+
"flora",
902
+
"floss",
903
+
"flour",
904
+
"flout",
905
+
"flown",
906
+
"fluff",
907
+
"fluid",
908
+
"fluke",
909
+
"flume",
910
+
"flung",
911
+
"flunk",
912
+
"flush",
913
+
"flute",
914
+
"flyer",
915
+
"foamy",
916
+
"focal",
917
+
"focus",
918
+
"foggy",
919
+
"foist",
920
+
"folio",
921
+
"folly",
922
+
"foray",
923
+
"force",
924
+
"forge",
925
+
"forgo",
926
+
"forte",
927
+
"forth",
928
+
"forty",
929
+
"forum",
930
+
"found",
931
+
"foyer",
932
+
"frail",
933
+
"frame",
934
+
"frank",
935
+
"fraud",
936
+
"freak",
937
+
"freed",
938
+
"freer",
939
+
"fresh",
940
+
"friar",
941
+
"fried",
942
+
"frill",
943
+
"frisk",
944
+
"fritz",
945
+
"frock",
946
+
"frond",
947
+
"front",
948
+
"frost",
949
+
"froth",
950
+
"frown",
951
+
"froze",
952
+
"fruit",
953
+
"fudge",
954
+
"fugue",
955
+
"fully",
956
+
"fungi",
957
+
"funky",
958
+
"funny",
959
+
"furor",
960
+
"furry",
961
+
"fussy",
962
+
"fuzzy",
963
+
"gaffe",
964
+
"gaily",
965
+
"gamer",
966
+
"gamma",
967
+
"gamut",
968
+
"gassy",
969
+
"gaudy",
970
+
"gauge",
971
+
"gaunt",
972
+
"gauze",
973
+
"gavel",
974
+
"gawky",
975
+
"gayer",
976
+
"gayly",
977
+
"gazer",
978
+
"gecko",
979
+
"geeky",
980
+
"geese",
981
+
"genie",
982
+
"genre",
983
+
"ghost",
984
+
"ghoul",
985
+
"giant",
986
+
"giddy",
987
+
"gipsy",
988
+
"girly",
989
+
"girth",
990
+
"given",
991
+
"giver",
992
+
"glade",
993
+
"gland",
994
+
"glare",
995
+
"glass",
996
+
"glaze",
997
+
"gleam",
998
+
"glean",
999
+
"glide",
1000
+
"glint",
1001
+
"gloat",
1002
+
"globe",
1003
+
"gloom",
1004
+
"glory",
1005
+
"gloss",
1006
+
"glove",
1007
+
"glyph",
1008
+
"gnash",
1009
+
"gnome",
1010
+
"godly",
1011
+
"going",
1012
+
"golem",
1013
+
"golly",
1014
+
"gonad",
1015
+
"goner",
1016
+
"goody",
1017
+
"gooey",
1018
+
"goofy",
1019
+
"goose",
1020
+
"gorge",
1021
+
"gouge",
1022
+
"gourd",
1023
+
"grace",
1024
+
"grade",
1025
+
"graft",
1026
+
"grail",
1027
+
"grain",
1028
+
"grand",
1029
+
"grant",
1030
+
"grape",
1031
+
"graph",
1032
+
"grasp",
1033
+
"grass",
1034
+
"grate",
1035
+
"grave",
1036
+
"gravy",
1037
+
"graze",
1038
+
"great",
1039
+
"greed",
1040
+
"green",
1041
+
"greet",
1042
+
"grief",
1043
+
"grill",
1044
+
"grime",
1045
+
"grimy",
1046
+
"grind",
1047
+
"gripe",
1048
+
"groan",
1049
+
"groin",
1050
+
"groom",
1051
+
"grope",
1052
+
"gross",
1053
+
"group",
1054
+
"grout",
1055
+
"grove",
1056
+
"growl",
1057
+
"grown",
1058
+
"gruel",
1059
+
"gruff",
1060
+
"grunt",
1061
+
"guard",
1062
+
"guava",
1063
+
"guess",
1064
+
"guest",
1065
+
"guide",
1066
+
"guild",
1067
+
"guile",
1068
+
"guilt",
1069
+
"guise",
1070
+
"gulch",
1071
+
"gully",
1072
+
"gumbo",
1073
+
"gummy",
1074
+
"guppy",
1075
+
"gusto",
1076
+
"gusty",
1077
+
"gypsy",
1078
+
"habit",
1079
+
"hairy",
1080
+
"halve",
1081
+
"handy",
1082
+
"happy",
1083
+
"hardy",
1084
+
"harem",
1085
+
"harpy",
1086
+
"harry",
1087
+
"harsh",
1088
+
"haste",
1089
+
"hasty",
1090
+
"hatch",
1091
+
"hater",
1092
+
"haunt",
1093
+
"haute",
1094
+
"haven",
1095
+
"havoc",
1096
+
"hazel",
1097
+
"heady",
1098
+
"heard",
1099
+
"heart",
1100
+
"heath",
1101
+
"heave",
1102
+
"heavy",
1103
+
"hedge",
1104
+
"hefty",
1105
+
"heist",
1106
+
"helix",
1107
+
"hello",
1108
+
"hence",
1109
+
"heron",
1110
+
"hilly",
1111
+
"hinge",
1112
+
"hippo",
1113
+
"hippy",
1114
+
"hitch",
1115
+
"hoard",
1116
+
"hobby",
1117
+
"hoist",
1118
+
"holly",
1119
+
"homer",
1120
+
"honey",
1121
+
"honor",
1122
+
"horde",
1123
+
"horny",
1124
+
"horse",
1125
+
"hotel",
1126
+
"hotly",
1127
+
"hound",
1128
+
"house",
1129
+
"hovel",
1130
+
"hover",
1131
+
"howdy",
1132
+
"human",
1133
+
"humid",
1134
+
"humor",
1135
+
"humph",
1136
+
"humus",
1137
+
"hunch",
1138
+
"hunky",
1139
+
"hurry",
1140
+
"husky",
1141
+
"hussy",
1142
+
"hutch",
1143
+
"hydro",
1144
+
"hyena",
1145
+
"hymen",
1146
+
"hyper",
1147
+
"icily",
1148
+
"icing",
1149
+
"ideal",
1150
+
"idiom",
1151
+
"idiot",
1152
+
"idler",
1153
+
"idyll",
1154
+
"igloo",
1155
+
"iliac",
1156
+
"image",
1157
+
"imbue",
1158
+
"impel",
1159
+
"imply",
1160
+
"inane",
1161
+
"inbox",
1162
+
"incur",
1163
+
"index",
1164
+
"inept",
1165
+
"inert",
1166
+
"infer",
1167
+
"ingot",
1168
+
"inlay",
1169
+
"inlet",
1170
+
"inner",
1171
+
"input",
1172
+
"inter",
1173
+
"intro",
1174
+
"ionic",
1175
+
"irate",
1176
+
"irony",
1177
+
"islet",
1178
+
"issue",
1179
+
"itchy",
1180
+
"ivory",
1181
+
"jaunt",
1182
+
"jazzy",
1183
+
"jelly",
1184
+
"jerky",
1185
+
"jetty",
1186
+
"jewel",
1187
+
"jiffy",
1188
+
"joint",
1189
+
"joist",
1190
+
"joker",
1191
+
"jolly",
1192
+
"joust",
1193
+
"judge",
1194
+
"juice",
1195
+
"juicy",
1196
+
"jumbo",
1197
+
"jumpy",
1198
+
"junta",
1199
+
"junto",
1200
+
"juror",
1201
+
"kappa",
1202
+
"karma",
1203
+
"kayak",
1204
+
"kebab",
1205
+
"khaki",
1206
+
"kinky",
1207
+
"kiosk",
1208
+
"kitty",
1209
+
"knack",
1210
+
"knave",
1211
+
"knead",
1212
+
"kneed",
1213
+
"kneel",
1214
+
"knelt",
1215
+
"knife",
1216
+
"knock",
1217
+
"knoll",
1218
+
"known",
1219
+
"koala",
1220
+
"krill",
1221
+
"label",
1222
+
"labor",
1223
+
"laden",
1224
+
"ladle",
1225
+
"lager",
1226
+
"lance",
1227
+
"lanky",
1228
+
"lapel",
1229
+
"lapse",
1230
+
"large",
1231
+
"larva",
1232
+
"lasso",
1233
+
"latch",
1234
+
"later",
1235
+
"lathe",
1236
+
"latte",
1237
+
"laugh",
1238
+
"layer",
1239
+
"leach",
1240
+
"leafy",
1241
+
"leaky",
1242
+
"leant",
1243
+
"leapt",
1244
+
"learn",
1245
+
"lease",
1246
+
"leash",
1247
+
"least",
1248
+
"leave",
1249
+
"ledge",
1250
+
"leech",
1251
+
"leery",
1252
+
"lefty",
1253
+
"legal",
1254
+
"leggy",
1255
+
"lemon",
1256
+
"lemur",
1257
+
"leper",
1258
+
"level",
1259
+
"lever",
1260
+
"libel",
1261
+
"liege",
1262
+
"light",
1263
+
"liken",
1264
+
"lilac",
1265
+
"limbo",
1266
+
"limit",
1267
+
"linen",
1268
+
"liner",
1269
+
"lingo",
1270
+
"lipid",
1271
+
"lithe",
1272
+
"liver",
1273
+
"livid",
1274
+
"llama",
1275
+
"loamy",
1276
+
"loath",
1277
+
"lobby",
1278
+
"local",
1279
+
"locus",
1280
+
"lodge",
1281
+
"lofty",
1282
+
"logic",
1283
+
"login",
1284
+
"loopy",
1285
+
"loose",
1286
+
"lorry",
1287
+
"loser",
1288
+
"louse",
1289
+
"lousy",
1290
+
"lover",
1291
+
"lower",
1292
+
"lowly",
1293
+
"loyal",
1294
+
"lucid",
1295
+
"lucky",
1296
+
"lumen",
1297
+
"lumpy",
1298
+
"lunar",
1299
+
"lunch",
1300
+
"lunge",
1301
+
"lupus",
1302
+
"lurch",
1303
+
"lurid",
1304
+
"lusty",
1305
+
"lying",
1306
+
"lymph",
1307
+
"lyric",
1308
+
"macaw",
1309
+
"macho",
1310
+
"macro",
1311
+
"madam",
1312
+
"madly",
1313
+
"mafia",
1314
+
"magic",
1315
+
"magma",
1316
+
"maize",
1317
+
"major",
1318
+
"maker",
1319
+
"mambo",
1320
+
"mamma",
1321
+
"mammy",
1322
+
"manga",
1323
+
"mange",
1324
+
"mango",
1325
+
"mangy",
1326
+
"mania",
1327
+
"manic",
1328
+
"manly",
1329
+
"manor",
1330
+
"maple",
1331
+
"march",
1332
+
"marry",
1333
+
"marsh",
1334
+
"mason",
1335
+
"masse",
1336
+
"match",
1337
+
"matey",
1338
+
"mauve",
1339
+
"maxim",
1340
+
"maybe",
1341
+
"mayor",
1342
+
"mealy",
1343
+
"meant",
1344
+
"meaty",
1345
+
"mecca",
1346
+
"medal",
1347
+
"media",
1348
+
"medic",
1349
+
"melee",
1350
+
"melon",
1351
+
"mercy",
1352
+
"merge",
1353
+
"merit",
1354
+
"merry",
1355
+
"metal",
1356
+
"meter",
1357
+
"metro",
1358
+
"micro",
1359
+
"midge",
1360
+
"midst",
1361
+
"might",
1362
+
"milky",
1363
+
"mimic",
1364
+
"mince",
1365
+
"miner",
1366
+
"minim",
1367
+
"minor",
1368
+
"minty",
1369
+
"minus",
1370
+
"mirth",
1371
+
"miser",
1372
+
"missy",
1373
+
"mocha",
1374
+
"modal",
1375
+
"model",
1376
+
"modem",
1377
+
"mogul",
1378
+
"moist",
1379
+
"molar",
1380
+
"moldy",
1381
+
"money",
1382
+
"month",
1383
+
"moody",
1384
+
"moose",
1385
+
"moral",
1386
+
"moron",
1387
+
"morph",
1388
+
"mossy",
1389
+
"motel",
1390
+
"motif",
1391
+
"motor",
1392
+
"motto",
1393
+
"moult",
1394
+
"mound",
1395
+
"mount",
1396
+
"mourn",
1397
+
"mouse",
1398
+
"mouth",
1399
+
"mover",
1400
+
"movie",
1401
+
"mower",
1402
+
"mucky",
1403
+
"mucus",
1404
+
"muddy",
1405
+
"mulch",
1406
+
"mummy",
1407
+
"munch",
1408
+
"mural",
1409
+
"murky",
1410
+
"mushy",
1411
+
"music",
1412
+
"musky",
1413
+
"musty",
1414
+
"myrrh",
1415
+
"nadir",
1416
+
"naive",
1417
+
"nanny",
1418
+
"nasal",
1419
+
"nasty",
1420
+
"natal",
1421
+
"naval",
1422
+
"navel",
1423
+
"needy",
1424
+
"neigh",
1425
+
"nerdy",
1426
+
"nerve",
1427
+
"never",
1428
+
"newer",
1429
+
"newly",
1430
+
"nicer",
1431
+
"niche",
1432
+
"niece",
1433
+
"night",
1434
+
"ninja",
1435
+
"ninny",
1436
+
"ninth",
1437
+
"noble",
1438
+
"nobly",
1439
+
"noise",
1440
+
"noisy",
1441
+
"nomad",
1442
+
"noose",
1443
+
"north",
1444
+
"nosey",
1445
+
"notch",
1446
+
"novel",
1447
+
"nudge",
1448
+
"nurse",
1449
+
"nutty",
1450
+
"nylon",
1451
+
"nymph",
1452
+
"oaken",
1453
+
"obese",
1454
+
"occur",
1455
+
"ocean",
1456
+
"octal",
1457
+
"octet",
1458
+
"odder",
1459
+
"oddly",
1460
+
"offal",
1461
+
"offer",
1462
+
"often",
1463
+
"olden",
1464
+
"older",
1465
+
"olive",
1466
+
"ombre",
1467
+
"omega",
1468
+
"onion",
1469
+
"onset",
1470
+
"opera",
1471
+
"opine",
1472
+
"opium",
1473
+
"optic",
1474
+
"orbit",
1475
+
"order",
1476
+
"organ",
1477
+
"other",
1478
+
"otter",
1479
+
"ought",
1480
+
"ounce",
1481
+
"outdo",
1482
+
"outer",
1483
+
"outgo",
1484
+
"ovary",
1485
+
"ovate",
1486
+
"overt",
1487
+
"ovine",
1488
+
"ovoid",
1489
+
"owing",
1490
+
"owner",
1491
+
"oxide",
1492
+
"ozone",
1493
+
"paddy",
1494
+
"pagan",
1495
+
"paint",
1496
+
"paler",
1497
+
"palsy",
1498
+
"panel",
1499
+
"panic",
1500
+
"pansy",
1501
+
"papal",
1502
+
"paper",
1503
+
"parer",
1504
+
"parka",
1505
+
"parry",
1506
+
"parse",
1507
+
"party",
1508
+
"pasta",
1509
+
"paste",
1510
+
"pasty",
1511
+
"patch",
1512
+
"patio",
1513
+
"patsy",
1514
+
"patty",
1515
+
"pause",
1516
+
"payee",
1517
+
"payer",
1518
+
"peace",
1519
+
"peach",
1520
+
"pearl",
1521
+
"pecan",
1522
+
"pedal",
1523
+
"penal",
1524
+
"pence",
1525
+
"penne",
1526
+
"penny",
1527
+
"perch",
1528
+
"peril",
1529
+
"perky",
1530
+
"pesky",
1531
+
"pesto",
1532
+
"petal",
1533
+
"petty",
1534
+
"phase",
1535
+
"phone",
1536
+
"phony",
1537
+
"photo",
1538
+
"piano",
1539
+
"picky",
1540
+
"piece",
1541
+
"piety",
1542
+
"piggy",
1543
+
"pilot",
1544
+
"pinch",
1545
+
"piney",
1546
+
"pinky",
1547
+
"pinto",
1548
+
"piper",
1549
+
"pique",
1550
+
"pitch",
1551
+
"pithy",
1552
+
"pivot",
1553
+
"pixel",
1554
+
"pixie",
1555
+
"pizza",
1556
+
"place",
1557
+
"plaid",
1558
+
"plain",
1559
+
"plait",
1560
+
"plane",
1561
+
"plank",
1562
+
"plant",
1563
+
"plate",
1564
+
"plaza",
1565
+
"plead",
1566
+
"pleat",
1567
+
"plied",
1568
+
"plier",
1569
+
"pluck",
1570
+
"plumb",
1571
+
"plume",
1572
+
"plump",
1573
+
"plunk",
1574
+
"plush",
1575
+
"poesy",
1576
+
"point",
1577
+
"poise",
1578
+
"poker",
1579
+
"polar",
1580
+
"polka",
1581
+
"polyp",
1582
+
"pooch",
1583
+
"poppy",
1584
+
"porch",
1585
+
"poser",
1586
+
"posit",
1587
+
"posse",
1588
+
"pouch",
1589
+
"pound",
1590
+
"pouty",
1591
+
"power",
1592
+
"prank",
1593
+
"prawn",
1594
+
"preen",
1595
+
"press",
1596
+
"price",
1597
+
"prick",
1598
+
"pride",
1599
+
"pried",
1600
+
"prime",
1601
+
"primo",
1602
+
"print",
1603
+
"prior",
1604
+
"prism",
1605
+
"privy",
1606
+
"prize",
1607
+
"probe",
1608
+
"prone",
1609
+
"prong",
1610
+
"proof",
1611
+
"prose",
1612
+
"proud",
1613
+
"prove",
1614
+
"prowl",
1615
+
"proxy",
1616
+
"prude",
1617
+
"prune",
1618
+
"psalm",
1619
+
"pubic",
1620
+
"pudgy",
1621
+
"puffy",
1622
+
"pulpy",
1623
+
"pulse",
1624
+
"punch",
1625
+
"pupil",
1626
+
"puppy",
1627
+
"puree",
1628
+
"purer",
1629
+
"purge",
1630
+
"purse",
1631
+
"pushy",
1632
+
"putty",
1633
+
"pygmy",
1634
+
"quack",
1635
+
"quail",
1636
+
"quake",
1637
+
"qualm",
1638
+
"quark",
1639
+
"quart",
1640
+
"quash",
1641
+
"quasi",
1642
+
"queen",
1643
+
"queer",
1644
+
"quell",
1645
+
"query",
1646
+
"quest",
1647
+
"queue",
1648
+
"quick",
1649
+
"quiet",
1650
+
"quill",
1651
+
"quilt",
1652
+
"quirk",
1653
+
"quite",
1654
+
"quota",
1655
+
"quote",
1656
+
"quoth",
1657
+
"rabbi",
1658
+
"rabid",
1659
+
"racer",
1660
+
"radar",
1661
+
"radii",
1662
+
"radio",
1663
+
"rainy",
1664
+
"raise",
1665
+
"rajah",
1666
+
"rally",
1667
+
"ralph",
1668
+
"ramen",
1669
+
"ranch",
1670
+
"randy",
1671
+
"range",
1672
+
"rapid",
1673
+
"rarer",
1674
+
"raspy",
1675
+
"ratio",
1676
+
"ratty",
1677
+
"raven",
1678
+
"rayon",
1679
+
"razor",
1680
+
"reach",
1681
+
"react",
1682
+
"ready",
1683
+
"realm",
1684
+
"rearm",
1685
+
"rebar",
1686
+
"rebel",
1687
+
"rebus",
1688
+
"rebut",
1689
+
"recap",
1690
+
"recur",
1691
+
"recut",
1692
+
"reedy",
1693
+
"refer",
1694
+
"refit",
1695
+
"regal",
1696
+
"rehab",
1697
+
"reign",
1698
+
"relax",
1699
+
"relay",
1700
+
"relic",
1701
+
"remit",
1702
+
"renal",
1703
+
"renew",
1704
+
"repay",
1705
+
"repel",
1706
+
"reply",
1707
+
"rerun",
1708
+
"reset",
1709
+
"resin",
1710
+
"retch",
1711
+
"retro",
1712
+
"retry",
1713
+
"reuse",
1714
+
"revel",
1715
+
"revue",
1716
+
"rhino",
1717
+
"rhyme",
1718
+
"rider",
1719
+
"ridge",
1720
+
"rifle",
1721
+
"right",
1722
+
"rigid",
1723
+
"rigor",
1724
+
"rinse",
1725
+
"ripen",
1726
+
"riper",
1727
+
"risen",
1728
+
"riser",
1729
+
"risky",
1730
+
"rival",
1731
+
"river",
1732
+
"rivet",
1733
+
"roach",
1734
+
"roast",
1735
+
"robin",
1736
+
"robot",
1737
+
"rocky",
1738
+
"rodeo",
1739
+
"roger",
1740
+
"rogue",
1741
+
"roomy",
1742
+
"roost",
1743
+
"rotor",
1744
+
"rouge",
1745
+
"rough",
1746
+
"round",
1747
+
"rouse",
1748
+
"route",
1749
+
"rover",
1750
+
"rowdy",
1751
+
"rower",
1752
+
"royal",
1753
+
"ruddy",
1754
+
"ruder",
1755
+
"rugby",
1756
+
"ruler",
1757
+
"rumba",
1758
+
"rumor",
1759
+
"rupee",
1760
+
"rural",
1761
+
"rusty",
1762
+
"sadly",
1763
+
"safer",
1764
+
"saint",
1765
+
"salad",
1766
+
"sally",
1767
+
"salon",
1768
+
"salsa",
1769
+
"salty",
1770
+
"salve",
1771
+
"salvo",
1772
+
"sandy",
1773
+
"saner",
1774
+
"sappy",
1775
+
"sassy",
1776
+
"satin",
1777
+
"satyr",
1778
+
"sauce",
1779
+
"saucy",
1780
+
"sauna",
1781
+
"saute",
1782
+
"savor",
1783
+
"savoy",
1784
+
"savvy",
1785
+
"scald",
1786
+
"scale",
1787
+
"scalp",
1788
+
"scaly",
1789
+
"scamp",
1790
+
"scant",
1791
+
"scare",
1792
+
"scarf",
1793
+
"scary",
1794
+
"scene",
1795
+
"scent",
1796
+
"scion",
1797
+
"scoff",
1798
+
"scold",
1799
+
"scone",
1800
+
"scoop",
1801
+
"scope",
1802
+
"score",
1803
+
"scorn",
1804
+
"scour",
1805
+
"scout",
1806
+
"scowl",
1807
+
"scram",
1808
+
"scrap",
1809
+
"scree",
1810
+
"screw",
1811
+
"scrub",
1812
+
"scrum",
1813
+
"scuba",
1814
+
"sedan",
1815
+
"seedy",
1816
+
"segue",
1817
+
"seize",
1818
+
"semen",
1819
+
"sense",
1820
+
"sepia",
1821
+
"serif",
1822
+
"serum",
1823
+
"serve",
1824
+
"setup",
1825
+
"seven",
1826
+
"sever",
1827
+
"sewer",
1828
+
"shack",
1829
+
"shade",
1830
+
"shady",
1831
+
"shaft",
1832
+
"shake",
1833
+
"shaky",
1834
+
"shale",
1835
+
"shall",
1836
+
"shalt",
1837
+
"shame",
1838
+
"shank",
1839
+
"shape",
1840
+
"shard",
1841
+
"share",
1842
+
"shark",
1843
+
"sharp",
1844
+
"shave",
1845
+
"shawl",
1846
+
"shear",
1847
+
"sheen",
1848
+
"sheep",
1849
+
"sheer",
1850
+
"sheet",
1851
+
"sheik",
1852
+
"shelf",
1853
+
"shell",
1854
+
"shied",
1855
+
"shift",
1856
+
"shine",
1857
+
"shiny",
1858
+
"shire",
1859
+
"shirk",
1860
+
"shirt",
1861
+
"shoal",
1862
+
"shock",
1863
+
"shone",
1864
+
"shook",
1865
+
"shoot",
1866
+
"shore",
1867
+
"shorn",
1868
+
"short",
1869
+
"shout",
1870
+
"shove",
1871
+
"shown",
1872
+
"showy",
1873
+
"shrew",
1874
+
"shrub",
1875
+
"shrug",
1876
+
"shuck",
1877
+
"shunt",
1878
+
"shush",
1879
+
"shyly",
1880
+
"siege",
1881
+
"sieve",
1882
+
"sight",
1883
+
"sigma",
1884
+
"silky",
1885
+
"silly",
1886
+
"since",
1887
+
"sinew",
1888
+
"singe",
1889
+
"siren",
1890
+
"sissy",
1891
+
"sixth",
1892
+
"sixty",
1893
+
"skate",
1894
+
"skier",
1895
+
"skiff",
1896
+
"skill",
1897
+
"skimp",
1898
+
"skirt",
1899
+
"skulk",
1900
+
"skull",
1901
+
"skunk",
1902
+
"slack",
1903
+
"slain",
1904
+
"slang",
1905
+
"slant",
1906
+
"slash",
1907
+
"slate",
1908
+
"sleek",
1909
+
"sleep",
1910
+
"sleet",
1911
+
"slept",
1912
+
"slice",
1913
+
"slick",
1914
+
"slide",
1915
+
"slime",
1916
+
"slimy",
1917
+
"sling",
1918
+
"slink",
1919
+
"sloop",
1920
+
"slope",
1921
+
"slosh",
1922
+
"sloth",
1923
+
"slump",
1924
+
"slung",
1925
+
"slunk",
1926
+
"slurp",
1927
+
"slush",
1928
+
"slyly",
1929
+
"smack",
1930
+
"small",
1931
+
"smart",
1932
+
"smash",
1933
+
"smear",
1934
+
"smell",
1935
+
"smelt",
1936
+
"smile",
1937
+
"smirk",
1938
+
"smite",
1939
+
"smith",
1940
+
"smock",
1941
+
"smoke",
1942
+
"smoky",
1943
+
"smote",
1944
+
"snack",
1945
+
"snail",
1946
+
"snake",
1947
+
"snaky",
1948
+
"snare",
1949
+
"snarl",
1950
+
"sneak",
1951
+
"sneer",
1952
+
"snide",
1953
+
"sniff",
1954
+
"snipe",
1955
+
"snoop",
1956
+
"snore",
1957
+
"snort",
1958
+
"snout",
1959
+
"snowy",
1960
+
"snuck",
1961
+
"snuff",
1962
+
"soapy",
1963
+
"sober",
1964
+
"soggy",
1965
+
"solar",
1966
+
"solid",
1967
+
"solve",
1968
+
"sonar",
1969
+
"sonic",
1970
+
"sooth",
1971
+
"sooty",
1972
+
"sorry",
1973
+
"sound",
1974
+
"south",
1975
+
"sower",
1976
+
"space",
1977
+
"spade",
1978
+
"spank",
1979
+
"spare",
1980
+
"spark",
1981
+
"spasm",
1982
+
"spawn",
1983
+
"speak",
1984
+
"spear",
1985
+
"speck",
1986
+
"speed",
1987
+
"spell",
1988
+
"spelt",
1989
+
"spend",
1990
+
"spent",
1991
+
"sperm",
1992
+
"spice",
1993
+
"spicy",
1994
+
"spied",
1995
+
"spiel",
1996
+
"spike",
1997
+
"spiky",
1998
+
"spill",
1999
+
"spilt",
2000
+
"spine",
2001
+
"spiny",
2002
+
"spire",
2003
+
"spite",
2004
+
"splat",
2005
+
"split",
2006
+
"spoil",
2007
+
"spoke",
2008
+
"spoof",
2009
+
"spook",
2010
+
"spool",
2011
+
"spoon",
2012
+
"spore",
2013
+
"sport",
2014
+
"spout",
2015
+
"spray",
2016
+
"spree",
2017
+
"sprig",
2018
+
"spunk",
2019
+
"spurn",
2020
+
"spurt",
2021
+
"squad",
2022
+
"squat",
2023
+
"squib",
2024
+
"stack",
2025
+
"staff",
2026
+
"stage",
2027
+
"staid",
2028
+
"stain",
2029
+
"stair",
2030
+
"stake",
2031
+
"stale",
2032
+
"stalk",
2033
+
"stall",
2034
+
"stamp",
2035
+
"stand",
2036
+
"stank",
2037
+
"stare",
2038
+
"stark",
2039
+
"start",
2040
+
"stash",
2041
+
"state",
2042
+
"stave",
2043
+
"stead",
2044
+
"steak",
2045
+
"steal",
2046
+
"steam",
2047
+
"steed",
2048
+
"steel",
2049
+
"steep",
2050
+
"steer",
2051
+
"stein",
2052
+
"stern",
2053
+
"stick",
2054
+
"stiff",
2055
+
"still",
2056
+
"stilt",
2057
+
"sting",
2058
+
"stink",
2059
+
"stint",
2060
+
"stock",
2061
+
"stoic",
2062
+
"stoke",
2063
+
"stole",
2064
+
"stomp",
2065
+
"stone",
2066
+
"stony",
2067
+
"stood",
2068
+
"stool",
2069
+
"stoop",
2070
+
"store",
2071
+
"stork",
2072
+
"storm",
2073
+
"story",
2074
+
"stout",
2075
+
"stove",
2076
+
"strap",
2077
+
"straw",
2078
+
"stray",
2079
+
"strip",
2080
+
"strut",
2081
+
"stuck",
2082
+
"study",
2083
+
"stuff",
2084
+
"stump",
2085
+
"stung",
2086
+
"stunk",
2087
+
"stunt",
2088
+
"style",
2089
+
"suave",
2090
+
"sugar",
2091
+
"suing",
2092
+
"suite",
2093
+
"sulky",
2094
+
"sully",
2095
+
"sumac",
2096
+
"sunny",
2097
+
"super",
2098
+
"surer",
2099
+
"surge",
2100
+
"surly",
2101
+
"sushi",
2102
+
"swami",
2103
+
"swamp",
2104
+
"swarm",
2105
+
"swash",
2106
+
"swath",
2107
+
"swear",
2108
+
"sweat",
2109
+
"sweep",
2110
+
"sweet",
2111
+
"swell",
2112
+
"swept",
2113
+
"swift",
2114
+
"swill",
2115
+
"swine",
2116
+
"swing",
2117
+
"swirl",
2118
+
"swish",
2119
+
"swoon",
2120
+
"swoop",
2121
+
"sword",
2122
+
"swore",
2123
+
"sworn",
2124
+
"swung",
2125
+
"synod",
2126
+
"syrup",
2127
+
"tabby",
2128
+
"table",
2129
+
"taboo",
2130
+
"tacit",
2131
+
"tacky",
2132
+
"taffy",
2133
+
"taint",
2134
+
"taken",
2135
+
"taker",
2136
+
"tally",
2137
+
"talon",
2138
+
"tamer",
2139
+
"tango",
2140
+
"tangy",
2141
+
"taper",
2142
+
"tapir",
2143
+
"tardy",
2144
+
"tarot",
2145
+
"taste",
2146
+
"tasty",
2147
+
"tatty",
2148
+
"taunt",
2149
+
"tawny",
2150
+
"teach",
2151
+
"teary",
2152
+
"tease",
2153
+
"teddy",
2154
+
"teeth",
2155
+
"tempo",
2156
+
"tenet",
2157
+
"tenor",
2158
+
"tense",
2159
+
"tenth",
2160
+
"tepee",
2161
+
"tepid",
2162
+
"terra",
2163
+
"terse",
2164
+
"testy",
2165
+
"thank",
2166
+
"theft",
2167
+
"their",
2168
+
"theme",
2169
+
"there",
2170
+
"these",
2171
+
"theta",
2172
+
"thick",
2173
+
"thief",
2174
+
"thigh",
2175
+
"thing",
2176
+
"think",
2177
+
"third",
2178
+
"thong",
2179
+
"thorn",
2180
+
"those",
2181
+
"three",
2182
+
"threw",
2183
+
"throb",
2184
+
"throw",
2185
+
"thrum",
2186
+
"thumb",
2187
+
"thump",
2188
+
"thyme",
2189
+
"tiara",
2190
+
"tibia",
2191
+
"tidal",
2192
+
"tiger",
2193
+
"tight",
2194
+
"tilde",
2195
+
"timer",
2196
+
"timid",
2197
+
"tipsy",
2198
+
"titan",
2199
+
"tithe",
2200
+
"title",
2201
+
"toast",
2202
+
"today",
2203
+
"toddy",
2204
+
"token",
2205
+
"tonal",
2206
+
"tonga",
2207
+
"tonic",
2208
+
"tooth",
2209
+
"topaz",
2210
+
"topic",
2211
+
"torch",
2212
+
"torso",
2213
+
"torus",
2214
+
"total",
2215
+
"totem",
2216
+
"touch",
2217
+
"tough",
2218
+
"towel",
2219
+
"tower",
2220
+
"toxic",
2221
+
"toxin",
2222
+
"trace",
2223
+
"track",
2224
+
"tract",
2225
+
"trade",
2226
+
"trail",
2227
+
"train",
2228
+
"trait",
2229
+
"tramp",
2230
+
"trash",
2231
+
"trawl",
2232
+
"tread",
2233
+
"treat",
2234
+
"trend",
2235
+
"triad",
2236
+
"trial",
2237
+
"tribe",
2238
+
"trice",
2239
+
"trick",
2240
+
"tried",
2241
+
"tripe",
2242
+
"trite",
2243
+
"troll",
2244
+
"troop",
2245
+
"trope",
2246
+
"trout",
2247
+
"trove",
2248
+
"truce",
2249
+
"truck",
2250
+
"truer",
2251
+
"truly",
2252
+
"trump",
2253
+
"trunk",
2254
+
"truss",
2255
+
"trust",
2256
+
"truth",
2257
+
"tryst",
2258
+
"tubal",
2259
+
"tuber",
2260
+
"tulip",
2261
+
"tulle",
2262
+
"tumor",
2263
+
"tunic",
2264
+
"turbo",
2265
+
"tutor",
2266
+
"twang",
2267
+
"tweak",
2268
+
"tweed",
2269
+
"tweet",
2270
+
"twice",
2271
+
"twine",
2272
+
"twirl",
2273
+
"twist",
2274
+
"twixt",
2275
+
"tying",
2276
+
"udder",
2277
+
"ulcer",
2278
+
"ultra",
2279
+
"umbra",
2280
+
"uncle",
2281
+
"uncut",
2282
+
"under",
2283
+
"undid",
2284
+
"undue",
2285
+
"unfed",
2286
+
"unfit",
2287
+
"unify",
2288
+
"union",
2289
+
"unite",
2290
+
"unity",
2291
+
"unlit",
2292
+
"unmet",
2293
+
"unset",
2294
+
"untie",
2295
+
"until",
2296
+
"unwed",
2297
+
"unzip",
2298
+
"upper",
2299
+
"upset",
2300
+
"urban",
2301
+
"urine",
2302
+
"usage",
2303
+
"usher",
2304
+
"using",
2305
+
"usual",
2306
+
"usurp",
2307
+
"utile",
2308
+
"utter",
2309
+
"vague",
2310
+
"valet",
2311
+
"valid",
2312
+
"valor",
2313
+
"value",
2314
+
"valve",
2315
+
"vapid",
2316
+
"vapor",
2317
+
"vault",
2318
+
"vaunt",
2319
+
"vegan",
2320
+
"venom",
2321
+
"venue",
2322
+
"verge",
2323
+
"verse",
2324
+
"verso",
2325
+
"verve",
2326
+
"vicar",
2327
+
"video",
2328
+
"vigil",
2329
+
"vigor",
2330
+
"villa",
2331
+
"vinyl",
2332
+
"viola",
2333
+
"viper",
2334
+
"viral",
2335
+
"virus",
2336
+
"visit",
2337
+
"visor",
2338
+
"vista",
2339
+
"vital",
2340
+
"vivid",
2341
+
"vixen",
2342
+
"vocal",
2343
+
"vodka",
2344
+
"vogue",
2345
+
"voice",
2346
+
"voila",
2347
+
"vomit",
2348
+
"voter",
2349
+
"vouch",
2350
+
"vowel",
2351
+
"vying",
2352
+
"wacky",
2353
+
"wafer",
2354
+
"wager",
2355
+
"wagon",
2356
+
"waist",
2357
+
"waive",
2358
+
"waltz",
2359
+
"warty",
2360
+
"waste",
2361
+
"watch",
2362
+
"water",
2363
+
"waver",
2364
+
"waxen",
2365
+
"weary",
2366
+
"weave",
2367
+
"wedge",
2368
+
"weedy",
2369
+
"weigh",
2370
+
"weird",
2371
+
"welch",
2372
+
"welsh",
2373
+
"whack",
2374
+
"whale",
2375
+
"wharf",
2376
+
"wheat",
2377
+
"wheel",
2378
+
"whelp",
2379
+
"where",
2380
+
"which",
2381
+
"whiff",
2382
+
"while",
2383
+
"whine",
2384
+
"whiny",
2385
+
"whirl",
2386
+
"whisk",
2387
+
"white",
2388
+
"whole",
2389
+
"whoop",
2390
+
"whose",
2391
+
"widen",
2392
+
"wider",
2393
+
"widow",
2394
+
"width",
2395
+
"wield",
2396
+
"wight",
2397
+
"willy",
2398
+
"wimpy",
2399
+
"wince",
2400
+
"winch",
2401
+
"windy",
2402
+
"wiser",
2403
+
"wispy",
2404
+
"witch",
2405
+
"witty",
2406
+
"woken",
2407
+
"woman",
2408
+
"women",
2409
+
"woody",
2410
+
"wooer",
2411
+
"wooly",
2412
+
"woozy",
2413
+
"wordy",
2414
+
"world",
2415
+
"worry",
2416
+
"worse",
2417
+
"worst",
2418
+
"worth",
2419
+
"would",
2420
+
"wound",
2421
+
"woven",
2422
+
"wrack",
2423
+
"wrath",
2424
+
"wreak",
2425
+
"wreck",
2426
+
"wrest",
2427
+
"wring",
2428
+
"wrist",
2429
+
"write",
2430
+
"wrong",
2431
+
"wrote",
2432
+
"wrung",
2433
+
"wryly",
2434
+
"yacht",
2435
+
"yearn",
2436
+
"yeast",
2437
+
"yield",
2438
+
"young",
2439
+
"youth",
2440
+
"zebra",
2441
+
"zesty",
2442
+
"zonal",
2443
+
]
2444
+
return words
2445
+
2446
+
2447
+
def main():
2448
+
runApp()
2449
+
2450
+
2451
+
main()
+80
python/oct8/level1/insertRowAndCol.py
+80
python/oct8/level1/insertRowAndCol.py
···
1
+
from typing import List
2
+
3
+
4
+
def mutatingInsertRowAndCol(L: List[List[int]], row: int, col: int, val: int):
5
+
"""Inserts a row and column into a matrix."""
6
+
L.insert(row, [val] * len(L[0]))
7
+
for i in range(len(L)):
8
+
L[i].insert(col, val)
9
+
10
+
11
+
def nonmutatingInsertRowAndCol(
12
+
L: List[List[int]], row: int, col: int, val: int
13
+
) -> List[List[int]]:
14
+
"""Inserts a row and column into a matrix without mutating the original matrix."""
15
+
new_list = [[v for v in l] for l in L]
16
+
17
+
new_list.insert(row, [val] * len(new_list[0]))
18
+
for i in range(len(new_list)):
19
+
new_list[i].insert(col, val)
20
+
21
+
return new_list
22
+
23
+
24
+
def testMutatingInsertRowAndCol():
25
+
print("Testing mutatingInsertRowAndCol()...", end="")
26
+
L = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
27
+
mutatingInsertRowAndCol(L, 1, 2, 42)
28
+
assert L == [
29
+
[1, 2, 42, 3, 4],
30
+
[42, 42, 42, 42, 42],
31
+
[5, 6, 42, 7, 8],
32
+
[9, 10, 42, 11, 12],
33
+
]
34
+
35
+
L = [[1, 2, 3], [4, 5, 6]]
36
+
mutatingInsertRowAndCol(L, 0, 0, 5)
37
+
assert L == [[5, 5, 5, 5], [5, 1, 2, 3], [5, 4, 5, 6]]
38
+
39
+
L = [[1, 2, 3], [4, 5, 6]]
40
+
mutatingInsertRowAndCol(L, 1, 3, 5)
41
+
assert L == [[1, 2, 3, 5], [5, 5, 5, 5], [4, 5, 6, 5]]
42
+
43
+
44
+
def testNonmutatingInsertRowAndCol():
45
+
print("Testing nonmutatingInsertRowAndCol()...", end="")
46
+
L = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
47
+
assert nonmutatingInsertRowAndCol(L, 1, 2, 42) == [
48
+
[1, 2, 42, 3, 4],
49
+
[42, 42, 42, 42, 42],
50
+
[5, 6, 42, 7, 8],
51
+
[9, 10, 42, 11, 12],
52
+
]
53
+
54
+
L = [[1, 2, 3], [4, 5, 6]]
55
+
assert nonmutatingInsertRowAndCol(L, 0, 0, 5) == [
56
+
[5, 5, 5, 5],
57
+
[5, 1, 2, 3],
58
+
[5, 4, 5, 6],
59
+
]
60
+
61
+
L = [[1, 2, 3], [4, 5, 6]]
62
+
assert nonmutatingInsertRowAndCol(L, 1, 3, 5) == [
63
+
[1, 2, 3, 5],
64
+
[5, 5, 5, 5],
65
+
[4, 5, 6, 5],
66
+
]
67
+
68
+
# Verify that the function is non-mutating
69
+
L = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
70
+
nonmutatingInsertRowAndCol(L, 1, 2, 42)
71
+
assert L == [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
72
+
print("Passed!")
73
+
74
+
75
+
def main():
76
+
testMutatingInsertRowAndCol()
77
+
testNonmutatingInsertRowAndCol()
78
+
79
+
80
+
main()
+24
python/oct8/level1/isRectangular.py
+24
python/oct8/level1/isRectangular.py
···
1
+
from typing import List
2
+
3
+
4
+
def isRectangular(L: List[List[int]]) -> bool:
5
+
"""Check if the length of each row is equal to the length of the first row."""
6
+
return all(len(row) == len(L[0]) for row in L)
7
+
8
+
9
+
def testIsRectangular():
10
+
print("Testing isRectangular()...", end="")
11
+
L = [[1, 2, 3], [4, 5, 6]]
12
+
assert isRectangular(L) == True
13
+
L = [[1, 2], [4, 5, 6]]
14
+
assert isRectangular(L) == False
15
+
L = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
16
+
assert isRectangular(L) == True
17
+
print("Passed!")
18
+
19
+
20
+
def main():
21
+
testIsRectangular()
22
+
23
+
24
+
main()
+30
python/oct8/level1/largestColumnSum.py
+30
python/oct8/level1/largestColumnSum.py
···
1
+
from typing import List
2
+
3
+
4
+
def largestColumnSum(L: List[List[int]]) -> int:
5
+
"""Return the largest sum of any column in the matrix."""
6
+
cols = [sum(col) for col in zip(*L)]
7
+
return max(cols)
8
+
9
+
10
+
def testLargestColumnSum():
11
+
print("Testing largestColumnSum()...", end="")
12
+
L = [[1, 2, 3], [4, 5, 6]]
13
+
assert largestColumnSum(L) == 9 # sum([3, 6])
14
+
15
+
L = [[3, 2, 1], [-5, -3, -6]]
16
+
assert largestColumnSum(L) == -1 # sum([2, -3])
17
+
18
+
L = [[1, 2, 3], [4, 5, 6], [7, -1, -2]]
19
+
assert largestColumnSum(L) == 12 # sum([1, 4, 7])
20
+
21
+
L = [[7, -1, -2]]
22
+
assert largestColumnSum(L) == 7 # sum([7])
23
+
print("Passed!")
24
+
25
+
26
+
def main():
27
+
testLargestColumnSum()
28
+
29
+
30
+
main()
+29
python/oct9/level1/bothLists.py
+29
python/oct9/level1/bothLists.py
···
1
+
from typing import List
2
+
3
+
4
+
def inBothLists(L: List[int], M: List[int]) -> List[int]:
5
+
"""Returns a list of unique elements that are present in both L and M."""
6
+
return list({x for x in L if x in M})
7
+
8
+
9
+
def testInBothLists():
10
+
print("Testing inBothLists()...", end="")
11
+
assert inBothLists([1, 2, 3], [3, 2, 4]) == [2, 3]
12
+
assert inBothLists([3, 2, 1], [4, 1, 2, 1]) == [1, 2]
13
+
assert inBothLists([3, 2, 1, 2], [2, 2, 2]) == [2]
14
+
assert inBothLists([1, 2, 3], [4, 5, 6, 1]) == [1]
15
+
assert inBothLists([3, 2, 1, 2], [4]) == []
16
+
17
+
# Verify that the function is nonmutating:
18
+
L = [1, 2, 3]
19
+
M = [3, 2, 4]
20
+
inBothLists(L, M)
21
+
assert L == [1, 2, 3] and M == [3, 2, 4]
22
+
print("Passed!")
23
+
24
+
25
+
def main():
26
+
testInBothLists()
27
+
28
+
29
+
main()
+28
python/oct9/level1/hasNoDuplicates.py
+28
python/oct9/level1/hasNoDuplicates.py
···
1
+
from typing import List
2
+
3
+
4
+
def hasNoDuplicates(L: List[int]) -> bool:
5
+
"""Returns True if the list has no duplicates, False otherwise."""
6
+
return len(set(L)) == len(L)
7
+
8
+
9
+
def testHasNoDuplicates():
10
+
print("Testing hasNoDuplicates()...", end="")
11
+
assert hasNoDuplicates([1, 2, 3, 2]) == False
12
+
assert hasNoDuplicates([1, 2, 3, 4]) == True
13
+
assert hasNoDuplicates([]) == True
14
+
assert hasNoDuplicates([42]) == True
15
+
assert hasNoDuplicates([42, 42]) == False
16
+
17
+
# Verify that the function is nonmutating:
18
+
L = [1, 2, 3, 2]
19
+
hasNoDuplicates(L)
20
+
assert L == [1, 2, 3, 2]
21
+
print("Passed!")
22
+
23
+
24
+
def main():
25
+
testHasNoDuplicates()
26
+
27
+
28
+
main()
+27
python/oct9/level1/isPermutation.py
+27
python/oct9/level1/isPermutation.py
···
1
+
from typing import List
2
+
3
+
4
+
def isPermutation(L: List[int]) -> bool:
5
+
"""Returns True if L is a permutation of the integers from 0 to len(L)-1."""
6
+
return sorted(L) == list(range(len(L)))
7
+
8
+
9
+
def testIsPermutation():
10
+
print("Testing isPermutation()...", end="")
11
+
assert isPermutation([0, 2, 1, 4, 3]) == True
12
+
assert isPermutation([1, 3, 0, 4, 2]) == True
13
+
assert isPermutation([1, 3, 5, 4, 2]) == False
14
+
assert isPermutation([1, 4, 0, 4, 2]) == False
15
+
16
+
# Verify that the function is nonmutating:
17
+
L = [0, 2, 1, 4, 3]
18
+
isPermutation(L)
19
+
assert L == [0, 2, 1, 4, 3]
20
+
print("Passed!")
21
+
22
+
23
+
def main():
24
+
testIsPermutation()
25
+
26
+
27
+
main()
+89
python/oct9/level1/movieAwards.py
+89
python/oct9/level1/movieAwards.py
···
1
+
from typing import Set, Tuple, Dict
2
+
3
+
4
+
def movieAwards(oscarResults: Set[Tuple[str, str]]) -> Dict[str, int]:
5
+
"""Returns a dictionary mapping movie titles to the number of awards they won."""
6
+
awards_count = {}
7
+
for _, movie in oscarResults:
8
+
awards_count[movie] = awards_count.get(movie, 0) + 1
9
+
return awards_count
10
+
11
+
12
+
def testMovieAwards():
13
+
print("Testing movieAwards()...", end="")
14
+
test = {
15
+
("Best Picture", "The Shape of Water"),
16
+
("Best Actor", "Darkest Hour"),
17
+
("Best Actress", "Three Billboards Outside Ebbing, Missouri"),
18
+
("Best Director", "The Shape of Water"),
19
+
}
20
+
result = {
21
+
"Darkest Hour": 1,
22
+
"Three Billboards Outside Ebbing, Missouri": 1,
23
+
"The Shape of Water": 2,
24
+
}
25
+
assert movieAwards(test) == result
26
+
27
+
test = {
28
+
("Best Picture", "Moonlight"),
29
+
("Best Director", "La La Land"),
30
+
("Best Actor", "Manchester by the Sea"),
31
+
("Best Actress", "La La Land"),
32
+
}
33
+
result = {"Moonlight": 1, "La La Land": 2, "Manchester by the Sea": 1}
34
+
assert movieAwards(test) == result
35
+
36
+
test = {
37
+
("Best Picture", "12 Years a Slave"),
38
+
("Best Director", "Gravity"),
39
+
("Best Actor", "Dallas Buyers Club"),
40
+
("Best Actress", "Blue Jasmine"),
41
+
}
42
+
result = {
43
+
"12 Years a Slave": 1,
44
+
"Gravity": 1,
45
+
"Dallas Buyers Club": 1,
46
+
"Blue Jasmine": 1,
47
+
}
48
+
assert movieAwards(test) == result
49
+
50
+
test = {
51
+
("Best Picture", "The King's Speech"),
52
+
("Best Director", "The King's Speech"),
53
+
("Best Actor", "The King's Speech"),
54
+
}
55
+
result = {"The King's Speech": 3}
56
+
assert movieAwards(test) == result
57
+
58
+
test = {
59
+
("Best Picture", "Spotlight"),
60
+
("Best Director", "The Revenant"),
61
+
("Best Actor", "The Revenant"),
62
+
("Best Actress", "Room"),
63
+
("Best Supporting Actor", "Bridge of Spies"),
64
+
("Best Supporting Actress", "The Danish Girl"),
65
+
("Best Original Screenplay", "Spotlight"),
66
+
("Best Adapted Screenplay", "The Big Short"),
67
+
("Best Production Design", "Mad Max: Fury Road"),
68
+
("Best Cinematography", "The Revenant"),
69
+
}
70
+
result = {
71
+
"Spotlight": 2,
72
+
"The Revenant": 3,
73
+
"Room": 1,
74
+
"Bridge of Spies": 1,
75
+
"The Danish Girl": 1,
76
+
"The Big Short": 1,
77
+
"Mad Max: Fury Road": 1,
78
+
}
79
+
assert movieAwards(test) == result
80
+
81
+
assert movieAwards(set()) == dict()
82
+
print("Passed!")
83
+
84
+
85
+
def main():
86
+
testMovieAwards()
87
+
88
+
89
+
main()
+29
python/oct9/level1/repeats.py
+29
python/oct9/level1/repeats.py
···
1
+
from typing import List
2
+
3
+
4
+
def repeats(L: List[int]) -> List[int]:
5
+
"""Returns a sorted list of unique elements that are present in L more than once."""
6
+
return list(sorted({x for x in L if L.count(x) > 1}))
7
+
8
+
9
+
def testRepeats():
10
+
print("Testing repeats()...", end="")
11
+
assert repeats([1, 2, 3, 2, 1]) == [1, 2]
12
+
assert repeats([1, 2, 3, 2, 2, 4]) == [2]
13
+
assert repeats([1, 5, 3, 5, 2, 3, 2, 1]) == [1, 2, 3, 5]
14
+
assert repeats([7, 9, 1, 3, 7, 1]) == [1, 7]
15
+
assert repeats(list(range(100))) == []
16
+
assert repeats(list(range(100)) * 5) == list(range(100))
17
+
18
+
# Verify that the function is nonmutating:
19
+
L = [1, 2, 3, 2, 1]
20
+
repeats(L)
21
+
assert L == [1, 2, 3, 2, 1]
22
+
print("Passed!")
23
+
24
+
25
+
def main():
26
+
testRepeats()
27
+
28
+
29
+
main()
+16
python/sep30/level1/numberOfBricks.py
+16
python/sep30/level1/numberOfBricks.py
···
1
+
from math import ceil
2
+
3
+
4
+
def numberOfBricks(steps: int) -> int:
5
+
"""Calculate the number of bricks in some steps using arithmetic series: S(n) = n(n+1)/2."""
6
+
return ceil((steps * (steps + 1)) / 2)
7
+
8
+
9
+
print("Testing numberOfBricks()...", end="")
10
+
assert numberOfBricks(0) == 0
11
+
assert numberOfBricks(1) == 1
12
+
assert numberOfBricks(2) == 3
13
+
assert numberOfBricks(3) == 6
14
+
assert numberOfBricks(4) == 10
15
+
assert numberOfBricks(10) == 55
16
+
print("Passed!")
+34
python/sep30/level1/pizzas.py
+34
python/sep30/level1/pizzas.py
···
1
+
def howManyPizzas(students: int, slicesPerStudent: int) -> int:
2
+
"""Check how many pizzas are needed given that each pizza has 8 slices."""
3
+
neededSlices = students * slicesPerStudent
4
+
return (neededSlices + 7) // 8
5
+
6
+
7
+
def leftoverPizzaSlices(students: int, slicesPerStudent: int) -> int:
8
+
"""Check how many slices of pizza will be leftover given that each pizza has 8 slices."""
9
+
neededSlices = students * slicesPerStudent
10
+
totalSlices = howManyPizzas(students, slicesPerStudent) * 8
11
+
return totalSlices - neededSlices
12
+
13
+
14
+
print("Testing howManyPizzas()...", end="")
15
+
assert howManyPizzas(8, 1) == 1
16
+
assert howManyPizzas(9, 1) == 2
17
+
assert howManyPizzas(5, 4) == 3
18
+
assert howManyPizzas(10, 2) == 3
19
+
assert howManyPizzas(0, 0) == 0
20
+
assert howManyPizzas(0, 3) == 0
21
+
assert howManyPizzas(10, 0) == 0
22
+
assert howManyPizzas(3, 4) == 2
23
+
print("Passed!")
24
+
25
+
print("Testing leftoverPizzaSlices()...", end="")
26
+
assert leftoverPizzaSlices(8, 1) == 0
27
+
assert leftoverPizzaSlices(9, 1) == 7
28
+
assert leftoverPizzaSlices(5, 4) == 4
29
+
assert leftoverPizzaSlices(10, 2) == 4
30
+
assert leftoverPizzaSlices(0, 0) == 0
31
+
assert leftoverPizzaSlices(0, 3) == 0
32
+
assert leftoverPizzaSlices(10, 0) == 0
33
+
assert leftoverPizzaSlices(3, 4) == 4
34
+
print("Passed!")
+26
python/sep30/level1/pythagoreanTriple.py
+26
python/sep30/level1/pythagoreanTriple.py
···
1
+
def isPythagoreanTriple(a: float, b: float, c: float) -> bool:
2
+
"""
3
+
Return true if all the following are True:
4
+
- check that numbers are ascending
5
+
- check that a^2 + b^2 = c^2
6
+
- check that a, b, and c are positive
7
+
"""
8
+
return (
9
+
(a < b and b < c) and ((a**2 + b**2) == c**2) and not (a < 0 or b < 0 or c < 0)
10
+
)
11
+
12
+
13
+
print("Testing isPythagoreanTriple()...", end="")
14
+
assert isPythagoreanTriple(3, 4, 5) == True # 3**2 + 4**2 == 5**2
15
+
assert isPythagoreanTriple(5, 4, 3) == False # wrong order
16
+
assert isPythagoreanTriple(4, 5, 3) == False # wrong order
17
+
18
+
assert isPythagoreanTriple(5, 12, 13) == True # 5**2 + 12**2 == 13**2
19
+
assert isPythagoreanTriple(13, 12, 5) == False # wrong order
20
+
assert isPythagoreanTriple(12, 13, 5) == False # wrong order
21
+
22
+
assert isPythagoreanTriple(-3, 4, 5) == False # no negatives
23
+
assert isPythagoreanTriple(0, 5, 5) == False # no 0's
24
+
assert isPythagoreanTriple(1, 1, 1) == False # 1**2 + 1**2 != 1**2
25
+
assert isPythagoreanTriple(3, 4, 6) == False # 3**2 + 4**2 != 6**2
26
+
print("Passed!")
+27
python/sep30/level1/triangleArea.py
+27
python/sep30/level1/triangleArea.py
···
1
+
from math import sqrt
2
+
3
+
4
+
def almostEqual(x: float, y: float) -> bool:
5
+
return abs(x - y) < 10**-9
6
+
7
+
8
+
def triangleArea(a: float, b: float, c: float) -> float:
9
+
"""Use herons formula: `sp = (a+b+c)/2` & `sqrt(sp(sp-a)(sp-b)(sp-c))` to calculate the area of a triangle using it's side lengths."""
10
+
semiperimeter = 0.5 * (a + b + c)
11
+
return sqrt(
12
+
semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c)
13
+
)
14
+
15
+
16
+
print("Testing triangleArea()...", end="")
17
+
assert almostEqual(triangleArea(3, 4, 5), 6)
18
+
assert almostEqual(triangleArea(15, 9, 12), 54)
19
+
assert almostEqual(triangleArea(7, 25, 24), 84)
20
+
assert almostEqual(triangleArea(8, 15, 17), 60)
21
+
assert almostEqual(triangleArea(0, 0, 0), 0)
22
+
assert almostEqual(triangleArea(1, 1, 1), sqrt(3) / 4)
23
+
assert almostEqual(triangleArea(5, 5, 5), 25 * sqrt(3) / 4)
24
+
assert almostEqual(triangleArea(12, 12, 12), 144 * sqrt(3) / 4)
25
+
assert almostEqual(triangleArea(7, 12, 18), sqrt(11063) / 4)
26
+
assert almostEqual(triangleArea(9.1, 11.7, 3), 7 * sqrt(3026) / 50)
27
+
print("Passed!")
+25
python/sep30/level2/dotsOverlap.py
+25
python/sep30/level2/dotsOverlap.py
···
1
+
from math import sqrt
2
+
3
+
4
+
def distance(a: tuple[float, float], b: tuple[float, float]) -> float:
5
+
"""Calculate the distance between two points using the Euclidean distance formula."""
6
+
return sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)
7
+
8
+
9
+
def dotsOverlap(
10
+
x1: float, y1: float, r1: float, x2: float, y2: float, r2: float
11
+
) -> bool:
12
+
"""Check if two circles overlap."""
13
+
d = distance((x1, y1), (x2, y2))
14
+
return d <= (r1 + r2)
15
+
16
+
17
+
print("Testing dotsOverlap()...")
18
+
assert dotsOverlap(0, 0, 2, 3, 0, 2) == True
19
+
assert dotsOverlap(0, 0, 2, 5, 0, 2) == False
20
+
assert dotsOverlap(0, 0, 2, 4, 0, 2) == True
21
+
assert dotsOverlap(-4, 5, 2, -3, 5, 5) == True
22
+
assert dotsOverlap(3, 3, 3, 3, -3, 2.99) == False
23
+
assert dotsOverlap(3, 3, 3, 3, -3, 3) == True
24
+
assert dotsOverlap(5, 3, 0, 5, 3, 0) == True
25
+
print("Passed!")
+18
python/sep30/level2/getGreen.py
+18
python/sep30/level2/getGreen.py
···
1
+
def fullRGB(rgb: int) -> str:
2
+
"""Left pad 0's to make the string of length 9."""
3
+
return str(rgb).zfill(9)
4
+
5
+
6
+
def getGreen(rgb: int) -> int:
7
+
"""Return the green portion of an RGB value."""
8
+
return int(fullRGB(rgb)[3:6])
9
+
10
+
11
+
print("Testing getGreen()...", end="")
12
+
assert getGreen(218112214) == 112
13
+
assert getGreen(134134134) == 134
14
+
assert getGreen(111019213) == 19
15
+
assert getGreen(221000000) == 0
16
+
assert getGreen(32175) == 32
17
+
assert getGreen(0) == 0
18
+
print("Passed!")
+20
python/sep30/level2/isGray.py
+20
python/sep30/level2/isGray.py
···
1
+
def fullRGB(rgb: int) -> str:
2
+
"""Left pad 0's to make the string of length 9."""
3
+
return str(rgb).zfill(9)
4
+
5
+
6
+
def isGray(rgb: int) -> bool:
7
+
"""Return True if the RGB value passed is considered 'gray'."""
8
+
rgbStr = fullRGB(rgb)
9
+
return (rgbStr[0:3] == rgbStr[3:6]) and (rgbStr[3:6] == rgbStr[6:9])
10
+
11
+
12
+
print("Testing isGray()...", end="")
13
+
assert isGray(112112112) == True
14
+
assert isGray(112112113) == False
15
+
assert isGray(123195060) == False
16
+
assert isGray(255255255) == True
17
+
assert isGray(0) == True
18
+
assert isGray(19019019) == True
19
+
assert isGray(175112) == False
20
+
print("Passed!")
+22
python/sep30/level2/nthFibonacciNumber.py
+22
python/sep30/level2/nthFibonacciNumber.py
···
1
+
from math import sqrt
2
+
3
+
4
+
def almostEqual(x: float, y: float) -> bool:
5
+
return abs(x - y) < 10**-9
6
+
7
+
8
+
def nthFibonacciNumber(n: int) -> float:
9
+
"""Return the nth Fibonacci number."""
10
+
n += 1
11
+
return ((1 + sqrt(5)) ** n - (1 - sqrt(5)) ** n) / (sqrt(5) * 2**n)
12
+
13
+
14
+
print("Testing nthFibonacciNumber()...", end="")
15
+
assert almostEqual(nthFibonacciNumber(0), 1)
16
+
assert almostEqual(nthFibonacciNumber(1), 1)
17
+
assert almostEqual(nthFibonacciNumber(2), 2)
18
+
assert almostEqual(nthFibonacciNumber(3), 3)
19
+
assert almostEqual(nthFibonacciNumber(4), 5)
20
+
assert almostEqual(nthFibonacciNumber(5), 8)
21
+
22
+
print("Passed!")
+26
python/sep30/level3/numberOfSteps.py
+26
python/sep30/level3/numberOfSteps.py
···
1
+
from math import sqrt, ceil
2
+
3
+
4
+
def numberOfSteps(bricks: int) -> int:
5
+
"""
6
+
Given a number of bricks return how many steps would be required to use them all given that:
7
+
S(0) = 0
8
+
S(n) = S(n-1) + n
9
+
Formula: `S=(-1+sqrt(1+8*n))/2`
10
+
"""
11
+
return ceil((-1 + sqrt(1 + 8 * bricks)) / 2)
12
+
13
+
14
+
print("Testing numberOfSteps()...", end="")
15
+
assert numberOfSteps(0) == 0
16
+
assert numberOfSteps(1) == 1
17
+
assert numberOfSteps(2) == 2
18
+
assert numberOfSteps(3) == 2
19
+
assert numberOfSteps(4) == 3
20
+
assert numberOfSteps(6) == 3
21
+
assert numberOfSteps(7) == 4
22
+
assert numberOfSteps(10) == 4
23
+
assert numberOfSteps(11) == 5
24
+
assert numberOfSteps(55) == 10
25
+
assert numberOfSteps(56) == 11
26
+
print("Passed!")
+29
python/sep30/level3/rectanglesOverlap.py
+29
python/sep30/level3/rectanglesOverlap.py
···
1
+
def rectanglesOverlap(
2
+
ax1: int, ay1: int, aw: float, ah: float, bx1: int, by1: int, bw: float, bh: float
3
+
) -> bool:
4
+
"""Check if two rectangles overlap."""
5
+
ax2 = ax1 + aw
6
+
ay2 = ay1 + ah
7
+
bx2 = bx1 + bw
8
+
by2 = by1 + bh
9
+
return (ax1 <= bx2 and ax2 >= bx1) and (ay1 <= by2 and ay2 >= by1)
10
+
11
+
12
+
print("Testing rectanglesOverlap()...", end="")
13
+
# Intersect at right of rectangle 1
14
+
assert rectanglesOverlap(1, 1, 5, 1, 6, 1, 2, 2) == True
15
+
# Intersect at top of rectangle 1
16
+
assert rectanglesOverlap(1, 4, 5, 3, 1, 5, 8, 3) == True
17
+
# Intersect at left of rectangle 1
18
+
assert rectanglesOverlap(1, 5, 6, 6, -4, 7, 5, 3) == True
19
+
# Intersect at bottom of rectanagle 1
20
+
assert rectanglesOverlap(10, 10, 3, 3, 9, 7, 3, 3) == True
21
+
# Partially overlapping rectangles
22
+
assert rectanglesOverlap(1, 7, 3, 6, 3, 4, 2, 5) == True
23
+
# Don't intersect
24
+
assert rectanglesOverlap(1, 4, 3, 3, 10, 10, 5, 5) == False
25
+
# Don't intersect, but x-coordinates overlap
26
+
assert rectanglesOverlap(1, 4, 30, 3, 10, 10, 5, 5) == False
27
+
# Don't intersect, but y-coordinates overlap
28
+
assert rectanglesOverlap(1, 4, 3, 15, 10, 10, 5, 5) == False
29
+
print("Passed!")
+37
python/sep30/level3/triangeAreaByCoordinates.py
+37
python/sep30/level3/triangeAreaByCoordinates.py
···
1
+
from math import sqrt
2
+
3
+
4
+
def almostEqual(x: float, y: float) -> bool:
5
+
return abs(x - y) < 10**-9
6
+
7
+
8
+
def distance(a: tuple[float, float], b: tuple[float, float]) -> float:
9
+
"""Calculate the distance between two points using the Euclidean distance formula."""
10
+
return sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2)
11
+
12
+
13
+
def triangleArea(a: float, b: float, c: float) -> float:
14
+
"""Calculate the area of a triangle using Heron's formula."""
15
+
semiperimeter = 0.5 * (a + b + c)
16
+
return sqrt(
17
+
semiperimeter * (semiperimeter - a) * (semiperimeter - b) * (semiperimeter - c)
18
+
)
19
+
20
+
21
+
def triangleAreaByCoordinates(
22
+
x1: float, y1: float, x2: float, y2: float, x3: float, y3: float
23
+
) -> float:
24
+
"""Calculate the area of a triangle given its coordinates."""
25
+
A = distance((x1, y1), (x2, y2))
26
+
B = distance((x2, y2), (x3, y3))
27
+
C = distance((x3, y3), (x1, y1))
28
+
return triangleArea(A, B, C)
29
+
30
+
31
+
print("Testing triangleAreaByCoordinates()...", end="")
32
+
assert almostEqual(triangleAreaByCoordinates(10, 11, 14, 11, 12, 13), 4)
33
+
assert isinstance(triangleAreaByCoordinates(10, 11, 14, 11, 12, 13), float)
34
+
assert almostEqual(triangleAreaByCoordinates(2, 4.0, 2, 7, 6.0, 7), 6)
35
+
assert almostEqual(triangleAreaByCoordinates(0, 0, 12.0, 0, 12, 5), 30)
36
+
assert almostEqual(triangleAreaByCoordinates(0, 0, 0, 1, 1, 1), 0.5)
37
+
print("Passed!")
+39
python/sep30/level4/blendColors.py
+39
python/sep30/level4/blendColors.py
···
1
+
COMPONENT_LENGTH = 3
2
+
3
+
4
+
def blend(c1: int, c2: int) -> int:
5
+
"""Get the median of 2 numbers."""
6
+
return round((c1 + c2) / 2)
7
+
8
+
9
+
def splitColors(rgb: int) -> tuple[int, int, int]:
10
+
"""Split an RGB value into its components."""
11
+
(r, g, b) = [
12
+
int(str(rgb).zfill(3 * COMPONENT_LENGTH)[i : i + COMPONENT_LENGTH])
13
+
for i in range(0, 3 * COMPONENT_LENGTH, COMPONENT_LENGTH)
14
+
]
15
+
return (r, g, b)
16
+
17
+
18
+
def blendColors(rgb1: int, rgb2: int) -> int:
19
+
"""Blend two colors represented as RGB values."""
20
+
(r1, g1, b1) = splitColors(rgb1)
21
+
(r2, g2, b2) = splitColors(rgb2)
22
+
(r3, g3, b3) = [
23
+
blend(r1, r2),
24
+
blend(g1, g2),
25
+
blend(b1, b2),
26
+
]
27
+
# Pad with leading zeros
28
+
(final_r, final_g, final_b) = [str(x).zfill(COMPONENT_LENGTH) for x in (r3, g3, b3)]
29
+
# Compose components and cast to integer
30
+
return int(final_r + final_g + final_b)
31
+
32
+
33
+
print("Testing blendColors()...", end="")
34
+
assert blendColors(204153050, 104000152) == 154076101
35
+
assert blendColors(220153102, 151189051) == 186171076
36
+
assert blendColors(153051153, 51204) == 76051178
37
+
assert blendColors(123456789, 123456789) == 123456789
38
+
assert blendColors(0, 0) == 0
39
+
print("Passed!")
+24
react/.gitignore
+24
react/.gitignore
···
1
+
# Logs
2
+
logs
3
+
*.log
4
+
npm-debug.log*
5
+
yarn-debug.log*
6
+
yarn-error.log*
7
+
pnpm-debug.log*
8
+
lerna-debug.log*
9
+
10
+
node_modules
11
+
dist
12
+
dist-ssr
13
+
*.local
14
+
15
+
# Editor directories and files
16
+
.vscode/*
17
+
!.vscode/extensions.json
18
+
.idea
19
+
.DS_Store
20
+
*.suo
21
+
*.ntvs*
22
+
*.njsproj
23
+
*.sln
24
+
*.sw?
+75
react/README.md
+75
react/README.md
···
1
+
# React + TypeScript + Vite
2
+
3
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+
Currently, two official plugins are available:
6
+
7
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh
8
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
9
+
10
+
## React Compiler
11
+
12
+
The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information.
13
+
14
+
Note: This will impact Vite dev & build performances.
15
+
16
+
## Expanding the ESLint configuration
17
+
18
+
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
19
+
20
+
```js
21
+
export default defineConfig([
22
+
globalIgnores(['dist']),
23
+
{
24
+
files: ['**/*.{ts,tsx}'],
25
+
extends: [
26
+
// Other configs...
27
+
28
+
// Remove tseslint.configs.recommended and replace with this
29
+
tseslint.configs.recommendedTypeChecked,
30
+
// Alternatively, use this for stricter rules
31
+
tseslint.configs.strictTypeChecked,
32
+
// Optionally, add this for stylistic rules
33
+
tseslint.configs.stylisticTypeChecked,
34
+
35
+
// Other configs...
36
+
],
37
+
languageOptions: {
38
+
parserOptions: {
39
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
40
+
tsconfigRootDir: import.meta.dirname,
41
+
},
42
+
// other options...
43
+
},
44
+
},
45
+
])
46
+
```
47
+
48
+
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
49
+
50
+
```js
51
+
// eslint.config.js
52
+
import reactX from 'eslint-plugin-react-x'
53
+
import reactDom from 'eslint-plugin-react-dom'
54
+
55
+
export default defineConfig([
56
+
globalIgnores(['dist']),
57
+
{
58
+
files: ['**/*.{ts,tsx}'],
59
+
extends: [
60
+
// Other configs...
61
+
// Enable lint rules for React
62
+
reactX.configs['recommended-typescript'],
63
+
// Enable lint rules for React DOM
64
+
reactDom.configs.recommended,
65
+
],
66
+
languageOptions: {
67
+
parserOptions: {
68
+
project: ['./tsconfig.node.json', './tsconfig.app.json'],
69
+
tsconfigRootDir: import.meta.dirname,
70
+
},
71
+
// other options...
72
+
},
73
+
},
74
+
])
75
+
```
+699
react/bun.lock
+699
react/bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "react",
6
+
"dependencies": {
7
+
"@tailwindcss/vite": "^4.1.17",
8
+
"draft-js": "^0.11.7",
9
+
"react": "^19.2.0",
10
+
"react-dom": "^19.2.0",
11
+
"react-router": "^7.9.6",
12
+
"tailwindcss": "^4.1.17",
13
+
},
14
+
"devDependencies": {
15
+
"@eslint/js": "^9.39.1",
16
+
"@happy-dom/global-registrator": "^20.0.10",
17
+
"@testing-library/jest-dom": "^6.9.1",
18
+
"@testing-library/react": "^16.3.0",
19
+
"@types/bun": "latest",
20
+
"@types/draft-js": "^0.11.20",
21
+
"@types/node": "^24.10.0",
22
+
"@types/react": "^19.2.2",
23
+
"@types/react-dom": "^19.2.2",
24
+
"@vitejs/plugin-react": "^5.1.0",
25
+
"babel-plugin-react-compiler": "^1.0.0",
26
+
"eslint": "^9.39.1",
27
+
"eslint-plugin-react-hooks": "^7.0.1",
28
+
"eslint-plugin-react-refresh": "^0.4.24",
29
+
"globals": "^16.5.0",
30
+
"typescript": "~5.9.3",
31
+
"typescript-eslint": "^8.46.3",
32
+
"vite": "^7.2.2",
33
+
},
34
+
},
35
+
},
36
+
"packages": {
37
+
"@adobe/css-tools": ["@adobe/css-tools@4.4.4", "", {}, "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg=="],
38
+
39
+
"@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
40
+
41
+
"@babel/compat-data": ["@babel/compat-data@7.28.5", "", {}, "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA=="],
42
+
43
+
"@babel/core": ["@babel/core@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw=="],
44
+
45
+
"@babel/generator": ["@babel/generator@7.28.5", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ=="],
46
+
47
+
"@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
48
+
49
+
"@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
50
+
51
+
"@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
52
+
53
+
"@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
54
+
55
+
"@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
56
+
57
+
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
58
+
59
+
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
60
+
61
+
"@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
62
+
63
+
"@babel/helpers": ["@babel/helpers@7.28.4", "", { "dependencies": { "@babel/template": "^7.27.2", "@babel/types": "^7.28.4" } }, "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w=="],
64
+
65
+
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
66
+
67
+
"@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
68
+
69
+
"@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
70
+
71
+
"@babel/runtime": ["@babel/runtime@7.28.4", "", {}, "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ=="],
72
+
73
+
"@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
74
+
75
+
"@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
76
+
77
+
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
78
+
79
+
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
80
+
81
+
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
82
+
83
+
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
84
+
85
+
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
86
+
87
+
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
88
+
89
+
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
90
+
91
+
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
92
+
93
+
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
94
+
95
+
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
96
+
97
+
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
98
+
99
+
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
100
+
101
+
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
102
+
103
+
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
104
+
105
+
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
106
+
107
+
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
108
+
109
+
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
110
+
111
+
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
112
+
113
+
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
114
+
115
+
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
116
+
117
+
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
118
+
119
+
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
120
+
121
+
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
122
+
123
+
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
124
+
125
+
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
126
+
127
+
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
128
+
129
+
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
130
+
131
+
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
132
+
133
+
"@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.2", "", {}, "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew=="],
134
+
135
+
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
136
+
137
+
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.2", "", { "dependencies": { "@eslint/core": "^0.17.0" } }, "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw=="],
138
+
139
+
"@eslint/core": ["@eslint/core@0.17.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ=="],
140
+
141
+
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
142
+
143
+
"@eslint/js": ["@eslint/js@9.39.1", "", {}, "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw=="],
144
+
145
+
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
146
+
147
+
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
148
+
149
+
"@happy-dom/global-registrator": ["@happy-dom/global-registrator@20.0.10", "", { "dependencies": { "@types/node": "^20.0.0", "happy-dom": "^20.0.10" } }, "sha512-GU0UBt9lJKhZlY/U0Bivj9ZVepDIQoAUupAAl/90THG4/urkzXNglkVYETsnt2pGBDgQ+4vBjMAbLu6XzcKcQA=="],
150
+
151
+
"@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="],
152
+
153
+
"@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="],
154
+
155
+
"@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="],
156
+
157
+
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
158
+
159
+
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
160
+
161
+
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
162
+
163
+
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
164
+
165
+
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
166
+
167
+
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
168
+
169
+
"@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
170
+
171
+
"@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
172
+
173
+
"@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
174
+
175
+
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.47", "", {}, "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw=="],
176
+
177
+
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.53.2", "", { "os": "android", "cpu": "arm" }, "sha512-yDPzwsgiFO26RJA4nZo8I+xqzh7sJTZIWQOxn+/XOdPE31lAvLIYCKqjV+lNH/vxE2L2iH3plKxDCRK6i+CwhA=="],
178
+
179
+
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.53.2", "", { "os": "android", "cpu": "arm64" }, "sha512-k8FontTxIE7b0/OGKeSN5B6j25EuppBcWM33Z19JoVT7UTXFSo3D9CdU39wGTeb29NO3XxpMNauh09B+Ibw+9g=="],
180
+
181
+
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.53.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-A6s4gJpomNBtJ2yioj8bflM2oogDwzUiMl2yNJ2v9E7++sHrSrsQ29fOfn5DM/iCzpWcebNYEdXpaK4tr2RhfQ=="],
182
+
183
+
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.53.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-e6XqVmXlHrBlG56obu9gDRPW3O3hLxpwHpLsBJvuI8qqnsrtSZ9ERoWUXtPOkY8c78WghyPHZdmPhHLWNdAGEw=="],
184
+
185
+
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.53.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-v0E9lJW8VsrwPux5Qe5CwmH/CF/2mQs6xU1MF3nmUxmZUCHazCjLgYvToOk+YuuUqLQBio1qkkREhxhc656ViA=="],
186
+
187
+
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.53.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ClAmAPx3ZCHtp6ysl4XEhWU69GUB1D+s7G9YjHGhIGCSrsg00nEGRRZHmINYxkdoJehde8VIsDC5t9C0gb6yqA=="],
188
+
189
+
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-EPlb95nUsz6Dd9Qy13fI5kUPXNSljaG9FiJ4YUGU1O/Q77i5DYFW5KR8g1OzTcdZUqQQ1KdDqsTohdFVwCwjqg=="],
190
+
191
+
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.53.2", "", { "os": "linux", "cpu": "arm" }, "sha512-BOmnVW+khAUX+YZvNfa0tGTEMVVEerOxN0pDk2E6N6DsEIa2Ctj48FOMfNDdrwinocKaC7YXUZ1pHlKpnkja/Q=="],
192
+
193
+
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-Xt2byDZ+6OVNuREgBXr4+CZDJtrVso5woFtpKdGPhpTPHcNG7D8YXeQzpNbFRxzTVqJf7kvPMCub/pcGUWgBjA=="],
194
+
195
+
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.53.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-+LdZSldy/I9N8+klim/Y1HsKbJ3BbInHav5qE9Iy77dtHC/pibw1SR/fXlWyAk0ThnpRKoODwnAuSjqxFRDHUQ=="],
196
+
197
+
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-8ms8sjmyc1jWJS6WdNSA23rEfdjWB30LH8Wqj0Cqvv7qSHnvw6kgMMXRdop6hkmGPlyYBdRPkjJnj3KCUHV/uQ=="],
198
+
199
+
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.53.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3HRQLUQbpBDMmzoxPJYd3W6vrVHOo2cVW8RUo87Xz0JPJcBLBr5kZ1pGcQAhdZgX9VV7NbGNipah1omKKe23/g=="],
200
+
201
+
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-fMjKi+ojnmIvhk34gZP94vjogXNNUKMEYs+EDaB/5TG/wUkoeua7p7VCHnE6T2Tx+iaghAqQX8teQzcvrYpaQA=="],
202
+
203
+
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.53.2", "", { "os": "linux", "cpu": "none" }, "sha512-XuGFGU+VwUUV5kLvoAdi0Wz5Xbh2SrjIxCtZj6Wq8MDp4bflb/+ThZsVxokM7n0pcbkEr2h5/pzqzDYI7cCgLQ=="],
204
+
205
+
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.53.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-w6yjZF0P+NGzWR3AXWX9zc0DNEGdtvykB03uhonSHMRa+oWA6novflo2WaJr6JZakG2ucsyb+rvhrKac6NIy+w=="],
206
+
207
+
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-yo8d6tdfdeBArzC7T/PnHd7OypfI9cbuZzPnzLJIyKYFhAQ8SvlkKtKBMbXDxe1h03Rcr7u++nFS7tqXz87Gtw=="],
208
+
209
+
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.53.2", "", { "os": "linux", "cpu": "x64" }, "sha512-ah59c1YkCxKExPP8O9PwOvs+XRLKwh/mV+3YdKqQ5AMQ0r4M4ZDuOrpWkUaqO7fzAHdINzV9tEVu8vNw48z0lA=="],
210
+
211
+
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.53.2", "", { "os": "none", "cpu": "arm64" }, "sha512-4VEd19Wmhr+Zy7hbUsFZ6YXEiP48hE//KPLCSVNY5RMGX2/7HZ+QkN55a3atM1C/BZCGIgqN+xrVgtdak2S9+A=="],
212
+
213
+
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.53.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-IlbHFYc/pQCgew/d5fslcy1KEaYVCJ44G8pajugd8VoOEI8ODhtb/j8XMhLpwHCMB3yk2J07ctup10gpw2nyMA=="],
214
+
215
+
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.53.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-lNlPEGgdUfSzdCWU176ku/dQRnA7W+Gp8d+cWv73jYrb8uT7HTVVxq62DUYxjbaByuf1Yk0RIIAbDzp+CnOTFg=="],
216
+
217
+
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-S6YojNVrHybQis2lYov1sd+uj7K0Q05NxHcGktuMMdIQ2VixGwAfbJ23NnlvvVV1bdpR2m5MsNBViHJKcA4ADw=="],
218
+
219
+
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.53.2", "", { "os": "win32", "cpu": "x64" }, "sha512-k+/Rkcyx//P6fetPoLMb8pBeqJBNGx81uuf7iljX9++yNBVRDQgD04L+SVXmXmh5ZP4/WOp4mWF0kmi06PW2tA=="],
220
+
221
+
"@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="],
222
+
223
+
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="],
224
+
225
+
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="],
226
+
227
+
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="],
228
+
229
+
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="],
230
+
231
+
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="],
232
+
233
+
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="],
234
+
235
+
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="],
236
+
237
+
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="],
238
+
239
+
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="],
240
+
241
+
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="],
242
+
243
+
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="],
244
+
245
+
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="],
246
+
247
+
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="],
248
+
249
+
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.17", "", { "dependencies": { "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "tailwindcss": "4.1.17" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-4+9w8ZHOiGnpcGI6z1TVVfWaX/koK7fKeSYF3qlYg2xpBtbteP2ddBxiarL+HVgfSJGeK5RIxRQmKm4rTJJAwA=="],
250
+
251
+
"@testing-library/dom": ["@testing-library/dom@10.4.1", "", { "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", "@types/aria-query": "^5.0.1", "aria-query": "5.3.0", "dom-accessibility-api": "^0.5.9", "lz-string": "^1.5.0", "picocolors": "1.1.1", "pretty-format": "^27.0.2" } }, "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg=="],
252
+
253
+
"@testing-library/jest-dom": ["@testing-library/jest-dom@6.9.1", "", { "dependencies": { "@adobe/css-tools": "^4.4.0", "aria-query": "^5.0.0", "css.escape": "^1.5.1", "dom-accessibility-api": "^0.6.3", "picocolors": "^1.1.1", "redent": "^3.0.0" } }, "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA=="],
254
+
255
+
"@testing-library/react": ["@testing-library/react@16.3.0", "", { "dependencies": { "@babel/runtime": "^7.12.5" }, "peerDependencies": { "@testing-library/dom": "^10.0.0", "@types/react": "^18.0.0 || ^19.0.0", "@types/react-dom": "^18.0.0 || ^19.0.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw=="],
256
+
257
+
"@types/aria-query": ["@types/aria-query@5.0.4", "", {}, "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw=="],
258
+
259
+
"@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
260
+
261
+
"@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
262
+
263
+
"@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
264
+
265
+
"@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
266
+
267
+
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
268
+
269
+
"@types/draft-js": ["@types/draft-js@0.11.20", "", { "dependencies": { "@types/react": "*", "immutable": "~3.7.4" } }, "sha512-bZHtHxXnCu4wlUXlDWrIlJSG2LJ6wcycSWoxcTCcGd0cVOm35p0vh87qpIPzGK2NALMMvJhQXdS330iYB3iGlw=="],
270
+
271
+
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
272
+
273
+
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
274
+
275
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
276
+
277
+
"@types/react": ["@types/react@19.2.6", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w=="],
278
+
279
+
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
280
+
281
+
"@types/whatwg-mimetype": ["@types/whatwg-mimetype@3.0.2", "", {}, "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA=="],
282
+
283
+
"@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.47.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/type-utils": "8.47.0", "@typescript-eslint/utils": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.47.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA=="],
284
+
285
+
"@typescript-eslint/parser": ["@typescript-eslint/parser@8.47.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ=="],
286
+
287
+
"@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.47.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.47.0", "@typescript-eslint/types": "^8.47.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA=="],
288
+
289
+
"@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0" } }, "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg=="],
290
+
291
+
"@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.47.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g=="],
292
+
293
+
"@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/utils": "8.47.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A=="],
294
+
295
+
"@typescript-eslint/types": ["@typescript-eslint/types@8.47.0", "", {}, "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A=="],
296
+
297
+
"@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.47.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.47.0", "@typescript-eslint/tsconfig-utils": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/visitor-keys": "8.47.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg=="],
298
+
299
+
"@typescript-eslint/utils": ["@typescript-eslint/utils@8.47.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.47.0", "@typescript-eslint/types": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ=="],
300
+
301
+
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.47.0", "", { "dependencies": { "@typescript-eslint/types": "8.47.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ=="],
302
+
303
+
"@vitejs/plugin-react": ["@vitejs/plugin-react@5.1.1", "", { "dependencies": { "@babel/core": "^7.28.5", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.47", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA=="],
304
+
305
+
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
306
+
307
+
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
308
+
309
+
"ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
310
+
311
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
312
+
313
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
314
+
315
+
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
316
+
317
+
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
318
+
319
+
"asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
320
+
321
+
"babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
322
+
323
+
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
324
+
325
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.29", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA=="],
326
+
327
+
"brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
328
+
329
+
"braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
330
+
331
+
"browserslist": ["browserslist@4.28.0", "", { "dependencies": { "baseline-browser-mapping": "^2.8.25", "caniuse-lite": "^1.0.30001754", "electron-to-chromium": "^1.5.249", "node-releases": "^2.0.27", "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" } }, "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ=="],
332
+
333
+
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
334
+
335
+
"callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
336
+
337
+
"caniuse-lite": ["caniuse-lite@1.0.30001755", "", {}, "sha512-44V+Jm6ctPj7R52Na4TLi3Zri4dWUljJd+RDm+j8LtNCc/ihLCT+X1TzoOAkRETEWqjuLnh9581Tl80FvK7jVA=="],
338
+
339
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
340
+
341
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
342
+
343
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
344
+
345
+
"concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
346
+
347
+
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
348
+
349
+
"cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
350
+
351
+
"core-js": ["core-js@3.47.0", "", {}, "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg=="],
352
+
353
+
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
354
+
355
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
356
+
357
+
"css.escape": ["css.escape@1.5.1", "", {}, "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg=="],
358
+
359
+
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
360
+
361
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
362
+
363
+
"deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
364
+
365
+
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
366
+
367
+
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
368
+
369
+
"dom-accessibility-api": ["dom-accessibility-api@0.6.3", "", {}, "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w=="],
370
+
371
+
"draft-js": ["draft-js@0.11.7", "", { "dependencies": { "fbjs": "^2.0.0", "immutable": "~3.7.4", "object-assign": "^4.1.1" }, "peerDependencies": { "react": ">=0.14.0", "react-dom": ">=0.14.0" } }, "sha512-ne7yFfN4sEL82QPQEn80xnADR8/Q6ALVworbC5UOSzOvjffmYfFsr3xSZtxbIirti14R7Y33EZC5rivpLgIbsg=="],
372
+
373
+
"electron-to-chromium": ["electron-to-chromium@1.5.255", "", {}, "sha512-Z9oIp4HrFF/cZkDPMpz2XSuVpc1THDpT4dlmATFlJUIBVCy9Vap5/rIXsASP1CscBacBqhabwh8vLctqBwEerQ=="],
374
+
375
+
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
376
+
377
+
"esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="],
378
+
379
+
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
380
+
381
+
"escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
382
+
383
+
"eslint": ["eslint@9.39.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.39.1", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g=="],
384
+
385
+
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@7.0.1", "", { "dependencies": { "@babel/core": "^7.24.4", "@babel/parser": "^7.24.4", "hermes-parser": "^0.25.1", "zod": "^3.25.0 || ^4.0.0", "zod-validation-error": "^3.5.0 || ^4.0.0" }, "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA=="],
386
+
387
+
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="],
388
+
389
+
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
390
+
391
+
"eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="],
392
+
393
+
"espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="],
394
+
395
+
"esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
396
+
397
+
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
398
+
399
+
"estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
400
+
401
+
"esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
402
+
403
+
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
404
+
405
+
"fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
406
+
407
+
"fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
408
+
409
+
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
410
+
411
+
"fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
412
+
413
+
"fbjs": ["fbjs@2.0.0", "", { "dependencies": { "core-js": "^3.6.4", "cross-fetch": "^3.0.4", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^0.7.18" } }, "sha512-8XA8ny9ifxrAWlyhAbexXcs3rRMtxWcs3M0lctLfB49jRDHiaxj+Mo0XxbwE7nKZYzgCFoq64FS+WFd4IycPPQ=="],
414
+
415
+
"fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="],
416
+
417
+
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
418
+
419
+
"file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
420
+
421
+
"fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
422
+
423
+
"find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
424
+
425
+
"flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="],
426
+
427
+
"flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="],
428
+
429
+
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
430
+
431
+
"gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
432
+
433
+
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
434
+
435
+
"globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="],
436
+
437
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
438
+
439
+
"graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="],
440
+
441
+
"happy-dom": ["happy-dom@20.0.10", "", { "dependencies": { "@types/node": "^20.0.0", "@types/whatwg-mimetype": "^3.0.2", "whatwg-mimetype": "^3.0.0" } }, "sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g=="],
442
+
443
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
444
+
445
+
"hermes-estree": ["hermes-estree@0.25.1", "", {}, "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw=="],
446
+
447
+
"hermes-parser": ["hermes-parser@0.25.1", "", { "dependencies": { "hermes-estree": "0.25.1" } }, "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA=="],
448
+
449
+
"ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
450
+
451
+
"immutable": ["immutable@3.7.6", "", {}, "sha512-AizQPcaofEtO11RZhPPHBOJRdo/20MKQF9mBLnVkBoyHi1/zXK8fzVdnEpSV9gxqtnh6Qomfp3F0xT5qP/vThw=="],
452
+
453
+
"import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
454
+
455
+
"imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
456
+
457
+
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
458
+
459
+
"is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
460
+
461
+
"is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
462
+
463
+
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
464
+
465
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
466
+
467
+
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
468
+
469
+
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
470
+
471
+
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
472
+
473
+
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
474
+
475
+
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
476
+
477
+
"json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
478
+
479
+
"json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
480
+
481
+
"json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
482
+
483
+
"keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
484
+
485
+
"levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
486
+
487
+
"lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="],
488
+
489
+
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="],
490
+
491
+
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="],
492
+
493
+
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="],
494
+
495
+
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="],
496
+
497
+
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="],
498
+
499
+
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="],
500
+
501
+
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="],
502
+
503
+
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="],
504
+
505
+
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="],
506
+
507
+
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="],
508
+
509
+
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="],
510
+
511
+
"locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
512
+
513
+
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
514
+
515
+
"loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
516
+
517
+
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
518
+
519
+
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
520
+
521
+
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
522
+
523
+
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
524
+
525
+
"micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
526
+
527
+
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
528
+
529
+
"minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
530
+
531
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
532
+
533
+
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
534
+
535
+
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
536
+
537
+
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
538
+
539
+
"node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
540
+
541
+
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
542
+
543
+
"optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
544
+
545
+
"p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="],
546
+
547
+
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
548
+
549
+
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
550
+
551
+
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
552
+
553
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
554
+
555
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
556
+
557
+
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
558
+
559
+
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
560
+
561
+
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
562
+
563
+
"pretty-format": ["pretty-format@27.5.1", "", { "dependencies": { "ansi-regex": "^5.0.1", "ansi-styles": "^5.0.0", "react-is": "^17.0.1" } }, "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ=="],
564
+
565
+
"promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
566
+
567
+
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
568
+
569
+
"queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
570
+
571
+
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
572
+
573
+
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
574
+
575
+
"react-is": ["react-is@17.0.2", "", {}, "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="],
576
+
577
+
"react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="],
578
+
579
+
"react-router": ["react-router@7.9.6", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-Y1tUp8clYRXpfPITyuifmSoE2vncSME18uVLgaqyxh9H35JWpIfzHo+9y3Fzh5odk/jxPW29IgLgzcdwxGqyNA=="],
580
+
581
+
"redent": ["redent@3.0.0", "", { "dependencies": { "indent-string": "^4.0.0", "strip-indent": "^3.0.0" } }, "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg=="],
582
+
583
+
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
584
+
585
+
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
586
+
587
+
"rollup": ["rollup@4.53.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.53.2", "@rollup/rollup-android-arm64": "4.53.2", "@rollup/rollup-darwin-arm64": "4.53.2", "@rollup/rollup-darwin-x64": "4.53.2", "@rollup/rollup-freebsd-arm64": "4.53.2", "@rollup/rollup-freebsd-x64": "4.53.2", "@rollup/rollup-linux-arm-gnueabihf": "4.53.2", "@rollup/rollup-linux-arm-musleabihf": "4.53.2", "@rollup/rollup-linux-arm64-gnu": "4.53.2", "@rollup/rollup-linux-arm64-musl": "4.53.2", "@rollup/rollup-linux-loong64-gnu": "4.53.2", "@rollup/rollup-linux-ppc64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-gnu": "4.53.2", "@rollup/rollup-linux-riscv64-musl": "4.53.2", "@rollup/rollup-linux-s390x-gnu": "4.53.2", "@rollup/rollup-linux-x64-gnu": "4.53.2", "@rollup/rollup-linux-x64-musl": "4.53.2", "@rollup/rollup-openharmony-arm64": "4.53.2", "@rollup/rollup-win32-arm64-msvc": "4.53.2", "@rollup/rollup-win32-ia32-msvc": "4.53.2", "@rollup/rollup-win32-x64-gnu": "4.53.2", "@rollup/rollup-win32-x64-msvc": "4.53.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-MHngMYwGJVi6Fmnk6ISmnk7JAHRNF0UkuucA0CUW3N3a4KnONPEZz+vUanQP/ZC/iY1Qkf3bwPWzyY84wEks1g=="],
588
+
589
+
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
590
+
591
+
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
592
+
593
+
"semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
594
+
595
+
"set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="],
596
+
597
+
"setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
598
+
599
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
600
+
601
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
602
+
603
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
604
+
605
+
"strip-indent": ["strip-indent@3.0.0", "", { "dependencies": { "min-indent": "^1.0.0" } }, "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ=="],
606
+
607
+
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
608
+
609
+
"supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
610
+
611
+
"tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="],
612
+
613
+
"tapable": ["tapable@2.3.0", "", {}, "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg=="],
614
+
615
+
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
616
+
617
+
"to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
618
+
619
+
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
620
+
621
+
"ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
622
+
623
+
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
624
+
625
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
626
+
627
+
"typescript-eslint": ["typescript-eslint@8.47.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.47.0", "@typescript-eslint/parser": "8.47.0", "@typescript-eslint/typescript-estree": "8.47.0", "@typescript-eslint/utils": "8.47.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-Lwe8i2XQ3WoMjua/r1PHrCTpkubPYJCAfOurtn+mtTzqB6jNd+14n9UN1bJ4s3F49x9ixAm0FLflB/JzQ57M8Q=="],
628
+
629
+
"ua-parser-js": ["ua-parser-js@0.7.41", "", { "bin": { "ua-parser-js": "script/cli.js" } }, "sha512-O3oYyCMPYgNNHuO7Jjk3uacJWZF8loBgwrfd/5LE/HyZ3lUIOdniQ7DNXJcIgZbwioZxk0fLfI4EVnetdiX5jg=="],
630
+
631
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
632
+
633
+
"update-browserslist-db": ["update-browserslist-db@1.1.4", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A=="],
634
+
635
+
"uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
636
+
637
+
"vite": ["vite@7.2.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ=="],
638
+
639
+
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
640
+
641
+
"whatwg-mimetype": ["whatwg-mimetype@3.0.0", "", {}, "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q=="],
642
+
643
+
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
644
+
645
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
646
+
647
+
"word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
648
+
649
+
"yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
650
+
651
+
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
652
+
653
+
"zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="],
654
+
655
+
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
656
+
657
+
"@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
658
+
659
+
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
660
+
661
+
"@happy-dom/global-registrator/@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="],
662
+
663
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.7.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg=="],
664
+
665
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.7.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA=="],
666
+
667
+
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="],
668
+
669
+
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.7", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-SeDnOO0Tk7Okiq6DbXmmBODgOAb9dp9gjlphokTUxmt8U3liIP1ZsozBahH69j/RJv+Rfs6IwUKHTgQYJ/HBAw=="],
670
+
671
+
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
672
+
673
+
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
674
+
675
+
"@testing-library/dom/aria-query": ["aria-query@5.3.0", "", { "dependencies": { "dequal": "^2.0.3" } }, "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A=="],
676
+
677
+
"@testing-library/dom/dom-accessibility-api": ["dom-accessibility-api@0.5.16", "", {}, "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg=="],
678
+
679
+
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
680
+
681
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
682
+
683
+
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
684
+
685
+
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
686
+
687
+
"happy-dom/@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="],
688
+
689
+
"micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
690
+
691
+
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
692
+
693
+
"@happy-dom/global-registrator/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
694
+
695
+
"@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
696
+
697
+
"happy-dom/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
698
+
}
699
+
}
+23
react/eslint.config.js
+23
react/eslint.config.js
···
1
+
import js from '@eslint/js'
2
+
import globals from 'globals'
3
+
import reactHooks from 'eslint-plugin-react-hooks'
4
+
import reactRefresh from 'eslint-plugin-react-refresh'
5
+
import tseslint from 'typescript-eslint'
6
+
import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+
export default defineConfig([
9
+
globalIgnores(['dist']),
10
+
{
11
+
files: ['**/*.{ts,tsx}'],
12
+
extends: [
13
+
js.configs.recommended,
14
+
tseslint.configs.recommended,
15
+
reactHooks.configs.flat.recommended,
16
+
reactRefresh.configs.vite,
17
+
],
18
+
languageOptions: {
19
+
ecmaVersion: 2020,
20
+
globals: globals.browser,
21
+
},
22
+
},
23
+
])
+13
react/index.html
+13
react/index.html
···
1
+
<!doctype html>
2
+
<html lang="en">
3
+
<head>
4
+
<meta charset="UTF-8" />
5
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+
<title>react</title>
8
+
</head>
9
+
<body>
10
+
<div id="root"></div>
11
+
<script type="module" src="/src/main.tsx"></script>
12
+
</body>
13
+
</html>
+40
react/package.json
+40
react/package.json
···
1
+
{
2
+
"name": "react-blog",
3
+
"private": true,
4
+
"version": "0.0.0",
5
+
"type": "module",
6
+
"scripts": {
7
+
"dev": "vite",
8
+
"build": "tsc -b && vite build",
9
+
"lint": "eslint .",
10
+
"preview": "vite preview"
11
+
},
12
+
"dependencies": {
13
+
"@tailwindcss/vite": "^4.1.17",
14
+
"draft-js": "^0.11.7",
15
+
"react": "^19.2.0",
16
+
"react-dom": "^19.2.0",
17
+
"react-router": "^7.9.6",
18
+
"tailwindcss": "^4.1.17"
19
+
},
20
+
"devDependencies": {
21
+
"@eslint/js": "^9.39.1",
22
+
"@happy-dom/global-registrator": "^20.0.10",
23
+
"@testing-library/jest-dom": "^6.9.1",
24
+
"@testing-library/react": "^16.3.0",
25
+
"@types/bun": "latest",
26
+
"@types/draft-js": "^0.11.20",
27
+
"@types/node": "^24.10.0",
28
+
"@types/react": "^19.2.2",
29
+
"@types/react-dom": "^19.2.2",
30
+
"@vitejs/plugin-react": "^5.1.0",
31
+
"babel-plugin-react-compiler": "^1.0.0",
32
+
"eslint": "^9.39.1",
33
+
"eslint-plugin-react-hooks": "^7.0.1",
34
+
"eslint-plugin-react-refresh": "^0.4.24",
35
+
"globals": "^16.5.0",
36
+
"typescript": "~5.9.3",
37
+
"typescript-eslint": "^8.46.3",
38
+
"vite": "^7.2.2"
39
+
}
40
+
}
+3
react/preload.ts
+3
react/preload.ts
+61
react/src/App.tsx
+61
react/src/App.tsx
···
1
+
import { posts } from "./lib/post";
2
+
import { BlogPostList } from "./components/BlogPostList";
3
+
import { Link } from "react-router";
4
+
import { useState } from "react";
5
+
6
+
export function App() {
7
+
const [searchBarDisplay, displaySearchBar] = useState(false);
8
+
return (
9
+
<>
10
+
<title>Posts</title>
11
+
<div className="w-screen p-5 flex flex-col items-center gap-10">
12
+
<nav className="flex justify-between items-center w-full sticky top-0">
13
+
<h1 className="text-3xl font-bold text-left">Blog App</h1>
14
+
{searchBarDisplay ? (
15
+
<>
16
+
<input
17
+
type="text"
18
+
placeholder="Search..."
19
+
className="border border-gray-300 rounded px-2 py-1"
20
+
onChange={(e) => {
21
+
// Implement search functionality here
22
+
}}
23
+
/>
24
+
<button
25
+
className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center"
26
+
onClick={() => displaySearchBar(false)}
27
+
>
28
+
Close
29
+
</button>
30
+
</>
31
+
) : (
32
+
<button
33
+
className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center"
34
+
onClick={() => displaySearchBar(true)}
35
+
>
36
+
Search
37
+
</button>
38
+
)}
39
+
<div className="flex w-full justify-end items-center">
40
+
<Link to="/post" className="w-1/3">
41
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
42
+
New Post
43
+
</div>
44
+
</Link>
45
+
</div>
46
+
</nav>
47
+
<div className="flex flex-col gap-4 md:grid md:grid-cols-3 items-center justify-between w-full">
48
+
<div className="flex w-full justify-end items-center">
49
+
<h1 className="text-5xl font-bold text-center">Posts</h1>
50
+
<Link to="/post" className="w-1/3">
51
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
52
+
New Post
53
+
</div>
54
+
</Link>
55
+
</div>
56
+
</div>
57
+
<BlogPostList posts={posts} />
58
+
</div>
59
+
</>
60
+
);
61
+
}
+17
react/src/component.test.tsx
+17
react/src/component.test.tsx
···
1
+
/// <reference lib="dom" />
2
+
3
+
import { test, expect } from "bun:test";
4
+
import { render, screen } from "@testing-library/react";
5
+
import "@testing-library/jest-dom";
6
+
import { App } from "./App";
7
+
import { BrowserRouter } from "react-router";
8
+
import { posts } from "./lib/post";
9
+
10
+
test("renders app page", () => {
11
+
render(
12
+
<BrowserRouter>
13
+
<App />
14
+
</BrowserRouter>,
15
+
);
16
+
expect(screen.getAllByRole("heading")).toHaveLength(posts.length + 1);
17
+
});
+95
react/src/components/BlogPostDetail.tsx
+95
react/src/components/BlogPostDetail.tsx
···
1
+
import { useParams, Outlet } from "react-router";
2
+
import { posts } from "../lib/post";
3
+
import { Link } from "react-router";
4
+
import { ContentState, convertFromRaw, Editor, EditorState } from "draft-js";
5
+
import { useState } from "react";
6
+
import { useNavigate } from "react-router";
7
+
8
+
export function BlogPostDetail({
9
+
deletePost,
10
+
}: {
11
+
deletePost: (id: number) => void;
12
+
}) {
13
+
const { postId } = useParams();
14
+
const post = posts.find((post) => post.id === parseInt(postId!));
15
+
const [editorState, setEditorState] = useState(() => {
16
+
try {
17
+
const data = JSON.parse(`"${post?.content ?? ""}"`);
18
+
return EditorState.createWithContent(convertFromRaw(data));
19
+
} catch {
20
+
console.log("fallback");
21
+
return EditorState.createWithContent(
22
+
ContentState.createFromText(post?.content ?? ""),
23
+
);
24
+
}
25
+
});
26
+
const navigate = useNavigate();
27
+
28
+
if (!post) {
29
+
return <div>Post not found</div>;
30
+
}
31
+
32
+
const formattedDate = new Date(post.datePosted).toLocaleDateString("en-US", {
33
+
month: "long",
34
+
day: "numeric",
35
+
year: "numeric",
36
+
});
37
+
38
+
return (
39
+
<>
40
+
<title>{post.title}</title>
41
+
<div className="md:grid md:grid-cols-3 flex flex-col w-full">
42
+
<Link
43
+
to="/"
44
+
className="text-gray-700 dark:text-gray-200 hover:text-gray-400 flex justify-center items-center w-16 mb-4 md:mb-0"
45
+
>
46
+
Home
47
+
</Link>
48
+
<h1 className="text-3xl md:text-4xl font-bold text-center mb-4 md:mb-0">
49
+
{post.title}
50
+
</h1>
51
+
<div className="flex md:justify-end items-center">
52
+
<Link to={`/post?postId=${post.id}`} className="w-1/3">
53
+
<div className="bg-blue-500 hover:bg-blue-700 text-white w-full font-bold py-2 px-4 rounded cursor-pointer text-center">
54
+
Edit Post
55
+
</div>
56
+
</Link>
57
+
</div>
58
+
</div>
59
+
<div className="flex flex-col gap-1 md:gap-2.5 justify-center items-center mb-1.5 md:mb-2.5">
60
+
<p className="text-gray-700 dark:text-gray-400 text-sm md:text-lg">
61
+
By: {post.author}
62
+
</p>
63
+
<p className="text-gray-600 dark:text-gray-500 text-xs md:text-base">
64
+
Published on {formattedDate}
65
+
</p>
66
+
</div>
67
+
<div className="text-sm md:text-lg md:w-3xl w-full md:mb-10">
68
+
<Editor
69
+
editorState={editorState}
70
+
onChange={setEditorState}
71
+
readOnly={true}
72
+
/>
73
+
</div>
74
+
<button
75
+
className="bg-red-500 hover:bg-red-600 text-white font-bold py-2 px-4 rounded cursor-pointer w-full md:w-3xl"
76
+
onClick={() => {
77
+
if (window.confirm("Are you sure you want to delete this post?")) {
78
+
deletePost(post.id);
79
+
navigate("/");
80
+
}
81
+
}}
82
+
>
83
+
Delete
84
+
</button>
85
+
</>
86
+
);
87
+
}
88
+
89
+
export function PostLayout() {
90
+
return (
91
+
<div className="flex flex-col justify-center gap-3.5 md:gap-5 items-center p-5 w-screen">
92
+
<Outlet />
93
+
</div>
94
+
);
95
+
}
+255
react/src/components/BlogPostForm.tsx
+255
react/src/components/BlogPostForm.tsx
···
1
+
import { posts, type BlogPost } from "../lib/post";
2
+
import { useRef, useState } from "react";
3
+
import { useNavigate } from "react-router";
4
+
import { useSearchParams } from "react-router";
5
+
import {
6
+
ContentState,
7
+
convertFromRaw,
8
+
convertToRaw,
9
+
Editor,
10
+
EditorState,
11
+
RichUtils,
12
+
} from "draft-js";
13
+
import "draft-js/dist/Draft.css";
14
+
15
+
export function BlogPostForm({
16
+
post,
17
+
onSubmit,
18
+
}: {
19
+
post: BlogPost | null;
20
+
onSubmit: (post: BlogPost) => void;
21
+
}) {
22
+
const [postState, setPostState] = useState({
23
+
id: post?.id ?? posts.length,
24
+
title: post?.title ?? "",
25
+
summary: post?.summary ?? "",
26
+
content: post?.content ?? "",
27
+
author: post?.author ?? "",
28
+
datePosted: post?.datePosted ?? new Date().toISOString().split("T")[0],
29
+
});
30
+
const [missing, setMissing] = useState<string[]>([]);
31
+
const [contentState, setContentState] = useState<EditorState>(() => {
32
+
if (post?.content) {
33
+
try {
34
+
const rawContent = JSON.parse(post.content);
35
+
return EditorState.createWithContent(convertFromRaw(rawContent));
36
+
} catch {
37
+
// Fallback to plain text if JSON parsing fails
38
+
return EditorState.createWithContent(
39
+
ContentState.createFromText(post.content),
40
+
);
41
+
}
42
+
}
43
+
return EditorState.createEmpty();
44
+
});
45
+
46
+
const editorRef = useRef<Editor>(null);
47
+
48
+
const navigate = useNavigate();
49
+
50
+
const handleChange = (
51
+
event: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
52
+
) => {
53
+
const { name, value } = event.target;
54
+
setPostState((prevState) => ({ ...prevState, [name]: value }));
55
+
};
56
+
57
+
const handleMissing = () => {
58
+
const missingFields = Object.entries(postState)
59
+
.map(([key, value]) => (value === "" ? key : null))
60
+
.filter((key) => key !== null);
61
+
setMissing(missingFields);
62
+
};
63
+
64
+
const handleContentChange = (content: EditorState) => {
65
+
setContentState(content);
66
+
const rawContent = convertToRaw(content.getCurrentContent());
67
+
setPostState((prevState) => ({
68
+
...prevState,
69
+
content: JSON.stringify(rawContent),
70
+
}));
71
+
};
72
+
73
+
const handleInlineStyle = (style: string) => {
74
+
setContentState((prevState) =>
75
+
RichUtils.toggleInlineStyle(prevState, style),
76
+
);
77
+
};
78
+
79
+
const handleSubmit = (event: React.MouseEvent<HTMLButtonElement>) => {
80
+
event.preventDefault();
81
+
82
+
if (
83
+
!postState.title ||
84
+
!postState.summary ||
85
+
!postState.content ||
86
+
!postState.author
87
+
) {
88
+
handleMissing();
89
+
return;
90
+
}
91
+
92
+
onSubmit(postState);
93
+
navigate("/");
94
+
};
95
+
96
+
return (
97
+
<form className="flex flex-col gap-4 dark:bg-slate-600 p-10 rounded-lg md:w-4xl w-md">
98
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
99
+
Title:
100
+
<input
101
+
type="text"
102
+
name="title"
103
+
className="border-gray-400 md:col-start-2 md:col-span-5 border rounded h-8 py-1 px-2 w-full"
104
+
value={postState.title}
105
+
onChange={handleChange}
106
+
required
107
+
/>
108
+
</label>
109
+
{missing.includes("title") && (
110
+
<p className="text-red-500">Title is required</p>
111
+
)}
112
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
113
+
Summary:
114
+
<textarea
115
+
name="summary"
116
+
className="border-gray-400 md:col-start-2 md:col-span-5 border rounded min-h-16 h-auto py-1 px-2 w-full"
117
+
value={postState.summary}
118
+
onChange={handleChange}
119
+
required
120
+
/>
121
+
</label>
122
+
{missing.includes("summary") && (
123
+
<p className="text-red-500">Summary is required</p>
124
+
)}
125
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
126
+
Content:
127
+
</label>
128
+
<div className="md:grid md:grid-cols-6">
129
+
<div className="md:col-start-2 md:col-span-5">
130
+
<div className="flex gap-2 mb-2 border-b pb-2">
131
+
<button
132
+
type="button"
133
+
onMouseDown={(e) => {
134
+
e.preventDefault();
135
+
handleInlineStyle("BOLD");
136
+
}}
137
+
className={`px-3 py-1 border rounded ${
138
+
contentState.getCurrentInlineStyle().has("BOLD")
139
+
? "bg-blue-500 text-white"
140
+
: "bg-gray-500"
141
+
}`}
142
+
>
143
+
<strong>B</strong>
144
+
</button>
145
+
<button
146
+
type="button"
147
+
onMouseDown={(e) => {
148
+
e.preventDefault();
149
+
handleInlineStyle("ITALIC");
150
+
}}
151
+
className={`px-3 py-1 border rounded ${
152
+
contentState.getCurrentInlineStyle().has("ITALIC")
153
+
? "bg-blue-500 text-white"
154
+
: "bg-gray-500"
155
+
}`}
156
+
>
157
+
<em>I</em>
158
+
</button>
159
+
<button
160
+
type="button"
161
+
onMouseDown={(e) => {
162
+
e.preventDefault();
163
+
handleInlineStyle("UNDERLINE");
164
+
}}
165
+
className={`px-3 py-1 border rounded ${
166
+
contentState.getCurrentInlineStyle().has("UNDERLINE")
167
+
? "bg-blue-500 text-white"
168
+
: "bg-gray-500"
169
+
}`}
170
+
>
171
+
<u>U</u>
172
+
</button>
173
+
</div>
174
+
175
+
{/* Editor */}
176
+
<div
177
+
className="border-gray-400 border rounded p-2 cursor-text min-h-48 pointer-events-auto select-text"
178
+
onMouseDown={(e) => {
179
+
if (e.target === e.currentTarget) {
180
+
e.preventDefault();
181
+
editorRef.current?.focus();
182
+
}
183
+
}}
184
+
>
185
+
<Editor
186
+
ref={editorRef}
187
+
editorState={contentState}
188
+
onChange={handleContentChange}
189
+
placeholder="Write your content here..."
190
+
/>
191
+
</div>
192
+
</div>
193
+
</div>
194
+
{missing.includes("content") && (
195
+
<p className="text-red-500">Content is required</p>
196
+
)}
197
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
198
+
Author:
199
+
<input
200
+
type="text"
201
+
name="author"
202
+
className="border-gray-400 md:col-start-2 md:col-span-5 border rounded h-8 py-1 px-2 w-full"
203
+
value={postState.author}
204
+
onChange={handleChange}
205
+
required
206
+
/>
207
+
</label>
208
+
{missing.includes("author") && (
209
+
<p className="text-red-500">Author is required</p>
210
+
)}
211
+
<label className="md:grid md:grid-cols-6 flex flex-col w-full gap-2">
212
+
Date Posted:
213
+
<input
214
+
type="date"
215
+
name="datePosted"
216
+
value={postState.datePosted}
217
+
onChange={handleChange}
218
+
required
219
+
/>
220
+
</label>
221
+
<button
222
+
type="button"
223
+
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded cursor-pointer"
224
+
onClick={handleSubmit}
225
+
>
226
+
{post ? "Save Post" : "Create Post"}
227
+
</button>
228
+
</form>
229
+
);
230
+
}
231
+
232
+
export function NewPostLayout() {
233
+
const searchParams = useSearchParams()[0];
234
+
const postId = parseInt(searchParams.get("postId") ?? "-1");
235
+
const post =
236
+
postId < 0 || postId >= posts.length
237
+
? null
238
+
: posts.find((p) => p.id === postId)!;
239
+
240
+
return (
241
+
<div className="flex flex-col gap-4 items-center justify-center dark:bg-slate-700 p-10 h-screen">
242
+
<h1 className="text-2xl font-bold">New Post</h1>
243
+
<BlogPostForm
244
+
post={post}
245
+
onSubmit={(post) => {
246
+
if (post.id < posts.length) {
247
+
posts[post.id] = post;
248
+
} else {
249
+
posts.push(post);
250
+
}
251
+
}}
252
+
/>
253
+
</div>
254
+
);
255
+
}
+33
react/src/components/BlogPostItem.tsx
+33
react/src/components/BlogPostItem.tsx
···
1
+
import { Link } from "react-router";
2
+
3
+
export function BlogPostItem({
4
+
title,
5
+
idx,
6
+
datePosted,
7
+
summary,
8
+
}: {
9
+
title: string;
10
+
idx: number;
11
+
datePosted: string;
12
+
summary: string;
13
+
}) {
14
+
return (
15
+
<>
16
+
<Link to={`/entries/${idx}`}>
17
+
<div className="border border-gray-300 p-3.5 md:p-5 rounded-md flex flex-col gap justify-center items-center max-w-lg">
18
+
<div className="flex flex-row gap-4 justify-center items-center">
19
+
<p className="text-gray-500">#{idx + 1}</p>
20
+
<h2 className="text-xl md:text-2xl dark:text-gray-300 text-gray-900 font-bold ">
21
+
{title}
22
+
</h2>
23
+
</div>
24
+
<p className="text-gray-500 text-sm">Posted on {datePosted}</p>
25
+
<div className="h-3" />
26
+
<p className="text-left w-full">
27
+
{summary.length > 100 ? summary.substring(0, 100) + "..." : summary}
28
+
</p>
29
+
</div>
30
+
</Link>
31
+
</>
32
+
);
33
+
}
+26
react/src/components/BlogPostList.tsx
+26
react/src/components/BlogPostList.tsx
···
1
+
import type { BlogPost } from "../lib/post";
2
+
import { BlogPostItem } from "./BlogPostItem";
3
+
4
+
export function BlogPostList({ posts }: { posts: BlogPost[] }) {
5
+
if (!posts.length) {
6
+
return <p className="text-lg text-center">No blog posts available</p>;
7
+
}
8
+
return (
9
+
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 items-center justify-center gap-5">
10
+
{posts
11
+
.sort(
12
+
(a, b) =>
13
+
new Date(b.datePosted).getTime() - new Date(a.datePosted).getTime(),
14
+
)
15
+
.map((post, idx) => (
16
+
<BlogPostItem
17
+
key={idx}
18
+
idx={post.id}
19
+
title={post.title}
20
+
summary={post.summary}
21
+
datePosted={post.datePosted}
22
+
/>
23
+
))}
24
+
</div>
25
+
);
26
+
}
+22
react/src/index.css
+22
react/src/index.css
···
1
+
@import "tailwindcss";
2
+
:root {
3
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
4
+
line-height: 1.5;
5
+
font-weight: 400;
6
+
7
+
color-scheme: light dark;
8
+
color: rgba(255, 255, 255, 0.87);
9
+
background-color: #242424;
10
+
11
+
font-synthesis: none;
12
+
text-rendering: optimizeLegibility;
13
+
-webkit-font-smoothing: antialiased;
14
+
-moz-osx-font-smoothing: grayscale;
15
+
}
16
+
17
+
@media (prefers-color-scheme: light) {
18
+
:root {
19
+
color: #213547;
20
+
background-color: #ffffff;
21
+
}
22
+
}
+39
react/src/lib/post.ts
+39
react/src/lib/post.ts
···
1
+
export interface BlogPost {
2
+
id: number;
3
+
datePosted: string;
4
+
title: string;
5
+
author: string;
6
+
summary: string;
7
+
content: string;
8
+
}
9
+
10
+
export const posts: BlogPost[] = [
11
+
{
12
+
id: 0,
13
+
datePosted: "2025-11-15",
14
+
title: "My First Blog Post",
15
+
author: "Samuel Shuert",
16
+
summary: "First blog post",
17
+
content:
18
+
"This is my first blog post. I am excited to share my thoughts with the world! lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
19
+
},
20
+
{
21
+
id: 1,
22
+
datePosted: "2025-11-17",
23
+
title: "My Second Blog Post",
24
+
author: "Samuel Shuert",
25
+
summary: "Another post",
26
+
content:
27
+
"This is my second blog post. I am excited to share my thoughts with the world!",
28
+
},
29
+
{
30
+
id: 2,
31
+
datePosted: "2025-11-18",
32
+
title: "My Third Blog Post",
33
+
author: "Samuel Shuert",
34
+
summary:
35
+
"The third blog post lorem ipsum dolor sit amet consectetur adipiscing elit The third blog post lorem ipsum dolor sit amet consectetur adipiscing elit",
36
+
content:
37
+
"This is my third blog post. I am excited to share my thoughts with the world!",
38
+
},
39
+
];
+32
react/src/main.tsx
+32
react/src/main.tsx
···
1
+
import { StrictMode } from "react";
2
+
import { createRoot } from "react-dom/client";
3
+
import { BrowserRouter, Routes, Route } from "react-router";
4
+
import "./index.css";
5
+
import { App } from "./App.tsx";
6
+
import { BlogPostDetail, PostLayout } from "./components/BlogPostDetail.tsx";
7
+
import { NewPostLayout } from "./components/BlogPostForm.tsx";
8
+
import { posts } from "./lib/post.ts";
9
+
10
+
const deletePost = (postId: number) => {
11
+
const index = posts.findIndex((post) => post.id === postId);
12
+
if (index !== -1) {
13
+
posts.splice(index, 1);
14
+
}
15
+
};
16
+
17
+
createRoot(document.getElementById("root")!).render(
18
+
<StrictMode>
19
+
<BrowserRouter>
20
+
<Routes>
21
+
<Route index element={<App />} />
22
+
<Route path="entries" element={<PostLayout />}>
23
+
<Route
24
+
path=":postId"
25
+
element={<BlogPostDetail deletePost={deletePost} />}
26
+
/>
27
+
</Route>
28
+
<Route path="post" element={<NewPostLayout />} />
29
+
</Routes>
30
+
</BrowserRouter>
31
+
</StrictMode>,
32
+
);
+28
react/tsconfig.app.json
+28
react/tsconfig.app.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
4
+
"target": "ES2022",
5
+
"useDefineForClassFields": true,
6
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+
"module": "ESNext",
8
+
"types": ["vite/client", "@types/bun"],
9
+
"skipLibCheck": true,
10
+
11
+
/* Bundler mode */
12
+
"moduleResolution": "bundler",
13
+
"allowImportingTsExtensions": true,
14
+
"verbatimModuleSyntax": true,
15
+
"moduleDetection": "force",
16
+
"noEmit": true,
17
+
"jsx": "react-jsx",
18
+
19
+
/* Linting */
20
+
"strict": true,
21
+
"noUnusedLocals": true,
22
+
"noUnusedParameters": true,
23
+
"erasableSyntaxOnly": true,
24
+
"noFallthroughCasesInSwitch": true,
25
+
"noUncheckedSideEffectImports": true
26
+
},
27
+
"include": ["src"]
28
+
}
+7
react/tsconfig.json
+7
react/tsconfig.json
+26
react/tsconfig.node.json
+26
react/tsconfig.node.json
···
1
+
{
2
+
"compilerOptions": {
3
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
4
+
"target": "ES2023",
5
+
"lib": ["ES2023"],
6
+
"module": "ESNext",
7
+
"types": ["@types/bun"],
8
+
"skipLibCheck": true,
9
+
10
+
/* Bundler mode */
11
+
"moduleResolution": "bundler",
12
+
"allowImportingTsExtensions": true,
13
+
"verbatimModuleSyntax": true,
14
+
"moduleDetection": "force",
15
+
"noEmit": true,
16
+
17
+
/* Linting */
18
+
"strict": true,
19
+
"noUnusedLocals": true,
20
+
"noUnusedParameters": true,
21
+
"erasableSyntaxOnly": true,
22
+
"noFallthroughCasesInSwitch": true,
23
+
"noUncheckedSideEffectImports": true
24
+
},
25
+
"include": ["vite.config.ts"]
26
+
}
+21
react/vite.config.ts
+21
react/vite.config.ts
···
1
+
import { defineConfig } from "vite";
2
+
import react from "@vitejs/plugin-react";
3
+
import tailwindcss from "@tailwindcss/vite";
4
+
5
+
// https://vite.dev/config/
6
+
export default defineConfig({
7
+
define: {
8
+
global: "globalThis",
9
+
},
10
+
server: {
11
+
allowedHosts: ["project.coded.codes"],
12
+
},
13
+
plugins: [
14
+
react({
15
+
babel: {
16
+
plugins: [["babel-plugin-react-compiler"]],
17
+
},
18
+
}),
19
+
tailwindcss(),
20
+
],
21
+
});
+34
server/.gitignore
+34
server/.gitignore
···
1
+
# dependencies (bun install)
2
+
node_modules
3
+
4
+
# output
5
+
out
6
+
dist
7
+
*.tgz
8
+
9
+
# code coverage
10
+
coverage
11
+
*.lcov
12
+
13
+
# logs
14
+
logs
15
+
_.log
16
+
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
17
+
18
+
# dotenv environment variable files
19
+
.env
20
+
.env.development.local
21
+
.env.test.local
22
+
.env.production.local
23
+
.env.local
24
+
25
+
# caches
26
+
.eslintcache
27
+
.cache
28
+
*.tsbuildinfo
29
+
30
+
# IntelliJ based IDEs
31
+
.idea
32
+
33
+
# Finder (MacOS) folder config
34
+
.DS_Store
+15
server/README.md
+15
server/README.md
+1
server/books.json
+1
server/books.json
···
1
+
[{"id":"9780553212471","title":"Frankenstein","author":"Mary Shelley"},{"id":"9780060935467","title":"To Kill a Mockingbird","author":"Harper Lee"},{"id":"9780141439518","title":"Pride and Prejudice","author":"Jane Austen"}]
+288
server/books.ts
+288
server/books.ts
···
1
+
import express, {
2
+
type NextFunction,
3
+
type Request,
4
+
type Response,
5
+
} from "express";
6
+
import { writeFile, readFile, exists } from "fs/promises";
7
+
8
+
const ISBN13 =
9
+
/^(?:ISBN(?:-13)?:? )?(?=[0-9]{13}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)[\d-]+$/;
10
+
11
+
interface Book {
12
+
id: string;
13
+
title: string;
14
+
author: string;
15
+
}
16
+
17
+
const initBooks: () => Promise<void> = async () => {
18
+
await writeFile(
19
+
"books.json",
20
+
JSON.stringify([
21
+
{
22
+
id: "9780553212471",
23
+
title: "Frankenstein",
24
+
author: "Mary Shelley",
25
+
},
26
+
{
27
+
id: "9780060935467",
28
+
title: "To Kill a Mockingbird",
29
+
author: "Harper Lee",
30
+
},
31
+
{
32
+
id: "9780141439518",
33
+
title: "Pride and Prejudice",
34
+
author: "Jane Austen",
35
+
},
36
+
]),
37
+
);
38
+
};
39
+
40
+
enum ErrorType {
41
+
NotFound,
42
+
InvalidId,
43
+
BadData,
44
+
AlreadyExists,
45
+
}
46
+
47
+
class BookError extends Error {
48
+
public readonly status: number;
49
+
constructor(err: ErrorType) {
50
+
let msg: string;
51
+
let st: number;
52
+
switch (err) {
53
+
case ErrorType.NotFound:
54
+
msg = "Book {{id}} not found";
55
+
st = 404;
56
+
break;
57
+
case ErrorType.InvalidId:
58
+
msg = "Invalid book id ({{id}}) [must be ISBN-13 formatted]";
59
+
st = 400;
60
+
break;
61
+
case ErrorType.BadData:
62
+
msg = "Invalid book data";
63
+
st = 400;
64
+
break;
65
+
case ErrorType.AlreadyExists:
66
+
msg = "Book with id {{id}} already exists";
67
+
st = 409;
68
+
break;
69
+
}
70
+
super(msg);
71
+
this.name = "BookError";
72
+
this.status = st;
73
+
}
74
+
}
75
+
76
+
const getBooks: () => Promise<Book[]> = async () => {
77
+
if (!(await exists("books.json"))) {
78
+
await initBooks();
79
+
}
80
+
const file = await readFile("books.json", "utf-8");
81
+
if (file.length < 4) {
82
+
await initBooks();
83
+
return await getBooks();
84
+
}
85
+
return JSON.parse(file);
86
+
};
87
+
88
+
const updateBook = async (task: Book): Promise<void> => {
89
+
const books = await getBooks();
90
+
const index = books.findIndex((b) => b.id === task.id);
91
+
if (index !== -1) {
92
+
books[index] = task;
93
+
} else {
94
+
books.push(task);
95
+
}
96
+
await writeFile("books.json", JSON.stringify(books));
97
+
};
98
+
99
+
const removeBook = async (id: string): Promise<void> => {
100
+
const books = await getBooks();
101
+
const index = books.findIndex((b) => b.id === id);
102
+
if (index !== -1) {
103
+
books.splice(index, 1);
104
+
await writeFile("books.json", JSON.stringify(books));
105
+
}
106
+
};
107
+
108
+
class BadDataIssues extends Error {
109
+
missingKeys: string[];
110
+
extraKeys: string[];
111
+
badValues: [string, string][];
112
+
113
+
constructor(
114
+
missingKeys: string[],
115
+
extraKeys: string[],
116
+
badValues: [string, string][],
117
+
) {
118
+
super("Bad data issues");
119
+
this.missingKeys = missingKeys;
120
+
this.extraKeys = extraKeys;
121
+
this.badValues = badValues;
122
+
}
123
+
}
124
+
125
+
const keyTypes = {
126
+
id: "ISBN13 code",
127
+
title: "string",
128
+
author: "string",
129
+
};
130
+
131
+
const validateBook = (task: { [key: string]: any }): Book => {
132
+
let missingKeys = ["id", "title", "author"].filter(
133
+
(key) => !Object.keys(task).includes(key),
134
+
);
135
+
let extraKeys = Object.keys(task).filter(
136
+
(key) => !["id", "title", "author"].includes(key),
137
+
);
138
+
let badValues = Object.entries(task)
139
+
.filter(([key, value]) => {
140
+
if (key === "id") return typeof value !== "string" || !ISBN13.test(value);
141
+
if (key === "title") return typeof value !== "string";
142
+
if (key === "author") return typeof value !== "string";
143
+
return false;
144
+
})
145
+
.map(
146
+
([key, _value]) =>
147
+
[key, keyTypes[key as keyof typeof keyTypes]] as [string, string],
148
+
);
149
+
if (missingKeys.length > 0 || extraKeys.length > 0 || badValues.length > 0) {
150
+
throw new BadDataIssues(missingKeys, extraKeys, badValues);
151
+
}
152
+
return task as Book;
153
+
};
154
+
155
+
const auth = async (req: Request, res: Response, next: NextFunction) => {
156
+
if (req.method === "GET") {
157
+
next();
158
+
return;
159
+
}
160
+
if (!req.headers.authorization) {
161
+
res.status(401).json({ error: "Unauthorized" });
162
+
return;
163
+
}
164
+
let token = req.headers.authorization.split(" ")[1];
165
+
if (token !== "password1!") {
166
+
res.status(401).json({ error: "Unauthorized" });
167
+
return;
168
+
}
169
+
next();
170
+
};
171
+
172
+
const errorHandler = (
173
+
err: Error,
174
+
_req: Request,
175
+
res: Response,
176
+
_next: NextFunction,
177
+
) => {
178
+
if (err instanceof BookError) {
179
+
let msg = err.message.replace("{{id}}", res.locals.id ?? "");
180
+
181
+
let obj: Map<string, any> = new Map<string, any>([
182
+
["error", `${err.name}: ${msg}`],
183
+
]);
184
+
185
+
if (res.locals.bdi) {
186
+
if (res.locals.bdi.missingKeys.length > 0) {
187
+
obj.set("missingKeys", res.locals.bdi.missingKeys);
188
+
}
189
+
if (res.locals.bdi.extraKeys.length > 0) {
190
+
obj.set("extraKeys", res.locals.bdi.extraKeys);
191
+
}
192
+
if (res.locals.bdi.badValues.length > 0) {
193
+
obj.set("badValues", res.locals.bdi.badValues);
194
+
}
195
+
}
196
+
197
+
res.status(err.status).json(Object.fromEntries(obj.entries()));
198
+
} else {
199
+
console.error(err.stack);
200
+
res.status(500).json({ error: "Internal Server Error" });
201
+
}
202
+
};
203
+
204
+
const router = express.Router();
205
+
206
+
router.use(express.json());
207
+
router.use((req, res, next) => {
208
+
console.log(`Recieved a ${req.method} request to ${req.url}`);
209
+
next();
210
+
});
211
+
router.use(auth);
212
+
213
+
router.get("/", async (_req, res) => {
214
+
res.json(await getBooks());
215
+
});
216
+
217
+
router.post("/", async (req, res) => {
218
+
const books = await getBooks();
219
+
try {
220
+
const bookData = validateBook(req.body);
221
+
res.locals.id = bookData.id;
222
+
if (books.filter((b) => b.id === bookData.id).length > 0) {
223
+
throw new BookError(ErrorType.AlreadyExists);
224
+
}
225
+
await updateBook(bookData);
226
+
res.status(201).json(bookData);
227
+
} catch (err) {
228
+
if (err instanceof BookError) {
229
+
throw err;
230
+
} else if (err instanceof BadDataIssues) {
231
+
res.locals.bdi = err;
232
+
throw new BookError(ErrorType.BadData);
233
+
} else {
234
+
res.status(500).json({ error: "Internal Server Error" });
235
+
}
236
+
}
237
+
});
238
+
239
+
router.get("/:id", async (req, res) => {
240
+
res.locals.id = req.params.id;
241
+
if (!ISBN13.test(req.params.id)) {
242
+
throw new BookError(ErrorType.InvalidId);
243
+
}
244
+
const books = await getBooks();
245
+
const book = books.find((b) => b.id == req.params.id);
246
+
if (!book) throw new BookError(ErrorType.NotFound);
247
+
res.json(book);
248
+
});
249
+
250
+
router.put("/:id", async (req, res) => {
251
+
res.locals.id = req.params.id;
252
+
if (!ISBN13.test(req.params.id)) {
253
+
throw new BookError(ErrorType.InvalidId);
254
+
}
255
+
const books = await getBooks();
256
+
const book = books.find((b) => b.id == req.params.id);
257
+
if (!book) throw new BookError(ErrorType.NotFound);
258
+
const bookData = validateBook(req.body);
259
+
await updateBook(bookData);
260
+
res.sendStatus(204);
261
+
});
262
+
263
+
router.delete("/reset", async (_req, res) => {
264
+
await initBooks();
265
+
res.sendStatus(204);
266
+
});
267
+
268
+
router.delete("/:id", async (req, res) => {
269
+
res.locals.id = req.params.id;
270
+
if (!ISBN13.test(req.params.id)) {
271
+
throw new BookError(ErrorType.InvalidId);
272
+
}
273
+
const books = await getBooks();
274
+
const book = books.find((b) => b.id == req.params.id);
275
+
if (!book) throw new BookError(ErrorType.NotFound);
276
+
await removeBook(book.id);
277
+
res.sendStatus(204);
278
+
});
279
+
280
+
router.all("/{*splat}", async (req, res) => {
281
+
res
282
+
.status(404)
283
+
.json({ error: `path: ${req.method} at /${req.params.splat} Not Found` });
284
+
});
285
+
286
+
router.use(errorHandler);
287
+
288
+
export default router;
+177
server/bun.lock
+177
server/bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "server",
6
+
"dependencies": {
7
+
"@types/express": "^5.0.6",
8
+
"express": "^5.2.1",
9
+
},
10
+
"devDependencies": {
11
+
"@types/bun": "latest",
12
+
},
13
+
"peerDependencies": {
14
+
"typescript": "^5",
15
+
},
16
+
},
17
+
},
18
+
"packages": {
19
+
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
20
+
21
+
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
22
+
23
+
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
24
+
25
+
"@types/express": ["@types/express@5.0.6", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^5.0.0", "@types/serve-static": "^2" } }, "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA=="],
26
+
27
+
"@types/express-serve-static-core": ["@types/express-serve-static-core@5.1.0", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*", "@types/send": "*" } }, "sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA=="],
28
+
29
+
"@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
30
+
31
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
32
+
33
+
"@types/qs": ["@types/qs@6.14.0", "", {}, "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ=="],
34
+
35
+
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
36
+
37
+
"@types/send": ["@types/send@1.2.1", "", { "dependencies": { "@types/node": "*" } }, "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ=="],
38
+
39
+
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
40
+
41
+
"accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="],
42
+
43
+
"body-parser": ["body-parser@2.2.1", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-nfDwkulwiZYQIGwxdy0RUmowMhKcFVcYXUU7m4QlKYim1rUtg83xm2yjZ40QjDuc291AJjjeSc9b++AWHSgSHw=="],
44
+
45
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
46
+
47
+
"bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
48
+
49
+
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
50
+
51
+
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
52
+
53
+
"content-disposition": ["content-disposition@1.0.1", "", {}, "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q=="],
54
+
55
+
"content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="],
56
+
57
+
"cookie": ["cookie@0.7.2", "", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="],
58
+
59
+
"cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="],
60
+
61
+
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
62
+
63
+
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
64
+
65
+
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
66
+
67
+
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
68
+
69
+
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
70
+
71
+
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
72
+
73
+
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
74
+
75
+
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
76
+
77
+
"escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
78
+
79
+
"etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
80
+
81
+
"express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="],
82
+
83
+
"finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="],
84
+
85
+
"forwarded": ["forwarded@0.2.0", "", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="],
86
+
87
+
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
88
+
89
+
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
90
+
91
+
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
92
+
93
+
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
94
+
95
+
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
96
+
97
+
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
98
+
99
+
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
100
+
101
+
"http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
102
+
103
+
"iconv-lite": ["iconv-lite@0.7.0", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ=="],
104
+
105
+
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
106
+
107
+
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
108
+
109
+
"is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="],
110
+
111
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
112
+
113
+
"media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="],
114
+
115
+
"merge-descriptors": ["merge-descriptors@2.0.0", "", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="],
116
+
117
+
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
118
+
119
+
"mime-types": ["mime-types@3.0.2", "", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="],
120
+
121
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
122
+
123
+
"negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="],
124
+
125
+
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
126
+
127
+
"on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
128
+
129
+
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
130
+
131
+
"parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
132
+
133
+
"path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="],
134
+
135
+
"proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="],
136
+
137
+
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
138
+
139
+
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
140
+
141
+
"raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="],
142
+
143
+
"router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="],
144
+
145
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
146
+
147
+
"send": ["send@1.2.0", "", { "dependencies": { "debug": "^4.3.5", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "fresh": "^2.0.0", "http-errors": "^2.0.0", "mime-types": "^3.0.1", "ms": "^2.1.3", "on-finished": "^2.4.1", "range-parser": "^1.2.1", "statuses": "^2.0.1" } }, "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw=="],
148
+
149
+
"serve-static": ["serve-static@2.2.0", "", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ=="],
150
+
151
+
"setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
152
+
153
+
"side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
154
+
155
+
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
156
+
157
+
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
158
+
159
+
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
160
+
161
+
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
162
+
163
+
"toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
164
+
165
+
"type-is": ["type-is@2.0.1", "", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="],
166
+
167
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
168
+
169
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
170
+
171
+
"unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
172
+
173
+
"vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
174
+
175
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
176
+
}
177
+
}
+13
server/index.ts
+13
server/index.ts
···
1
+
import express from "express";
2
+
import tasks from "./tasks.ts";
3
+
import books from "./books.ts";
4
+
5
+
const app = express();
6
+
const port = process.env["NODE_PORT"] ?? 5173;
7
+
8
+
app.use("/tasks", tasks);
9
+
app.use("/books", books);
10
+
11
+
app.listen(port, () => {
12
+
console.log(`Server listening on port ${port}`);
13
+
});
+16
server/package.json
+16
server/package.json
···
1
+
{
2
+
"name": "server",
3
+
"module": "index.ts",
4
+
"type": "module",
5
+
"private": true,
6
+
"devDependencies": {
7
+
"@types/bun": "latest"
8
+
},
9
+
"peerDependencies": {
10
+
"typescript": "^5"
11
+
},
12
+
"dependencies": {
13
+
"@types/express": "^5.0.6",
14
+
"express": "^5.2.1"
15
+
}
16
+
}
server/tasks.json
server/tasks.json
This is a binary file and will not be displayed.
+146
server/tasks.ts
+146
server/tasks.ts
···
1
+
import express from "express";
2
+
import { readFile, writeFile, exists } from "fs/promises";
3
+
4
+
const router = express.Router();
5
+
6
+
interface Task {
7
+
id: string;
8
+
title: string;
9
+
completed: boolean;
10
+
}
11
+
12
+
const getTasks: () => Promise<Task[]> = async () => {
13
+
if (!(await exists("tasks.json"))) {
14
+
await writeFile("tasks.json", JSON.stringify([]));
15
+
}
16
+
const file = await readFile("tasks.json", "utf-8");
17
+
if (file.length === 0) {
18
+
return [];
19
+
}
20
+
return JSON.parse(file);
21
+
};
22
+
23
+
const updateTask = async (task: Task): Promise<void> => {
24
+
const tasks = await getTasks();
25
+
const index = tasks.findIndex((t) => t.id === task.id);
26
+
if (index !== -1) {
27
+
tasks[index] = task;
28
+
} else {
29
+
tasks.push(task);
30
+
}
31
+
await writeFile("tasks.json", JSON.stringify(tasks));
32
+
};
33
+
34
+
const removeTask = async (id: string): Promise<void> => {
35
+
const tasks = await getTasks();
36
+
const index = tasks.findIndex((t) => t.id === id);
37
+
if (index !== -1) {
38
+
tasks.splice(index, 1);
39
+
await writeFile("tasks.json", JSON.stringify(tasks));
40
+
}
41
+
};
42
+
43
+
router.use(express.json());
44
+
45
+
router.get("/", async (_req, res) => {
46
+
res.json(await getTasks());
47
+
});
48
+
49
+
router.post("/", async (req, res) => {
50
+
if (!(typeof req.body.title === "string")) {
51
+
res.status(400).send("Invalid title");
52
+
return;
53
+
}
54
+
const newTask = {
55
+
id: Math.random().toString(16).substring(2, 8),
56
+
title: req.body.title,
57
+
completed: false,
58
+
};
59
+
await updateTask(newTask);
60
+
res.json(newTask).status(201);
61
+
});
62
+
63
+
router.get("/:id", async (req, res) => {
64
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
65
+
if (!task) {
66
+
res.status(404).send("Task not found");
67
+
} else {
68
+
res.json(task);
69
+
}
70
+
});
71
+
72
+
router.put("/:id", async (req, res) => {
73
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
74
+
if (!task) {
75
+
res.status(404).send("Task not found");
76
+
} else {
77
+
const missing = [];
78
+
if (req.body.title === undefined) missing.push("title");
79
+
if (req.body.completed === undefined) missing.push("completed");
80
+
if (missing.length > 0) {
81
+
res
82
+
.status(400)
83
+
.send(
84
+
`Missing field${missing.length > 1 ? "s" : ""}: ${missing.join(", ")}`,
85
+
);
86
+
}
87
+
const badTypes = [];
88
+
if (!(typeof req.body.title === "string")) badTypes.push("title");
89
+
if (!(typeof req.body.completed === "boolean")) badTypes.push("completed");
90
+
if (badTypes.length > 0) {
91
+
res
92
+
.status(400)
93
+
.send(
94
+
`Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
95
+
);
96
+
return;
97
+
}
98
+
task.title = req.body.title ?? task.title;
99
+
task.completed = req.body.completed ?? task.completed;
100
+
await updateTask(task);
101
+
res.json(task);
102
+
}
103
+
});
104
+
105
+
router.patch("/:id", async (req, res) => {
106
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
107
+
if (!task) {
108
+
res.status(404).send("Task not found");
109
+
} else {
110
+
const badTypes = [];
111
+
if (
112
+
Object.keys(req.body).includes("title") &&
113
+
!(typeof req.body.title === "string")
114
+
)
115
+
badTypes.push("title");
116
+
if (
117
+
Object.keys(req.body).includes("completed") &&
118
+
!(typeof req.body.completed === "boolean")
119
+
)
120
+
badTypes.push("completed");
121
+
if (badTypes.length > 0) {
122
+
res
123
+
.status(400)
124
+
.send(
125
+
`Invalid type${badTypes.length > 1 ? "s" : ""}: ${badTypes.join(", ")}`,
126
+
);
127
+
return;
128
+
}
129
+
task.title = req.body.title ?? task.title;
130
+
task.completed = req.body.completed ?? task.completed;
131
+
await updateTask(task);
132
+
res.json(task);
133
+
}
134
+
});
135
+
136
+
router.delete("/:id", async (req, res) => {
137
+
const task = (await getTasks()).find((t) => t.id === req.params.id);
138
+
if (!task) {
139
+
res.status(404).send("Task not found");
140
+
} else {
141
+
await removeTask(task.id);
142
+
res.status(204).send();
143
+
}
144
+
});
145
+
146
+
export default router;
+29
server/tsconfig.json
+29
server/tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
// Environment setup & latest features
4
+
"lib": ["ESNext"],
5
+
"target": "ESNext",
6
+
"module": "Preserve",
7
+
"moduleDetection": "force",
8
+
"jsx": "react-jsx",
9
+
"allowJs": true,
10
+
11
+
// Bundler mode
12
+
"moduleResolution": "bundler",
13
+
"allowImportingTsExtensions": true,
14
+
"verbatimModuleSyntax": true,
15
+
"noEmit": true,
16
+
17
+
// Best practices
18
+
"strict": true,
19
+
"skipLibCheck": true,
20
+
"noFallthroughCasesInSwitch": true,
21
+
"noUncheckedIndexedAccess": true,
22
+
"noImplicitOverride": true,
23
+
24
+
// Some stricter flags (disabled by default)
25
+
"noUnusedLocals": false,
26
+
"noUnusedParameters": false,
27
+
"noPropertyAccessFromIndexSignature": false
28
+
}
29
+
}
+36
ts/bun.lock
+36
ts/bun.lock
···
1
+
{
2
+
"lockfileVersion": 1,
3
+
"workspaces": {
4
+
"": {
5
+
"name": "ts",
6
+
"dependencies": {
7
+
"glob-to-regex.js": "^1.2.0",
8
+
},
9
+
"devDependencies": {
10
+
"@types/bun": "latest",
11
+
},
12
+
"peerDependencies": {
13
+
"typescript": "^5",
14
+
},
15
+
},
16
+
},
17
+
"packages": {
18
+
"@types/bun": ["@types/bun@1.3.0", "", { "dependencies": { "bun-types": "1.3.0" } }, "sha512-+lAGCYjXjip2qY375xX/scJeVRmZ5cY0wyHYyCYxNcdEXrQ4AOe3gACgd4iQ8ksOslJtW4VNxBJ8llUwc3a6AA=="],
19
+
20
+
"@types/node": ["@types/node@24.8.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q=="],
21
+
22
+
"@types/react": ["@types/react@19.2.2", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA=="],
23
+
24
+
"bun-types": ["bun-types@1.3.0", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-u8X0thhx+yJ0KmkxuEo9HAtdfgCBaM/aI9K90VQcQioAmkVp3SG3FkwWGibUFz3WdXAdcsqOcbU40lK7tbHdkQ=="],
25
+
26
+
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
27
+
28
+
"glob-to-regex.js": ["glob-to-regex.js@1.2.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ=="],
29
+
30
+
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
31
+
32
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
33
+
34
+
"undici-types": ["undici-types@7.14.0", "", {}, "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA=="],
35
+
}
36
+
}
+1
ts/index.ts
+1
ts/index.ts
···
1
+
console.log("Nothing actually here");
+6
ts/oct21/collatz.ts
+6
ts/oct21/collatz.ts
+8
ts/oct21/evenDigits.ts
+8
ts/oct21/evenDigits.ts
+6
ts/oct21/fizzBuzz.ts
+6
ts/oct21/fizzBuzz.ts
+23
ts/oct21/index.test.ts
+23
ts/oct21/index.test.ts
···
1
+
import { expect, test } from "bun:test";
2
+
3
+
import { fizzBuzz } from "./fizzBuzz";
4
+
import { collatz } from "./collatz";
5
+
import { evenDigits } from "./evenDigits";
6
+
7
+
test("FizzBuzz", () => {
8
+
expect(fizzBuzz(1)).toBe("1");
9
+
expect(fizzBuzz(3)).toBe("fizz");
10
+
expect(fizzBuzz(5)).toBe("buzz");
11
+
expect(fizzBuzz(15)).toBe("fizzBuzz");
12
+
});
13
+
14
+
test("Collatz", () => {
15
+
expect(collatz(3)).toEqual([3, 10, 5, 16, 8, 4, 2, 1]);
16
+
expect(collatz(1)).toEqual([1, 4, 2, 1]);
17
+
expect(collatz(4)).toEqual([4, 2, 1]);
18
+
});
19
+
20
+
test("All Even Digits", () => {
21
+
expect(evenDigits(2486)).toBe(true);
22
+
expect(evenDigits(1234)).toBe(false);
23
+
});
+15
ts/package.json
+15
ts/package.json
+48
ts/searchEngine/crawler.test.ts
+48
ts/searchEngine/crawler.test.ts
···
1
+
import { describe, it, beforeEach, expect } from "bun:test";
2
+
import { SearchIndex } from ".";
3
+
import { Crawler, RobotsParser } from "./crawler";
4
+
import { sleep } from "bun";
5
+
6
+
describe("Robots Parser", () => {
7
+
it("should parse robots.txt file", () => {
8
+
const robotsTxt = `
9
+
User-agent: *
10
+
User-agent: crawl
11
+
Disallow: /admin
12
+
Allow: /public
13
+
14
+
User-Agent: crawl
15
+
Disallow: /no-robots
16
+
`;
17
+
const robotsParser = new RobotsParser(robotsTxt);
18
+
const { allows, disallows } = robotsParser.getUrlsForUA("crawl");
19
+
const urls = {
20
+
allows,
21
+
disallows,
22
+
};
23
+
expect(allows.has("/public")).toBe(true);
24
+
expect(disallows.has("/admin")).toBe(true);
25
+
expect(RobotsParser.checkUserAgent(urls, "/admin")).toBe(false);
26
+
expect(RobotsParser.checkUserAgent(urls, "/public")).toBe(true);
27
+
});
28
+
});
29
+
30
+
describe("Crawler", () => {
31
+
let crawler: Crawler;
32
+
beforeEach(() => {
33
+
crawler = new Crawler("SmartFridge", new SearchIndex());
34
+
});
35
+
36
+
it("should crawl a page", () => {
37
+
const url = new URL("https://google.com");
38
+
crawler.crawl(url);
39
+
crawler.on("storePage", (url) => {
40
+
console.log(`Page stored: ${url}`);
41
+
sleep(4000).then(() => {
42
+
crawler.emit("stop");
43
+
expect(crawler.index.size()).toBe(1);
44
+
});
45
+
});
46
+
// expect(crawler.index).toBe(1);
47
+
});
48
+
});
+211
ts/searchEngine/crawler.ts
+211
ts/searchEngine/crawler.ts
···
1
+
import { SearchIndex } from ".";
2
+
import { toRegex } from "glob-to-regex.js";
3
+
import { EventEmitter } from "node:events";
4
+
5
+
interface RobotUrls {
6
+
allows: Set<string>;
7
+
disallows: Set<string>;
8
+
}
9
+
10
+
export class RobotsParser {
11
+
disallow: Map<string, Set<string>> = new Map();
12
+
allow: Map<string, Set<string>> = new Map();
13
+
14
+
constructor(text: string) {
15
+
const lines = text
16
+
.split("\n")
17
+
.filter((l) => !/^\s*#.*$/.test(l)) // remove full-line comments
18
+
.map((l) => l.replace(/\s*#.*$/, "")); // remove end-of-line comments
19
+
lines.push("");
20
+
21
+
const blocks: Array<Array<string>> = [];
22
+
let current_block: Array<string> = [];
23
+
lines.forEach((line) => {
24
+
if (line == "") {
25
+
if (current_block.length == 0) return; // ignore consecutive empty lines
26
+
blocks.push(current_block);
27
+
current_block = new Array();
28
+
} else {
29
+
current_block.push(line);
30
+
}
31
+
});
32
+
33
+
blocks.forEach((block) => {
34
+
let uas: string[] = [];
35
+
let disallows: string[] = [];
36
+
let allows: string[] = [];
37
+
block.forEach((line) => {
38
+
line = line.trim().toLowerCase();
39
+
const fields: Array<string> = line.split(/\s*:\s*/);
40
+
if (fields.length < 2) return;
41
+
if (fields[0] == "user-agent") {
42
+
uas.push(fields[1]!);
43
+
} else if (fields[0] == "disallow") {
44
+
disallows.push(fields[1]!);
45
+
} else if (fields[0] == "allow") {
46
+
allows.push(fields[1]!);
47
+
}
48
+
});
49
+
uas.forEach((ua) => {
50
+
ua = ua.toLowerCase();
51
+
this.disallow.set(
52
+
ua,
53
+
new Set([...(this.disallow.get(ua) || []), ...disallows]),
54
+
);
55
+
this.allow.set(
56
+
ua,
57
+
new Set([...(this.allow.get(ua) || []), ...allows]),
58
+
);
59
+
});
60
+
});
61
+
}
62
+
63
+
static checkUserAgent(urls: RobotUrls, url: string): boolean {
64
+
const { allows, disallows } = urls;
65
+
const allowed = allows
66
+
.values()
67
+
.map((allow) => {
68
+
const regex = toRegex(allow);
69
+
return regex.test(url);
70
+
})
71
+
.reduce((acc, curr) => acc || curr, false);
72
+
if (allowed) {
73
+
return true;
74
+
}
75
+
const disallowed = disallows
76
+
.values()
77
+
.map((disallow) => {
78
+
const regex = toRegex(disallow);
79
+
return regex.test(url);
80
+
})
81
+
.reduce((acc, curr) => acc || curr, false);
82
+
return !disallowed;
83
+
}
84
+
85
+
getUrlsForUA(ua: string): RobotUrls {
86
+
ua = ua.toLowerCase();
87
+
const allowUAs = this.allow
88
+
.keys()
89
+
.filter((key) => toRegex(key).test(ua));
90
+
const disallowUAs = this.disallow
91
+
.keys()
92
+
.filter((key) => toRegex(key).test(ua));
93
+
let allows = new Set<string>();
94
+
let disallows = new Set<string>();
95
+
96
+
allowUAs.forEach((ua) => {
97
+
const allow = this.allow.get(ua);
98
+
if (allow) {
99
+
allows = allows.union(allow);
100
+
}
101
+
});
102
+
disallowUAs.forEach((ua) => {
103
+
const disallow = this.disallow.get(ua);
104
+
if (disallow) {
105
+
disallows = disallows.union(disallow);
106
+
}
107
+
});
108
+
return {
109
+
allows,
110
+
disallows,
111
+
};
112
+
}
113
+
}
114
+
115
+
const urlRegex = /https?:\/\/[^\s\"]+/g;
116
+
export class Crawler extends EventEmitter {
117
+
private robots: Map<string, RobotUrls> = new Map(); // hostname, robots allowed and disallowed for the sepcified UA
118
+
private visited: Set<URL> = new Set(); // URLS
119
+
120
+
constructor(
121
+
private readonly UA: string,
122
+
public index: SearchIndex,
123
+
) {
124
+
super();
125
+
this.on("addURL", (url: URL) => {
126
+
console.log(`Adding URL: ${url}`);
127
+
void this.processPage(url);
128
+
});
129
+
this.once("stop", () => {
130
+
this.removeAllListeners();
131
+
});
132
+
}
133
+
134
+
private async checkDisallowed(url: URL): Promise<boolean> {
135
+
const robots =
136
+
this.robots.get(url.hostname) || (await this.getRobotsTxt(url));
137
+
return !RobotsParser.checkUserAgent(robots, url.toString());
138
+
}
139
+
140
+
private async getRobotsTxt(url: URL): Promise<RobotUrls> {
141
+
const robotsTxtUrl = new URL(
142
+
`${url.protocol}//${url.hostname}/robots.txt`,
143
+
);
144
+
145
+
const response = await fetch(robotsTxtUrl, {
146
+
headers: {
147
+
"User-Agent": this.UA,
148
+
},
149
+
});
150
+
if (response.status !== 200)
151
+
return { allows: new Set(), disallows: new Set() };
152
+
if (!response.headers.get("content-type")?.startsWith("text/plain"))
153
+
return { allows: new Set(), disallows: new Set() };
154
+
const robotsTxt = await response.text();
155
+
const parsed = new RobotsParser(robotsTxt);
156
+
const forUA = parsed.getUrlsForUA(this.UA);
157
+
this.robots.set(url.hostname, forUA);
158
+
return forUA;
159
+
}
160
+
161
+
private async addOutlinks(html: string): Promise<void> {
162
+
const links = html.matchAll(urlRegex);
163
+
if (!links) return;
164
+
for (const [link, ..._] of links) {
165
+
console.log(link);
166
+
const url = new URL(link);
167
+
if (await this.checkDisallowed(url)) {
168
+
this.emit("addURL", url);
169
+
}
170
+
}
171
+
}
172
+
173
+
// private getText(html: string): string {
174
+
// const parser = new DOMParser();
175
+
// const doc = parser.parseFromString(html, "text/html");
176
+
// return doc.body.textContent || "";
177
+
// }
178
+
179
+
private async getPage(url: URL) {
180
+
if (this.visited.has(url)) return;
181
+
if (await this.checkDisallowed(url)) return;
182
+
const page = await fetch(url);
183
+
this.visited.add(url);
184
+
if (!page.ok) return;
185
+
if (!page.headers.get("Content-Type")?.startsWith("text/html")) return;
186
+
187
+
return await page.text();
188
+
}
189
+
190
+
private async processPage(url: URL) {
191
+
const page = await this.getPage(url);
192
+
if (!page) return;
193
+
await this.addOutlinks(page);
194
+
this.index.addPage(url.toString(), page);
195
+
this.emit("storePage", url);
196
+
}
197
+
198
+
crawl(url_str: string | URL) {
199
+
this.emit("addURL", new URL(url_str));
200
+
}
201
+
}
202
+
203
+
let crawler = new Crawler("SmartFridge", new SearchIndex());
204
+
205
+
const url = new URL("https://example.com");
206
+
crawler.crawl(url);
207
+
crawler.on("storePage", (url) => {
208
+
console.log(`Page stored: ${url}`);
209
+
console.log("entries:", crawler.index.size());
210
+
crawler.emit("stop");
211
+
});
+138
ts/searchEngine/index.test.ts
+138
ts/searchEngine/index.test.ts
···
1
+
import { describe, it, beforeEach, expect } from "bun:test";
2
+
import { SearchIndex } from ".";
3
+
import { Crawler } from "./crawler";
4
+
5
+
describe("Search Index", () => {
6
+
let index: SearchIndex;
7
+
beforeEach(() => {
8
+
index = new SearchIndex();
9
+
});
10
+
11
+
it("should add a new page to the index", () => {
12
+
index.addPage(
13
+
"https://www.example.com",
14
+
"This is a sample webpage about dogs",
15
+
);
16
+
expect(index.getPagesForKeyword("dogs")).toContain(
17
+
"https://www.example.com",
18
+
);
19
+
});
20
+
it("should return an empty list for the keyword", () => {
21
+
expect(index.getPagesForKeyword("pineapple")).toBeEmpty();
22
+
});
23
+
it("should update a page in the index", () => {
24
+
index.addPage(
25
+
"https://www.example.com",
26
+
"This is a sample web page about dogs",
27
+
);
28
+
index.updatePage(
29
+
"https://www.example.com",
30
+
"This is a sample web page about cats",
31
+
);
32
+
expect(index.getPagesForKeyword("dogs")).not.toContain(
33
+
"https://www.example.com",
34
+
);
35
+
expect(index.getPagesForKeyword("cats")).toContain(
36
+
"https://www.example.com",
37
+
);
38
+
});
39
+
it("should remove a page from the index", () => {
40
+
index.addPage(
41
+
"https://www.example.com",
42
+
"This is a sample web page about cats",
43
+
);
44
+
index.removePage("https://www.example.com");
45
+
expect(index.getPagesForKeyword("cats")).not.toContain(
46
+
"https://www.example.com",
47
+
);
48
+
});
49
+
it("should return relevant pages for a keyword", () => {
50
+
index.addPage(
51
+
"https://www.example.com",
52
+
"This is a sample web page about cats",
53
+
);
54
+
expect(index.getPagesForKeyword("cats")).toContain(
55
+
"https://www.example.com",
56
+
);
57
+
});
58
+
it("should return multiple relavent pages that share a keyword", () => {
59
+
index.addPage(
60
+
"https://www.pineapple-world.com",
61
+
"We have lots of pineapples. You've never seen this many pineapples before.",
62
+
);
63
+
index.addPage(
64
+
"https://www.pineapple-is-my-favorite-fruit.com",
65
+
"I love pineapples, it's all I eat. I mean I REALLY LOVE PINEAPPLES",
66
+
);
67
+
68
+
expect(index.getPagesForKeyword("pineapples")).toBeArrayOfSize(2);
69
+
});
70
+
});
71
+
72
+
describe("Search Algorithm", () => {
73
+
let index: SearchIndex;
74
+
beforeEach(() => {
75
+
index = new SearchIndex();
76
+
index.addPage(
77
+
"https://www.beans.com",
78
+
"beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans beans",
79
+
);
80
+
index.addPage("https://www.beans-are-ok.com", "beans are ok I guess");
81
+
index.addPage("https://testsite.com", "beans");
82
+
index.addPage(
83
+
"https://www.example.com/cats",
84
+
"This is a sample web page about cats",
85
+
);
86
+
index.addPage(
87
+
"https://www.example.com/dogs",
88
+
"This is a sample web page about dogs and training",
89
+
);
90
+
index.addPage(
91
+
"https://www.training.com",
92
+
"This is a general training website",
93
+
);
94
+
index.addPage(
95
+
"https://www.pineapple-world.com",
96
+
"We have lots of pineapples. You've never seen this many pineapples before.",
97
+
);
98
+
index.addPage(
99
+
"https://www.pineapple-is-my-favorite-fruit.com",
100
+
"I love pineapples, it's all I eat. I mean I REALLY LOVE PINEAPPLES",
101
+
);
102
+
index.addPage(
103
+
"https://www.example.com/ml",
104
+
"This is a page about machine learning",
105
+
);
106
+
});
107
+
108
+
it("should return relevant pages for a single keyword search", () => {
109
+
const results = index.search("cats");
110
+
expect(results).toContain("https://www.example.com/cats");
111
+
});
112
+
113
+
it("should return relevant pages for a multi keyword search", () => {
114
+
const results = index.search("dogs training");
115
+
expect(results).toContainAllValues([
116
+
"https://www.example.com/dogs",
117
+
"https://www.training.com",
118
+
]);
119
+
expect(results.indexOf("https://www.example.com/dogs")).toBe(0);
120
+
expect(results.indexOf("https://www.training.com")).toBe(1);
121
+
});
122
+
123
+
it("should return relevant pages for a phrase search", () => {
124
+
const results = index.search("machine learning");
125
+
expect(results).toContain("https://www.example.com/ml");
126
+
});
127
+
128
+
it("should rank results properly by relevance", () => {
129
+
const results = index.search("beans");
130
+
expect(results.indexOf("https://www.beans.com")).toBe(0);
131
+
expect(results.indexOf("https://www.beans-are-ok.com")).toBe(1);
132
+
expect(results.indexOf("https://testsite.com")).toBe(2);
133
+
const results2 = index.search("beans beans");
134
+
expect(results2.indexOf("https://www.beans.com")).toBe(0);
135
+
expect(results2.indexOf("https://www.beans-are-ok.com")).toBe(1);
136
+
expect(results.indexOf("https://testsite.com")).toBe(2);
137
+
});
138
+
});
+221
ts/searchEngine/index.ts
+221
ts/searchEngine/index.ts
···
1
+
const articles = ["a", "an", "the", "this"];
2
+
3
+
const prepositions = [
4
+
"in",
5
+
"on",
6
+
"at",
7
+
"to",
8
+
"from",
9
+
"by",
10
+
"with",
11
+
"for",
12
+
"of",
13
+
];
14
+
15
+
const conjunctions = ["and", "or", "but", "yet", "so"];
16
+
17
+
const pronouns = ["i", "me", "he", "she", "it", "we", "they", "you"];
18
+
19
+
const auxiliaryVerbs = [
20
+
"is",
21
+
"are",
22
+
"am",
23
+
"be",
24
+
"been",
25
+
"being",
26
+
"has",
27
+
"have",
28
+
"had",
29
+
"do",
30
+
"does",
31
+
"did",
32
+
];
33
+
34
+
const commonVerbs = ["go", "get", "make", "take", "see", "come", "think"];
35
+
36
+
const adverbs = ["very", "more", "most", "also", "just", "only"];
37
+
38
+
const otherCommon = [
39
+
"not",
40
+
"no",
41
+
"yes",
42
+
"some",
43
+
"any",
44
+
"all",
45
+
"each",
46
+
"every",
47
+
"what",
48
+
"which",
49
+
"who",
50
+
"when",
51
+
"where",
52
+
"why",
53
+
"how",
54
+
];
55
+
56
+
const contractions = [
57
+
"its",
58
+
"youve",
59
+
"youre",
60
+
"weve",
61
+
"were",
62
+
"itd",
63
+
"youd",
64
+
"yall",
65
+
];
66
+
67
+
const stopWords = new Set([
68
+
...articles,
69
+
...prepositions,
70
+
...adverbs,
71
+
...otherCommon,
72
+
...commonVerbs,
73
+
...conjunctions,
74
+
...pronouns,
75
+
...auxiliaryVerbs,
76
+
...contractions,
77
+
]);
78
+
79
+
export class SearchIndex {
80
+
private index: Map<string, [string, number][]>;
81
+
82
+
constructor() {
83
+
this.index = new Map<string, [string, number][]>();
84
+
}
85
+
86
+
private getPhrases(
87
+
words: string[],
88
+
filter: boolean[],
89
+
): Map<string, number> {
90
+
const wordGroups: string[][] = [];
91
+
let currentSlice: string[] = [];
92
+
for (const [index, val] of filter.entries()) {
93
+
if (val) {
94
+
currentSlice.push(words[index]!);
95
+
continue;
96
+
}
97
+
if (currentSlice.length > 1) wordGroups.push(currentSlice);
98
+
currentSlice = [];
99
+
}
100
+
const subPhrases: string[] = wordGroups.flatMap((group) =>
101
+
this.getSubPhrases(group),
102
+
);
103
+
const subPhraseDict = new Map<string, number>();
104
+
for (const sp of subPhrases) {
105
+
subPhraseDict.set(sp, (subPhraseDict.get(sp) || 0) + 1);
106
+
}
107
+
return subPhraseDict;
108
+
}
109
+
110
+
private getSubPhrases(phrase: string[]): string[] {
111
+
const subPhrases: string[] = [phrase.join(" ")];
112
+
for (let i = 2; i < phrase.length; i++) {
113
+
for (let offset = 0; offset + i < phrase.length + 1; offset++) {
114
+
const subPhrase = phrase.slice(offset, offset + i).join(" ");
115
+
subPhrases.push(subPhrase);
116
+
}
117
+
}
118
+
return subPhrases;
119
+
}
120
+
121
+
private extractKeywords(pageContent: string): Map<string, number> {
122
+
let words: string[] = pageContent
123
+
.split(/\s+/)
124
+
.map((str) => str.replaceAll(/[^\w]+/g, "").toLowerCase())
125
+
.filter((str) => str.length > 0);
126
+
words = [...words, "a"];
127
+
const filter = words.map((word) => !stopWords.has(word));
128
+
const keywords = new Set<string>(words).difference(stopWords);
129
+
const keywordMap = new Map<string, number>(
130
+
keywords.values().map((kw) => [kw, 0]),
131
+
);
132
+
133
+
for (let word of words) {
134
+
if (keywords.has(word)) {
135
+
keywordMap.set(word, (keywordMap.get(word) || 0) + 1);
136
+
}
137
+
}
138
+
const phrases = this.getPhrases(words, filter);
139
+
return new Map([...keywordMap, ...phrases]);
140
+
}
141
+
142
+
addPage(url: string, pageContent: string): void {
143
+
let keywords = this.extractKeywords(pageContent);
144
+
for (let [kw, count] of keywords.entries()) {
145
+
if (this.index.has(kw)) {
146
+
let prev = this.index.get(kw)!;
147
+
prev.push([url, count]);
148
+
this.index.set(kw, prev);
149
+
} else {
150
+
this.index.set(kw, [[url, count]]);
151
+
}
152
+
}
153
+
}
154
+
155
+
updatePage(url: string, pageContent: string): void {
156
+
this.removePage(url);
157
+
this.addPage(url, pageContent);
158
+
}
159
+
160
+
removePage(url: string): void {
161
+
this.index.entries().forEach(([keyword, urls]) => {
162
+
const index = urls.findIndex(([u, _]) => u === url);
163
+
if (index >= 0) {
164
+
urls.splice(index, 1);
165
+
if (urls.length === 0) {
166
+
this.index.delete(keyword);
167
+
}
168
+
}
169
+
});
170
+
}
171
+
172
+
checkPage(search: string): boolean {
173
+
for (const urls of this.index.values()) {
174
+
for (const [url, _] of urls) {
175
+
if (search === url) {
176
+
return true;
177
+
}
178
+
}
179
+
}
180
+
return false;
181
+
}
182
+
183
+
size() {
184
+
return this.index.size;
185
+
}
186
+
187
+
getPagesForKeyword(keyword: string): string[] {
188
+
const pages = this.index.get(keyword);
189
+
if (!pages) {
190
+
return [];
191
+
}
192
+
return Array.from(pages)
193
+
.sort((a, b) => a[1] - b[1])
194
+
.map(([url, _]) => url);
195
+
}
196
+
197
+
search(query: string): string[] {
198
+
const urls = new Map<string, number>();
199
+
const keys = this.extractKeywords(query);
200
+
for (const [key, count] of keys.entries()) {
201
+
const pages = this.index.get(key);
202
+
if (!pages) continue;
203
+
for (const [url, v] of pages) {
204
+
urls.set(
205
+
url,
206
+
urls.has(url)
207
+
? (urls.get(url) || 0) + v * count
208
+
: v * count,
209
+
);
210
+
}
211
+
}
212
+
urls.forEach((value, key) => {
213
+
if (key.includes(query)) {
214
+
value += 10;
215
+
}
216
+
});
217
+
return Array.from(urls.entries())
218
+
.sort((a, b) => b[1] - a[1])
219
+
.map(([url, _]) => url);
220
+
}
221
+
}
ts/searchEngine/mainLoop.plan
ts/searchEngine/mainLoop.plan
This is a binary file and will not be displayed.
+29
ts/tsconfig.json
+29
ts/tsconfig.json
···
1
+
{
2
+
"compilerOptions": {
3
+
// Environment setup & latest features
4
+
"lib": ["ESNext"],
5
+
"target": "ESNext",
6
+
"module": "Preserve",
7
+
"moduleDetection": "force",
8
+
"jsx": "react-jsx",
9
+
"allowJs": true,
10
+
11
+
// Bundler mode
12
+
"moduleResolution": "bundler",
13
+
"allowImportingTsExtensions": true,
14
+
"verbatimModuleSyntax": true,
15
+
"noEmit": true,
16
+
17
+
// Best practices
18
+
"strict": true,
19
+
"skipLibCheck": true,
20
+
"noFallthroughCasesInSwitch": true,
21
+
"noUncheckedIndexedAccess": true,
22
+
"noImplicitOverride": true,
23
+
24
+
// Some stricter flags (disabled by default)
25
+
"noUnusedLocals": false,
26
+
"noUnusedParameters": false,
27
+
"noPropertyAccessFromIndexSignature": false
28
+
}
29
+
}