numpy数组元素同步变更及Tim Sort排序HDF5数组异常问题
问题1:numpy数组元素与列表元素同步变更问题
为何将2D numpy数组中的元素追加到列表后,修改原数组对应元素时,列表中的元素也会同步发生变化?
原因
numpy结构化数组(或对象数组)的单个元素本质是对原数组内存区域的引用/视图,而非独立副本。当你将数组元素追加到列表时,列表中存储的是该元素的引用,而非数据的拷贝。因此,修改原数组中的元素时,实际上是在修改同一个内存地址中的数据,列表里的元素自然会同步变化。
解决办法
如果需要避免这种同步变更,在将元素追加到列表前,创建元素的副本:
left.append(array[l + i].copy()) # 对单个元素创建副本 # 或者直接对切片创建副本 left = array[l:m+1].copy().tolist()
问题2:Tim Sort排序HDF5转numpy数组异常问题
尝试使用稳定排序算法Tim Sort对HDF5文件数据进行排序,已将HDF5文件中的数据转换为2D numpy数组,插入排序步骤执行正常,但调试时发现,在合并阶段将预排序的left或right数组元素逐一赋值给原数组时,left和right数组也会被修改,导致整个数据块内仅重复出现单个元素,排序逻辑被打乱,无法得到正确的排序结果。
异常原因
问题根源和问题1完全一致:在merge函数中,left和right列表直接追加了原numpy数组的元素引用,而非独立副本。当执行array[k] = left[i]或array[k] = right[j]时,若k的位置属于left或right对应的原数组区间,修改array[k]会同时改变left或right列表中对应位置的元素(因为它们指向同一块内存)。后续合并时,left/right的元素已经被篡改,最终导致所有元素被覆盖为同一个值。
修复后的代码
修改merge函数,创建left和right时使用元素副本:
from collections import deque import h5py import sys filename = sys.argv[1] with h5py.File(filename, "r") as f: a_group_key = list(f.keys())[0] ds_arr = f[a_group_key][()] MINIMUM= 32 def find_minrun(n): r = 0 while n >= MINIMUM: r |= n & 1 n >>= 1 return n + r def insertion_sort(array, left, right): for i in range(left+1,right+1): j = i while array[j-1][11]>array[j][11] and j>left : array[[j, j-1]] = array[[j-1, j]] j -= 1 return array def merge(array, l, m, r): array_length1= m - l + 1 array_length2 = r - m left = [] right = [] # 关键修改:创建元素副本而非直接引用 for i in range(0, array_length1): left.append(array[l + i].copy()) for i in range(0, array_length2): right.append(array[m + 1 + i].copy()) i=0 j=0 k=l while j < array_length2 and i < array_length1: if left[i][11] <= right[j][11]: array[k] = left[i] i += 1 else: array[k] = right[j] j += 1 k += 1 while i < array_length1: array[k] = left[i] k += 1 i += 1 while j < array_length2: array[k] = right[j] k += 1 j += 1 def tim_sort(array): n = len(array) minrun = find_minrun(n) for start in range(0, n, minrun): end = min(start + minrun - 1, n - 1) insertion_sort(array, start, end) size = minrun while size < n: # 修正变量名冲突:原循环变量名与merge函数内的left列表重名 for left_idx in range(0, n, 2 * size): mid = min(n - 1, left_idx + size - 1) right_idx = min((left_idx + 2 * size - 1), (n - 1)) merge(array, left_idx, mid, right_idx) size = 2 * size tim_sort(ds_arr)
额外说明:原代码中tim_sort函数的循环变量名left与merge函数内的left列表重名,虽不影响运行但易造成混淆,已修改为left_idx。
效果验证
修复后,left和right列表存储的是独立副本,修改原数组时不会篡改这两个列表的内容,合并阶段能正常比较元素值,最终得到正确的排序结果。
内容的提问来源于stack exchange,提问作者Prajjwal Das

