Golang中如何对切片中的切片进行去重

Golang中如何对切片中的切片进行去重 大家好,

希望这个主题没问题。如果不合适,请随时删除。

我是Go语言的新手,主要是想学习。我写了一些代码(我认为)可以工作,但我对其他方法和建设性的反馈很感兴趣。特别是不使用反射的方法。

问题:

我有一个字符串切片的切片,其中存在多个重复的切片。我需要返回一个去除了重复项的、唯一的切片切片。我尝试不使用反射来实现,但这让我头大。

提前感谢任何指点/建议/评论。

(我没有看到预览按钮,如果格式有问题,我可能会编辑此内容)

代码:

Go Playground 链接

package main

import (
	"fmt"
	"reflect"
)

func main() {

	// 这些数据都将来自XML导入,我只是在模拟我将看到的数据类型

	jb := []string{"James", "Bond"}
	mp := []string{"Mrs", "Money", "Penny"}
	xp := []string{"Mr", "Q"}

	// 这是目标唯一切片中已存在的数据,应该保留
	us := []string{"Im", "unique", "leave", "me", "here"}

	// 创建我们的切片切片。包含许多我们想要移除的重复项。
	duplicateSlices := [][]string{jb, mp, mp, jb, mp, jb, mp, jb, xp, xp, xp, jb, mp}

	// 这个切片应该包含唯一的条目,加上原始值
	uniqueSlices := [][]string{us}

	for x := range duplicateSlices {
		match := false
		for y := range uniqueSlices {

			if (reflect.DeepEqual(duplicateSlices[x], uniqueSlices[y])) == false {
				match = false
			} else {
				match = true
				break
			}
		}
		if match == false {
			uniqueSlices = append(uniqueSlices, duplicateSlices[x])
		}
	}
	fmt.Println(uniqueSlices)

}

更多关于Golang中如何对切片中的切片进行去重的实战教程也可以访问 https://www.itying.com/category-94-b0.html

3 回复

谢谢肖恩,这说得通!我没想到可以把“两个字符串是否相等”的判断放到一个函数里去处理。我之前试图在一个代码块里完成所有事情,结果把自己搞糊涂了。

也谢谢你的循环小技巧 🙂

更多关于Golang中如何对切片中的切片进行去重的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


你好,@joe19,你是在问是否可以不使用反射,从而移除对 reflect.DeepEqual 的调用吗?如果是这样,并且你只处理 []string 类型的切片,那么你可以这样做:

func stringsEqual(a, b []string) bool {
    if len(a) != len(b) {
        return false
    }
    for i, x := range a {
        if x != b[i] {
            return false
        }
    }
    return true
}

我还想说,你的循环可以简化:

		exists := false
		for y := range uniqueSlices {
			exists = reflect.DeepEqual(duplicateSlices[x], uniqueSlices[y])
			if exists {
				break
			}
		}
		if !exists {
			uniqueSlices = append(uniqueSlices, duplicateSlices[x])
		}
	}

可以使用 map[string]struct{} 配合自定义键生成函数来实现切片去重,避免使用反射。这里提供两种实现方案:

方案一:使用字符串连接作为键

package main

import (
	"fmt"
	"strings"
)

func deduplicateSlices(slices [][]string) [][]string {
	seen := make(map[string]struct{})
	result := make([][]string, 0, len(slices))
	
	for _, slice := range slices {
		// 使用分隔符连接字符串作为键
		key := strings.Join(slice, "|")
		if _, exists := seen[key]; !exists {
			seen[key] = struct{}{}
			result = append(result, slice)
		}
	}
	
	return result
}

func main() {
	jb := []string{"James", "Bond"}
	mp := []string{"Mrs", "Money", "Penny"}
	xp := []string{"Mr", "Q"}
	us := []string{"Im", "unique", "leave", "me", "here"}
	
	duplicateSlices := [][]string{jb, mp, mp, jb, mp, jb, mp, jb, xp, xp, xp, jb, mp}
	
	// 包含原始唯一切片
	allSlices := append([][]string{us}, duplicateSlices...)
	uniqueSlices := deduplicateSlices(allSlices)
	
	fmt.Println(uniqueSlices)
}

方案二:使用更安全的键生成方法

package main

import (
	"fmt"
	"strconv"
)

func sliceToKey(slice []string) string {
	var builder strings.Builder
	builder.WriteString(strconv.Itoa(len(slice)))
	
	for _, s := range slice {
		builder.WriteByte('|')
		builder.WriteString(strconv.Itoa(len(s)))
		builder.WriteByte(':')
		builder.WriteString(s)
	}
	
	return builder.String()
}

func deduplicateSlicesSafe(slices [][]string) [][]string {
	seen := make(map[string]struct{})
	result := make([][]string, 0, len(slices))
	
	for _, slice := range slices {
		key := sliceToKey(slice)
		if _, exists := seen[key]; !exists {
			seen[key] = struct{}{}
			result = append(result, slice)
		}
	}
	
	return result
}

func main() {
	jb := []string{"James", "Bond"}
	mp := []string{"Mrs", "Money", "Penny"}
	xp := []string{"Mr", "Q"}
	us := []string{"Im", "unique", "leave", "me", "here"}
	
	duplicateSlices := [][]string{jb, mp, mp, jb, mp, jb, mp, jb, xp, xp, xp, jb, mp}
	
	// 包含原始唯一切片
	allSlices := append([][]string{us}, duplicateSlices...)
	uniqueSlices := deduplicateSlicesSafe(allSlices)
	
	fmt.Println(uniqueSlices)
}

方案三:通用泛型实现(Go 1.18+)

package main

import (
	"fmt"
	"strconv"
	"strings"
)

func Deduplicate[T comparable](slices [][]T) [][]T {
	seen := make(map[string]struct{})
	result := make([][]T, 0, len(slices))
	
	for _, slice := range slices {
		var keyBuilder strings.Builder
		keyBuilder.WriteString(strconv.Itoa(len(slice)))
		
		for _, v := range slice {
			keyBuilder.WriteByte('|')
			fmt.Fprintf(&keyBuilder, "%v", v)
		}
		
		key := keyBuilder.String()
		if _, exists := seen[key]; !exists {
			seen[key] = struct{}{}
			result = append(result, slice)
		}
	}
	
	return result
}

func main() {
	jb := []string{"James", "Bond"}
	mp := []string{"Mrs", "Money", "Penny"}
	xp := []string{"Mr", "Q"}
	us := []string{"Im", "unique", "leave", "me", "here"}
	
	duplicateSlices := [][]string{jb, mp, mp, jb, mp, jb, mp, jb, xp, xp, xp, jb, mp}
	
	allSlices := append([][]string{us}, duplicateSlices...)
	uniqueSlices := Deduplicate(allSlices)
	
	fmt.Println(uniqueSlices)
}

这些方法都不需要使用反射,通过将切片转换为唯一的字符串键来实现去重。方案二通过包含长度信息来避免分隔符冲突,是最安全的选择。

回到顶部