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.
できること
回答を途中で切り上げるというエージェントの癖を正します。不完全な出力は壊れた出力とみなし、ファイル全体を求められたら全体を、コンポーネント5つなら5つを出します。// ...、// rest of code、// TODO、「簡潔さのため」、「残りも同様」、実装の代わりの骨組みといった手抜きの書き方を禁止します。エージェントはまず独立した成果物の数を数え、1つずつ完全に作り、回答前に依頼と数を照合します。長い回答がトークンの上限に近づいたときは、残りを圧縮せず、[PAUSED - 5 件中 2 件完了] のような目印を付けて区切りのよいところで止まり、「続けて」と言えばそこから正確に再開します。
出力
完全なファイル、関数、セクション。あなたが埋める箇所は残りません。
こんな場面に
長いコードファイル、一式のテスト、網羅的なリスト、複数部分からなる文書など、「全部」が必要な作業全般。約50行と短く、題材を選びません。
低リスク:指示のみのファイルで、スクリプトはなく、ネットワークにも接続せず、ファイルも書き込みません。回答の完全さが変わるだけで、エージェントにできることは変わりません。非常に長い回答はやはり上限に達することがあるため、「一時停止して続ける」動作が重要です。一度試用し、6つの関数と38のテストが完全な形で返り、プレースホルダーはありませんでした(コードは実行していません)。最初の試行ではこの Skill が呼ばれず、完全で省略のない出力を求める依頼のときに動作します。作者 Leonxlnx、MIT。