CODE-0004 · kimi-k2.7-code (default)
def int_to_roman(n):
    if not isinstance(n, int) or not (1 <= n <= 3999):
        raise ValueError("Integer must be in range 1..3999")
    
    values = [
        (1000, "M"), (900, "CM"), (500, "D"), (400, "CD"),
        (100, "C"), (90, "XC"), (50, "L"), (40, "XL"),
        (10, "X"), (9, "IX"), (5, "V"), (4, "IV"), (1, "I")
    ]
    
    result = []
    for value, symbol in values:
        while n >= value:
            result.append(symbol)
            n -= value
    return "".join(result)


def roman_to_int(s):
    if not s:
        raise ValueError("Empty string is not a valid Roman numeral")
    
    # Map of single symbols to values
    single_values = {
        'I': 1, 'V': 5, 'X': 10, 'L': 50,
        'C': 100, 'D': 500, 'M': 1000
    }
    
    # Valid subtractive pairs
    subtractive_pairs = {
        'IV': 4, 'IX': 9, 'XL': 40, 'XC': 90, 'CD': 400, 'CM': 900
    }
    
    # Check for invalid characters
    for char in s:
        if char not in single_values:
            raise ValueError(f"Invalid character in Roman numeral: {char}")
    
    result = 0
    i = 0
    n = len(s)
    
    # Track what was last added to enforce non-increasing order of "blocks"
    # and to detect non-canonical forms
    last_value = float('inf')
    
    while i < n:
        # Try to match a subtractive pair first
        if i + 1 < n:
            pair = s[i:i+2]
            if pair in subtractive_pairs:
                # Check for non-canonical: pairs like "VX", "LC", "DM" are invalid
                # Also check ordering: we shouldn't have a larger or equal value before
                pair_value = subtractive_pairs[pair]
                if pair_value >= last_value:
                    raise ValueError("Non-canonical Roman numeral: invalid ordering")
                result += pair_value
                last_value = pair_value
                i += 2
                continue
        
        # Single symbol
        symbol = s[i]
        value = single_values[symbol]
        
        # Check ordering: symbols must generally be non-increasing
        # But we need to be careful: V, L, D can only appear once and can't be subtracted from
        if value >= last_value:
            # Same symbol repeated is only allowed for I, X, C, M (up to 3 times)
            if value == last_value:
                # Check if we're about to make a 4-repeat (already had same, and this is same)
                # Actually we need to check the run length
                pass  # Will check below with context
            else:
                raise ValueError("Non-canonical Roman numeral: invalid ordering")
        
        # Check for more than 3 repeats of I, X, C, M
        # Check for repeats of V, L, D
        # We need to look ahead and behind to check runs
        
        result += value
        last_value = value
        i += 1
    
    # Verify the result by re-parsing more strictly
    # Let's do a full validation pass
    
    # Check for invalid patterns: more than 3 in a row, V/L/D repeated, invalid subtractive
    # Re-parse with stricter checks
    
    # Validate by trying to match canonical patterns strictly
    _validate_roman(s)
    
    return result


def _validate_roman(s):
    """Strict validation of canonical Roman numerals."""
    if not s:
        raise ValueError("Empty string")
    
    # Valid symbols
    valid_chars = set('IVXLCDM')
    if not set(s).issubset(valid_chars):
        raise ValueError("Invalid characters")
    
    # Check for repeated V, L, or D
    for char in 'VLD':
        if s.count(char) > 1:
            raise ValueError(f"Non-canonical: repeated {char}")
    
    # Check for more than 3 consecutive identical symbols
    i = 0
    while i < len(s):
        j = i
        while j < len(s) and s[j] == s[i]:
            j += 1
        run_length = j - i
        if run_length > 3 and s[i] in 'IXCM':
            raise ValueError(f"Non-canonical: more than 3 consecutive {s[i]}")
        elif run_length > 1 and s[i] in 'VLD':
            raise ValueError(f"Non-canonical: repeated {s[i]}")
        i = j
    
    # Now parse and verify structure
    # Use a state machine / greedy approach matching canonical forms
    
    # Define valid patterns in order
    patterns = [
        ('M', 1000), ('CM', 900), ('D', 500), ('CD', 400),
        ('C', 100), ('XC', 90), ('L', 50), ('XL', 40),
        ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)
    ]
    
    pos = 0
    last_matched_value = float('inf')
    
    while pos < len(s):
        matched = False
        for pattern, value in patterns:
            if s.startswith(pattern, pos):
                # Check ordering: must be strictly decreasing value (or equal for repeats)
                if value > last_matched_value:
                    raise ValueError("Non-canonical: invalid ordering")
                # For subtractive pairs, ensure we don't have invalid preceding context
                if pattern in ('CM', 'CD', 'XC', 'XL', 'IX', 'IV'):
                    # These are already valid; check that previous wasn't same or larger
                    if value >= last_matched_value and last_matched_value != float('inf'):
                        # Actually this is fine if last was larger, but we need strict
                        pass
                pos += len(pattern)
                last_matched_value = value
                matched = True
                break
        
        if not matched:
            raise ValueError("Non-canonical: invalid pattern")
    
    # Additional check: verify no ambiguous patterns like "IIX", "VX", etc.
    # The greedy parse above should catch most, but let's verify with a regex-like check
    
    # Check specific invalid patterns
    invalid_substrings = ['IIII', 'VV', 'LL', 'DD', 'XXXX', 'CCCC', 'MMMM',
                          'IL', 'IC', 'ID', 'IM', 'VX', 'VC', 'VD', 'VM',
                          'LC', 'LD', 'LM', 'XD', 'XM', 'DM']
    for bad in invalid_substrings:
        if bad in s:
            raise ValueError(f"Non-canonical: contains {bad}")
    
    # Verify the total value is in range
    total = 0
    i = 0
    while i < len(s):
        if i + 1 < len(s) and s[i:i+2] in {'CM': 900, 'CD': 400, 'XC': 90, 'XL': 40, 'IX': 9, 'IV': 4}:
            total += {'CM': 900, 'CD': 400, 'XC': 90, 'XL': 40, 'IX': 9, 'IV': 4}[s[i:i+2]]
            i += 2
        else:
            total += {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}[s[i]]
            i += 1
    
    if not (1 <= total <= 3999):
        raise ValueError("Value out of range")


def roman_to_int(s):
    if not s:
        raise ValueError("Empty string is not a valid Roman numeral")
    
    # Validate strict canonical form
    _validate_roman(s)
    
    # Now compute the value
    subtractive_pairs = {'CM': 900, 'CD': 400, 'XC': 90, 'XL': 40, 'IX': 9, 'IV': 4}
    single_values = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, 'M': 1000}
    
    result = 0
    i = 0
    while i < len(s):
        if i + 1 < len(s) and s[i:i+2] in subtractive_pairs:
            result += subtractive_pairs[s[i:i+2]]
            i += 2
        else:
            result += single_values[s[i]]
            i += 1
    
    return result