電波ビーチ

☆(ゝω・)v

Goのsortパッケージを少し調べた

組込みじゃないんかい

Goは素晴らしい言語なのでソートのためにsortパッケージを呼ぶことができるのです。手間かけてimportしたソートにはぬくもりがありますね。

sortパッケージはこちら。

src/sort/sort.go - The Go Programming Language

基本的な使い方

ググればたくさんでてきますが、こちらがすごくわかりやすかったです。ありがとうございました。

midori5.hatenablog.com

text.baldanders.info

なにもしなくてもstirng, float64, int型は以下のような感じでソートできるそうです。ここではint型についてだけ書いてますが残りの型もお察しの感じです

sort.Ints(slices)
sort.IntSlice(slices).Sort()
sort.Sort(sort.IntSlice(a))

これらは全部同じ結果になります。IntSliceStringSliceFloat64Sliceはそれぞれ[]int[]string[]float64エイリアスで、sort.Interfaceインターフェースの各メソッドとsort.Sort()を実装しています。sort.Ints()の中身も、

// Ints sorts a slice of ints in increasing order.
func Ints(a []int) { Sort(IntSlice(a)) }

のようにsort.Sortを内部的に呼んでいるだけなので、結局スライスをIntSliceなりでキャストするだけで使えるって感じでしょうか。

降順は専用の別の関数があって

sort.Sort(sort.Reverse(sort.IntSlice(s)))

とすればいいです。

type reverse struct {
      // This embedded Interface permits Reverse to use the methods of
      // another Interface implementation.
      Interface
  }
  
  // Less returns the opposite of the embedded implementation's Less method.
  func (r reverse) Less(i, j int) bool {
      return r.Interface.Less(j, i)
  }
  
  // Reverse returns the reverse order for data.
  func Reverse(data Interface) Interface {
      return &reverse{data}
  }

reverse構造体がsort.InterfaceをまるまるパクっていてLessに渡すときにiとjを逆に渡しているという、理性ではわかるけど実際に呼び出すとき非常にめんどくさい書き方と毎回思ってます。代案はありません。

ちなみにどういうソートを使っているか、なんですが、データ量によって内部でアルゴリズムを切り替えているらしいです。解読は諦めましたが。

複数のキーでソートする

C#でいうとこのOrderBy().ThenBy()...pythonでいうとこのsortedlist = sorted(unsortedlist, key=lambda x:(x[0], x[2])) みたいなやつをやりたいですがGoにはメソッドチェーンなんて存在しないので関数型をやりくりする必要があります。

ドキュメントのサンプル( https://golang.org/pkg/sort/#example__sortMultiKey のexample_SortMultiKeyってやつ)をみるとOrderBy関数にsort.Interface用のLessメソッドを可変長引数で複数わたせるようにしておき、ターゲットとなるスライスとそれらメソッド郡をまとめた別の構造体multiSorterからソートをしている様子です(わかりづらい説明)。実際のLessの実装はmainでやっていますがたいていどこのサンプルをみてもそうやっていました。なるほど。

OrderByを作ってそっちにLessメソッド群を渡すのがなんか複雑でやりたくねぇなと思ったので、自分はsort.Sort()にそのまま渡すのが見た目的にシンプルだしいいやってことでそんな感じでやってみました。備忘録がわりにおいときます。

package main
import (
    "fmt"
    "time"
    "sort"
)

type profile struct{
    name string
    class int
    height int
    data time.Time
    threesize [3]int
}

// lessfuncのエイリアス
type lessFunc func(i, j *profile)bool

// ターゲットになるスライスとlessfuncのスライスをまとめた構造体
type idleSorter struct{
    idle []profile
    lessfunc []lessFunc
}
// にsort.Interfaceを定義
func (is idleSorter)Len()int{
    return len(is.idle)
}
func (is idleSorter)Swap(i, j int){
    is.idle[i], is.idle[j] = is.idle[j], is.idle[i]
}
func (is idleSorter)Less(i, j int)bool{
    var k int    // 最後にkつかうのでfor簡易文ではなくここで宣言
    p, q := &is.idle[i], &is.idle[j]
    for k=0; k<len(is.lessfunc)-1; k++{
        less := is.lessfunc[k]
        // less(p, q)が真 -> swapが発生 偽だと発生しない
        switch{
            case less(p, q):
                return true
            case less(q, p):
                return false
        }
        // p==qならもっかいkを回すことになる
    }
    // すべてのlessfuncにおいてp==qならばそんなんもう同値ってことだしどっちでもかまわんからk番目を返す
    return is.lessfunc[k](p, q)
}

// ターゲットになるやつ
var aqours = []profile{
    {name: "黒澤ルビィ", class: 1, height: 154, data: setData(9, 21), threesize: [3]int{76,56,79}},
    {name: "国木田花丸", class: 1, height: 152, data: setData(3, 4), threesize: [3]int{83,57,83}},
    {name: "津島善子", class: 1, height: 156, data: setData(7, 13), threesize: [3]int{79, 58, 80}},
    {name: "高海千歌", class: 2, height: 157, data: setData(8, 1), threesize: [3]int{82,59,83}},
    {name: "桜内梨子", class: 2, height: 160, data: setData(9, 19), threesize: [3]int{80, 58, 82}},
    {name: "渡辺曜", class: 2, height: 157, data: setData(4, 17), threesize: [3]int{82, 57, 81}},
    {name: "黒澤ダイヤ", class: 3, height: 162, data: setData(1, 1), threesize: [3]int{80,57, 80}},
    {name: "松浦果南", class: 3, height: 162, data: setData(2, 10), threesize: [3]int{83,58,84}},
    {name: "小原鞠莉", class: 3, height: 163, data: setData(6, 13), threesize: [3]int{87,60,84}},
}

func setData(month, day int)  time.Time{
    t := time.Date(0000, time.Month(month), day, 0, 0, 0, 0, time.Local)
    return t
}


func main(){
    // lessfuncを実装
    byBast := func(p1, p2 *profile)bool{
        return p1.threesize[0]<p2.threesize[0]
    }
    byClass := func(p1, p2 *profile)bool{
        return p1.class<p2.class
    }
    // 逆順も同じようにここで実装する
    byBastDescending := func(p1, p2 *profile)bool{
        return p1.threesize[0]>p2.threesize[0]
    }
    // 誕生日 time.TimeもBefore/Afterで比較してboolを返せる
    byDate := func(p1, p2 *profile)bool{
        return p1.data.Before(p2.data)
    }

    fmt.Println("おっぱい昇順")
    tes := idleSorter{idle: aqours, lessfunc: []lessFunc{byBast}}
    sort.Sort(tes)
    for i, v := range aqours{
        fmt.Printf("[%d] name: %s bastsize: %d\n", i+1, v.name, v.threesize[0])
    }
    fmt.Println("誕生日昇順")
    sort.Sort(idleSorter{idle: aqours, lessfunc: []lessFunc{byDate}})
    for i, v := range aqours{
        fmt.Printf("[%d] name: %-7s birthday: %s\n", i+1, v.name, v.data.Format("1月2日"))
    }
    fmt.Println("学年昇順でおっぱい降順")
    sort.Sort(idleSorter{idle: aqours, lessfunc: []lessFunc{byClass, byBastDescending}})
    for i, v := range aqours{
        fmt.Printf("[%d] name: %-7s class: %d bast: %d\n", i+1, v.name, v.class, v.threesize[0])
    }
}

ちなみにこのサンプルコードのところに、

Note that it can call the less functions twice per call. We could change the functions to return -1, 0, 1 and reduce the number of calls for greater efficiency: an exercise for the reader.

とあったのですがやりかたがさっぱり思いつきません。an exercise for the reader.(これは読者への宿題だよ☆)じゃねえよ

sort.Sliceを使ってみる

ついでに、Go1.8から実装されたこちらのメソッドを使ってみます。

func Slice(slice interface{}, less func(i, j int) bool)

とのことで、第二引数の無名関数によっていちいちless用の関数を作って渡してやる手間が省けます。素晴らしい。

The sort is not guaranteed to be stable. For a stable sort, use SliceStable.

とのことなのですが安定ソートってなんでしょう。。。わからんので無視します

fmt.Println("sort.Sliceを使ってみる おっぱいがでかい順->尻がでかい順")
sort.Slice(aqours, func(i, j int)bool{
    // でかい順なので不等号の向きに注意
    p, q := aqours[i], aqours[j]
    if p.threesize[0]>q.threesize[0]{
        return true
    }else if p.threesize[0]<q.threesize[0]{
        return false
    }else{
        return p.threesize[2]>q.threesize[2]
    }
})
for i, v := range aqours{
    fmt.Printf("[%d] name: %-7s bast: %d hip: %d\n", i+1, v.name, v.threesize[0], v.threesize[2])
}

3年生のみなさん強いですね。。

おわりに

バストの英語表記がbastじゃなくてbustっていま知りました。