Skip to content

Instantly share code, notes, and snippets.

@rmorey
Created August 20, 2025 02:24
Show Gist options
  • Select an option

  • Save rmorey/bf74ca69ec6555a52d10a515b6b434c0 to your computer and use it in GitHub Desktop.

Select an option

Save rmorey/bf74ca69ec6555a52d10a515b6b434c0 to your computer and use it in GitHub Desktop.
5T's Pressed Pipe Counter
import React, { useMemo, useState } from "react";
/**
* Pressed Pipe Counter
* --------------------
* A minimal, production-ready React app that counts "pressed" pipes in a string.
* A pipe '|' is pressed iff its immediate left neighbor is ')' and its immediate right
* neighbor is '('. Equivalently, count occurrences of the substring ")|(".
*
* - Live validation for allowed characters: only '(', ')' and '|'.
* - Large, monospace input with instant results.
* - Visualizer highlights pressed pipes.
* - Sample cases and a random generator for quick testing.
* - Pure React + Tailwind (no external component libs) for maximal portability.
*/
// Utility: validate characters and return indices of invalid ones
function findInvalidPositions(s: string): number[] {
const bad: number[] = [];
for (let i = 0; i < s.length; i++) {
const ch = s[i];
if (ch !== '(' && ch !== ')' && ch !== '|') bad.push(i);
}
return bad;
}
// Utility: count pressed pipes via regex (counts ")|("
function countPressedRegex(s: string): number {
const m = s.match(/\)\|\(/g);
return m ? m.length : 0;
}
// Utility: also return the exact indices of '|' characters that are pressed
function pressedPipeIndices(s: string): number[] {
const out: number[] = [];
for (let i = 1; i < s.length - 1; i++) {
if (s[i] === '|' && s[i - 1] === ')' && s[i + 1] === '(') out.push(i);
}
return out;
}
// Generate a random valid test string
function randomString(len: number): string {
const alphabet = ['(', ')', '|'];
let res = "";
for (let i = 0; i < len; i++) {
res += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return res;
}
// Some curated sample strings
const SAMPLES: { label: string; s: string }[] = [
{ label: "Single pressed", s: ")|(" },
{ label: "None pressed", s: "()|()" },
{ label: "Two pressed", s: ")|()|(" },
{ label: "No presses (double pipe)", s: "((||))" },
{ label: "Mixed", s: "))(|)(()|(" },
];
export default function PressedPipesApp() {
const [input, setInput] = useState<string>(")|()|((|())|(()|()|((" );
const [randLen, setRandLen] = useState<number>(24);
const invalidPositions = useMemo(() => findInvalidPositions(input), [input]);
const pressedIndices = useMemo(() => pressedPipeIndices(input), [input]);
const count = useMemo(() => countPressedRegex(input), [input]);
const hasInvalid = invalidPositions.length > 0;
const copyCount = async () => {
try {
await navigator.clipboard.writeText(String(count));
alert("Copied result to clipboard.");
} catch (e) {
console.error(e);
alert("Couldn't copy. You can copy manually: " + count);
}
};
const copyPressedIndices = async () => {
try {
await navigator.clipboard.writeText(JSON.stringify(pressedIndices));
alert("Copied pressed indices to clipboard.");
} catch (e) {
console.error(e);
alert("Couldn't copy. You can copy manually: " + JSON.stringify(pressedIndices));
}
};
return (
<div className="min-h-screen bg-zinc-50 text-zinc-900">
<div className="max-w-4xl mx-auto px-4 py-10">
<header className="mb-8">
<h1 className="text-3xl sm:text-4xl font-bold tracking-tight">Pressed Pipe Counter</h1>
<p className="mt-2 text-zinc-600">
Enter a string using only <code className="px-1 bg-zinc-100 rounded">(</code>,
<code className="px-1 bg-zinc-100 rounded">)</code>, and <code className="px-1 bg-zinc-100 rounded">|</code>.
A pipe <code className="px-1 bg-zinc-100 rounded">|</code> is <em>pressed</em> iff its left neighbor is
<code className="px-1 bg-zinc-100 rounded">)</code> and right neighbor is <code className="px-1 bg-zinc-100 rounded">(</code>.
</p>
</header>
{/* Controls */}
<div className="grid gap-4 sm:grid-cols-[1fr_auto] items-start">
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder={"Type here… (only '(', ')', '|')"}
className={
"w-full h-40 sm:h-48 p-4 rounded-2xl border outline-none font-mono text-base leading-6 " +
(hasInvalid ? "border-red-400 focus:ring-2 ring-red-300" : "border-zinc-300 focus:ring-2 ring-blue-300")
}
/>
<div className="flex sm:flex-col gap-3 sm:gap-2">
<button
onClick={() => setInput("")}
className="px-4 py-2 rounded-xl border border-zinc-300 bg-white hover:bg-zinc-50 shadow-sm"
>
Clear
</button>
<button
onClick={() => setInput(randomString(randLen))}
className="px-4 py-2 rounded-xl border border-zinc-300 bg-white hover:bg-zinc-50 shadow-sm"
title="Generate a random valid string"
>
Random
</button>
<button
onClick={copyCount}
className="px-4 py-2 rounded-xl border border-zinc-300 bg-white hover:bg-zinc-50 shadow-sm"
>
Copy Count
</button>
</div>
</div>
{/* Random length slider */}
<div className="mt-3 flex items-center gap-3">
<label htmlFor="len" className="text-sm text-zinc-600">Random length:</label>
<input
id="len"
type="range"
min={3}
max={200}
value={randLen}
onChange={(e) => setRandLen(parseInt(e.target.value, 10))}
className="w-56"
/>
<span className="text-sm tabular-nums">{randLen}</span>
</div>
{/* Result cards */}
<section className="mt-6 grid gap-4 sm:grid-cols-3">
<div className="p-4 rounded-2xl bg-white border border-zinc-200 shadow-sm">
<div className="text-sm text-zinc-500">Pressed pipes</div>
<div className="mt-1 text-3xl font-semibold tabular-nums">{count}</div>
<div className="mt-3 text-xs text-zinc-500">O(n) time · O(1) space</div>
</div>
<div className={"p-4 rounded-2xl border shadow-sm " + (hasInvalid ? "bg-red-50 border-red-200" : "bg-white border-zinc-200") }>
<div className={"text-sm " + (hasInvalid ? "text-red-600" : "text-zinc-500")}>Validation</div>
{hasInvalid ? (
<div className="mt-1 text-sm text-red-700">
{invalidPositions.length} invalid {invalidPositions.length === 1 ? 'character' : 'characters'} at indices {" "}
<code className="bg-white px-1 rounded">[{invalidPositions.join(', ')}]</code>
</div>
) : (
<div className="mt-1 text-sm text-emerald-700">All characters valid ✓</div>
)}
</div>
<div className="p-4 rounded-2xl bg-white border border-zinc-200 shadow-sm">
<div className="text-sm text-zinc-500">Pressed indices</div>
<div className="mt-1 text-sm">
<code className="bg-zinc-50 px-2 py-1 rounded">[{pressedIndices.join(", ")}]</code>
</div>
<div className="mt-2">
<button onClick={copyPressedIndices} className="text-sm underline text-blue-600 hover:text-blue-700">
Copy indices
</button>
</div>
</div>
</section>
{/* Visualizer */}
<section className="mt-8">
<h2 className="text-lg font-semibold mb-2">Visualizer</h2>
<div className="rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm overflow-x-auto">
<div className="font-mono text-base leading-6 whitespace-pre overflow-x-auto">
{input.length === 0 ? (
<span className="text-zinc-400">(your string will render here)</span>
) : (
input.split("").map((ch, i) => {
const isPressed = i > 0 && i < input.length - 1 && ch === '|' && input[i - 1] === ')' && input[i + 1] === '(';
const invalid = ch !== '(' && ch !== ')' && ch !== '|';
return (
<span
key={i}
className={
(invalid ? "bg-red-200 text-red-900" : isPressed ? "bg-amber-200 text-amber-900" : "") +
" rounded px-0.5"
}
title={invalid ? `Invalid character at ${i}` : isPressed ? `Pressed '|' at ${i}` : undefined}
>
{ch}
</span>
);
})
)}
</div>
<div className="mt-2 text-xs text-zinc-500">
<span className="inline-block rounded px-1 bg-amber-200 text-amber-900 mr-2">'|'</span> pressed
<span className="inline-block rounded px-1 bg-red-200 text-red-900 ml-4">char</span> invalid
</div>
</div>
</section>
{/* Samples */}
<section className="mt-10">
<h2 className="text-lg font-semibold mb-2">Try examples</h2>
<div className="flex flex-wrap gap-2">
{SAMPLES.map(({ label, s }) => (
<button
key={label}
onClick={() => setInput(s)}
className="px-3 py-1.5 rounded-xl border border-zinc-300 bg-white hover:bg-zinc-50 shadow-sm text-sm"
title={s}
>
{label}
</button>
))}
</div>
</section>
{/* Tests */}
<section className="mt-10">
<h2 className="text-lg font-semibold mb-2">Built-in sanity tests</h2>
<div className="rounded-2xl border border-zinc-200 bg-white p-4 shadow-sm">
<Tests />
</div>
</section>
<footer className="mt-12 text-xs text-zinc-500">
Made with React & Tailwind. Algorithm: count occurrences of ")|(". Complexity O(n).
</footer>
</div>
</div>
);
}
function Tests() {
const cases: { s: string; expected: number }[] = [
{ s: ")|(", expected: 1 },
{ s: "||", expected: 0 },
{ s: "()|()", expected: 0 },
{ s: ")|()|(", expected: 2 },
{ s: "((||))", expected: 0 },
{ s: ")|(())|(()", expected: 1 },
];
return (
<div className="grid gap-2">
{cases.map((c, idx) => {
const got = countPressedRegex(c.s);
const ok = got === c.expected;
return (
<div key={idx} className="flex items-center justify-between px-3 py-2 rounded-xl border text-sm"
style={{ borderColor: ok ? "#d1fae5" : "#fecaca", background: ok ? "#ecfdf5" : "#fef2f2" }}
>
<code className="font-mono mr-2">"{c.s}"</code>
<div className="flex items-center gap-3">
<span className="text-zinc-600">expected <b>{c.expected}</b></span>
<span className={ok ? "text-emerald-700" : "text-red-700"}>{ok ? "pass" : `fail (got ${got})`}</span>
</div>
</div>
);
})}
</div>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment