golang: Insert to a sorted slice

huangapple go评论155阅读模式
英文:

golang: Insert to a sorted slice

问题

将元素插入已排序切片的最有效方法是什么?

我尝试了几种方法,但最终都使用了至少两次追加操作,据我了解,这会创建切片的新副本。

英文:

What's the most efficient way of inserting an element to a sorted slice?

I tried a couple of things but all ended up using at least 2 appends which as I understand makes a new copy of the slice

答案1

得分: 18

这是如何在已排序的字符串切片中插入元素的方法:

完整示例的 Go Playground 链接:https://play.golang.org/p/4RkVgEpKsWq

func Insert(ss []string, s string) []string {
    i := sort.SearchStrings(ss, s)
    ss = append(ss, "")
    copy(ss[i+1:], ss[i:])
    ss[i] = s
    return ss
}

以上是插入元素的代码示例。

英文:

Here is how to insert into a sorted slice of strings:

Go Playground Link to full example: https://play.golang.org/p/4RkVgEpKsWq

func Insert(ss []string, s string) []string {
    i := sort.SearchStrings(ss, s)
    ss = append(ss, "")
    copy(ss[i+1:], ss[i:])
    ss[i] = s
    return ss
}

答案2

得分: 9

如果切片具有足够的容量,那么就不需要进行新的复制。
插入位置后的元素可以向右移动。
只有当切片的容量不足时,才需要创建一个新的切片并复制所有的值。

请记住,切片并不是为了快速插入而设计的。
所以在使用切片时不会有奇迹般的解决方案。
你可以创建一个自定义的数据结构来提高效率,
但显然会有其他的权衡。

在这个过程中可以优化的一个关键点是快速找到插入点。如果切片是有序的,那么可以使用二分查找以O(log n)的时间复杂度来完成。
然而,考虑到复制切片末尾的昂贵操作或在必要时重新分配内存,这可能并不重要。

英文:

If the slice has enough capacity then there's no need for a new copy.
The elements after the insert position can be shifted to the right.
Only when the slice doesn't have enough capacity,
a new slice and copying all values will be necessary.

Keep in mind that slices are not designed for fast insertion.
So there won't be a miracle solution here using slices.
You could create a custom data structure to make this more efficient,
but obviously there will be other trade-offs.

One point that can be optimized in the process is finding the insertion point quickly. If the slice is sorted, then you can use binary search to perform this in O(log n) time.
However, this might not matter much,
considering the expensive operation of copying the end of the slice,
or reallocating when necessary.

答案3

得分: 7

我喜欢@likebike的答案,但它只适用于字符串。这是通用版本,适用于任何有序类型的切片(需要Go 1.18):

func Insert[T constraints.Ordered](ts []T, t T) []T {
	var dummy T
	ts = append(ts, dummy) // 扩展切片

	i, _ := slices.BinarySearch(ts, t) // 找到插入位置
	copy(ts[i+1:], ts[i:])             // 腾出空间
	ts[i] = t
	return ts
}

请注意,这使用了golang.org/x/exp/slices包,但几乎可以肯定它将在Go 1.19的标准库中包含。

Go Playground中尝试一下

英文:

I like @likebike's answer but it only works for strings. Here is the generic version that will work for a slice of any ordered type (requires Go 1.18):

func Insert[T constraints.Ordered](ts []T, t T) []T {
	var dummy T
	ts = append(ts, dummy) // extend the slice

	i, _ := slices.BinarySearch(ts, t) // find slot
	copy(ts[i+1:], ts[i:])             // make room
	ts[i] = t
	return ts
}

Note that this uses the package golang.org/x/exp/slices but this will almost certainly be included in the std Go library in Go 1.19.

Try it in the Go Playground

答案4

得分: 4

问题有两个部分:找到插入值的位置和插入值。

使用 sort 包 中的搜索函数,通过二分搜索高效地找到插入索引。

使用一次 append 调用,高效地将值插入切片中:

// insertAt 在索引 i 处将 v 插入到 s 中,并返回新的切片。
func insertAt(data []int, i int, v int) []int {
    if i == len(data) {
        // 在末尾插入是简单的情况。
        return append(data, v)
    }

    // 通过将插入索引处的值向上移动一个索引来为插入的元素腾出空间。
    // 当 cap(data) 大于 len(data) 时,调用 append 不会分配内存。
    data = append(data[:i+1], data[i:]...)

    // 插入新元素。
    data[i] = v

    // 返回更新后的切片。
    return data
}

下面是将值插入已排序切片的代码:

func insertSorted(data []int, v int) []int {
    i := sort.Search(len(data), func(i int) bool { return data[i] >= v })
    return insertAt(data, i, v)
}

此答案中的代码使用了 int 类型的切片。根据实际数据调整类型。

此答案中的 sort.Search 调用可以替换为调用辅助函数 sort.SearchInts。我在此答案中展示 sort.Search,因为该函数适用于任何类型的切片。

如果您不想添加重复值,请在插入之前检查搜索索引处的值:

func insertSortedNoDups(data []int, v int) []int {
    i := sort.Search(len(data), func(i int) bool { return data[i] >= v })
    if i < len(data) && data[i] == v {
        return data
    }
    return insertAt(data, i, v)
}
英文:

There are two parts to the problem: finding where to insert the value and inserting the value.

Use the sort package search functions to efficiently find the insertion index using binary search.

Use a single call to append to efficiently insert a value into a slice:

// insertAt inserts v into s at index i and returns the new slice.
func insertAt(data []int, i int, v int) []int {
	if i == len(data) {
		// Insert at end is the easy case.
		return append(data, v)
	}

	// Make space for the inserted element by shifting
	// values at the insertion index up one index. The call
	// to append does not allocate memory when cap(data) is
	// greater ​than len(data).
	data = append(data[:i+1], data[i:]...)

	// Insert the new element.
	data[i] = v

	// Return the updated slice.
	return data
}

Here's the code for inserting a value a sorted slice:

func insertSorted(data []int, v int) []int {
	i := sort.Search(len(data), func(i int) bool { return data[i] &gt;= v })
	return insertAt(data, i, v)
}

The code in this answer uses a slice of int. Adjust the type to match your actual data.

The call to sort.Search in this answer can be replaced with a call to the helper function sort.SearchInts. I show sort.Search in this answer because the function applies to a slice of any type.

If you do not want to add duplicate values, check the value at the search index before inserting:

func insertSortedNoDups(data []int, v int) []int {
	i := sort.Search(len(data), func(i int) bool { return data[i] &gt;= v })
	if i &lt; len(data) &amp;&amp; data[i] == v {
		return data
	}
	return insertAt(data, i, v)
}

答案5

得分: 2

你可以使用堆(heap):

package main

import (
   "container/heap"
   "sort"
)

type slice struct { sort.IntSlice }
func (s slice) Pop() interface{} { return 0 }

func (s *slice) Push(x interface{}) {
   (*s).IntSlice = append((*s).IntSlice, x.(int))
}

func main() {
   s := &slice{
      sort.IntSlice{11, 10, 14, 13},
   }
   heap.Init(s)
   heap.Push(s, 12)
   println(s.IntSlice[0] == 10)
}

请注意,堆并不是严格排序的,但是“最小元素”保证是第一个元素。此外,我在示例中没有实现Pop函数,你可能需要自己实现。

https://golang.org/pkg/container/heap

英文:

You could use a heap:

package main

import (
   &quot;container/heap&quot;
   &quot;sort&quot;
)

type slice struct { sort.IntSlice }
func (s slice) Pop() interface{} { return 0 }

func (s *slice) Push(x interface{}) {
   (*s).IntSlice = append((*s).IntSlice, x.(int))
}

func main() {
   s := &amp;slice{
      sort.IntSlice{11, 10, 14, 13},
   }
   heap.Init(s)
   heap.Push(s, 12)
   println(s.IntSlice[0] == 10)
}

Note that a heap is not strictly sorted, but the "minimum element" is guaranteed
to be the first element. Also I did not implement the Pop function in my
example, you would want to do that.

https://golang.org/pkg/container/heap

答案6

得分: 2

这里提到了两种在已知位置i时插入切片的方法:

data = append(data, "")
copy(data[i+1:], data[i:])
data[i] = s

data = append(data[:i+1], data[i:]...)
data[i] = s

我刚刚使用go1.18beta2对它们进行了基准测试,第一种解决方案大约快了10%。

英文:

There are two approaches mentioned here to insert into the slice when the position i is known:

data = append(data, &quot;&quot;)
copy(data[i+1:], data[i:])
data[i] = s

and

data = append(data[:i+1], data[i:]...)
data[i] = s

I just benchmarked both with go1.18beta2, and the first solution is approximately 10% faster.

答案7

得分: 2

没有依赖关系,具有重复选项的通用数据类型(go 1.18)

时间复杂度:Log2(n) + 1

import "golang.org/x/exp/constraints"
import "golang.org/x/exp/slices"

func InsertionSort[T constraints.Ordered](array []T, value T, canDupicate bool) []T {
    pos, isFound := slices.BinarySearch(array, value)
    if canDupicate || !isFound {
        array = slices.Insert(array, pos, value)
    }
    return array
}

完整版本:https://go.dev/play/p/P2_ou2Fqs37

英文:

no dependency, generic data type with duplicated options. (go 1.18)

time complexity : Log2(n) + 1

import &quot;golang.org/x/exp/constraints&quot;
import &quot;golang.org/x/exp/slices&quot;

func InsertionSort[T constraints.Ordered](array []T, value T, canDupicate bool) []T {
	pos, isFound := slices.BinarySearch(array, value)
    if canDupicate || !isFound {
       array = slices.Insert(array, pos, value)
    }
	return array
}

full version : https://go.dev/play/p/P2_ou2Fqs37

答案8

得分: 0

play : https://play.golang.org/p/dUGmPurouxA

array1 := []int{1, 3, 4, 5}

//想要在索引1处插入
insertAtIndex := 1
temp := append([]int{}, array1[insertAtIndex:]...)
array1 = append(array1[0:insertAtIndex], 2)
array1 = append(array1, temp...)

fmt.Println(array1)
英文:

play : https://play.golang.org/p/dUGmPurouxA

array1 := []int{1, 3, 4, 5}

	//want to insert at index 1
	insertAtIndex := 1
	temp := append([]int{}, array1[insertAtIndex:]...)
	array1 = append(array1[0:insertAtIndex], 2)
	array1 = append(array1, temp...)

	fmt.Println(array1)

答案9

得分: 0

你可以尝试下面的代码。它基本上使用了Go语言的排序包。

package main

import "sort"
import "fmt"

func main() {

	data := []int{20, 21, 22, 24, 25, 26, 28, 29, 30, 31, 32}
	var items = []int{23, 27}
	for _, x := range items {
		i := sort.Search(len(data), func(i int) bool { return data[i] >= x })
		if i < len(data) && data[i] == x {
			fmt.Println(i)
		} else {
			data = append(data, 0)
			copy(data[i+1:], data[i:])
			data[i] = x
		}
		fmt.Println(data)
	}
}
英文:

You can try the below code. It basically uses the golang sort package

package main

import &quot;sort&quot;
import &quot;fmt&quot;

func main() {

	data := []int{20, 21, 22, 24, 25, 26, 28, 29, 30, 31, 32}
	var items = []int{23, 27}
	for _, x := range items {
		i := sort.Search(len(data), func(i int) bool { return data[i] &gt;= x })
		if i &lt; len(data) &amp;&amp; data[i] == x {
			fmt.Println(i)
		} else {
			data = append(data, 0)
			copy(data[i+1:], data[i:])
			data[i] = x
		}
		fmt.Println(data)
	}
}


huangapple
  • 本文由 发表于 2017年3月12日 19:43:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/42746972.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定