Skip to content

Commit

Permalink
implemented more testing and compiled finished program
Browse files Browse the repository at this point in the history
  • Loading branch information
kenny101 committed Mar 29, 2024
1 parent 0b238c9 commit 2c22db3
Show file tree
Hide file tree
Showing 8 changed files with 378 additions and 131 deletions.
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,54 @@
# ecl-take-home
# ECL-Take-Home

**Note:** This take-home uses the [Bun.sh](Bun.sh) runtime rather than NodeJS. Some benefits of this include:

- Directly running TypeScript without transpiler step
- Easier end-to-end command line testing by running bash commands in TypeScript
- Compiling an executable allowing users to run programs without installing the Bun runtime
- And much more!

## How do I use this?

```bash
# With Bun.sh (Requires installing the Bun runtime)
bun i # installs the node dependencies
bun run cli.ts <pathname> <N highest scores>

# Running with compiled executable:
./highest ./data/basic.data 2
```

## Running tests (requires Bun installed)

See `/tests` folder for comprehensive unit and end-to-end testing. All tests can be run with the command:

```bash
bun test
```

## Building the executable (optional)

A tested executable for MacOS, Linux and Windows have been added. `(./highest and ./highest.exe).` If for some reason the executable does not run on your OS, Bun can build the executable for only the host operating system with the command: For additional documentation see [here.](https://bun.sh/docs/bundler/executables)

```bash
bun build ./cli.ts --compile --outfile highest
```

## Folder Structure

- `data`: .data files for testing and `gen.js`
- `highest.exe`: Program executable for running on Windows
- `highest`: Program executable for running on MacOS / Linux
- `tests`: Folder containing unit tests
- `endToEnd.test.ts`: Runs the program as a bash command for end-to-end testing
- `sort.test.ts`: Unit testing for sort functions
- `assignment-instructions`: Given documentation for assignment
- `cli.ts`: Main program for the command line program
- `utils.ts`: Utility functions and types for implementation and testing

## Optimizations and Performance

Rather than the typical approach of sorting an entire list with the built in sorting functions (quicksort / mergesort), this program uses the heap data structure to optimize the sorting time complexity. This reduces the overall time complexity from O(N log N) to O(N log K) where K is the size of the output and where N is the number of records.

- Space Complexity: O(N) where N is the number of records
- Time Complexity: O(N log K) where N is the number of records and K is the size of the output
Binary file modified bun.lockb
100644 → 100755
Binary file not shown.
29 changes: 16 additions & 13 deletions cli.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import { parseArgs } from "util";
import { readFileLineByLine, type Output, sortOutputsByScoreDescending } from "./utils";
import {
readFileLineByLine,
type Output,
sortOutputsByScoreDescending,
} from "./utils";

const { positionals } = parseArgs({
args: process.argv.slice(2),
options: {
flag1: {
type: 'boolean',
type: "boolean",
},
flag2: {
type: 'string',
type: "string",
},
},
strict: true,
Expand All @@ -18,27 +22,26 @@ const { positionals } = parseArgs({
// Extract the pathname and K from command-line arguments
const [filePath, K] = positionals;

// console.log("File Path:", filePath);
// console.log("K:", K);
if (!filePath || !K) {
console.error("Usage: ./highest <file_path> <N highest scores>");
process.exit(1);
}

const file = Bun.file(filePath);
const fileExists = await file.exists();

if (!fileExists) {
console.error("Invalid path to file.");
process.exit(1);
}
if (!filePath || !K) {
console.error("Usage: ./highest <file_path> <N highest scores>");
process.exit(1);
}

// O(N) Space Complexity
const output: Output[] = await readFileLineByLine(filePath);

// N log K Time Complexity
const sortedOutput: Output[] = sortOutputsByScoreDescending(output, parseInt(K, 10));
const sortedOutput: Output[] = sortOutputsByScoreDescending(
output,
parseInt(K, 10)
);

console.log(sortedOutput);
console.log(JSON.stringify(sortedOutput));
process.exit(0);

10 changes: 10 additions & 0 deletions data/valid_but_has_newline.data
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
3830591998918656: {"id":"2208ef95-355c-53a6-96bc-206a4831f2fe","data":"Tu pidneg zamucaj favunew cozihje likheuk sorova nu fek izu uwjibu ukcoro de sa cil tetgisnes fu. Betoguze ajzade to et miabga men ivaholhiz mahojnew gote hugdowij luk kihtuhmin kivsimef cuur sav. Tahmihaz esapvih hivgig fuizutun naje becibut hufdosde keztafic zevpuwan vees teffa fob. To apifovo tuzonam zih hedu zepazca nav rolom af hi dewje ba gabad aloboemo ivucekoz sinih ud fitsacuf. Fahgavut zerlan pom bar turuv uko ti refa giboub kepejeg acu gofvezcas cuupfal no tiruduhew."}
548113328635904: {"id":"d5887987-bf5d-5813-b524-722ffff11882","data":"Vubitaira sero dihraize lifo ne menaw padirasu ojatum lobapu vowi opfirip revisa pa leg imsip vawilfi rije farpuge. Amujasib bejajmab tuhzuddum egusezo mulop zezanupec wahelwu kelahdod viuwvo seceki me asfizbir. Geros fih viwmi lunashok woh zilfos senzoz me icagu pagojos pinba vador ovuteswa ga cet efijef riwkicga dabe. Avraj semeil tutsu coca osa mumut owri fuffem gezis bokoh nebeniapu izabac ivi lub fersasugo baj biegilo se. Zahwe hahosa ho fi vo teh vimini adilu ozruop powo hone na dabupkof tetnut."}
4322085113430016: {"id":"8f5330b9-0279-5a67-aee3-d6acf221a53a","data":"Losu nihpe upaveit ci vaslugoz zus di el pe alimosepa zolev kaj. Ojo acesijme go redit rulseise levfeta kidofap cigguvkal lenibe se voteiv sonehemo. Sopumveg fej pafuke loral di gonbuh wat adimazaf zur rugodiipi gahoru nafuka ezu feilkuv sagizo ujiurona vuzde gaswa. Gagoru weicava osme jo ot wo ucbej honjufuw egasaoba sahfo puv hifreuni magajmas. Cate mit huganij ace onetu gaim ulaaze ujjus kug pa upugenweh sogezo jamsak wadipse teblujnis."}
6348702421614592: {"id":"1fef0dbc-c75b-5959-835f-80e5f15b6da1","data":"Riliv kaliku laiza zen daze egeni gu tesak iw sog ekefowal atimoon em mehujemo. Sivlal afhorti rej ofomuror ragwubsi pehwepri vuspa citenaaku behop per utlo mepaneg bodcer sa habohgus genawma. Bu guk kab kivsalom tag awozewid zabubfu dap ga ni moeta najga suwi tongibwat pudoptu ne. Fikobef zunar jaloni ademaj bunviduz uw uzsa laz samnudzec oh ja faowajur bikbomu inoulga ujuon. Kuwnuvac figuim saoblet huhez ipulev fulueti nibzahov jor izisbos zufod pewhu deknaevi tik."}
7667194055884800: {"id":"32483d86-83cb-5778-811c-156c6854b9c0","data":"Ubovomul tu wucduwpa seveoh hu fuga eke sefek ova fogiv da jodzu todlihno. Abaufufol wavauf nu gudvudsew ilbos mobavhu imiza vef es we emofeb powraju unofuvoz zicmesij cemano ozo. Ajo hocoidu kan tidtog ulhow ivazav unojupem gu kakhalo ezcavzo hi sacgaen fese za. Tin ciwej lunef paccamag olfi ejozeemi zugisu wuscivok vud lulop zif ce asufih iwosuvbek hajzumev zupji ledzasoc. Inobadehi saih gisogim ta elijisej zeminiuci bufin badkifocu ba leere mum binkokop ceb."}
4564320390217728: {"id":"d3ab1bb3-3ea2-569b-a464-a9369486e02e","data":"Pejo zan pebu terop ka lil hod se bus fe ejki omukom heri. Civuhket mudatevuj la nawit guteak vasu olojip doloc lifde awzulsam upuahuro poste bah. We mog juvgep ki capdal hav fetfi feke zovizku sor haphicib bof pi pubug rigzi mogo. Bo jauca bauva rowwij ledniw da ki nijrahto dit za jaari lizuz udfilkov wu tijilu. Ref we oredizor cacmazja itimitu kov lunututo riakwuv noma ete pununni na up so vervac ebukir kivoj."}

8864076170002432: {"id":"a39307d3-4826-54d5-8e1b-5b7d8994a716","data":"Opduebe no tiz gahnup misa lecoga iliviw bow ra be vodihza gesra eb mujma. De relafhac ji an upuhe sin put ibji bazabi efaozcit ifaka cipow dig vahmahev sigte. Vij itihejic kokra sugha kefes oduwiila babtes kiocen gi rik bi jaw nivul bel tu vusuh jojew piavwug. Vito zowdoso nuta wedjatgas wila jaokkur vit uwe tulda aceri il kafic hoktumo fidu immipo laf. Kizibinu wet dernid neuhkac noapiwe odainuic katnej bikdovbi hihiviz cipa zav di nouzade wifirup rujdod wirledca."}
2310595193864192: {"id":"637e63c6-c4c7-5227-9b52-35611ed8a511","data":"Uzu cezkacag tej cefduc asic nuelu zugle ce wehtokcej wejhe idkol fi segwoh vop muno ciw nop. Ni powkal sodnocup favor lop nejjof ugaelurol sa pum eneve jicze esalo ap poc ivev bam ta. Vuplis huzimudu sotejiru runog figfi zasor getucez saclagmo laronu wa bam fijassus zonfaruz ekoudo totgevkan ru joruin tawbi. Sizfus azgolwo te avhokut ezawu kafi kecoba aninokced zow mevsigud owotojafo dubcam afu kibow kahta riharo zuldodapo ovjinu. Lof jazogav kuvebu itse cursac zezhila hihfucnus echizus sulegu lum orewobec geor uhebi usaubeki faih metpu me."}
1440883412238336: {"id":"47cc8f91-8df6-5906-b092-0e95fc7fa4ff","data":"Ro mun juobu owpapos le himivmu pargiwli dusho nupipcu vupfujli rifitcih kazcecuce lumo ir. Gat bidsuzko odanemki lohipav bop aj besu humfakimu garilawu zodopti ni vefepjom fub gole tig. Fo bahusozu ozu nerzezsam geri pul kamugun zurebo bu hi didun laic zug toj suvse cajwuw nerzoawu hofuvan. Lifhi pecegipi deh soip ejar zuegjof cagom avgo cun ebivolfe rufi te gubzu poh huzasne icigetvi. Puhjefiw idcip bi wivewa hal ducwer biv kegogaj ikzomuke hil beupu ug reven li nukif."}
Binary file added highest
Binary file not shown.
2 changes: 1 addition & 1 deletion test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { $ } from "bun";

// Function to run the CLI command and return the output
async function runCli(command: string) {
await $`bun run ../cli.ts invalid/path 3`;
await $`bun run cli.ts invalid/path 3`;
}

await runCli('')
178 changes: 164 additions & 14 deletions tests/endToEnd.test.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,170 @@
import { expect, test, describe } from "bun:test";
import { $ } from "bun";
import { $, type ShellOutput } from "bun";

// Function to run the CLI command and return the output
async function runCli(command: string) {
await $`bun run ../cli.ts invalid/path 3`;
}
// Function to run the CLI command and return the shell output (Only works on MacOS / Linux)
// async function runCli(args: string): Promise<ShellOutput> {
// const command = `bun run cli.ts ${args}`
// console.log("ran:", command)
// const result: ShellOutput = await $`${command}`;
// return result;
// }

describe('CLI Tests', async () => {
test('Should display error message for invalid file path', () => {
// const output = await runCli('invalid/path 3');
// expect(output).toContain('Invalid path to file.');
});
describe("CLI Tests", () => {
test("Should display error message for invalid file path and exit code 1", async () => {
const output: ShellOutput = await $`bun run cli.ts invalid/path 3`;
expect(output.stderr.toString()).toBe("Invalid path to file.\n");
expect(output.exitCode).toBe(1);
});

test('Should display usage message for missing arguments', () => {
const output = runCli('');
expect(output).toContain('Usage: ./highest <file_path> <N highest scores>');
});
test("Invalid args should display usage message for missing arguments and exit with code 1", async () => {
const output: ShellOutput = await $`bun run cli.ts ./data/basic.data`;
expect(output.stderr.toString()).toBe(
"Usage: ./highest <file_path> <N highest scores>\n"
);
expect(output.exitCode).toBe(1);
});

test("Invalid JSON in data file should display JSON error message and exit with code 2", async () => {
const output: ShellOutput = await $`bun run cli.ts ./data/bad_json.data 3`;

expect(output.stderr.toString()).toBe(
"Error Parsing JSON: At least one value contains invalid JSON\n"
);
expect(output.exitCode).toBe(2);
});

test("Missing colon in data file should display error message and exit with code 2", async () => {
const output: ShellOutput =
await $`bun run cli.ts ./data/missing_colon.data 3`;
expect(output.stderr.toString()).toBe(
"Invalid data format: missing colon separator\n"
);
expect(output.exitCode).toBe(2);
});

test("Correctly parses basic input file and exit with code 0", async () => {
const output: ShellOutput = await $`bun run cli.ts ./data/basic.data 3`;
const expectedSortedOutput = [
{
score: "8864076170002432",
id: "a39307d3-4826-54d5-8e1b-5b7d8994a716",
},
{
score: "7667194055884800",
id: "32483d86-83cb-5778-811c-156c6854b9c0",
},
{
score: "6348702421614592",
id: "1fef0dbc-c75b-5959-835f-80e5f15b6da1",
},
];

expect(output.stderr.toString()).toBe("");
expect(output.stdout.toString()).toBe(
JSON.stringify(expectedSortedOutput) + "\n"
);
expect(output.exitCode).toBe(0);
});

test("Correctly parses input file with new line and exit with code 0", async () => {
const output: ShellOutput =
await $`bun run cli.ts ./data/valid_but_has_newline.data 3`;
const expectedSortedOutput = [
{
score: "8864076170002432",
id: "a39307d3-4826-54d5-8e1b-5b7d8994a716",
},
{
score: "7667194055884800",
id: "32483d86-83cb-5778-811c-156c6854b9c0",
},
{
score: "6348702421614592",
id: "1fef0dbc-c75b-5959-835f-80e5f15b6da1",
},
];

expect(output.stderr.toString()).toBe("");
expect(output.stdout.toString()).toBe(
JSON.stringify(expectedSortedOutput) + "\n"
);
expect(output.exitCode).toBe(0);
});

test("Correctly parses example_input_data_1.data input file and exit with code 0", async () => {
const output: ShellOutput =
await $`bun run cli.ts ./data/example_input_data_1.data 3`;

const expectedSortedOutput = [
{
score: "8872929053900800",
id: "e956fcad-f815-5d0e-bc30-50e64792ab99",
},
{
score: "8864076170002432",
id: "a39307d3-4826-54d5-8e1b-5b7d8994a716",
},
{
score: "8747967389368320",
id: "4ad170bf-252c-5c1c-a563-31ddb0770910",
},
];

expect(output.stderr.toString()).toBe("");
expect(output.stdout.toString()).toBe(
JSON.stringify(expectedSortedOutput) + "\n"
);
expect(output.exitCode).toBe(0);
});

test("Correctly parses example_input_data_2.data input file and exit with code 0", async () => {
const output: ShellOutput =
await $`bun run cli.ts ./data/example_input_data_2.data 3`;

const expectedSortedOutput = [
{
score: "9005026213101568",
id: "a6546eba-9ee4-572c-898a-5dc8fb595502",
},
{
score: "9003802642350080",
id: "58ded152-9a9e-5f98-a17b-d862043b513f",
},
{
score: "9000580766760960",
id: "2afece21-09ff-5a4b-aa6a-5e59698be2e0",
},
];
expect(output.stderr.toString()).toBe("");
expect(output.stdout.toString()).toBe(
JSON.stringify(expectedSortedOutput) + "\n"
);
expect(output.exitCode).toBe(0);
});

test("Correctly parses example_input_data_3.data input file and exit with code 0", async () => {
const output: ShellOutput =
await $`bun run cli.ts ./data/example_input_data_3.data 3`;

const expectedSortedOutput = [
{
score: "9007182007762944",
id: "d1efcfc6-9298-5856-bece-ed4242c36b5d",
},
{
score: "9007125380464640",
id: "88e23d99-64d7-5939-8a21-e68a637a0566",
},
{
score: "9006994092457984",
id: "45ad2cf6-9673-523f-a20e-3c31df0e488a",
},
];

expect(output.stderr.toString()).toBe("");
expect(output.stdout.toString()).toBe(
JSON.stringify(expectedSortedOutput) + "\n"
);
expect(output.exitCode).toBe(0);
});
});
Loading

0 comments on commit 2c22db3

Please sign in to comment.