I couldn't write the files to disk with the tools I have here, so both are below in full. I haven't run them. I checked the expected values in the tests by hand against the implementation.
Behavior choices the tests rely on:
- A word is a run of letters or digits, and apostrophes inside a word are kept (
don'tis one word). Hyphens split words (well-knownis two). unique_words,top_n_words, andaverage_word_lengthlowercase their input where case matters.average_word_lengthcounts apostrophes in a word's length.- Sentences end at
.,!, or?runs followed by whitespace or end of text.3.14therefore doesn't split, but abbreviations likeDr.do. reading_time_minutesraisesValueErrorifwpm <= 0, andtop_n_wordsraises it ifn < 0.
textstats.py
"""textstats: small, dependency-free helpers for computing text statistics.
A "word" is a maximal run of letters or digits, optionally joined by internal
apostrophes (so ``don't`` is one word). Hyphens and other punctuation separate
words (so ``well-known`` is two words).
"""
import re
from collections import Counter
_WORD_RE = re.compile(r"[^\W_]+(?:['\u2019][^\W_]+)*")
_SENTENCE_END_RE = re.compile(r"[.!?]+(?=\s|$)")
_ALNUM_RE = re.compile(r"[^\W_]")
def word_count(text):
"""Return the number of words in ``text``.
Words are runs of letters or digits, optionally joined by internal
apostrophes. Punctuation and whitespace are ignored, and hyphens split
words.
Args:
text: The string to analyse.
Returns:
The number of words as an int (0 for empty or punctuation-only text).
Example:
>>> word_count("Hello, world!")
2
"""
return len(_WORD_RE.findall(text))
def unique_words(text):
"""Return the distinct words in ``text`` as a sorted list.
Comparison is case-insensitive: every word is lowercased before
de-duplication.
Args:
text: The string to analyse.
Returns:
An alphabetically sorted list of unique lowercase words. The list is
empty if ``text`` contains no words.
Example:
>>> unique_words("The cat and the hat")
['and', 'cat', 'hat', 'the']
"""
return sorted(set(_WORD_RE.findall(text.lower())))
def top_n_words(text, n):
"""Return the ``n`` most frequent words in ``text`` with their counts.
Comparison is case-insensitive. Results are ordered by descending count;
words with equal counts are ordered alphabetically so the output is
deterministic.
Args:
text: The string to analyse.
n: How many words to return. If ``n`` exceeds the number of distinct
words, all distinct words are returned. ``0`` returns an empty list.
Returns:
A list of ``(word, count)`` tuples, at most ``n`` long.
Raises:
ValueError: If ``n`` is negative.
Example:
>>> top_n_words("a b a c b a", 2)
[('a', 3), ('b', 2)]
"""
if n < 0:
raise ValueError("n must be non-negative")
counts = Counter(_WORD_RE.findall(text.lower()))
ordered = sorted(counts.items(), key=lambda item: (-item[1], item[0]))
return ordered[:n]
def average_word_length(text):
"""Return the mean length, in characters, of the words in ``text``.
Word length includes any internal apostrophes (``don't`` has length 5).
Surrounding punctuation is not counted.
Args:
text: The string to analyse.
Returns:
The average word length as a float, or ``0.0`` if there are no words.
Example:
>>> average_word_length("a bb ccc")
2.0
"""
words = _WORD_RE.findall(text)
if not words:
return 0.0
return sum(len(word) for word in words) / len(words)
def sentence_count(text):
"""Return the number of sentences in ``text``.
A sentence ends at a run of ``.``, ``!`` or ``?`` that is followed by
whitespace or the end of the text. Periods inside tokens such as ``3.14``
therefore do not end a sentence. Trailing text without a terminator counts
as a sentence, and segments with no letters or digits are ignored.
Abbreviations such as ``Dr.`` are treated as sentence ends.
Args:
text: The string to analyse.
Returns:
The number of sentences as an int.
Example:
>>> sentence_count("Hi. How are you? Fine!")
3
"""
segments = _SENTENCE_END_RE.split(text)
return sum(1 for segment in segments if _ALNUM_RE.search(segment))
def reading_time_minutes(text, wpm=200):
"""Return the estimated reading time of ``text`` in minutes.
The estimate is the word count divided by the reading speed.
Args:
text: The string to analyse.
wpm: Reading speed in words per minute. Must be positive. Defaults
to 200.
Returns:
The estimated reading time in minutes as a float (0.0 for empty text).
Raises:
ValueError: If ``wpm`` is zero or negative.
Example:
>>> reading_time_minutes("word " * 100)
0.5
"""
if wpm <= 0:
raise ValueError("wpm must be positive")
return word_count(text) / wpm
test_textstats.py
"""Tests for textstats."""
import pytest
import textstats
# --- word_count -------------------------------------------------------------
def test_word_count_empty_string():
assert textstats.word_count("") == 0
def test_word_count_simple_sentence():
assert textstats.word_count("hello world") == 2
def test_word_count_ignores_punctuation_and_extra_whitespace():
assert textstats.word_count("Hello, world!\n\nfoo") == 3
def test_word_count_keeps_contractions_as_one_word():
assert textstats.word_count("don't stop") == 2
def test_word_count_splits_hyphenated_words():
assert textstats.word_count("a well-known fact") == 4
def test_word_count_counts_numbers():
assert textstats.word_count("I have 3 apples") == 4
def test_word_count_punctuation_only():
assert textstats.word_count("... !!! ???") == 0
# --- unique_words -----------------------------------------------------------
def test_unique_words_sorted_and_deduplicated():
assert textstats.unique_words("the cat and the hat") == ["and", "cat", "hat", "the"]
def test_unique_words_case_insensitive():
assert textstats.unique_words("Apple apple APPLE") == ["apple"]
def test_unique_words_empty_text():
assert textstats.unique_words("") == []
def test_unique_words_ignores_punctuation():
assert textstats.unique_words("Hi, hi! Ho.") == ["hi", "ho"]
# --- top_n_words ------------------------------------------------------------
def test_top_n_words_orders_by_count():
assert textstats.top_n_words("a b a c b a", 2) == [("a", 3), ("b", 2)]
def test_top_n_words_ties_broken_alphabetically():
assert textstats.top_n_words("zebra apple mango", 2) == [("apple", 1), ("mango", 1)]
def test_top_n_words_n_larger_than_vocabulary():
assert textstats.top_n_words("x y", 10) == [("x", 1), ("y", 1)]
def test_top_n_words_zero_returns_empty_list():
assert textstats.top_n_words("a b c", 0) == []
def test_top_n_words_negative_raises():
with pytest.raises(ValueError):
textstats.top_n_words("a b c", -1)
def test_top_n_words_case_insensitive():
assert textstats.top_n_words("The the THE cat", 1) == [("the", 3)]
def test_top_n_words_empty_text():
assert textstats.top_n_words("", 5) == []
# --- average_word_length ----------------------------------------------------
def test_average_word_length_empty_text_is_zero():
assert textstats.average_word_length("") == 0.0
def test_average_word_length_single_word():
assert textstats.average_word_length("abc") == pytest.approx(3.0)
def test_average_word_length_multiple_words():
assert textstats.average_word_length("a bb ccc") == pytest.approx(2.0)
def test_average_word_length_ignores_surrounding_punctuation():
assert textstats.average_word_length("hello, world!") == pytest.approx(5.0)
def test_average_word_length_counts_apostrophes():
assert textstats.average_word_length("don't") == pytest.approx(5.0)
def test_average_word_length_non_integer_result():
assert textstats.average_word_length("a bb") == pytest.approx(1.5)
# --- sentence_count ---------------------------------------------------------
def test_sentence_count_empty_text():
assert textstats.sentence_count("") == 0
def test_sentence_count_single_sentence():
assert textstats.sentence_count("Hello world.") == 1
def test_sentence_count_mixed_terminators():
assert textstats.sentence_count("Hi. How are you? Fine!") == 3
def test_sentence_count_without_terminator():
assert textstats.sentence_count("No terminator here") == 1
def test_sentence_count_ellipsis_and_interrobang():
assert textstats.sentence_count("Wait... what?!") == 2
def test_sentence_count_decimal_number_does_not_split():
assert textstats.sentence_count("Pi is 3.14 today.") == 1
def test_sentence_count_punctuation_only():
assert textstats.sentence_count(" ... ") == 0
def test_sentence_count_across_newlines():
assert textstats.sentence_count("One.\nTwo.") == 2
# --- reading_time_minutes ---------------------------------------------------
def test_reading_time_default_wpm_one_minute():
text = " ".join(["word"] * 200)
assert textstats.reading_time_minutes(text) == pytest.approx(1.0)
def test_reading_time_default_wpm_half_minute():
text = " ".join(["word"] * 100)
assert textstats.reading_time_minutes(text) == pytest.approx(0.5)
def test_reading_time_custom_wpm():
text = " ".join(["word"] * 50)
assert textstats.reading_time_minutes(text, wpm=100) == pytest.approx(0.5)
def test_reading_time_empty_text_is_zero():
assert textstats.reading_time_minutes("") == 0.0
def test_reading_time_zero_wpm_raises():
with pytest.raises(ValueError):
textstats.reading_time_minutes("some words", wpm=0)
def test_reading_time_negative_wpm_raises():
with pytest.raises(ValueError):
textstats.reading_time_minutes("some words", wpm=-50)
Save them side by side in one directory and run pytest test_textstats.py. The module has exactly the six functions you listed. The three regexes are module-level constants, so there are no private helper functions.
Test run: two files requested in full. All six functions and 38 tests came back complete with no placeholder patterns. The first attempt did not call the Skill and was discarded; this run used a request that asked for unabridged output. The code was not executed.
Qué hace
Anula el hábito predeterminado de truncar las respuestas. Trata una salida parcial como una salida defectuosa: si pides un archivo completo, recibes el archivo completo; y si pides cinco componentes, recibes cinco. Prohíbe una lista de patrones de atajo, como // ..., // rest of code, // TODO, «for brevity», «the rest follows the same pattern» y esqueletos que se ofrecen en lugar de una implementación. El agente primero cuenta los entregables distintos, construye cada uno por completo y luego compara el recuento con la solicitud antes de responder. Cuando una respuesta larga se acerca al límite de tokens, no comprime el resto: se detiene en un punto de corte limpio con un marcador como [PAUSED - 2 of 5 complete] y continúa exactamente desde ahí cuando dices «continue».
Salida
Archivos, funciones o secciones completos, sin nada que tengas que completar tú.
Ideal para
Cualquier tarea en la que necesites el resultado completo: archivos de código largos, un conjunto completo de pruebas, listas exhaustivas o documentos de varias partes. Es breve (unas 50 líneas) y funciona con cualquier tema.
Riesgo bajo: solo instrucciones, sin scripts, sin acceso a la red y sin escritura de archivos. Solo cambia el grado de completitud de las respuestas, no lo que el agente puede hacer. Las respuestas muy largas pueden seguir alcanzando el límite de tokens, así que importa el comportamiento de pausa y continuación. Se probó una vez: seis funciones y 38 pruebas se devolvieron completas, sin patrones de marcador de posición, y el código no se ejecutó. El primer intento no invocó la Skill; se activa cuando tu solicitud pide una salida completa y sin abreviar. Por Leonxlnx, MIT.