Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter

加入好友
加入社群
Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

目標情境
你有一組按出現順序的區塊(例如 headings),
每個有一個開始位置 start,但還沒有 end。
想要自動補出每個 heading 的 end:

目前節點的結束 = 下一個節點的開始
最後一個節點的結束 = 全部元素總長度(total_elements)
原始程式片段

starts = [h['start'] for h in headings]
ends = starts[1:] + [total_elements]  
# len(ends) == len(headings)
for h, e in zip(headings, ends):
    h['end'] = e

為什麼這樣寫?
starts:收集每個區塊起點
starts[1:]:往右平移(對齊為「下一個開始」)

  • [total_elements]:補最後一個區塊的結束邊界
    zip(headings, ends):一一對應(第 i 個 heading 對應第 i 個 end)
    這種方法避免用索引 for i in range(len(headings)):,更直觀且不容易 off-by-one。

簡化示例
假設:

headings = [
    {'title': 'A', 'start': 0},
    {'title': 'B', 'start': 10},
    {'title': 'C', 'start': 25},
]
total_elements = 40 #文件最結尾元素的index

步驟:

starts = [0, 10, 25]
starts[1:] = [10, 25]
ends = [10, 25] + [40]  → [10, 25, 40]
zip 對應
  A -> 10
  B -> 25
  C -> 40

輸出:

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

視覺化

時間軸:

A: [0          10)
B:           [10      25)
C:                       [25        40)

每個區塊半開區間:start 包含、end 不含(常見設計,方便相鄰無重疊)。

等價(索引版本)對照(不建議)

for i, h in enumerate(headings):
    if i < len(headings) - 1:
        # len(headings) - 1 等效於
        # headings.index(headings[-1])
        h['end'] = headings[i+1]['start']
    else:
        h['end'] = total_elements

雖然功能相同,但 zip 寫法更扁平、少條件分支。

常見延伸

  1. 適用於:章節區間、Token 區間、影片片段、Log 段落、程式語法節點範圍
  2. 若需要「長度」也可以:
for h, e in zip(headings, ends):
    h['end'] = e
    h['length'] = e - h['start']

常見錯誤提醒

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

如果包含被刪除節點?
在建立 starts 之前要先過濾掉已移除的 heading,否則 end 會對錯物件。

推薦hahow線上學習python: https://igrape.net/30afN

使用next 與 迭代器 處理:

headings = [
    {'title': 'A', 'start': 0},
    {'title': 'B', 'start': 10},
    {'title': 'C', 'start': 25},
]
total_elements = 40 #文件最結尾元素的index

it = iter(headings)          # 取得一個會記住位置的迭代器
prev = next(it, None)        # 取出第一個元素 headings 為空回傳 None

if prev is not None:         # 空列表時直接略過
    for curr in it:          # 這裡的 it 從第二個元素開始
        # 目的上一個 end 補成當前的 start
        prev['end'] = curr['start']
        # 移動滑動視窗現在這個 curr 會在下一輪變成 prev
        prev = curr

    # 迴圈結束時prev 指向最後一個元素沒人幫它設定 end
    prev['end'] = total_elements

prev = next(it, None) 拿到第一個元素(例:{‘title’: ‘A’, ‘start’: 0}),接下來的 for curr in it: 會從「第二個元素」開始迭代,因為迭代器已經前進過一次。整個寫法的目的,就是把「前一個元素的 end = 下一個元素的 start」這件事寫得乾淨、無索引、無 if 分支。

  1. 原始資料與目標
    資料:
headings = [
    {'title': 'A', 'start': 0},
    {'title': 'B', 'start': 10},
    {'title': 'C', 'start': 25},
]
total_elements = 40

目標:補出

A.end = 10
B.end = 25
C.end = 40

2. 為什麼需要「前一個 vs 當前」模式?

因為我們要設定「上一個 heading 的 end = 下一個 heading 的 start」。
這是一種「滑動視窗長度為 2」的需求。
如果用索引版本:headings[i]['end'] = headings[i+1]['start'],最後一個要特判。
改用 iterator + prev/curr,可以讓核心邏輯在無分支的 for 迴圈中執行。

3. 迭代器關鍵行為示意

it = iter(headings)
prev = next(it, None)

此時:

it 指向「下一個要被取出的元素位置」
prev = 第一個元素(A)
迭代器內部「已經消耗掉」第一個,所以剩下的可迭代元素是:B, C
接下來:

for curr in it:
    ...

curr 依序會是:

  1. 第一輪:curr = B
  2. 第二輪:curr = C
  3. 結束:沒有更多元素

4. 逐步模擬(表格)

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

5. 加上詳細註解版

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

6. 如果用 debug print 看會更清楚

it = iter(headings)
prev = next(it, None)
print("INIT prev =", prev)

if prev is not None:
    for curr in it:
        print("LOOP prev=", prev, "curr=", curr)
        prev['end'] = curr['start']
        prev = curr
    print("AFTER LOOP prev (last) =", prev)
    prev['end'] = total_elements

輸出:

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

7. 這種寫法的優點

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

8. 常見初學者誤解點

Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王
  1. 與「索引版」對照(語意拆解)
    索引版:
for i in range(len(headings)-1):
    headings[i]['end'] = headings[i+1]['start']
headings[-1]['end'] = total_elements

iterator 版是將:

「i 與 i+1」→ 「prev 與 curr」
「最後一個單獨處理」→ 「迴圈後補」

10. 如果要同時算長度(延伸)

it = iter(headings)
prev = next(it, None)
if prev is not None:
    for curr in it:
        prev['end'] = curr['start']
        prev['length'] = prev['end'] - prev['start']
        prev = curr
    prev['end'] = total_elements
    prev['length'] = prev['end'] - prev['start']

11. 更抽象:把它想成「兩兩配對」

你正在做的,其實就是「(A,B), (B,C),最後 (C,None)」。
iterator 寫法本質上就是自己做 pairwise。

12. 極簡 mental model(一句話版)

先抓第一個作「前一個」,然後讓迴圈只關心「設定前一個的 end」,最後補尾巴。

推薦hahow線上學習python: https://igrape.net/30afN

加入好友
加入社群
Python: 用 zip 將「起點列表」轉成區間結束點:從 starts 推導 end ; next() 與 迭代器iter - 儲蓄保險王

儲蓄保險王

儲蓄險是板主最喜愛的儲蓄工具,最喜愛的投資理財工具則是ETF,最喜愛的省錢工具則是信用卡

You may also like...

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *