:Filter 的反向序列過濾與映射)
深入理解 lo 迭代器包中的 Reject 系列函數(shù)Filter 的反向序列過濾與映射【免費(fèi)下載鏈接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)項(xiàng)目地址: https://gitcode.com/GitHub_Trending/lo/lo導(dǎo)讀本文聚焦 samber/lo 迭代器子包it包位于倉(cāng)庫(kù) it/ 目錄中的Reject、RejectI、RejectMap與RejectMapI四個(gè)序列操作函數(shù)。它們是Filter系列的反向操作保留謂詞返回 false 的元素同時(shí)具備惰性求值、支持提前終止、保留自定義序列類型等特性。讀完本文你將掌握這組函數(shù)在 Go 1.23iter.Seq序列上的全部用法、底層實(shí)現(xiàn)原理以及它們?cè)趥}(cāng)庫(kù)內(nèi)部如WithoutBy、WithoutNth、DropByIndex的真實(shí)組合場(chǎng)景。一、Reject 與 Filter一枚硬幣的兩面在 docs/data/it-reject.md 的定位中Reject被明確定義為Filter 的反向操作Reject is the opposite of FilterFilter保留謂詞返回 true 的元素而Reject保留謂詞返回 false 的元素。這一語(yǔ)義與 JavaScript Lodash 的reject完全一致也與倉(cāng)庫(kù)中 slice 版本的 core-reject.md 呼應(yīng)——兩者都返回謂詞為 false 的元素。該文檔登記了四個(gè)變體函數(shù)全部位于it包c(diǎn)ategory:itersubCategory:sequence構(gòu)成完整的反向過濾工具族函數(shù)簽名特性Rejectfunc RejectT any, I ~func(func(T) bool) bool) I按元素值過濾RejectIfunc RejectIT any, I ~func(func(T) bool) bool) I謂詞攜帶索引RejectMapfunc RejectMapT, R any (R, bool)) iter.Seq[R]過濾 類型轉(zhuǎn)換映射RejectMapIfunc RejectMapIT, R any (R, bool)) iter.Seq[R]過濾 映射 索引二、Reject基礎(chǔ)的反向過濾2.1 用法Reject遍歷序列僅產(chǎn)出predicate返回 false 的元素并保持原始相對(duì)順序collection : func(yield func(int) bool) { yield(1) yield(2) yield(3) yield(4) } filtered : it.Reject(collection, func(x int) bool { return x%2 0 }) var result []int for item : range filtered { result append(result, item) } // result contains [1, 3]這段示例出自 docs/data/it-reject.mdit包的正式示例測(cè)試 it/seq_example_test.go 也給出了等價(jià)的可運(yùn)行驗(yàn)證func ExampleReject() { list : slices.Values([]int{0, 1, 2, 3, 4, 5}) result : Reject(list, func(x int) bool { return x%2 0 }) fmt.Printf(%v, slices.Collect(result)) // Output: [1 3 5] }注意ExampleReject用slices.Values將普通切片轉(zhuǎn)換為iter.Seq這正是it包的設(shè)計(jì)初衷——所有函數(shù)面向Go 1.23 的iter序列工作源碼文件頭部有//go:build go1.23構(gòu)建約束見 it/seq.go。2.2 源碼實(shí)現(xiàn)// Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return true for. func RejectT any, I ~func(func(T) bool) bool) I { return RejectI(collection, func(item T, _ int) bool { return predicate(item) }) }對(duì)應(yīng)源碼見 it/seq.go。可以看到Reject只是RejectI的薄封裝它丟棄索引參數(shù)直接委托給RejectI。這與Filter/FilterI、Map/MapI的架構(gòu)完全一致——整個(gè)it包以帶索引的基礎(chǔ)實(shí)現(xiàn) 無索引便捷封裝為統(tǒng)一模式。三、RejectI帶索引的反向過濾3.1 用法RejectI的謂詞額外接收元素索引適合需要按位置過濾的場(chǎng)景collection : func(yield func(string) bool) { yield(a) yield(b) yield(c) } filtered : it.RejectI(collection, func(item string, index int) bool { return index 1 }) var result []string for item : range filtered { result append(result, item) } // result contains [a, c]3.2 源碼實(shí)現(xiàn)與惰性求值func RejectIT any, I ~func(func(T) bool) bool) I { return func(yield func(T) bool) { var i int for item : range collection { if !predicate(item, i) !yield(item) { return } i } } }對(duì)應(yīng)源碼見 it/seq.go這段實(shí)現(xiàn)包含兩個(gè)關(guān)鍵設(shè)計(jì)惰性求值RejectI返回的是一個(gè)新的迭代函數(shù)閉包而不是立即遍歷并物化結(jié)果。只有當(dāng)消費(fèi)者在for ... range中拉取元素時(shí)謂詞才會(huì)被執(zhí)行。這意味著它可以串聯(lián)在無限序列或流式管線上使用。提前終止傳播注意!yield(item)的判斷——當(dāng)消費(fèi)者下游返回 false 表示提前停止時(shí)RejectI會(huì)立刻return停止繼續(xù)消費(fèi)上游從而將提前終止信號(hào)沿管線向上游傳播避免不必要的計(jì)算。3.3 泛型約束類型保留I ~func(func(T) bool)是一個(gè)近似類型約束underlying type 為函數(shù)類型它允許傳入任何以func(func(T) bool)為底層類型的自定義序列類型且返回類型I會(huì)保留該自定義類型。測(cè)試 it/seq_test.go 專門驗(yàn)證了這一點(diǎn)type myStrings iter.Seq[string] allStrings : myStrings(values(, foo, bar)) nonempty : RejectI(allStrings, func(x string, _ int) bool { return len(x) 0 }) is.IsType(nonempty, allStrings, type preserved)同樣Reject的測(cè)試也覆蓋了該行為it/seq_test.go并驗(yàn)證了元素順序保持如{Smith,foo,Domin,bar,Olivia}過濾掉長(zhǎng)度 3 的名字后得到[foo, bar]見 it/seq_test.go。四、RejectMap 與 RejectMapI過濾與映射的融合4.1 用法RejectMap在一次遍歷中同時(shí)完成反向過濾和類型轉(zhuǎn)換映射是FilterMap的反向版本。回調(diào)函數(shù)返回兩個(gè)值映射操作的結(jié)果以及該結(jié)果元素是否應(yīng)被包含true 包含false 排除collection : func(yield func(int) bool) { yield(1) yield(2) yield(3) yield(4) } filtered : it.RejectMap(collection, func(x int) (string, bool) { return fmt.Sprintf(item-%d, x), x%2 0 }) var result []string for item : range filtered { result append(result, item) } // result contains [item-1, item-3]在這個(gè)例子中偶數(shù)元素的第二個(gè)返回值是 true應(yīng)包含但RejectMap的語(yǔ)義與FilterMap相反只有第二個(gè)返回值為 false 的元素才會(huì)被保留因此最終得到item-1和item-3。4.2 源碼實(shí)現(xiàn)RejectMap委托給RejectMapIit/seq.go核心實(shí)現(xiàn)如下it/seq.gofunc RejectMapIT, R any (R, bool)) iter.Seq[R] { return func(yield func(R) bool) { var i int for item : range collection { if r, ok : callback(item, i); !ok !yield(r) { return } i } } }關(guān)鍵點(diǎn)在!ok !yield(r)這一行!ok回調(diào)第二個(gè)返回值 ok 為 false 時(shí)才繼續(xù)實(shí)現(xiàn)反向包含 !yield(r)一旦消費(fèi)者提前終止立即返回。注意這里與RejectI的差異RejectMapI返回類型是iter.Seq[R]輸出元素類型R可以與輸入T不同因此映射過程順帶完成了類型轉(zhuǎn)換如示例中int → string。4.3 測(cè)試驗(yàn)證測(cè)試 it/seq_test.go 覆蓋了數(shù)值與字符串兩類場(chǎng)景例如r1 : RejectMap(valuesint64, func(x int64) (string, bool) { if x%2 0 { return strconv.FormatInt(x, 10), false } return , true }) is.Equal([]string{2, 4}, slices.Collect(r1))偶數(shù)的回調(diào)返回(2, false)——即被保留所以輸出為[2, 4]。字符串用例cpu, gpu, mouse, keyboard后綴為pu的映射為xpu并保留驗(yàn)證了RejectMap的正確性。同時(shí)RejectI與RejectMapI的測(cè)試都調(diào)用了assertSeqSupportBreak輔助函數(shù)見 it/seq_test.go進(jìn)一步確認(rèn)了提前終止語(yǔ)義在帶索引變體中同樣生效。五、底層原理與設(shè)計(jì)要點(diǎn)總結(jié)綜合 it/seq.go 的四個(gè)實(shí)現(xiàn)可以歸納出該函數(shù)族的設(shè)計(jì)模式函數(shù)即序列所有返回的I/iter.Seq[R]本質(zhì)都是func(yield func(T) bool)形式的拉取式迭代器遵循 Go 1.23iter.Seq約定天然支持for range。封裝金字塔Reject → RejectI、RejectMap → RejectMapI無索引版本一律通過丟棄索引參數(shù)委托給帶索引版本避免邏輯重復(fù)。單遍掃描與 slice 版lo.Reject的預(yù)分配切片實(shí)現(xiàn)見 slice.goresult : make(Slice, 0, len(collection))不同it版不物化結(jié)果不產(chǎn)生中間切片適合流式處理。標(biāo)準(zhǔn)庫(kù)互操作輸入輸出均可直接與slices.Collect、slices.Values等標(biāo)準(zhǔn)庫(kù)函數(shù)銜接實(shí)現(xiàn)切片 ? 序列的平滑轉(zhuǎn)換。六、倉(cāng)庫(kù)內(nèi)部的真實(shí)組合場(chǎng)景Reject并非孤立函數(shù)它被倉(cāng)庫(kù)其他迭代器操作復(fù)用來構(gòu)建更高級(jí)的語(yǔ)義WithoutByit/intersect.go先通過lo.Keyify構(gòu)造排除鍵集合再用Reject剔除鍵命中排除集的元素func WithoutByT any, K comparable, I ~func(func(T) bool) K, exclude ...K) I { set : lo.Keyify(exclude) return Reject(collection, func(item T) bool { return lo.HasKey(set, transform(item)) }) }WithoutNthit/intersect.go用RejectI按索引剔除指定位置的元素本質(zhì)是按位置反向過濾return RejectI(collection, func(_ T, index int) bool { return lo.HasKey(set, index) })DropByIndexit/seq.go同樣委托給RejectI通過索引集合實(shí)現(xiàn)任意位置批量刪除。這三處復(fù)用證明Reject/RejectI是it包中排除類操作without/drop的底層基礎(chǔ)設(shè)施理解了它們就等于理解了整個(gè)排除族函數(shù)的實(shí)現(xiàn)骨架。七、與 slice 版本 lo.Reject 的對(duì)照如果你更熟悉主包的切片 API可以對(duì)比 docs/data/core-reject.md 中的簽名func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice差異要點(diǎn)維度lo.Rejectcore/sliceit.Rejectiter/sequence輸入/輸出~[]T切片iter.Seq/ 自定義序列I求值時(shí)機(jī)立即返回新切片惰性消費(fèi)時(shí)才執(zhí)行謂詞簽名始終帶(item, index)Reject無索引RejectI帶索引額外變體RejectErr錯(cuò)誤中斷、FilterReject雙路分區(qū)RejectMap/RejectMapI映射融合選擇建議處理內(nèi)存中的小切片、需要錯(cuò)誤處理或一次性分區(qū)時(shí)用 core 版處理流式數(shù)據(jù)、自定義序列類型、需要管道串聯(lián)與提前終止時(shí)用it版。兩者的語(yǔ)義保留謂詞為 false 的元素完全一致可放心切換。八、注意事項(xiàng)與最佳實(shí)踐謂詞語(yǔ)義容易混淆Reject保留的是謂詞返回false的元素而RejectMap中保留的是回調(diào)第二個(gè)返回值ok為false的元素。兩個(gè)false的判定方向是一致的都是反向包含閱讀代碼時(shí)留意即可。惰性帶來的副作用時(shí)機(jī)謂詞在for range消費(fèi)時(shí)才執(zhí)行不要假設(shè)調(diào)用Reject的瞬間謂詞已被調(diào)用若謂詞有副作用如日志、計(jì)數(shù)其觸發(fā)順序由消費(fèi)者的拉取節(jié)奏決定。空序列與全排除輸入為空序列時(shí)返回的迭代器直接不產(chǎn)出任何元素這是迭代器函數(shù)的自然行為無需特殊處理。結(jié)合slices.Collect物化需要普通切片結(jié)果時(shí)用slices.Collect(it.Reject(seq, pred))一行完成轉(zhuǎn)換這正是 it/seq_example_test.go 的慣用寫法。提前終止是性能特性當(dāng)Reject處于長(zhǎng)管道中間位置時(shí)下游一旦停止如it.Take取夠數(shù)量上游遍歷會(huì)因!yield()檢查而立即終止這是將Reject置于管道中段而非末段時(shí)的核心性能優(yōu)勢(shì)。參考文檔docs/data/it-reject.md實(shí)現(xiàn)it/seq.go測(cè)試it/seq_test.go示例it/seq_example_test.go組合復(fù)用it/intersect.go、it/seq.go切片對(duì)應(yīng)版本slice.go、docs/data/core-reject.md【免費(fèi)下載鏈接】lo A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...)項(xiàng)目地址: https://gitcode.com/GitHub_Trending/lo/lo創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考