barissozudogru/swe-bench-mini huggingface dataset:
Repository: stringkit (difficulty: easy)

Issue:
slugify() is supposed to collapse any run of separators into a single dash, but 'Hello   World' becomes 'hello---world' instead of 'hello-world'. Multiple spaces should produce exactly one dash.

Source files:
core.py: import re


def slugify(text):
    """Lowercase, strip, and replace separators with dashes."""
    text = text.strip().lower()
    return re.sub(r"\s", "-", text)


Test files:
test_core.py: from core import slugify


def test_slugify_collapses_whitespace():
    assert slugify("Hello   World") == "hello-world"
    assert slugify("  a  b  ") == "a-b"


Patch:
--- a/core.py
+++ b/core.py
@@ -4,4 +4,4 @@
 def slugify(text):
     """Lowercase, strip, and replace separators with dashes."""
     text = text.strip().lower()
-    return re.sub(r"\s", "-", text)
+    return re.sub(r"\s+", "-", text)
Repository: mathkit (difficulty: easy)

Issue:
safe_divide(x, 0) raises ZeroDivisionError and crashes the report generator. When the denominator is zero it should return None instead of raising.

Source files:
core.py: def safe_divide(numerator, denominator):
    return numerator / denominator


Test files:
test_core.py: from core import safe_divide


def test_safe_divide_handles_zero():
    assert safe_divide(10, 2) == 5
    assert safe_divide(10, 0) is None


Patch:
--- a/core.py
+++ b/core.py
@@ -1,2 +1,4 @@
 def safe_divide(numerator, denominator):
+    if denominator == 0:
+        return None
     return numerator / denominator
Repository: collectkit (difficulty: medium)

Issue:
append_item() uses a mutable default argument for the bucket, so items leak between calls that don't pass their own list. Calling it twice without a bucket returns ['a', 'b'] on the second call.

Source files:
core.py: def append_item(item, bucket=[]):
    """Append item to bucket and return the bucket."""
    bucket.append(item)
    return bucket


Test files:
test_core.py: from core import append_item


def test_append_item_no_shared_default():
    assert append_item("a") == ["a"]
    assert append_item("b") == ["b"]


Patch:
--- a/core.py
+++ b/core.py
@@ -1,4 +1,6 @@
-def append_item(item, bucket=[]):
+def append_item(item, bucket=None):
     """Append item to bucket and return the bucket."""
+    if bucket is None:
+        bucket = []
     bucket.append(item)
     return bucket
Repository: pagekit (difficulty: medium)

Issue:
paginate() drops the final partial page. With 10 items and page_size 3 it returns 3 pages instead of 4 — the last item is lost. The page count should round up.

Source files:
core.py: def paginate(items, page_size):
    """Split items into pages of at most page_size."""
    n_pages = len(items) // page_size
    return [items[i * page_size:(i + 1) * page_size] for i in range(n_pages)]


Test files:
test_core.py: from core import paginate


def test_paginate_keeps_partial_page():
    pages = paginate(list(range(10)), 3)
    assert len(pages) == 4
    assert pages[-1] == [9]


Patch:
--- a/core.py
+++ b/core.py
@@ -1,4 +1,4 @@
 def paginate(items, page_size):
     """Split items into pages of at most page_size."""
-    n_pages = len(items) // page_size
+    n_pages = (len(items) + page_size - 1) // page_size
     return [items[i * page_size:(i + 1) * page_size] for i in range(n_pages)]
Repository: seqkit (difficulty: easy)

Issue:
fib(0) returns 1 but should return 0. The Fibonacci sequence starts 0, 1, 1, 2, 3 — the base case for n == 0 is wrong.

Source files:
core.py: def fib(n):
    """Return the nth Fibonacci number (0-indexed)."""
    if n < 2:
        return 1
    return fib(n - 1) + fib(n - 2)


Test files:
test_core.py: from core import fib


def test_fib_base_cases():
    assert fib(0) == 0
    assert fib(1) == 1
    assert fib(7) == 13


Patch:
--- a/core.py
+++ b/core.py
@@ -1,5 +1,5 @@
 def fib(n):
     """Return the nth Fibonacci number (0-indexed)."""
     if n < 2:
-        return 1
+        return n
     return fib(n - 1) + fib(n - 2)
Repository: listkit (difficulty: medium)

Issue:
flatten() only goes one level deep. Given [1, [2, [3, 4]]] it returns [1, 2, [3, 4]] instead of [1, 2, 3, 4]. It should flatten arbitrarily nested lists.

Source files:
core.py: def flatten(items):
    """Flatten a nested list of arbitrary depth."""
    result = []
    for item in items:
        if isinstance(item, list):
            result.extend(item)
        else:
            result.append(item)
    return result


Test files:
test_core.py: from core import flatten


def test_flatten_deeply_nested():
    assert flatten([1, [2, [3, 4]]]) == [1, 2, 3, 4]
    assert flatten([[1], [[2]], 3]) == [1, 2, 3]


Patch:
--- a/core.py
+++ b/core.py
@@ -3,7 +3,7 @@
     result = []
     for item in items:
         if isinstance(item, list):
-            result.extend(item)
+            result.extend(flatten(item))
         else:
             result.append(item)
     return result
Repository: dictkit (difficulty: medium)

Issue:
deep_merge() overwrites nested dicts wholesale instead of merging them. merging {'a': {'x': 1}} with {'a': {'y': 2}} loses 'x'. Nested dicts on both sides should be merged recursively.

Source files:
core.py: def deep_merge(base, override):
    """Recursively merge override into a copy of base."""
    result = dict(base)
    for key, value in override.items():
        result[key] = value
    return result


Test files:
test_core.py: from core import deep_merge


def test_deep_merge_preserves_nested_keys():
    merged = deep_merge({"a": {"x": 1}}, {"a": {"y": 2}})
    assert merged == {"a": {"x": 1, "y": 2}}


Patch:
--- a/core.py
+++ b/core.py
@@ -2,5 +2,8 @@
     """Recursively merge override into a copy of base."""
     result = dict(base)
     for key, value in override.items():
-        result[key] = value
+        if isinstance(value, dict) and isinstance(result.get(key), dict):
+            result[key] = deep_merge(result[key], value)
+        else:
+            result[key] = value
     return result
Repository: searchkit (difficulty: medium)

Issue:
binary_search() misses the last element of the array. Searching for the largest value returns -1 because the high bound is set to len(arr) - 1 but the loop condition excludes it.

Source files:
core.py: def binary_search(arr, target):
    """Return the index of target in sorted arr, or -1."""
    low, high = 0, len(arr) - 1
    while low < high:
        mid = (low + high) // 2
        if arr[mid] == target:
            return mid
        elif arr[mid] < target:
            low = mid + 1
        else:
            high = mid - 1
    return -1


Test files:
test_core.py: from core import binary_search


def test_binary_search_finds_last():
    arr = [1, 3, 5, 7, 9]
    assert binary_search(arr, 9) == 4
    assert binary_search(arr, 1) == 0
    assert binary_search(arr, 4) == -1


Patch:
--- a/core.py
+++ b/core.py
@@ -1,7 +1,7 @@
 def binary_search(arr, target):
     """Return the index of target in sorted arr, or -1."""
     low, high = 0, len(arr) - 1
-    while low < high:
+    while low <= high:
         mid = (low + high) // 2
         if arr[mid] == target:
             return mid
Repository: tempkit (difficulty: easy)

Issue:
celsius_to_fahrenheit() uses the wrong formula — it multiplies by 9/5 but forgets to add 32, so 100C comes out as 180 instead of 212.

Source files:
core.py: def celsius_to_fahrenheit(celsius):
    """Convert Celsius to Fahrenheit."""
    return celsius * 9 / 5


Test files:
test_core.py: from core import celsius_to_fahrenheit


def test_celsius_to_fahrenheit():
    assert celsius_to_fahrenheit(0) == 32
    assert celsius_to_fahrenheit(100) == 212


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,3 @@
 def celsius_to_fahrenheit(celsius):
     """Convert Celsius to Fahrenheit."""
-    return celsius * 9 / 5
+    return celsius * 9 / 5 + 32
Repository: orderkit (difficulty: easy)

Issue:
dedupe() sorts its output instead of preserving first-seen order. For ['b', 'a', 'b', 'c', 'a'] it returns ['a', 'b', 'c'] but should return ['b', 'a', 'c'] — duplicates removed, original order kept.

Source files:
core.py: def dedupe(items):
    """Remove duplicates, preserving first-seen order."""
    return sorted(set(items))


Test files:
test_core.py: from core import dedupe


def test_dedupe_preserves_order():
    assert dedupe(["b", "a", "b", "c", "a"]) == ["b", "a", "c"]


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,9 @@
 def dedupe(items):
     """Remove duplicates, preserving first-seen order."""
-    return sorted(set(items))
+    seen = set()
+    result = []
+    for item in items:
+        if item not in seen:
+            seen.add(item)
+            result.append(item)
+    return result
Repository: clampkit (difficulty: easy)

Issue:
clamp() has its min and max swapped, so clamp(5, 0, 10) returns 0 instead of 5. Values inside the range should pass through unchanged.

Source files:
core.py: def clamp(value, low, high):
    """Clamp value into the inclusive range [low, high]."""
    return max(high, min(low, value))


Test files:
test_core.py: from core import clamp


def test_clamp_range():
    assert clamp(5, 0, 10) == 5
    assert clamp(-3, 0, 10) == 0
    assert clamp(42, 0, 10) == 10


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,3 @@
 def clamp(value, low, high):
     """Clamp value into the inclusive range [low, high]."""
-    return max(high, min(low, value))
+    return max(low, min(high, value))
Repository: retrykit (difficulty: medium)

Issue:
retry() runs the function one fewer time than requested. With attempts=3 and a function that fails twice then succeeds, it gives up after 2 tries and raises. It should make exactly `attempts` calls.

Source files:
core.py: def retry(func, attempts=3):
    """Call func up to `attempts` times until it does not raise."""
    last_exc = None
    for _ in range(attempts - 1):
        try:
            return func()
        except Exception as exc:
            last_exc = exc
    raise last_exc


Test files:
test_core.py: from core import retry


def test_retry_uses_all_attempts():
    state = {'calls': 0}

    def flaky():
        state['calls'] += 1
        if state['calls'] < 3:
            raise ValueError('not yet')
        return 'ok'

    assert retry(flaky, attempts=3) == 'ok'
    assert state['calls'] == 3


Patch:
--- a/core.py
+++ b/core.py
@@ -1,7 +1,7 @@
 def retry(func, attempts=3):
     """Call func up to `attempts` times until it does not raise."""
     last_exc = None
-    for _ in range(attempts - 1):
+    for _ in range(attempts):
         try:
             return func()
         except Exception as exc:
Repository: cachekit (difficulty: hard)

Issue:
The LRU cache evicts the most-recently-used entry instead of the least-recently-used one. After filling a cache of size 2 and touching the first key, inserting a third key should evict the untouched key, not the one we just read.

Source files:
core.py: from collections import OrderedDict


class LRUCache:
    """Fixed-capacity cache that evicts the least-recently-used key."""

    def __init__(self, capacity):
        self.capacity = capacity
        self.store = OrderedDict()

    def get(self, key):
        if key not in self.store:
            return None
        return self.store[key]

    def put(self, key, value):
        self.store[key] = value
        self.store.move_to_end(key)
        if len(self.store) > self.capacity:
            self.store.popitem(last=True)


Test files:
test_core.py: from core import LRUCache


def test_lru_evicts_least_recently_used():
    cache = LRUCache(2)
    cache.put("a", 1)
    cache.put("b", 2)
    assert cache.get("a") == 1
    cache.put("c", 3)
    assert cache.get("b") is None
    assert cache.get("a") == 1
    assert cache.get("c") == 3


Patch:
--- a/core.py
+++ b/core.py
@@ -11,10 +11,11 @@
     def get(self, key):
         if key not in self.store:
             return None
+        self.store.move_to_end(key)
         return self.store[key]
 
     def put(self, key, value):
         self.store[key] = value
         self.store.move_to_end(key)
         if len(self.store) > self.capacity:
-            self.store.popitem(last=True)
+            self.store.popitem(last=False)
Repository: datekit (difficulty: medium)

Issue:
date_range() is documented as inclusive of the end date but excludes it. date_range(date(2024,1,1), date(2024,1,3)) returns two days instead of three.

Source files:
core.py: from datetime import timedelta


def date_range(start, end):
    """Return all dates from start to end, inclusive."""
    days = (end - start).days
    return [start + timedelta(days=i) for i in range(days)]


Test files:
test_core.py: from datetime import date

from core import date_range


def test_date_range_is_inclusive():
    rng = date_range(date(2024, 1, 1), date(2024, 1, 3))
    assert len(rng) == 3
    assert rng[-1] == date(2024, 1, 3)


Patch:
--- a/core.py
+++ b/core.py
@@ -4,4 +4,4 @@
 def date_range(start, end):
     """Return all dates from start to end, inclusive."""
     days = (end - start).days
-    return [start + timedelta(days=i) for i in range(days)]
+    return [start + timedelta(days=i) for i in range(days + 1)]
Repository: csvkit (difficulty: medium)

Issue:
parse_row() splits on every comma, so quoted fields containing commas are torn apart. 'a,"b,c",d' should parse to three fields ['a', 'b,c', 'd'] but currently yields four.

Source files:
core.py: def parse_row(line):
    """Parse one CSV row into a list of fields."""
    return line.split(",")


Test files:
test_core.py: from core import parse_row


def test_parse_row_respects_quotes():
    assert parse_row('a,"b,c",d') == ["a", "b,c", "d"]


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,7 @@
+import csv
+import io
+
+
 def parse_row(line):
     """Parse one CSV row into a list of fields."""
-    return line.split(",")
+    return next(csv.reader(io.StringIO(line)))
Repository: wordkit (difficulty: easy)

Issue:
word_count() counts empty strings when the text has trailing or double spaces, so 'a  b ' reports 4 words instead of 2. split() with no argument already handles this.

Source files:
core.py: def word_count(text):
    """Count the words in text."""
    return len(text.split(" "))


Test files:
test_core.py: from core import word_count


def test_word_count_ignores_extra_spaces():
    assert word_count("a  b ") == 2
    assert word_count("one two three") == 3


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,3 @@
 def word_count(text):
     """Count the words in text."""
-    return len(text.split(" "))
+    return len(text.split())
Repository: palinkit (difficulty: easy)

Issue:
is_palindrome() is case-sensitive and counts punctuation, so 'A man, a plan, a canal: Panama' is reported as not a palindrome. It should ignore case and non-alphanumeric characters.

Source files:
core.py: def is_palindrome(text):
    """Return True if text reads the same forwards and backwards."""
    return text == text[::-1]


Test files:
test_core.py: from core import is_palindrome


def test_is_palindrome_ignores_case_and_punctuation():
    assert is_palindrome("A man, a plan, a canal: Panama")
    assert not is_palindrome("hello")


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,4 @@
 def is_palindrome(text):
     """Return True if text reads the same forwards and backwards."""
-    return text == text[::-1]
+    cleaned = [c.lower() for c in text if c.isalnum()]
+    return cleaned == cleaned[::-1]
Repository: statskit (difficulty: medium)

Issue:
median() returns the wrong value for even-length inputs — it picks the upper-middle element instead of averaging the two middle elements. median([1, 2, 3, 4]) should be 2.5, not 3.

Source files:
core.py: def median(values):
    """Return the median of a list of numbers."""
    ordered = sorted(values)
    mid = len(ordered) // 2
    return ordered[mid]


Test files:
test_core.py: from core import median


def test_median_even_length():
    assert median([1, 2, 3, 4]) == 2.5
    assert median([5, 1, 3]) == 3


Patch:
--- a/core.py
+++ b/core.py
@@ -2,4 +2,6 @@
     """Return the median of a list of numbers."""
     ordered = sorted(values)
     mid = len(ordered) // 2
+    if len(ordered) % 2 == 0:
+        return (ordered[mid - 1] + ordered[mid]) / 2
     return ordered[mid]
Repository: rangekit (difficulty: medium)

Issue:
running_total() reports cumulative sums but is off by one: the first element is omitted, so running_total([1, 2, 3]) returns [1, 3] instead of [1, 3, 6].

Source files:
core.py: def running_total(values):
    """Return the list of cumulative sums."""
    totals = []
    acc = 0
    for value in values[1:]:
        acc += value
        totals.append(acc)
    return totals


Test files:
test_core.py: from core import running_total


def test_running_total_includes_first():
    assert running_total([1, 2, 3]) == [1, 3, 6]


Patch:
--- a/core.py
+++ b/core.py
@@ -2,7 +2,7 @@
     """Return the list of cumulative sums."""
     totals = []
     acc = 0
-    for value in values[1:]:
+    for value in values:
         acc += value
         totals.append(acc)
     return totals
Repository: titlekit (difficulty: medium)

Issue:
title_case() uppercases the letter after an apostrophe, so "don't stop" becomes "Don'T Stop". Only the first letter of each space-separated word should be capitalized; the rest of the word should be left untouched.

Source files:
core.py: def title_case(text):
    """Capitalize the first letter of each word."""
    return text.title()


Test files:
test_core.py: from core import title_case


def test_title_case_keeps_apostrophes():
    assert title_case("don't stop") == "Don't Stop"


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,3 @@
 def title_case(text):
     """Capitalize the first letter of each word."""
-    return text.title()
+    return " ".join(word[:1].upper() + word[1:] for word in text.split(" "))
Repository: windowkit (difficulty: hard)

Issue:
sliding_max() returns the max of the whole list repeated, instead of the max within each window of size k. For [1, 3, 2, 5, 4] with k=2 it should return [3, 3, 5, 5].

Source files:
core.py: def sliding_max(values, k):
    """Return the max of each contiguous window of size k."""
    overall = max(values)
    return [overall for _ in range(len(values) - k + 1)]


Test files:
test_core.py: from core import sliding_max


def test_sliding_max_window():
    assert sliding_max([1, 3, 2, 5, 4], 2) == [3, 3, 5, 5]


Patch:
--- a/core.py
+++ b/core.py
@@ -1,4 +1,3 @@
 def sliding_max(values, k):
     """Return the max of each contiguous window of size k."""
-    overall = max(values)
-    return [overall for _ in range(len(values) - k + 1)]
+    return [max(values[i:i + k]) for i in range(len(values) - k + 1)]
Repository: versionkit (difficulty: medium)

Issue:
compare_versions() compares version strings lexically, so '1.10' is treated as less than '1.9'. Components should be compared numerically: compare_versions('1.10', '1.9') should return 1.

Source files:
core.py: def compare_versions(a, b):
    """Return -1, 0, or 1 comparing dotted version strings."""
    if a < b:
        return -1
    if a > b:
        return 1
    return 0


Test files:
test_core.py: from core import compare_versions


def test_compare_versions_numeric():
    assert compare_versions("1.10", "1.9") == 1
    assert compare_versions("1.2", "1.2") == 0
    assert compare_versions("2.0", "10.0") == -1


Patch:
--- a/core.py
+++ b/core.py
@@ -1,7 +1,9 @@
 def compare_versions(a, b):
     """Return -1, 0, or 1 comparing dotted version strings."""
-    if a < b:
+    pa = [int(p) for p in a.split(".")]
+    pb = [int(p) for p in b.split(".")]
+    if pa < pb:
         return -1
-    if a > b:
+    if pa > pb:
         return 1
     return 0
Repository: intervalkit (difficulty: easy)

Issue:
in_range() is documented as inclusive of both endpoints but uses a strict upper comparison, so in_range(10, 0, 10) returns False. A value equal to the high bound should be considered in range.

Source files:
core.py: def in_range(value, low, high):
    """Return True if low <= value <= high (inclusive)."""
    return low <= value < high


Test files:
test_core.py: from core import in_range


def test_in_range_includes_upper_bound():
    assert in_range(10, 0, 10) is True
    assert in_range(0, 0, 10) is True
    assert in_range(11, 0, 10) is False


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,3 @@
 def in_range(value, low, high):
     """Return True if low <= value <= high (inclusive)."""
-    return low <= value < high
+    return low <= value <= high
Repository: roundkit (difficulty: easy)

Issue:
round_money() is meant to round to 2 decimal places by default for currency, but the default ndigits is 0, so round_money(1.005) returns 1.0 instead of 1.0 cents precision. The default should be 2.

Source files:
core.py: def round_money(amount, ndigits=0):
    """Round a monetary amount, defaulting to cents (2 dp)."""
    return round(amount, ndigits)


Test files:
test_core.py: from core import round_money


def test_round_money_defaults_to_cents():
    assert round_money(1.239) == 1.24
    assert round_money(10.0) == 10.0
    assert round_money(3.14159, 3) == 3.142


Patch:
--- a/core.py
+++ b/core.py
@@ -1,3 +1,3 @@
-def round_money(amount, ndigits=0):
+def round_money(amount, ndigits=2):
     """Round a monetary amount, defaulting to cents (2 dp)."""
     return round(amount, ndigits)
Repository: tallykit (difficulty: medium)

Issue:
count_word() uses a mutable dict as its default counts argument, so tallies leak between calls that don't pass their own counter. Calling it twice without a counter accumulates the first call's result.

Source files:
core.py: def count_word(word, counts={}):
    """Increment word in counts and return counts."""
    counts[word] = counts.get(word, 0) + 1
    return counts


Test files:
test_core.py: from core import count_word


def test_count_word_no_shared_default():
    assert count_word("a") == {"a": 1}
    assert count_word("b") == {"b": 1}


Patch:
--- a/core.py
+++ b/core.py
@@ -1,4 +1,6 @@
-def count_word(word, counts={}):
+def count_word(word, counts=None):
     """Increment word in counts and return counts."""
+    if counts is None:
+        counts = {}
     counts[word] = counts.get(word, 0) + 1
     return counts
Repository: emailkit (difficulty: medium)

Issue:
is_valid_email() accepts strings with extra text around the address because the pattern is not anchored, so 'bad x@y.com bad' is reported as valid. The whole string must match the pattern, not just a part.

Source files:
core.py: import re


def is_valid_email(text):
    """Return True if text is a single valid email address."""
    return re.search(r"[^@\s]+@[^@\s]+\.[^@\s]+", text) is not None


Test files:
test_core.py: from core import is_valid_email


def test_is_valid_email_anchors_whole_string():
    assert is_valid_email("user@example.com") is True
    assert is_valid_email("bad user@example.com bad") is False
    assert is_valid_email("no-at-sign") is False


Patch:
--- a/core.py
+++ b/core.py
@@ -3,4 +3,4 @@
 
 def is_valid_email(text):
     """Return True if text is a single valid email address."""
-    return re.search(r"[^@\s]+@[^@\s]+\.[^@\s]+", text) is not None
+    return re.fullmatch(r"[^@\s]+@[^@\s]+\.[^@\s]+", text) is not None
Repository: passkit (difficulty: medium)

Issue:
is_strong_password() uses 'or' between its requirements, so any single satisfied rule makes it return True —  'aaaaaaaa' passes. A strong password must satisfy ALL the rules (length, digit, and uppercase).

Source files:
core.py: def is_strong_password(pw):
    """Strong password: >=8 chars, has a digit, has an uppercase letter."""
    return (
        len(pw) >= 8
        or any(c.isdigit() for c in pw)
        or any(c.isupper() for c in pw)
    )


Test files:
test_core.py: from core import is_strong_password


def test_is_strong_password_requires_all_rules():
    assert is_strong_password("Abcdef12") is True
    assert is_strong_password("aaaaaaaa") is False
    assert is_strong_password("Ab1") is False


Patch:
--- a/core.py
+++ b/core.py
@@ -2,6 +2,6 @@
     """Strong password: >=8 chars, has a digit, has an uppercase letter."""
     return (
         len(pw) >= 8
-        or any(c.isdigit() for c in pw)
-        or any(c.isupper() for c in pw)
+        and any(c.isdigit() for c in pw)
+        and any(c.isupper() for c in pw)
     )