Advent of code

◂ 9 décembre 2024 ▸

  Disk Fragmenter : Déplacer des valeurs par bloc dans des endroits disponibles.
2378984580121520505930414446619015763552254 … 
Sûrement plus simple et propre en utilisant des listes chaînées plutôt que des tableaux.
J'ai créé une classe File juste pour pouvoir faire fichier.id au lieu du fichier[id] d'un dictionnaire et que c'est plus propre (mais moins rapide) que des fichier[1].
  1. code.py
  2. diff_versionBug_versionCorrigee.diffy
TEST = False
if TEST:
    line = "2333133121414131402"
else:
    with open("input.txt", 'r', encoding='utf-8') as f:
        line = f.read()[:-1]

NOT_FOUND = -1

class File():
    def __init__(self, length=0, id=None):
        self.length = length
        self.id = id

    def is_free_space(self):
        return self.id is None

    # This function converts [4,7] to [7,7,7,7]
    def get_blocks(self):
        return self.length * [self.id]

    def __repr__(self):
        if self.is_free_space():
            return f"{{{self.length} empty block{'s' if self.length > 1 else ''}}}"
        return f"{{File #{self.id} × {self.length} blocks}}"

    # This function converts  files [[2,7], [3,None], [1,5]]  to  blocks [7,7,None,None,None,5]
    @staticmethod
    def to_blocks(files):
        return sum((file.get_blocks() for file in files), [])


system1_blocks = []     # list of blocks of the system
system2 = []            # list of files of the system (file id + number of blocks)
for i,c in enumerate(line):
    file = File(length = int(c))
    if i % 2 == 0:
        file.id = i//2
    system1_blocks.extend(file.get_blocks())
    system2.append(file)

### Could have just created system1_blocks as:   system1_blocks = File.to_blocks(system2)


# Part 1: moving block by block
def defrag_part1(system):
    free_pos = next((k for k in range(len(system)) if system[k] is None), NOT_FOUND)   # like system[k].index(None) but NOT_FOUND (=-1) if nothing found
    last_block = system.pop()
    while free_pos != NOT_FOUND:
        system[free_pos] = last_block
        last_block = system.pop()
        while last_block is None:
            last_block = system.pop()
        free_pos = next((k for k in range(free_pos + 1, len(system)) if system[k] is None), NOT_FOUND)   # search from free_pos+1 to speed things up
    system.append(last_block)
    #print(''.join([str(c)+'/' if c is not None else '.' for c in system]))
    return system


# Part 2: moving whole file segment of blocks
def defrag_part2(files):
    file_index = len(files)

    #### OPTIMIZATION :  if there's no place for some file, there will be no place for later file with same size or more ####
    length_not_available = max(file.length for file in files) + 1   # or just make it 10 as length are 1 digit in the input 
    
    while file_index > 0:
        file_index -= 1
        while files[file_index].is_free_space():
            file_index -= 1
            if file_index == 0:
                return files
        file = files[file_index]
        if file.length >= length_not_available:
            continue
        unalloc_index = next((index for index in range(file_index) if files[index].is_free_space() and files[index].length >= file.length), NOT_FOUND)
        if unalloc_index == NOT_FOUND:
            length_not_available = file.length
            continue
        if files[unalloc_index].length > file.length:   # separate empty blocks into two parts
            files[unalloc_index].length -= file.length
            files.insert(unalloc_index, File(file.length))
            file_index += 1
        files[unalloc_index], files[file_index] = files[file_index], files[unalloc_index]  # move file
    return files


def checksum(blocks):
    return sum(i * blocks[i] for i in range(len(blocks)) if blocks[i] is not None)

print("Réponse partie 1:", checksum(defrag_part1(system1_blocks)))
print("Réponse partie 2:", checksum(File.to_blocks(defrag_part2(system2))))
# should be 63614979355824 and 97898222299196