← 제니앱

글자수 세기

글자, 공백 제외, 단어, 줄, UTF-8 바이트 수를 세며, 한글 1글자는 3바이트입니다.

count.tpz
// Character counter. A chars / words / lines / bytes counter written purely in Topaz.
// Key identifiers: 세기=count, 공백인가=isWhitespace, 카드=card, 글=char, 이름=label,
//   값=value, 텍스트=text, 글자들=chars, 전체글자=totalChars, 공백제외=nonSpaceChars,
//   공백들=whitespaceSet, 줄수=lineCount, 바이트수=byteCount, 단어수=wordCount, 단어중=inWord,
//   통계항목=statItem, 통계=stats, 항목=item, 스캔=scan(누적 상태), 누적=accumulator
// 글자수 세기. Topaz로만 작성한 글자 / 단어 / 줄 / 바이트 카운터입니다.
// 호스트의 텍스트(편집기 textarea)를 읽어 HTML 통계 카드 조각을 출력합니다.
// Reads the host's text (the editor textarea) and emits HTML stat-card fragments.
// 순수하고 결정적입니다. 같은 텍스트면 인터프리터와 네이티브/wasm 빌드에서 같은 HTML이 나옵니다.
// Pure and deterministic: same text yields the same HTML across interpreter and native/wasm builds.
// 바이트 수는 `str.byteLength()`(UTF-8)를 씁니다. 한글 스칼라는 3바이트이므로 바이트 제한
// Byte count uses `str.byteLength()` (UTF-8). Each Hangul scalar is 3 bytes, so byte-limited
// 서식(바이트 제한이 있는 서식 등)에는 스칼라 수가 아니라 이 값이 필요합니다.
// formats (e.g. forms with byte limits) need this value rather than the scalar count.

// 단어를 나누는 기준인 ASCII 공백(스페이스/탭/줄바꿈/CR) 스칼라들의 집합입니다.
// The set of ASCII whitespace scalars (space/tab/newline/CR) that delimit words.
let 공백들 = Set.of(" ", "\t", "\n", "\r")

// 이 스칼라가 단어를 나누는 기준인 공백 스칼라 중 하나인가?
// Is this scalar one of the word-delimiting whitespace scalars?
function 공백인가(글: string) -> bool {
  글 in 공백들
}

// 통계 항목 하나: 고정 라벨과 그 값. 사용자 텍스트는 담지 않습니다.
// One stat item: a fixed label and its value. Never holds user text.
record 통계항목 { 이름: string, 값: int }

// 통계 카드 하나. 라벨 위에 큰 숫자 하나입니다(숫자와 고정 라벨만 쓰고 사용자 텍스트는 출력하지 않습니다).
// One stat card: a big number above a label (only numbers and fixed labels, never user text).
function 카드(항목: 통계항목) -> string {
  "<div class=\"stat\"><b>{항목.값}</b><span>{항목.이름}</span></div>"
}

// 단어 세기 전환 스캔의 누적 상태: 지금까지 센 단어 수와 직전 글자가 비공백이었는지.
// Word-count transition-scan state: words counted so far and whether the previous scalar was non-whitespace.
record 스캔 { 수: int, 단어중: bool }

function 세기(텍스트: string) -> string {
  let 글자들 = 텍스트.scalars()
  let 전체글자 = 글자들.length
  let 공백제외 = 글자들.filter(글 => !공백인가(글)).length
  let 줄수 = 텍스트.split("\n").length
  let 바이트수 = 텍스트.byteLength()

  // 단어는 공백이 아닌 글자들의 연속입니다(공백에서 비공백으로 넘어가는 전환마다 셉니다).
  // A word is a run of non-whitespace chars (count each space-to-non-space transition).
  // 가변 루프 대신 전환 스캔을 누적 레코드에 접습니다.
  // Fold the transition scan into an accumulator record instead of a mutable loop.
  let 단어수 = 글자들.reduce(스캔 { 수: 0, 단어중: false }, (누적, 글) => {
    let 공백 = 공백인가(글)
    스캔 { 수: 누적.수 + (if !공백 && !누적.단어중 { 1 } else { 0 }), 단어중: !공백 }
  }).수

  let 통계 = [
    통계항목 { 이름: "글자 수", 값: 전체글자 },
    통계항목 { 이름: "공백 제외", 값: 공백제외 },
    통계항목 { 이름: "단어 수", 값: 단어수 },
    통계항목 { 이름: "줄 수", 값: 줄수 },
    통계항목 { 이름: "바이트 (UTF-8)", 값: 바이트수 },
  ]
  "<div class=\"stats\">" + 통계.map(카드).join("") + "</div>"
}

print(세기(input()))
66글자 수
54공백 제외
13단어 수
2줄 수
165바이트 (UTF-8)