Python List Rotate: 方法汇总

博客围绕Python数组元素向右循环移位问题展开,要求空间复杂度为O(1)。介绍了三种解法:一是直接循环移位,可对移动次数取模;二是通过三次反转实现移位,时间复杂度低;三是利用Python列表负索引特性重构数组,是否满足空间复杂度要求待探讨。

问题描述

给定一个python的list和一个正整数k,考虑将数组内的元素向右shiftk个位置(感觉用shift表示比较丝滑一些,其实类似于循环移位操作)。
e.g.

list_to_be_shifted=[1,2,3,4,5,6,7]
bitnum = 3
shift(list)
>>> list
[5, 6, 7, 1, 2, 3, 4]

问题本身的解决方案很多,但是题目要求使用空间复杂度为O(1)来实现,也就是利用原数组就地正法,这样一来对题目就多了不少限制。

开干

解法一

最直接的办法当然就是直接开干一波带走,你让我移k个位置,那我就一个个来呗,看谁先把谁送走了(这玩笑开不得,毕竟它只是一堆破 铜 烂 铁,Markdown不需要引号)。所以干法如下:

def shift(self, nums: list, k: int) -> None:
    """
    Do not return anything, modify nums in-place instead.
    """
    while k > 0:
        temp = nums[-1]
        for i in range(len(nums) - 1):
            nums[len(nums) - 1 - i] = nums[len(nums) - 2 - i]
        nums[0] = temp
        k = k - 1

值得注意的一点是,当数组长度len(nums)<k时,我们并不需要老老实实移动k次,将k直接对len(nums)取模即可,因此上面可以加上一句

k = k % len(nums)

鲁迅先生说过(好吧其实不是他说的):只要是循环结构我们都可以轻易地把它变成递归。但是在这里使用递归的话其实增加了堆栈开销,并不满足要求的空间复杂度,这里只是给一个思路:

def shift_recursion(self, nums: list, k: int) -> None:
    """
    Do not return anything, modify nums in-place instead.
    """
    k = k % len(nums)
    
    if k > 0:
        temp = nums[-1]
        for i in range(len(nums) - 1):
            nums[len(nums) - 1 - i] = nums[len(nums) - 2 - i]
        nums[0] = temp
        self.rotate_recursion(nums, k - 1)

这一做法本质上与循环结构没有大的区别,不再多说。

解法二,Reverse真香!

其实抛开一位位移动的想法,我们很容易就可以想到,为什么不一下子把后面待移动的k位直接放到前面去?这样子不是既省时又省力吗?好家伙,就这么干。稍加思索我们不难发现,所谓的这种移位操作,可以直接通过三次反转来实现!各位看官们,动一动小脑筋,问题就是这么简单(其实俺也没想到可以这么干,Leetcoder牛逼就完事了,自己画了一张丑图,大家将就着看看)。
在这里插入图片描述
可以看到,只需要简单的三次Reverse即可实现题目所要求的功能,可以说是大道至简,具体的python实现如下:

def reverse(self, array: list, begin: int, end: int) -> None:
    while begin < end:
        temp = array[end]
        array[end] = array[begin]
        array[begin] = temp
        begin += 1
        end -= 1


def shift_reverse(self, nums: list, k: int) -> None:
    if k == 0:
        return
    if k > len(nums):
        k = k % len(nums)
    
    nums.reverse()
    self.reverse(nums, 0, k - 1)
    self.reverse(nums, k, len(nums) - 1)

以上代码可以完美实现所要求的功能,且时间复杂度大大降低(使用上面逐个移位的方法对于大型的输入数组是会timeout的…大家注意了)。

解法三,充分利用Python特性

对于Python的list对象,负索引是一个创造性的发明(反正俺在以前学过的语言里面没见过,暂且认为它是具有创造性的叭,有问题提请大家指正!),这样一来,本身我就可以把数组的一个位置当做数组的头部,对数组进行重排列,这正是我们这一问题的本源!其实所谓的旋转,不过是换了数组的起始位置,旋转两位,其实就是从 -2位开始当做数组头,一直到-3位到达数组尾(当然这里的 -3也就是 +(len(nums) - k)),这是我们重构数组时索引结束的位置,那么使用列表生成式直接构建旋转后的数组的两种方法如下:

# method1
# Actually for python, we can use list generator and negative index
def rotate_generator(self, nums: list, k: int) -> None:
    nums[:] = nums[-k: len(nums)] + nums[0: -k]
# method2
def rotate_generator_new(self, nums: list, k: int) -> None:
    nums[:] = [nums[i] for i in range(- (k % len(nums)), len(nums) - (k % len(nums)))]

关于上述两种方法是否满足空间复杂度为 **O(1)**的要求,还请大家自行理解(就我个人的实验结果来看,方法一在旋转前后,nums指针的值未发生变化,至少说没有开辟新的堆内存空间,但是我觉得它不是空间复杂度为O(1)的实现方法,如有错误还请指正)。

self.ico.ClearLayer() # self.ico.DispWork(sig_layer) # self.ico.DispLayer(sm_layer) # self.ico.DispLayer(dat_layer) self.__renderPDF(sig_layer) #在找到的那一层操作,正面或背面 self.ico.DelLayer(self.tmpLays) mes = f'输出目录:{self.filePath},继续将打开PDF' ans = messageBox.showMessage( bitmap='information', title='PDF输出完成', message=mes, buttons=['退出', '继续']) if ans == '继续': os.system(f"/usr/bin/evince {self.filePath} &") self.incam.COM('disp_on') # TODO 转换成png 放到output里面 self.__pdf2PNG() return 0 def __pdf2PNG(self): cmd = f"convert -density 120 -quality 80 -background white -alpha remove {self.filePath} {self.imgPath}" os.system(cmd) def __renderPDF(self, sig_lay): """ 渲染PDF:设置文档结构并构建内容 """ canv = ReportCanvas(self.filePath, pagesize=self.pageSize) self.canvas = canv self.drawingParams = self.__setTemplateParams() # 设置模板的默认参数 self.bgTemp = DrawingTemplate( canv, A4[0], A4[1], self.drawingParams) lM = 0 rM = 0 tM = 0 bM = 0 self.doc = SimpleDocTemplate(self.filePath, pagesize=self.pageSize, topMargin=tM, bottomMargin=bM, leftMargin=lM, rightMargin=rM, title="MI-13", author=self.userCN)#filePath:最终存放路径; pageSize:画布大小 self.__setPageFrame(self.doc) story = [] story.append(NextPageTemplate('p1')) g2c = self.__createGerber(sig_lay) story.append(FrameBreak()) story.append(g2c) story.append(PageBreak()) self.doc.build(story) # def __setPageFrame(self, doc: SimpleDocTemplate): """设置每一页框架分布 """ frames = [] fh = doc.height / 3 padX = self.drawingParams['padx'] padY = self.drawingParams['pady'] tableFrame = Frame(x1=padX, y1=padY + fh * 2, width=doc.width, height=fh, id='f1') gerberFrame = Frame( x1=padX, y1=padY, width=doc.width, height=fh * 2, id='f2') frames.append(tableFrame) frames.append(gerberFrame) doc.addPageTemplates([PageTemplate(id='p1', frames=frames)]) def getMergeLay(self, lay): """ 将lay备份并将备份层合并为surface """ mergeLay = f'{lay}_merge' self.ico.DelLayer(mergeLay) self.ico.ClearAll() self.ico.DispWork(lay, number=1) self.incam.COM( f'sel_copy_other,dest=layer_name,target_layer={mergeLay},invert=no,dx=0,dy=0,size=0,x_anchor=0,y_anchor=0') self.ico.DispWork(mergeLay, number=1) self.incam.COM( 'sel_cont_resize,accuracy=25.4,break_to_islands=yes,island_size=0,hole_size=0,drill_filter=no,corner_ctl=no') return mergeLay def getDnxSigLayMapping(self, lay: str): """ 获取dnx孔层与其钻带的起始信号层之间的映射关系(1:1) :param dnxLayers:线路层 :return:起始终止是线路层的所有钻孔 """ dnxSigLayMapping = [] for drl in self.ico.GetLayerMatrix()['drlAllLay']: startLay = self.ico.GetLayerMatrix()['drlThrough'][drl]['start'] endLay = self.ico.GetLayerMatrix()['drlThrough'][drl]['end'] if startLay == lay or endLay == lay: dnxSigLayMapping.append(drl) return dnxSigLayMapping # 分析创建光绘的关键部分 def __createGerber(self, sigLay): layerMatrix = self.ico.GetLayerMatrix() sig_out_list = layerMatrix['sigOutLay'] sm_lay_list = layerMatrix['smAllLay'] toRead = [] # 1. 合并信号层为 surface mergeSigLay = self.getMergeLay(sigLay) self.tmpLays.append(mergeSigLay) mergeSigLayFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, mergeSigLay) toRead.append(Feature(mergeSigLayFilePath, layerType='signal')) # 3. 添加阻焊层 idx = sig_out_list.index(sigLay) # 获取对应的阻焊层 sm_layer = sm_lay_list[idx] maskFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, sm_layer) toRead.append(Feature(maskFilePath, layerType='solder_mask')) # 2. 添加钻孔层 drlSet = self.getDnxSigLayMapping(sigLay) for drl in drlSet: drlFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, drl) toRead.append(Feature(drlFilePath, layerType='document')) # === 创建高亮圆圈层 === highlight_layer = "pofv_highlight_circle" if self.ico.IsLayerExist([highlight_layer]): self.ico.DelLayer([highlight_layer]) self.ico.CreateOrEmptyLay(layer_list=[highlight_layer]) # 获取要高亮的孔坐标(只标第一个) if sigLay not in self.sigDimension or not self.sigDimension[sigLay]['point_x']: # 没有坐标,跳过画圈 pass else: x = float(self.sigDimension[sigLay]['point_x'][0]) y = float(self.sigDimension[sigLay]['point_y'][0]) self.ico.ClearLayer() self.ico.DispWork(highlight_layer) self.incam.COM(f"add_pad,symbol=r180,polarity=positive,x={x},y={y},mirror=no,angle=0,direction=ccw,resize=0,xscale=1,yscale=1") self.incam.COM("sel_feat2outline,width=3.0,location=on_edge,offset=0.1,polarity=as_feature,keep_original=no,text2limit=no")# 轮廓线 self.incam.COM("arc2lines,arc_line_tol=1") self.ico.DelLayer(highlight_layer + '+++') self.ico.ClearLayer() # sys.exit(0) # 高亮层也导出为 Gerber Feature self.tmpLays.append(highlight_layer) # 确保后续清理" hlight_path = self.ico.getFeatureFile(self.JOB, self.workStep, highlight_layer) toRead.append(Feature(hlight_path, layerType='signal', strokeColor = "#fcfcf6")) # === 计算尺寸和偏移 === unitSizeX, unitSizeY = self.ico.GetStepSize(self.workStep)[0:2] gbWidth = self.doc.width * 0.6 gbHeigth = self.doc.height * 2 / 3 * 0.6 offsetX = self.getOffsetXY(gbWidth, gbHeigth, unitSizeX, unitSizeY, pagesize=( self.doc.width, self.doc.height * 2 / 3))[0] # 创建绘图对象 g2c = Gerber2Canvas( gbWidth, gbHeigth, unitSizeX, unitSizeY, offsetX, 0.1, Origin.leftdown, self.canvas, toRead, rotate=0 ) # === 添加标注箭头=== dimension = [] if sigLay in self.sigDimension and self.sigDimension[sigLay]['point_x']: pointX = self.sigDimension[sigLay]['point_x'][0] pointY = self.sigDimension[sigLay]['point_y'][0] dimensionSingle = Dimension( x0=unitSizeX / 2, y0=unitSizeY * 1.2, x1=float(pointX), y1=float(pointY), direct=Direct.one_arrow, dist='POFV孔铜厚度测量位置', dimColor="#0400FF", # dimLineColor="#0800FF" # ) dimension.append(dimensionSingle) if dimension: g2c.addDimension(dimension) return g2c @staticmethod def getOffsetXY(gbWidth: float, gbHeight: float, unitSizeX: float, unitSizeY: float, pagesize: tuple = (A4[0], A4[1])): """ 获取使Gerber在PDF中居中显示的偏移量 :param gbWidth: pdf中 gerber宽度 :param gbHeight: pdf中 gerber长度 :param unitSizeX: unit宽 :param unitSizeY: unit长 :param pagesize: PDF宽和长 :return: x,y的偏移量 # 创建绘图对象 g2c = Gerber2Canvas(gbWidth,gbHeight,unitSizeX,unitSizeY,offsetX, offsetY, Origin.leftdown,self.canvas,toRead, rotate=0) """ scale1 = math.ceil(gbWidth / unitSizeX) scale2 = math.ceil(gbHeight / unitSizeY) scale = scale1 if scale1 < scale2 else scale2 offsetX = (pagesize[0] - unitSizeX * scale) / (2 * scale) offsetY = (pagesize[1] - unitSizeY * scale) / (2 * scale) return offsetX, offsetY 还有个问题,# === 正确添加阻焊层 === sm_layer = sm_lay_list[idx] maskFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, sm_layer) toRead.append(Feature(maskFilePath, layerType='solder_mask')) # 在绘制阻焊层的时候,在整体顺时针转了90度
最新发布
12-02
你遇到的问题: > **阻焊层(solder mask)在绘制时整体顺时针旋转了 90°** 这是一个典型的 **坐标系对齐问题**,尤其是在使用 `Gerber2Canvas` 渲染多图层时,某些图层可能因为原始数据方向不一致或未统一归一化而导致显示偏转。 --- ## 🔍 问题定位 你在代码中这样添加阻焊层: ```python toRead.append(Feature(maskFilePath, layerType='solder_mask')) ``` 而其他图层(如信号层、高亮层)没有旋转,唯独 `solder_mask` 层旋转了 —— 说明: ✅ 阻焊层的 Gerber 文件本身是“横向布局” ❌ 渲染器没有自动纠正其方向 🔧 `Gerber2Canvas` 没有为该图层做角度补偿 --- ## ✅ 根本原因分析 ### 可能原因 1:Gerber 文件内部定义了非零旋转(APERTURE 或 HEADER 中有 `MOMENT`, `ROT` 等) 虽然标准 Gerber 不支持旋转指令,但有些工具导出时会通过: - 坐标变换预处理 - 使用非标准扩展(如 IPC-356A + 用户自定义) - 图形镜像/旋转后重新生成坐标 导致实际图形方向与信号层不一致。 ### 可能原因 2:InCam 内部显示正常,但导出的 `.gbr` 文件坐标已旋转 你在 InCam 界面看到的是“逻辑视图”,而 `getFeatureFile()` 导出的是“物理文件”。如果该层在数据库里被标记为旋转过,但导出时没还原 → 输出文件就是转过的。 ### 可能原因 3:`Gerber2Canvas` 对 `layerType` 的渲染行为不同 比如它对 `'signal'` 和 `'solder_mask'` 使用不同的默认 `rotate` 参数(例如基于模板配置),也可能导致差异。 --- ## ✅ 解决方案汇总 ### ✅ 方案一:【推荐】在 `Feature` 中显式设置 `rotate` 角度(临时修正) 由于你发现只有阻焊层转了 90°,我们可以手动逆向旋转回来: ```python toRead.append( Feature( maskFilePath, layerType='solder_mask', rotate=-90, # 逆时针转 90° 补偿顺时针偏差 strokeColor='#00FF00', strokeWidth=0.1 ) ) ``` 📌 注意: - `rotate` 单位是度(degree) - 正值 = 逆时针;负值 = 顺时针 - 所以你要“抵消顺时针90°”,就写 `-90` --- ### ✅ 方案二:检查并强制重置阻焊层的方向(前置修复) 在调用 `getFeatureFile` 前,确保该层没有旋转属性: ```python # 在 getMergeLay 类似地处理 sm_layer self.ico.ClearAll() self.ico.DispWork(sm_layer, number=1) # 如果你知道这个层需要旋转回来,先用 Incam 命令修正 self.incam.COM("sel_rotate,angle=-90,center_x=0,center_y=0") # 尝试预矫正 corrected_sm = f"{sm_layer}_corrected" self.ico.CreateOrEmptyLay([corrected_sm]) self.incam.COM(f"sel_copy_other,dest=layer_name,target_layer={corrected_sm},...") maskFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, corrected_sm) ``` 然后添加: ```python toRead.append(Feature(maskFilePath, layerType='solder_mask')) # 已矫正 ``` --- ### ✅ 方案三:统一所有图层的输出前处理(最佳实践) 写一个通用函数来“标准化图层方向”: ```python def export_standardized_layer(self, layer_name, target_name=None): """导出一个标准化方向的图层(无旋转、居中)""" if target_name is None: target_name = layer_name + "_std" self.ico.ClearAll() self.ico.DispWork(layer_name, number=1) # 查询是否有旋转属性?假设你能获取 metadata meta = self.ico.GetLayerProps(layer_name) # 假设存在此方法 angle = meta.get('rotate', 0) if abs(angle) > 1e-3: self.incam.COM(f"sel_rotate,angle={-angle},center_x=0,center_y=0") self.ico.CreateOrEmptyLay([target_name]) self.incam.COM(f"sel_copy_other,dest=layer_name,target_layer={target_name},...") return self.ico.getFeatureFile(self.JOB, self.workStep, target_name) ``` 使用方式: ```python std_sm_path = self.export_standardized_layer(sm_layer, f"{sm_layer}_std") toRead.append(Feature(std_sm_path, layerType='solder_mask')) ``` --- ### ✅ 方案四:修改 `Gerber2Canvas` 全局行为(高级) 如果你控制 `sublib/Gerber2SVG.py`,可以在 `quickInit` 或解析阶段加入自动检测和校正逻辑: ```python # 伪代码:在 Gerber2SVG.py 中 if self.layerType == 'solder_mask': self.rotate = getattr(feature, 'rotate', -90) # 默认补 -90 ``` 或者从文件名判断是否需矫正: ```python if 'SM_L' in os.path.basename(filePath) and 'BOT' not in filePath: self.rotate = -90 ``` 但这属于侵入式修改,建议优先用上层解决。 --- ## 🧪 如何验证是不是文件本身就旋转了? 你可以手动打开 `maskFilePath` 这个 `.gbr` 文件,查看内容: ```bash head -n 50 "$maskFilePath" ``` 看看有没有类似: ``` G70 G75 G36 X100000Y200000D02* X100000Y300000D01* ... ``` 再对比信号层的坐标走向。如果一个是水平走线为主,另一个是垂直,则确认方向不同。 也可以用 [https://gerber-viewer.com](https://gerber-viewer.com) 分别上传两个文件看效果。 --- ## ✅ 最终建议流程(你现在马上可以做的) ```python # === 添加阻焊层(带旋转补偿)=== idx = sig_out_list.index(sigLay) sm_layer = sm_lay_list[idx] maskFilePath = self.ico.getFeatureFile(self.JOB, self.workStep, sm_layer) toRead.append( Feature( maskFilePath, layerType='solder_mask', rotate=-90, # ✅ 关键:补偿顺时针90度 strokeColor='#008000', # 可选:绿色表示阻焊 strokeWidth=0.05 ) ) ``` 👉 加了 `rotate=-90` 后,应该就能和其他图层对齐! --- ## ✅ 总结 | 问题 | 原因 | 解法 | |------|------|------| | 阻焊层顺时针转90° | 文件方向与其他层不一致 | 显式设置 `rotate=-90` | | 多图层错位 | 缺少统一归一化步骤 | 前置标准化处理 | | 仅个别层异常 | 渲染器对 `layerType` 区别对待 | 统一风格或改类型 | > ⚠️ 提醒:不要依赖“视觉上看起来对”而不改底层逻辑。长期应建立“图层标准化导出”机制。 --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值