← Runnable Projects

阅读中文版本

Character Frequency Histogram CLI

A standard-library CLI that counts letter and digit frequencies in a text file and prints a sorted ASCII bar chart for the top characters.

beginnerjavascriptunverified

Project

node-text-histogram

Entry point: histogram.mjs

Install

node --version

Run

node histogram.mjs input.txt

Test

node --test histogram.test.mjs

Expected output

For a sample text the CLI prints one line per character with a frequency-sorted bar chart built from hash marks.

Dependencies

  • node >=20

What it does

Counts letter and digit frequencies in a text file, lowercased and punctuation-free, and prints a sorted ASCII bar chart for the top twenty characters.

import { readFileSync } from 'node:fs';

export function countChars(text) {
  const counts = new Map();
  for (const ch of text.toLowerCase()) {
    if (/[a-z0-9]/.test(ch)) counts.set(ch, (counts.get(ch) ?? 0) + 1);
  }
  return counts;
}

export function renderHistogram(counts, top = 20, barScale = 1) {
  const entries = [...counts.entries()].sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1)).slice(0, top);
  return entries.map(([ch, n]) => ch + ' | ' + '#'.repeat(Math.max(1, Math.round(n / barScale))) + ' ' + n).join('\n');
}

const [, , file] = process.argv;
if (file) {
  const counts = countChars(readFileSync(file, 'utf8'));
  const max = Math.max(1, ...counts.values());
  const scale = Math.max(1, Math.ceil(max / 40));
  console.log(renderHistogram(counts, 20, scale));
}
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { countChars, renderHistogram } from './histogram.mjs';

test('counts letters case-insensitively and skips punctuation', () => {
  const counts = countChars('Aa! b?');
  assert.equal(counts.get('a'), 2);
  assert.equal(counts.get('b'), 1);
  assert.equal(counts.size, 2);
});

test('histogram sorts by frequency and includes the count', () => {
  const chart = renderHistogram(new Map([['e', 5], ['t', 2], ['a', 9]]));
  const lines = chart.split('\n');
  assert.match(lines[0], /^a /);
  assert.match(lines[0], /9$/);
  assert.equal(lines.length, 3);
});

Run and verify

node --test histogram.test.mjs
node histogram.mjs input.txt

Run the tests, then point the CLI at any text file. Bar width scales so the longest bar stays readable.

Expected Output

The test run should print two passing tests for case-insensitive counting and frequency sorting. The CLI’s console.log output should print one line per character — glyph, bar of hash marks, and raw count — sorted by frequency. For the histogram test vector of a:9, e:5, t:2 the Output: is:

a | ######### 9
e | ##### 5
t | ## 2

Counts collapse case and skip punctuation before ranking, ties break alphabetically, and the bar scale divides counts so the longest bar stays within forty columns on large files.

Limitations

Counts collapse case and ignore punctuation and multi-byte grapheme clusters, which suits letter-frequency analysis rather than full Unicode text.