Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions src/problem1/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Problem 1 — `sum_to_n`

Three implementations of `sum_to_n(n)` that return `1 + 2 + ... + n`.

## Requirements

- [Node.js](https://nodejs.org/) (any recent LTS version — no dependencies required)

## Run

From the repository root:

```bash
node src/problem1/index.js
```

You should see:

```
sum_to_n_a(5) = 15
sum_to_n_b(100) = 5050
sum_to_n_c(10) = 55
```

## Try it yourself

Open a Node REPL and import the functions:

```bash
node
```

```js
const { sum_to_n_a, sum_to_n_b, sum_to_n_c } = require('./src/problem1/index.js');

sum_to_n_a(5); // 15 — iterative for-loop
sum_to_n_b(100); // 5050 — Gauss closed-form formula
sum_to_n_c(10); // 55 — recursion
```

## Implementations

| Function | Approach | Time | Space | Notes |
| -------------- | --------------------------------- | ---- | ----- | ---------------------------------------------- |
| `sum_to_n_a` | Iterative `for` loop | O(n) | O(1) | Simple and predictable. |
| `sum_to_n_b` | Closed-form formula `n*(n+1)/2` | O(1) | O(1) | Fastest; no iteration. |
| `sum_to_n_c` | Recursion `n + sum_to_n_c(n - 1)` | O(n) | O(n) | Elegant, but risks stack overflow for large n. |

## Assumptions

- `n` is a positive integer (`n > 0`).
- The result is always less than `Number.MAX_SAFE_INTEGER`.
36 changes: 36 additions & 0 deletions src/problem1/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/**
* Three implementations of sum_to_n(n) that return 1 + 2 + ... + n.
*
* Tradeoffs:
* - sum_to_n_a (loop): simple and predictable, O(n) time / O(1) space.
* - sum_to_n_b (formula): fastest, O(1) time / O(1) space, no iteration.
* - sum_to_n_c (recursion): elegant but risks stack overflow for very large n,
* O(n) time / O(n) space due to the call stack.
*/

// Iterative for-loop accumulation. Time: O(n), Space: O(1).
function sum_to_n_a(n) {
let total = 0;
for (let i = 1; i <= n; i++) total += i;
return total;
}

// Closed-form Gauss formula n*(n+1)/2. Time: O(1), Space: O(1).
function sum_to_n_b(n) {
return (n * (n + 1)) / 2;
}

// Recursive definition sum(n) = n + sum(n-1). Time: O(n), Space: O(n) call stack.
function sum_to_n_c(n) {
if (n <= 1) return n;
return n + sum_to_n_c(n - 1);
}

module.exports = { sum_to_n_a, sum_to_n_b, sum_to_n_c };

// Tiny self-test, only runs when this file is executed directly via `node`.
if (require.main === module) {
console.log('sum_to_n_a(5) =', sum_to_n_a(5)); // expect 15
console.log('sum_to_n_b(100) =', sum_to_n_b(100)); // expect 5050
console.log('sum_to_n_c(10) =', sum_to_n_c(10)); // expect 55
}
24 changes: 24 additions & 0 deletions src/problem2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
7 changes: 7 additions & 0 deletions src/problem2/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules/
coverage/
.pnpm-store/
pnpm-lock.yaml
package-lock.json
pnpm-lock.yaml
yarn.lock
11 changes: 11 additions & 0 deletions src/problem2/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"endOfLine": "lf",
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80,
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindStylesheet": "src/index.css",
"tailwindFunctions": ["cn", "cva"]
}
85 changes: 85 additions & 0 deletions src/problem2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Switch Swap

A small currency-swap form that lets you trade between any two tokens listed in
Switcheo's public price feed. The "to" amount is computed live from the
`priceFrom / priceTo` ratio, and submitting simulates a backend swap with a
loading state and a success summary.

## Screenshots

| Form | Submitting | Complete |
| :--: | :--------: | :------: |
| ![Swap form](./image-swap-form.png) | ![Swapping](./image-swapping.png) | ![Swap completed](./image-swap-completed.png) |

## Tech stack

- [Vite](https://vitejs.dev/) + React 19 + TypeScript
- [Tailwind CSS v4](https://tailwindcss.com/) (with `tw-animate-css`)
- [shadcn/ui](https://ui.shadcn.com/) primitives (`Button`, theme provider)
- [Radix UI](https://www.radix-ui.com/) (`Popover` for the token picker)
- [Hugeicons](https://hugeicons.com/) for iconography
- Inter Variable via `@fontsource-variable/inter`

## Getting started

```bash
cd src/problem2
npm install
npm run dev
```

The dev server prints the local URL (defaults to <http://localhost:5173>; Vite
picks the next free port if it's already taken).

## Scripts

| Script | Purpose |
| ------------------- | ---------------------------------- |
| `npm run dev` | Start the Vite dev server |
| `npm run build` | Type-check (`tsc -b`) and build |
| `npm run preview` | Serve the production build locally |
| `npm run lint` | Run ESLint |
| `npm run typecheck` | Run TypeScript only (no emit) |
| `npm run format` | Format with Prettier |

## Features

- Searchable token combobox with keyboard navigation (up/down/enter)
- Auto-computed "you receive" amount from live prices
- Direction-swap button with subtle rotate animation
- Inline validation with specific messages for empty / zero / negative /
non-numeric / same-token cases. Invalid keystrokes (letters, multiple dots,
minus signs, scientific notation) are filtered out before reaching state.
- Loading skeleton while prices fetch, retry panel on failure
- Simulated submit with spinner and success summary
- Light/dark theme (press `d` to toggle)
- Responsive down to **320px** viewport width: card padding, font sizes, and
the token-picker popover all collapse to fit small screens without
horizontal scroll

## Code layout

```
src/
components/swap/
SwapForm.tsx # Presentational form, ~130 lines
SwapStatusPanels.tsx # Skeleton / Error / Success panels
TokenAmountField.tsx # Single amount input + token select
TokenSelect.tsx # Searchable combobox built on Radix Popover
TokenIcon.tsx # SVG icon with initials fallback
hooks/
useSwapForm.ts # All form state, derivation, and actions
useTokens.ts # Async fetch + reload via reducer
lib/
format.ts # Number formatting + permissive parsing
validation.ts # Pure amount validator + onChange sanitiser
prices.ts # Live Switcheo price endpoint adapter
```

## Data sources

- Prices: <https://interview.switcheo.com/prices.json>
(deduplicated by symbol, keeping the most recent valid quote)
- Token icons:
`https://raw.githubusercontent.com/Switcheo/token-icons/main/tokens/<SYMBOL>.svg`
with a graceful initial-letters fallback when a symbol has no icon.
25 changes: 25 additions & 0 deletions src/problem2/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "radix-mira",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "hugeicons",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}
23 changes: 23 additions & 0 deletions src/problem2/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
Binary file added src/problem2/image-swap-completed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/problem2/image-swap-form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/problem2/image-swapping.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 16 additions & 26 deletions src/problem2/index.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,17 @@
<html>

<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fancy Form</title>

<!-- You may add more stuff here -->
<link href="style.css" rel="stylesheet" />
</head>

<body>

<!-- You may reorganise the whole HTML, as long as your form achieves the same effect. -->
<form onsubmit="return !1">
<h5>Swap</h5>
<label for="input-amount">Amount to send</label>
<input id="input-amount" />

<label for="output-amount">Amount to receive</label>
<input id="output-amount" />

<button>CONFIRM SWAP</button>
</form>
<script src="script.js"></script>
</body>

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta
name="description"
content="Currency swap form powered by Switcheo's price feed."
/>
<title>Switch Swap — Currency Swap</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading