Created
May 25, 2021 07:30
-
-
Save victorusachev/95d1a99167a987c1e61e85a392c6a4a9 to your computer and use it in GitHub Desktop.
Implement the conversion of the dotted version string to an integer and reverse conversion. This approach can be used to compactly store large amounts of version data.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
Implement the conversion of the dotted version string to an integer and reverse conversion. | |
This approach can be used to compactly store large amounts of version data. | |
""" | |
import struct | |
from typing import List, Optional | |
import pytest | |
UNSIGNED_SHORT_FORMAT = 'H' # 0 <= number <= 65535 | |
UNSIGNED_SHORT_MAX_NUMBER = 65_535 | |
def _int_to_bytes(number: int, signed=False) -> bytes: | |
if not signed and number < 0: | |
raise ValueError | |
length = (8 + (number + (number < 0)).bit_length()) // 8 | |
_bytes = int.to_bytes(number, length=length, byteorder='big', signed=signed) | |
return _bytes | |
def _int_from_bytes(binary_data: bytes, signed=False) -> Optional[int]: | |
return int.from_bytes(binary_data, byteorder='big', signed=signed) | |
def _list_int_to_bytes(version_list: List[int]) -> bytes: | |
try: | |
version_bytes = struct.pack('<' + UNSIGNED_SHORT_FORMAT * len(version_list), *version_list) | |
except struct.error as exc: | |
raise ValueError from exc | |
return version_bytes | |
def _bytes_to_list_int(binary_data: bytes) -> List[int]: | |
version_list = [x[0] for x in struct.iter_unpack('<' + UNSIGNED_SHORT_FORMAT, binary_data)] | |
return version_list | |
def _list_int_to_dotted(version_list: List[int]) -> str: | |
dotted_version = '.'.join(map(str, version_list)) | |
return dotted_version | |
def _dotted_to_list_int(dotted_version: str) -> List[int]: | |
version_list = list(map(int, dotted_version.split('.'))) | |
return version_list | |
def encode_version_to_number(dotted_version: Optional[str]) -> Optional[int]: | |
if not dotted_version: | |
return None | |
version_list = _dotted_to_list_int(dotted_version) | |
version_list = [min(x, UNSIGNED_SHORT_MAX_NUMBER) for x in version_list] | |
version_list.insert(0, 1) # an additional value is needed to keep the leading zero | |
version_bytes = _list_int_to_bytes(version_list) | |
version_int = _int_from_bytes(version_bytes) | |
return version_int | |
def decode_version_from_number(endcoded_version: Optional[int]) -> Optional[str]: | |
if endcoded_version is None: | |
return None | |
version_bytes = _int_to_bytes(endcoded_version) | |
version_list = _bytes_to_list_int(version_bytes) | |
version_list.pop(0) # an additional value is needed to keep the leading zero | |
dotted_version = _list_int_to_dotted(version_list) | |
return dotted_version | |
@pytest.mark.parametrize( | |
'version', | |
[ | |
None, | |
'0', | |
'10', | |
'65535', | |
'0.1', | |
'0.0.0', | |
'0.1.0', | |
'14.4.2', | |
'87.0.4280.77', | |
'14.0.3.4', | |
'90.0.4430.212', | |
'90.0.818.66', | |
'0.1.2.3.65535.5', | |
] | |
) | |
def test_coding_versions(version: str) -> None: | |
encoded_version = encode_version_to_number(version) | |
decoded_version = decode_version_from_number(encoded_version) | |
assert decoded_version == version |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment