diff --git a/reimplement/set.py b/reimplement/set.py index 02d6077..aefaff8 100644 --- a/reimplement/set.py +++ b/reimplement/set.py @@ -45,6 +45,22 @@ def int_to_char(value: int) -> str: raise ValueError("Value must be in the range [0, 63] for base64 encoding.") +def char_to_int(char: str) -> int: + """Convert a base64 character to its corresponding integer value.""" + if "0" <= char <= "9": + return ord(char) - ord("0") + elif "a" <= char <= "z": + return ord(char) - ord("a") + 10 + elif "A" <= char <= "Z": + return ord(char) - ord("A") + 36 + elif char == "+": + return 62 + elif char == "/": + return 63 + else: + raise ValueError("Character must be a valid base64 character.") + + def encode_golomb_Mshift(cnt: int, bpp: int) -> int: """Calculate Mshift paramter for encoding.""" Mshift = bpp - (cnt.bit_length() - 1) - 1 @@ -122,10 +138,117 @@ def encode_set(hash_values: List[int], bpp: int) -> str: return int_to_char(bpp) + int_to_char(Mshift) + base64_chars -def rpmsetcmp(): +def decode_base64(encoded_str: str) -> List[int]: + """Decode a base64 string into a list of bits.""" + bits = [] + + for char in encoded_str: + value = char_to_int(char) + for i in range(6): + bits.append((value >> (5 - i)) & 1) + + return bits + + +def decode_golomb(bits: List[int], Mshift: int) -> List[int]: + """Decode a list of bits encoded with Golomb coding into delta values.""" + delta_values = [] + i = 0 + + while i < len(bits): + q = 0 + + while i < len(bits) and bits[i] == 0: + q += 1 + i += 1 + + if i >= len(bits): + if q > 5: + raise ValueError("Invalid Golomb encoding: too many leading zeros.") + break + + i += 1 # skip the separator bit + + r = 0 + for j in range(Mshift): + if i < len(bits): + if bits[i] == 1: + r |= 1 << j + else: + i += 1 + + delta_value = (q << Mshift) | r + delta_values.append(delta_value) + + return delta_values + + +def decode_delta(delta_values: List[int]) -> List[int]: + """Decode a list of delta values into hash values.""" + last_hash = delta_values[0] + hash_values = [last_hash] + + for i in range(1, len(delta_values)): + hash_values.append(hash_values[i - 1] + delta_values[i]) + + return hash_values + + +def downsample_set(hash_values: List[int], bpp: int) -> List[int]: + """Reduce a set of (bpp + 1) values to a set of bpp values, keeping it sorted.""" + # WIP pass +def decode_set_init(encoded_str: str) -> Tuple[int, int]: + """Decode the initial part of the set-string to extract bpp and Mshift.""" + if len(encoded_str) < 2: + raise ValueError("Encoded string is too short to contain bpp and Mshift.") + + bpp = char_to_int(encoded_str[0]) + Mshift = char_to_int(encoded_str[1]) + + assert check_bpp(bpp), "bpp must be between 2 and 64" + assert check_Mshift(Mshift), "Mshift must be between 1 and 63" + + return bpp, Mshift + + +def decode_set(encoded_str: str, Mshift: int) -> List[int]: + """Decode the set-string representation into a list of hash values.""" + data_str = encoded_str[2:] + + str_bits = decode_base64(data_str) + str_delta = decode_golomb(str_bits, Mshift) + hash_values = decode_delta(str_delta) + + return hash_values + + +def rpmsetcmp(str1: str, str2: str) -> int: + if str1[:3] == "set:": + str1 = str1[3:] + if str2[:3] == "set:": + str2 = str2[3:] + + bpp1, Mshift1 = decode_set_init(str1) + bpp2, Mshift2 = decode_set_init(str2) + hash_values1 = decode_set(str1, Mshift1) + hash_values2 = decode_set(str2, Mshift2) + + while bpp1 > bpp2: + hash_values1 = downsample_set(hash_values1, bpp1) + bpp1 -= 1 + + while bpp2 > bpp1: + hash_values2 = downsample_set(hash_values2, bpp2) + bpp2 -= 1 + + # WIP + + return 0 + + def set_new() -> Set: """Create a new Set instance with cnt initialized to 0 and an empty list of labels.""" return Set(cnt=0, labels=[]) @@ -190,3 +313,18 @@ if __name__ == "__main__": result = set_fini(my_set, 8) print(result) set_free(my_set) + + # example with comparison + set1 = set_new() + set_add(set1, "label1") + set_add(set1, "label2") + str1 = set_fini(set1, 8) + set2 = set_new() + set_add(set2, "label1") + set_add(set2, "label2") + set_add(set2, "label3") + str2 = set_fini(set2, 8) + assert str1 is not None and str2 is not None, "Set strings should not be None" + comparison_result = rpmsetcmp(str1, str2) + assert comparison_result == -1, "Expected str1 to be less than str2" + print(comparison_result)