Open5

スライス操作

beatsbeats
package benchmark

import (
	"testing"
)

func BenchmarkA(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ss := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
		s := "5"
		A(ss, s)
	}
}

func BenchmarkB(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ss := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
		s := "5"
		B(ss, s)
	}
}

// 要素がユニークであることが前提
func A(ss []string, s string) []string {
	r := make([]string, 0, len(ss)-1)
	for i := range ss {
		if ss[i] == s {
			r = append([]string{ss[i]}, r...)
			continue
		}
		r = append(r, ss[i])
	}
	return r
}

// 要素がユニークであることが前提
func B(ss []string, s string) []string {
	for i := range ss {
		if ss[i] == s {
			v := ss[i]
			ss = append([]string{v}, append(ss[:i], ss[i+1:]...)...)
		}
	}
	return ss
}

BenchmarkA-4   	 1881720	       861.2 ns/op	     400 B/op	       4 allocs/op
BenchmarkB-4   	 4549692	       238.9 ns/op	     176 B/op	       2 allocs/op
beatsbeats
func BenchmarkC(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ss := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
		s := "5"
		C(ss, s)
	}
}

func C(ss []string, s string) []string {
	for i := range ss {
		if ss[i] == s {
			v := ss[i]
			ss = append(ss[:i], ss[i+1:]...)
			ss = append([]string{v}, ss...)
		}
	}
	return ss
}
BenchmarkC-4   	 5156166	       231.0 ns/op	     176 B/op	       2 allocs/op
beatsbeats
func BenchmarkD(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ss := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
		s := "5"
		D(ss, s)
	}
}

func D(ss []string, s string) []string {
	for i, v := range ss {
		if ss[i] == s {
			ss = append([]string{v}, append(ss[:i], ss[i+1:]...)...)
		}
	}
	return ss
}

改めて全て計測

BenchmarkA-4     2718588               424.5 ns/op
BenchmarkB-4     5561485               231.6 ns/op
BenchmarkC-4     5404134               224.3 ns/op
BenchmarkD-4     4500566               235.8 ns/op
beatsbeats
// moveToFront moves needle to the front of haystack, in place if possible.
func moveToFront(needle string, haystack []string) []string {
	if len(haystack) != 0 && haystack[0] == needle {
		return haystack
	}
	prev := needle
	for i, elem := range haystack {
		switch {
		case i == 0:
			haystack[0] = needle
			prev = elem
		case elem == needle:
			haystack[i] = prev
			return haystack
		default:
			haystack[i] = prev
			prev = elem
		}
	}
	return append(haystack, prev)
}

haystack := []string{"a", "b", "c", "d", "e"} // [a b c d e]
haystack = moveToFront("c", haystack)         // [c a b d e]
haystack = moveToFront("f", haystack)         // [f c a b d e]

出典:https://github.com/golang/go/wiki/SliceTricks

先頭にneedleを詰めた後は、needleが元々いた要素に遭遇するまでは、要素を一つずつ後ろにずらし、
needleが元々いた要素に遭遇したら一つ前の要素をneedleがneedleが元々いた要素に詰め替えreturnする。(needleが元々いた要素以降は不変)

beatsbeats

https://github.com/thoas/go-funk を使ってみる。

func BenchmarkE(b *testing.B) {
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		ss := []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}
		s := "5"
		E(ss, s)
	}
}

func E(ss []string, s string) []string {
	ss = funk.SubtractString(ss, []string{s})
	return append([]string{s}, ss...)
}
BenchmarkE-4   	  770540	      1511 ns/op	     947 B/op	       7 allocs/op

予想はしていたがアロケートが多いしパフォーマンスは落ちる。