PHP实现:选中行按指定起始位置重排display_order
问题描述
面试CRUD任务中的排序重排需求:需按display_order列排序展示数据,支持勾选多行后指定起始位置重排,规则如下:
- 示例1:起始位置为2时,勾选行(id:86、77、44)从位置2开始依次排列,其余未勾选行按原展示顺序(排除已勾选行)依次后移
- 示例2:起始位置为8时,勾选行从位置8开始排列,原位置8及之后的未勾选行自动后移
现有reorder_list函数输出结果不符合预期,需调试修正。
现有代码
def reorder_list(queryset, sortable_data): selected_ids = sortable_data.get('selected_ids', []) start_position = sortable_data.get('start_position', 1) # 分离选中和未选中的项 selected_items = [item for item in queryset if item.id in selected_ids] unselected_items = [item for item in queryset if item.id not in selected_ids] # 重新排序 reordered = [] # 添加起始位置之前的未选中项 reordered.extend(unselected_items[:start_position-1]) # 添加选中项 reordered.extend(selected_items) # 添加剩余的未选中项 reordered.extend(unselected_items[start_position-1:]) # 更新display_order for index, item in enumerate(reordered, start=1): item.display_order = index return reordered
测试数据
QuerySet数据(按display_order初始排序)
| id | display_order |
|---|---|
| 101 | 1 |
| 95 | 2 |
| 86 | 3 |
| 77 | 4 |
| 62 | 5 |
| 44 | 6 |
| 31 | 7 |
| 22 | 8 |
| 15 | 9 |
Sortable请求数据
示例1请求
{ "selected_ids": [86,77,44], "start_position": 2 }
预期结果:
| id | display_order |
|---|---|
| 101 | 1 |
| 86 | 2 |
| 77 | 3 |
| 44 | 4 |
| 95 | 5 |
| 62 | 6 |
| 31 | 7 |
| 22 | 8 |
| 15 | 9 |
示例2请求
{ "selected_ids": [86,77,44], "start_position": 8 }
预期结果:
| id | display_order |
|---|---|
| 101 | 1 |
| 95 | 2 |
| 62 | 3 |
| 31 | 4 |
| 22 | 5 |
| 15 | 6 |
| 86 | 7 |
| 77 | 8 |
| 44 | 9 |
问题分析
原代码的核心错误:
- 未保证输入顺序正确性:直接使用传入的
queryset,但QuerySet默认排序可能不是按display_order,导致后续分离选中/未选中项的顺序与展示顺序不符。 - 起始位置切片逻辑错误:
unselected_items是排除选中项后的列表,其索引与原始展示顺序的位置无对应关系,直接用unselected_items[:start_position-1]会导致起始位置前后的项截取错误。
修正后的代码
def reorder_list(queryset, sortable_data): selected_ids = set(sortable_data.get('selected_ids', [])) start_position = sortable_data.get('start_position', 1) # 确保按display_order排序,保证处理顺序与展示顺序一致 sorted_items = sorted(queryset, key=lambda x: x.display_order) selected = [] pre_unselected = [] post_unselected = [] # 遍历排序后的项,按规则划分三组 for item in sorted_items: if item.id in selected_ids: selected.append(item) else: # 原始位置从1开始计数 original_pos = sorted_items.index(item) + 1 if original_pos < start_position: pre_unselected.append(item) else: post_unselected.append(item) # 拼接最终排序列表 reordered = pre_unselected + selected + post_unselected # 更新display_order字段 for idx, item in enumerate(reordered, start=1): item.display_order = idx return reordered
优化版(避免O(n)的index查询)
def reorder_list(queryset, sortable_data): selected_ids = set(sortable_data.get('selected_ids', [])) start_position = sortable_data.get('start_position', 1) # 按display_order排序,同时记录原始位置 sorted_with_pos = [(item, idx+1) for idx, item in enumerate(sorted(queryset, key=lambda x: x.display_order))] selected = [] pre_unselected = [] post_unselected = [] for item, original_pos in sorted_with_pos: if item.id in selected_ids: selected.append(item) else: if original_pos < start_position: pre_unselected.append(item) else: post_unselected.append(item) reordered = pre_unselected + selected + post_unselected for idx, item in enumerate(reordered, start=1): item.display_order = idx return reordered
代码说明
- 强制排序:先将QuerySet按
display_order排序,确保后续处理的是正确的展示顺序。 - 高效划分:使用集合存储选中ID,将判断操作的时间复杂度从O(n)降到O(1)。
- 精准分组:根据每个未选中项在原始展示顺序中的位置,划分到起始位置前/后两组,保证未选中项的相对顺序不变。
- 正确拼接:按「起始位置前的未选中项 → 选中项 → 起始位置后的未选中项」的顺序拼接,完全符合需求规则。
内容的提问来源于stack exchange,提问作者Hey It's Cheff
相关产品推荐
相关产品推荐

