Go'da bir dilimi sıralamak, içinde arama yapmak ya da öğe eklemek ve silmek için bugün ilk bakılacak yer slices paketidir; map anahtarlarını ve değerlerini işlemek için maps paketi, karşılaştırma fonksiyonları için cmp paketi vardır. Üçü de Go 1.21 ile standart kütüphaneye girdi ve generics üzerine kurulu oldukları için her öğe tipiyle çalışırlar. Eski sort paketi hâlâ desteklenir, ancak belgesi birçok fonksiyonun yanına "yeni slices karşılığı çoğu durumda daha rahat kullanılır ve daha hızlı çalışır" notunu düşer.

Bu yazıda slices.Sort, SortFunc, SortStableFunc, BinarySearch, Contains, Index, Insert, Delete, Compact gibi fonksiyonları; maps.Keys, Clone, DeleteFunc, Equal fonksiyonlarını ve sort paketinden geçişi çalışan örneklerle görüyorsunuz. Dilimlerin iç yapısı (uzunluk, kapasite, ortak dizi) için append ve copy, iterator döndüren fonksiyonların mantığı için range over func yazısına bakabilirsiniz.

slices paketine genel bakış

slices paketine genel bakış: Sıralama, Sıralı mı?, Arama, İkili arama

slices paketindeki fonksiyonların çoğu S ~[]E biçiminde bir tip parametresi alır; yani yalnızca []int değil, altında dilim olan kendi tiplerinizle de (type Liste []Urun gibi) çalışırlar. Öğeleri karşılaştıran fonksiyonlar iki aileye ayrılır: cmp.Ordered kısıtlı olanlar (Sort, BinarySearch, Min, Max) sayı ve string gibi sıralanabilir tiplerle doğrudan çalışır; adı Func ile bitenler ise karşılaştırmayı sizin verdiğiniz bir fonksiyona bırakır ve struct gibi her tipte kullanılabilir. comparable kısıtlı olanlar (Contains, Index, Compact, Equal) yalnızca == ile karşılaştırılabilen tipler ister. Kısıtların ne anlama geldiği için comparable ve cmp.Ordered yazısına bakın.

AmaçFonksiyonlarNot
SıralamaSort, SortFunc, SortStableFuncyerinde sıralar, değer döndürmez
Sıralı mı?IsSorted, IsSortedFuncbool döndürür
AramaContains, ContainsFunc, Index, IndexFuncdoğrusal arama
İkili aramaBinarySearch, BinarySearchFuncdilim önceden sıralı olmalı
En küçük / büyükMin, Max, MinFunc, MaxFuncboş dilimde panic
DeğiştirmeInsert, Delete, DeleteFunc, Replace, Compactyeni dilimi döndürür, sonucu atayın
Kopya ve kapasiteClone, Grow, Clip, Concat, RepeatClone yüzeysel kopyadır
KarşılaştırmaEqual, EqualFunc, Compare, CompareFuncnil ve boş dilim Equal için eşit
IteratorAll, Values, Backward, Collect, Sorted, ChunkGo 1.23 ile geldi

Sıralama, arama ve temel işlemler

Aşağıdaki örnek bir puan listesini sıralar, ikili arama yapar, en küçük ve en büyük değeri bulur, ardışık tekrarları siler, araya öğe ekler ve listeyi ters çevirir. Her satırın çıktısı dosyanın sonundaki yorum bloğunda.

go
package main

import (
	"fmt"
	"slices"
)

func main() {
	puan := []int{72, 95, 41, 88, 95, 60}
	slices.Sort(puan)
	fmt.Println(puan)

	i, bulundu := slices.BinarySearch(puan, 88)
	fmt.Println(i, bulundu)
	i, bulundu = slices.BinarySearch(puan, 80)
	fmt.Println(i, bulundu)

	fmt.Println(slices.Contains(puan, 41), slices.Index(puan, 95))
	fmt.Println(slices.Min(puan), slices.Max(puan))

	puan = slices.Compact(puan)
	fmt.Println(puan)

	puan = slices.Insert(puan, 2, 65)
	puan = slices.Delete(puan, 0, 1)
	fmt.Println(puan)

	slices.Reverse(puan)
	fmt.Println(puan)
}

// Çıktı:
// [41 60 72 88 95 95]
// 3 true
// 3 false
// true 4
// 41 95
// [41 60 72 88 95]
// [60 65 72 88 95]
// [95 88 72 65 60]

Birkaç ayrıntıya dikkat edin. BinarySearch iki değer döndürür: hedefin bulunduğu ilk konum ya da bulunamadıysa sıralamayı bozmadan eklenebileceği konum, bir de gerçekten bulunup bulunmadığını söyleyen bir bool. 80 aranırken dönen 3, 80'in 72 ile 88 arasına girmesi gerektiğini söyler. Compact yalnızca ardışık tekrarları tek kopyaya indirir; Unix'teki uniq komutu gibi çalıştığı için tekrarları tamamen temizlemek istiyorsanız önce sıralamanız gerekir. Sort ve Reverse dilimi yerinde değiştirir ve bir şey döndürmez; Compact, Insert ve Delete ise değiştirilmiş dilimi döndürür, bu yüzden sonucu tekrar değişkene atamanız gerekir.

SortFunc ile çok alanlı sıralama

Struct dilimlerini sıralamak için slices.SortFunc kullanılır. Verdiğiniz karşılaştırma fonksiyonu a, b'den küçükse negatif, büyükse pozitif, eşitse sıfır döndürmelidir. Sayılar için cmp.Compare, string'ler için strings.Compare bu sözleşmeye uyan hazır fonksiyonlardır. Birden fazla alana göre sıralamak için cmp.Or çok kullanışlıdır: argümanlarından sıfır olmayan ilkini döndürür, yani birinci karşılaştırma eşitlik verirse ikinciye geçer. Azalan sıralama için argümanların yerini değiştirmeniz (cmp.Compare(b.Fiyat, a.Fiyat)) yeterlidir.

go
package main

import (
	"cmp"
	"fmt"
	"slices"
	"strings"
)

type Urun struct {
	Ad    string
	Fiyat int
}

func main() {
	urunler := []Urun{
		{"Klavye", 750},
		{"Fare", 300},
		{"Monitör", 4200},
		{"Kulaklık", 750},
	}

	// Önce fiyata göre azalan, eşitse ada göre artan sırala.
	slices.SortFunc(urunler, func(a, b Urun) int {
		return cmp.Or(
			cmp.Compare(b.Fiyat, a.Fiyat),
			strings.Compare(a.Ad, b.Ad),
		)
	})
	fmt.Println(urunler)

	// Ada göre sıralı dilimde ikili arama
	slices.SortFunc(urunler, func(a, b Urun) int { return strings.Compare(a.Ad, b.Ad) })
	i, ok := slices.BinarySearchFunc(urunler, "Klavye", func(u Urun, ad string) int {
		return strings.Compare(u.Ad, ad)
	})
	fmt.Println(i, ok)

	pahali := slices.IndexFunc(urunler, func(u Urun) bool { return u.Fiyat > 1000 })
	fmt.Println(urunler[pahali].Ad)

	ucuz := slices.MinFunc(urunler, func(a, b Urun) int { return cmp.Compare(a.Fiyat, b.Fiyat) })
	fmt.Println(ucuz)
}

// Çıktı:
// [{Monitör 4200} {Klavye 750} {Kulaklık 750} {Fare 300}]
// 1 true
// Monitör
// {Fare 300}

Belge, SortFunc'un kararlı (stable) olmadığını, yani eşit öğelerin özgün sırasının korunmayabileceğini açıkça yazar. Karşılaştırma fonksiyonunun bir "strict weak ordering" tanımlaması gerekir; kısaca tutarlı olmalı, aynı iki öğe için bir kez "küçük" bir kez "büyük" dememelidir. Eşitlerin yerinde kalması önemliyse slices.SortStableFunc kullanın. Örnekteki BinarySearchFunc çağrısında hedefin tipinin (string) öğe tipinden (Urun) farklı olabildiğine dikkat edin: karşılaştırma fonksiyonu func(E, T) int biçimindedir ve dilim aynı ölçüte göre sıralanmış olmalıdır.

String ve ondalıklı sayı sıralamasında sürprizler

String ve ondalıklı sayı sıralamasında sürprizler

Dil spesifikasyonuna göre string'ler bayt bayt (lexically byte-wise) karşılaştırılır. Bu, Türkçe harflerin UTF-8 kodlamasında birden fazla bayt kapladığı ve bu baytların ASCII harflerden büyük olduğu anlamına gelir: "Çanta" ve "Şeker", "Zil"den sonra gelir. Ondalıklı sayılarda ise slices.Sort belgesi NaN değerlerinin diğerlerinden önce sıralandığını, slices.Max belgesi de dilimde bir NaN varsa sonucun NaN olacağını söyler.

go
package main

import (
	"fmt"
	"math"
	"slices"
)

func main() {
	kelimeler := []string{"Zil", "Çanta", "Armut", "Ilık", "Şeker"}
	slices.Sort(kelimeler)
	fmt.Println(kelimeler)

	olcum := []float64{3.5, math.NaN(), 1.2}
	slices.Sort(olcum)
	fmt.Println(olcum, slices.Max(olcum))
}

// Çıktı:
// [Armut Ilık Zil Çanta Şeker]
// [NaN 1.2 3.5] NaN

Kullanıcıya gösterilen listelerde alfabetik Türkçe sıra gerekiyorsa bayt sırasına güvenmeyin; standart strings.Compare dil kurallarını bilmez. Ölçüm verilerinde NaN olabiliyorsa sıralamadan ya da Max/Min çağırmadan önce math.IsNaN ile ayıklamak sonuçların anlamlı kalmasını sağlar. String'lerin bayt ve rune yapısı string, rune ve UTF-8 yazısında anlatılıyor.

Yerinde değiştiren fonksiyonlar ve sıfırlama

Delete, DeleteFunc, Compact, CompactFunc ve Replace dilimi yerinde değiştirir ve belgelerine göre yeni uzunluk ile eski uzunluk arasında kalan öğeleri sıfırlar. Bu, eski dilim değişkenini kullanmaya devam ederseniz şaşırtıcı sonuçlar görebileceğiniz anlamına gelir. Aşağıda a dilimi Delete sonrasında hâlâ beş öğelidir ama son iki öğesi sıfırlanmıştır:

go
package main

import (
	"fmt"
	"slices"
)

func main() {
	a := []int{1, 2, 3, 4, 5}
	b := slices.Delete(a, 1, 3)
	fmt.Println(b, len(b))
	fmt.Println(a)

	c := []int{1, 2, 3}
	d := slices.Clone(c)
	d[0] = 99
	fmt.Println(c, d)

	for parca := range slices.Chunk([]string{"a", "b", "c", "d", "e"}, 2) {
		fmt.Println(parca)
	}

	var bos []int
	fmt.Println(slices.Equal(bos, []int{}))
}

// Çıktı:
// [1 4 5] 3
// [1 4 5 0 0]
// [1 2 3] [99 2 3]
// [a b]
// [c d]
// [e]
// true

Clone yeni bir ortak dizi ayırdığı için kopyada yaptığınız değişiklik asla aslını etkilemez, ancak belge bunun yüzeysel bir kopya olduğunu hatırlatır: öğeler işaretçi ya da başka bir dilim içeriyorsa bunlar paylaşılmaya devam eder. Chunk ile alınan alt dilimler kırpılmıştır (kapasiteleri uzunluklarına eşittir), bu yüzden bir parçaya append yapmak komşu parçanın üzerine yazmaz. Son satır, Equal fonksiyonunun nil dilim ile boş dilimi eşit saydığını gösteriyor.

Kapasite ve birleştirme: Grow, Clip, Concat, Repeat

Bu dört fonksiyon dilimin içeriğinden çok kapasitesiyle ilgilidir. Grow(s, n), gerekiyorsa kapasiteyi artırarak en az n öğenin yeni bir bellek ayırmadan eklenebileceğini garanti eder; n negatifse panic olur. Kaç öğe ekleyeceğinizi önceden biliyorsanız döngüden önce bir kez Grow çağırmak, append'in yol boyunca birkaç kez yeniden ayırma yapmasını önler. Clip(s) ise tam tersini yapar ve kullanılmayan kapasiteyi atar; sonucu s[:len(s):len(s)] ile aynıdır. Bir dilimi başka bir koda verirken o kodun sizin ortak dizinizin sonuna yazmasını istemiyorsanız Clip işe yarar.

Concat birden fazla dilimi uç uca ekleyerek yeni bir dilim döndürür ve sonuç boşsa nil verir. Repeat(x, n) dilimi n kez tekrarlar; sonucun uzunluğu ve kapasitesi len(x) * n'dir ve belgeye göre sonuç hiçbir zaman nil olmaz. Sayı negatifse ya da çarpım taşarsa panic olur. Bu fonksiyonların hepsi sonucu bir değer olarak döndürür; append gibi davranırlar ve dönen dilimi kullanmanız gerekir.

maps paketi: anahtarlar, kopyalama ve karşılaştırma

maps paketi daha küçüktür. Keys, Values ve All Go 1.23'ten beri dilim değil iterator döndürür; belgeye göre dolaşma sırası belirtilmemiştir ve bir çağrıdan diğerine aynı olacağı garanti edilmez. Sıralı anahtar listesi için slices.Sorted(maps.Keys(m)) yazılır. Clone yüzeysel bir kopya döndürür, Copy(dst, src) kaynaktaki tüm çiftleri hedefe ekler ve var olan anahtarların değerini ezer, DeleteFunc koşulu sağlayan çiftleri siler, Equal iki map'in aynı anahtar ve değerleri içerip içermediğini == ile kontrol eder. Collect ve Insert ise bir iter.Seq2 iterator'ünden map oluşturur ya da var olan map'e ekler.

Aşağıdaki örneğin son iki satırı bu fonksiyonları kullanıyor; ilk üç satır ise bir sonraki bölümde anlatılan sort karşılaştırmasına ait.

go
package main

import (
	"cmp"
	"fmt"
	"maps"
	"slices"
	"sort"
)

type Ogrenci struct {
	Ad  string
	Not int
}

type NotaGore []Ogrenci

func (a NotaGore) Len() int           { return len(a) }
func (a NotaGore) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a NotaGore) Less(i, j int) bool { return a[i].Not < a[j].Not }

func main() {
	sinif := []Ogrenci{{"Ece", 85}, {"Can", 70}, {"Ada", 85}, {"Efe", 60}}

	// Eski yol: sort.Interface
	eski := slices.Clone(sinif)
	sort.Stable(NotaGore(eski))
	fmt.Println(eski)

	// Eski yol: sort.Slice (indeks alan less fonksiyonu)
	eski2 := slices.Clone(sinif)
	sort.SliceStable(eski2, func(i, j int) bool { return eski2[i].Not < eski2[j].Not })
	fmt.Println(eski2)

	// Yeni yol: slices.SortStableFunc (öğe alan cmp fonksiyonu)
	yeni := slices.Clone(sinif)
	slices.SortStableFunc(yeni, func(a, b Ogrenci) int { return cmp.Compare(a.Not, b.Not) })
	fmt.Println(yeni)

	m := map[string]int{"c": 3, "a": 1, "b": 2}
	fmt.Println(slices.Sorted(maps.Keys(m)))
	kopya := maps.Clone(m)
	maps.DeleteFunc(kopya, func(k string, v int) bool { return v%2 == 1 })
	fmt.Println(kopya, maps.Equal(m, kopya))
}

// Çıktı:
// [{Efe 60} {Can 70} {Ece 85} {Ada 85}]
// [{Efe 60} {Can 70} {Ece 85} {Ada 85}]
// [{Efe 60} {Can 70} {Ece 85} {Ada 85}]
// [a b c]
// map[b:2] false

sort paketinden slices paketine geçiş

sort paketinden slices paketine geçiş: Karşılaştırma, Tip güvenliği, Kararlı sıralama, İkili arama

sort paketinde dilim sıralamanın iki klasik yolu vardır. Birincisi sort.Interface'i (Len, Less, Swap) uygulayan bir tip tanımlayıp sort.Sort ya da sort.Stable çağırmaktır. İkincisi sort.Slice ya da sort.SliceStable ile indeks alan bir less fonksiyonu vermektir. sort.Slice argümanını any olarak alır ve belgesine göre dilim olmayan bir değer verilirse panic olur; yani tip hatası derleme zamanında değil çalışma zamanında ortaya çıkar. slices.SortFunc ise öğelerin kendisini alır ve tip denetimi derleyicide yapılır. Yukarıdaki örnekte üç yöntem de kararlı sıralama yaptığı için Ece ile Ada, eşit notlarına rağmen özgün sıralarını korudu.

Konusortslices
Karşılaştırmaless(i, j int) bool, indekslecmp(a, b E) int, öğeyle
Tip güvenliğisort.Slice any alır, yanlış tipte panicgeneric, derleme zamanında denetlenir
Kararlı sıralamasort.Stable, sort.SliceStableslices.SortStableFunc
İkili aramasort.Search(n, f), sort.SearchIntsslices.BinarySearch, BinarySearchFunc
Hazır tiplersort.Ints, sort.Strings, sort.Float64sslices.Sort tüm Ordered tipler için
Belge önerisiçoğu fonksiyonda slices karşılığını öneriryeni kod için tercih edilen

sort.Ints, sort.Strings ve sort.Float64s belgelerinde Go 1.22'den beri bu fonksiyonların doğrudan slices.Sort'u çağırdığı yazar; dolayısıyla eski kodu hemen değiştirmeniz gerekmez, davranış aynıdır. sort.Slice, sort.SliceStable, sort.Sort ve sort.Stable için ise belge yeni slices fonksiyonlarının çoğu durumda daha rahat ve daha hızlı olduğunu not eder. sort.Interface hâlâ işe yarar: sıralanacak şey dilim değilse (örneğin kendi veri yapınızdaki öğeler) bu arayüzü uygulamak doğal yoldur.

Sık yapılan hatalar

Dönen dilimi atamamak

slices.Delete(s, 0, 1) yazıp sonucu atlamak en yaygın hatadır. Fonksiyon s değişkeninin uzunluğunu değiştiremez (dilim başlığı değerle geçirilir), yalnızca yeni başlığı döndürür. Aynı kural Insert, Compact, Replace, DeleteFunc ve Grow için de geçerlidir.

Sıralanmamış dilimde ikili arama

BinarySearch belgesi dilimin artan sırada olması gerektiğini söyler; sırasız bir dilimde hata vermez, yalnızca yanlış sonuç döndürür. BinarySearchFunc kullanıyorsanız karşılaştırma fonksiyonu dilimin sıralandığı ölçütle aynı olmalıdır.

Boş dilimde Min ve Max

slices.Min, Max, MinFunc ve MaxFunc boş dilimde panic olur. Veri dışarıdan geliyorsa önce len(s) > 0 kontrolü yapın. MaxFunc ve MinFunc birden fazla eşit sonuç varsa ilkini döndürür.

Çıkarma ile karşılaştırma fonksiyonu yazmak

return a.Yas - b.Yas kısa görünür, ama tam sayılar çok büyük ya da çok küçükse çıkarma taşabilir ve işaret ters döner. cmp.Compare her zaman -1, 0 veya +1 döndürür ve ondalıklı sayılarda NaN durumunu da tanımlı biçimde ele alır; belgeye göre NaN her sayıdan küçük, iki NaN birbirine eşit sayılır.

Map sırasına güvenmek

Testte bir kez doğru sırada görünen map çıktısı başka bir çalıştırmada değişebilir. Kullanıcıya gösterilen ya da dosyaya yazılan her listeyi açıkça sıralayın.

Sık Sorulan Sorular

slices.Sort hangi algoritmayı kullanıyor?

Paket belgesi belirli bir algoritma adı vermez; yalnızca Sort'un artan sırada sıraladığını ve SortFunc'un kararlı olmadığını belirtir. Kararlılık gerekiyorsa SortStableFunc'u seçin, gerisi uygulama ayrıntısıdır.

Bir dilimin tekrarsız hâlini nasıl alırım?

Önce slices.Sort ile sıralayıp sonra slices.Compact çağırabilirsiniz; Compact yalnızca ardışık tekrarları sildiği için sıralama şarttır. Sırayı korumanız gerekiyorsa görülen öğeleri bir map'te tutan küçük bir döngü daha uygundur.

maps.Keys neden dilim döndürmüyor?

Go 1.23 ile maps.Keys ve maps.Values iterator döndürecek biçimde eklendi. Dilim isterseniz slices.Collect(maps.Keys(m)), sıralı dilim isterseniz slices.Sorted(maps.Keys(m)) yazın.

sort paketi kaldırılacak mı?

Hayır. Go öğrenme rehberinde de değinilen Go 1 uyumluluk sözü gereği sort paketi desteklenmeye devam eder. Belgeler yalnızca yeni kodda slices fonksiyonlarını önerir.

Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.

İlgili içerikler: Go öğrenme rehberi · Go iterator ve range over func · append ve copy · map kullanımı · generics kısıtları · array ve slice farkı