Go slices.CompactFunc: รวมข้อมูลซ้ำที่ติดกันด้วยกติกาของเรา
ถ้าข้อมูลซ้ำกันแต่ == ไม่ใช่กติกาที่ธุรกิจต้องการ เช่น ชื่อที่ต่างกันแค่ตัวพิมพ์ใหญ่เล็ก slices.CompactFunc ช่วยรวม สมาชิกที่เท่ากันและอยู่ติดกัน โดยให้เราเขียนฟังก์ชันเปรียบเทียบเองครับ
WHY: deduplicate โดยไม่ทิ้งความหมายของโดเมน
slices.Compact ใช้ == จึงเหมาะกับค่าที่ comparable และนิยามความเท่ากันตรงไปตรงมา แต่ระบบจริงอาจถือว่า API กับ api คือคิวเดียวกัน หรือ record สองตัวเท่ากันเมื่อมีรหัสเดียวกัน การแยกกติกานี้เป็นฟังก์ชันทำให้คนอ่านเห็นเจตนาชัดและทดสอบ edge case ได้
ข้อสำคัญคือ CompactFunc ไม่ค้นหาค่าซ้ำทั่ว slice มันรวมเฉพาะ “ก้อนที่ติดกัน” เหมือนพนักงานรวมใบสั่งโต๊ะเดียวกันที่วางต่อกันบนเคาน์เตอร์ ถ้ากระจายอยู่คนละจุด ต้อง sort/group ก่อน หรือใช้ map เมื่อไม่ต้องรักษาลำดับ
HOW: เก็บตัวแรกของแต่ละก้อน
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
queue := []string{"API", "api", "Billing", "billing", "Search"}
got := slices.CompactFunc(queue, strings.EqualFold)
want := []string{"API", "Billing", "Search"}
if !slices.Equal(got, want) {
panic(fmt.Sprintf("got %v want %v", got, want))
}
if got[0] != "API" {
panic("CompactFunc must keep the first item in a run")
}
fmt.Println(got)
}
ฟังก์ชันแก้ slice เดิมและคืน slice ที่สั้นลง ไม่ได้สร้างรายการใหม่ทั้งก้อน ตั้งแต่ Go รุ่นปัจจุบัน ช่องท้ายที่หลุดจากผลจะถูก zero เพื่อไม่ค้าง reference โดยไม่จำเป็น แต่ caller ที่ถือ backing array เดียวกันยังอาจเห็นการเปลี่ยนแปลง จึง slices.Clone ก่อนถ้าต้นฉบับต้องคงเดิม
เลือกเครื่องมือให้ตรงงาน
| สถานการณ์ | ทางเลือก |
|---|---|
ค่าซ้ำอยู่ติดกันและใช้ == | slices.Compact |
| ค่าซ้ำอยู่ติดกันแต่ equality เป็นกติกาธุรกิจ | slices.CompactFunc |
| ค่าซ้ำกระจายและลำดับไม่สำคัญ | map/set |
| ค่าซ้ำกระจายแต่ต้องรักษาลำดับพบครั้งแรก | map ช่วยจำ + slice ผลลัพธ์ |
Recap
- ยืนยันก่อนว่าค่าซ้ำอยู่ติดกัน
- เขียน equality ให้เป็นฟังก์ชันสั้นและไม่มี side effect
- จำว่าเก็บตัวแรกของแต่ละก้อน
- clone ก่อนเมื่อห้ามแก้ backing array เดิม