利用DeepSeek辅助编写优化求解Advent of Code 2024第9题 磁盘碎片整理第二部分的python程序

编程达人挑战赛·第4期 10w+人浏览 240人参与

这个用SQL实现暂时有困难,所以先清理思路,然后告诉DeepSeek按此编写。

对第9题第二部分的分析如下:依据此思路编写python程序,不做别的。
约束条件

  1. 只移动文件,新位置终点< 原位置起点,文件按编号从大到小移动,所以无需考虑文件移动后腾出的空位
  2. 一个空位可能用多次,只要 放入的文件总长度<=空位长度,因为文件没有移动二次的,同一个空位长度始终递减
    实现
    设置2个表,一个文件表,一个空位表,记录编号,起始终止位置,数据结构:4个数组fs, fe, ss,se,下标就是编号
    每次移动,记录文件新位置,更新空位表被填入的编号新起始终止位置
    外循环:文件ID递减就是从fs, fe倒序
    内循环:空位ID递增就是从ss, se正序,两种情况:当se>=fs时跳出,当找到合适空位填充完成跳出
    推论:循环终止条件,当前文件位置左侧没有空位
    或者 当前文件左侧文件(ID比它小)的长度都比左侧空位长度大,设置左侧文件长度最大值和空位最大值变量跟踪,也可考虑设置每种长度空位的最左编号变量跟踪,减少无谓搜索
    计算校验和只要对文件表计算,对fs,fe处理,下标*(终点-起点+1)累计求和

奇怪的是,他好像没看懂,还是按照自己的理解,按题目描述那样先把紧缩格式还原为文件块和空闲块实际布局,被我否定了。

你无需按原题实现blocks,只要按我上个提示中的分析思路编写即可,列表也无需动态增删元素,只要按下标操作即可。

他终于明白,
好的,我理解你的意思:我们不需要实际维护 blocks 列表,而是用四个数组 fs, fe, ss, se 分别表示文件和空闲区的起止位置,通过更新这些数组来模拟移动过程,最后计算校验和。

这次的程序可以运行,用时2秒,已经快于我在网上找的python程序。而且创造性地利用了等差数列求和公式计算校验和,避免逐个位置累加。

C:\d>timer64 python ds2409b.py
6363913128533


Kernel  Time =     0.015 =    0%
User    Time =     2.109 =   99%
Process Time =     2.125 =  100%    Virtual  Memory =     10 MB
Global  Time =     2.121 =  100%    Physical Memory =     15 MB

它关键的循环存在一个明显的可优化之处。

    # 2. 按文件ID从大到小移动
    for fid in range(n_files - 1, -1, -1):
        length = fe[fid] - fs[fid] + 1
        # 找最左边的能容纳的空闲区
        for si in range(n_spaces):
            if se[si] < fs[fid] and se[si] - ss[si] + 1 >= length:

继续提示

因为文件id永远大于空闲区id,位置也是,所以我把内循环改为 for si in range(fid): if se[si] - ss[si] + 1 >= length:其他没改,就从2秒多减少到0.9秒,请在此基础上,实现我说的设置每种长度空位的最左编号变量跟踪,这样range的下限还能改大,减少循环次数

他说
好的,我们可以维护一个字典 min_space_index,记录每种长度对应的最左空闲区的索引。这样在查找时可以直接从可能的最小索引开始。

改回来的程序确实快了很多,但结果不对。经过调试,初始化字典没问题,动态更新字典逻辑有误。去掉动态更新字典部分,比不用字典的版本也有所提高,用时0.742秒。
继续提示

更新最左最小索引有错误,比如2没有移动到新腾出的空闲位置,请修改

结果越改越慢,要3秒多

继续提示

这个版本比不更新索引更慢,当往一个空闲块填文件后,只有两种会受影响,1.原长度最左索引必然改变,2.新长度最左索引可能改为它,其他不受影响,所以不用全都更新,更新方法:把原长度最左索引改为这个块右边第一个等于原长度空闲,跳出循环。比较新长度最左索引,如果位置在当前块左,才更新,只提供过程中update_min_space_index的修改版本,初始化min_space_index不用变。其他代码也无需给出

他的输出

def update_min_space_index_optimized(changed_si, old_length, new_length):
    """优化更新:只更新受影响的长度"""
    # 1. 更新原长度 old_length 的最左索引
    if old_length in min_space_index and min_space_index[old_length] == changed_si:
        # 需要重新找 old_length 的最左索引
        new_min_index = n_spaces
        for si in range(changed_si + 1, n_spaces):
            if ss[si] <= se[si] and se[si] - ss[si] + 1 >= old_length:
                new_min_index = si
                break
        min_space_index[old_length] = new_min_index
    
    # 2. 如果新长度 > 0,更新新长度 new_length 的最左索引
    if new_length > 0:
        if new_length not in min_space_index or min_space_index[new_length] > changed_si:
            min_space_index[new_length] = changed_si

在移动文件后的更新部分这样调用:

# 更新空闲区
old_space_length = se[found_si] - ss[found_si] + 1
if old_space_length > length:
    # 部分使用
    ss[found_si] = new_end + 1
    new_space_length = old_space_length - length
    update_min_space_index_optimized(found_si, old_space_length, new_space_length)
else:
    # 完全用尽
    ss[found_si] = se[found_si] + 1
    update_min_space_index_optimized(found_si, old_space_length, 0)  # 新长度为0表示无效

按他说的改完后结果正确,用时还是0.7秒多,没有变化。

我把它的更新索引函数的关键语句复制粘贴到原有代码中,而不用函数调用,结果很好,一下子降到0.056秒。经过检查,文件和空闲块循环次数从25334129降到了14515。

最终代码如下

# Standard Library
import pathlib


def get_input(filename: str) -> list[str]:
    """
    Takes a filename and returns a list of lines from the file
    """
    output = []
    file = pathlib.Path(filename)
    with open(file) as f:
        for line in f:
            tmp_line = line.strip()
            output.append(tmp_line)
    return output


def solve():
    import sys
    data = get_input("2409-input.txt")[0] #sys.stdin.read().strip()

    # 1. 解析得到 files 和 spaces
    fs, fe = [], []
    ss, se = [], []
    pos = 0
    is_file = True
    file_id = 0
    for ch in data:
        length = int(ch)
        if is_file:
            fs.append(pos)
            fe.append(pos + length - 1)
            file_id += 1
        else:
            ss.append(pos)
            se.append(pos + length - 1)
        pos += length
        is_file = not is_file

    n_files = len(fs)
    n_spaces = len(ss)

    # 2. 初始化 min_space_index
    min_space_index = {}
    for length in range(1, max((se[i] - ss[i] + 1) for i in range(n_spaces)) + 1):
        # 找到第一个长度 >= length 的空闲区
        for si in range(n_spaces):
            if se[si] - ss[si] + 1 >= length:
                min_space_index[length] = si
                break
        else:
            min_space_index[length] = n_spaces  # 表示不存在
    cnt=0
    # 3. 按文件ID从大到小移动
    for fid in range(n_files - 1, -1, -1):
        length = fe[fid] - fs[fid] + 1
        
        # 确定搜索起点
        start_si = min_space_index.get(length, n_spaces)
        if start_si >= n_spaces:
            continue
            
        # 查找合适的空闲区
        found_si = -1
        for si in range(start_si, fid):  # 只搜索文件左边的空闲区
            cnt+=1
            if ss[si] > se[si]:  # 无效空闲区
                continue
            if se[si] < fs[fid] and se[si] - ss[si] + 1 >= length:
                found_si = si
                break
        if found_si == -1:
            continue
            
        # 移动文件
        cnt+=1
        new_start = ss[found_si]
        new_end = new_start + length - 1
        fs[fid], fe[fid] = new_start, new_end
        
        # 更新空闲区
        old_space_length = se[found_si] - ss[found_si] + 1
        if old_space_length > length:
            # 部分使用
            ss[found_si] = new_end + 1
            new_space_length = old_space_length - length
            # 更新 min_space_index[old_space_length],因为该空闲区变小,不符合要求
            for si in range(found_si + 1, fid):
                if ss[si] <= se[si] and se[si] - ss[si] + 1 >= old_space_length:
                    min_space_index[old_space_length] = si
                    break
            # 更新 min_space_index[new_space_length],因为该空闲区变小后比现有的min_space_index[new_space_length]更左

            if min_space_index[new_space_length] > found_si:  
                min_space_index[new_space_length] = found_si                           

        else:
            # 完全用尽
            ss[found_si] = se[found_si] + 1  # 标记无效
            # 更新 min_space_index[old_space_length],因为该空闲区消失
            for si in range(found_si + 1, fid):
                if ss[si] <= se[si] and se[si] - ss[si] + 1 >= old_space_length:
                    min_space_index[old_space_length] = si
                    break        

    # 4. 计算校验和
    checksum = 0
    for fid in range(n_files):
        start, end = fs[fid], fe[fid]
        #print(fid, start, end)
        checksum += fid * (start + end) * (end - start + 1) // 2
    print(checksum, cnt)

if __name__ == "__main__":
    solve()
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值