Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Go Conference 2019 Autumn Go で超高速な 経路探索エンジンをつくる...

avvmoto
October 28, 2019

Go Conference 2019 Autumn Go で超高速な 経路探索エンジンをつくる/Go Conference 2019 Autumn go-ch

avvmoto

October 28, 2019
Tweet

More Decks by avvmoto

Other Decks in Programming

Transcript

  1. Copyright (C) 2018 DeNA Co.,Ltd. All Rights Reserved. 1 Go

    で超高速な 経路探索エンジンをつくる 2019 10/28 井本 裕 オートモーティブ事業本部 DeNA Co., Ltd.
  2. © DeNA Co., Ltd. 経路探索の使いみち 1. アプリ上で、到着予測時間を出す 2. 交通シミュレーター ⁃

    サービス改善用に、シミュレーターで実験 ⁃ 膨大な回数、経路探索計算を行う 10 注:開発中のダミー画面です 実際と異なる場合がございます
  3. © DeNA Co., Ltd. 地図のデータ化 - 最短経路を求めるにあたって、道路の形状は問題とならない - 必要なもの -

    どこと、どこが繋がっているか? - 繋がっている場所の、移動コストは? - 道路をグラフ(データ構造)として表現し問題を解く 13 地図データ(c) OpenStreetMap
  4. © DeNA Co., Ltd. グラフ - グラフの構成要素 - Node -

    頂点のこと - Edge - 辺のこと。Node と Node をつなぐ。 - 重み、向きの有無がある - 今回は重み付き有向グラフとして道路を表現 14 Node Edge 一方通行
  5. © DeNA Co., Ltd. ダイクストラ法 - 最短経路問題を解くアルゴリズム - 出発となるノードから、近い順に全方向に向かって最短経路を列挙していく -

    後述する経路探索の手法の基礎となる 問題 起点Aから全ノードの、最短経路とコストを調べる。 アルゴリズム ノードの集合を2つ用意しておく。 - 確定ノード: 最短経路と、そのコストが確定したノード。 - 未確定ノード: 最短経路と、そのコストが未確定。暫定の値が入っている 16 A B C D E 1 2 4 3 4 2 1
  6. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP3: 前ステップで確定ノードになったノードから (今回の場合ノードA)、 繋がっているノードを未確定ノードに入れる。

    (ノードB, C, D) また、未確定ノードの暫定コストを更新する。 確定ノード: [A] 未確定ノード: [B, C, D] 19 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 ∞→1 ∞ ∞ → 4 ∞→2
  7. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP4: 全てのノードが確定ノードになっていなければ、STEP2 に戻る。 つまり、未確定ノードから、一番コストの小さいノードを探す。

    これを確定ノードとする。 (今回の場合はノードC) このとき、ノードCへは、 A→Cとたどるのが最小経路と言える。 確定ノード: [A, C] 未確定ノード: [B, D] 20 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 ∞ 4 2 C
  8. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP5: STEP2, 3 を繰り返していく。

    前ステップで確定ノードになったノードから (今回の場合ノードC)、 繋がっているノードを未確定ノードに入れる。 (ノードD, E) Dについては、Aから行くよりも、Cから行くほうがコストが安いので、 暫定コストを更新する。Eも暫定コスト更新する。 確定ノード: [A, C] 未確定ノード: [B, D, E] 21 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 ∞ → 5 4 → 3 2
  9. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP6: STEP2, 3 を繰り返していく。

    未確定ノードのうち、一番暫定コスト安いBを 確定ノードにする。 確定ノード: [A, C, B] 未確定ノード: [D, E] 22 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 5 3 2 B
  10. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP7: STEP2, 3 を繰り返していく。

    前ステップで確定ノードになったノードから (今回の場合ノードB)、 繋がっているノードの、暫定コストを更新する。 B→Dと行くのはコスト5で、既存の暫定コストよりも悪化するので 更新しない。 確定ノード: [A, C, B] 未確定ノード: [D, E] 23 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 5 3 2
  11. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP8: STEP2, 3 を繰り返していく。

    未確定ノードのうち、一番暫定コスト安いDを 確定ノードにする。 確定ノード: [A, C, B, D] 未確定ノード: [E] 24 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 5 3 2 D
  12. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP8: STEP2, 3 を繰り返していく。

    Dを確定ノードにしたので、隣接ノードEの暫定コストを更新する。 D→Eと行くとコスト4であり、既存の暫定コストよりも小さい。 そのためEの暫定コストを更新する。 確定ノード: [A, C, B, D] 未確定ノード: [E] 25 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 5→4 3 2
  13. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP9: STEP2, 3 を繰り返していく。

    未確定ノードのうち、一番暫定コスト安いDを 確定ノードにする。 確定ノード: [A, C, B, D, E] 未確定ノード: [] 26 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 4 3 2 E
  14. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP10: STEP2, 3 を繰り返していく。

    未確定ノードのうち、一番暫定コスト安いEを 確定ノードにする。 確定ノード: [A, C, B, D, E] 未確定ノード: [] 27 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 4 3 2 E
  15. © DeNA Co., Ltd. ダイクストラ法 問題:起点Aから全ノードの、最短経路とコストを求める。 STEP11: 全てのノードのコストと最短経路が確定したので、終了! 確定ノード: [A,

    C, B, D, E] 未確定ノード: [] 28 A B C D E 1 2 4 3 4 2 1 赤:確定ノードや、確定コスト 黒:未確定ノード、暫定コスト 緑:今回更新した、暫定コスト 0 1 4 3 2
  16. © DeNA Co., Ltd. ダイクストラ法 まとめ 1. 起点の最小距離を0、ほかのノードの値を未定義(∞)に設定 2. 未確定ノードのうち、最小値のコストのノードを見つけ、確定ノードとする。

    3. 2で確定ノードとなったノードjから伸びているノードを、未確定ノードに入れる。もし必 要があれば、暫定的なコストを更新する。 4. 全てのノードが確定ノードになっていなければ、 STEP2に戻る 29
  17. © DeNA Co., Ltd. ダイクストラ法の課題は計算速度 - 地図のノード数の規模感 数十万(タクシーの1交通圏)〜数百万(東京神奈川全体)〜 1.5千万(日本全体) -

    Dijkstra の計算量は O(N^2) であり、数百万件を1-10msでは捌けない go + gonum, i5 3.1GHz, 横浜市西区中区を中心としたエリアの道路グラフをサンプリングしてグラフを作成。ランダム 100経路の平均値 30
  18. © DeNA Co., Ltd. Bidirectional Dijkstra’s Algorithm ▪ 開始点と到達点の両端からダイクストラで交互に探索を行う ▪

    もう片方で探索済みのノードにあたったら終了 ⁃ それが最短経路であるためにはもう少し条件がある ▪ 探索範囲が劇的に減るので速い source: http://www.cs.princeton.edu/courses/archive/spr06/cos423/Handouts/EPP%20shortest%20path%20algorithms.pdf Dijkstra での探索範囲 Bidirectional Dijkstra での探索範 囲
  19. © DeNA Co., Ltd. 事前計算しておく手法 - 事前計算しておくことで、二点間の最短経路検索(以後 Query と呼ぶ)を高速に行う手 法が多数提案されている

    - それぞれ、事前計算のコストやQueryのコストの傾向が異なる。 33 Bast, Hannah, et al. "Route planning in transportation networks." Algorithm engineering. Springer, Cham, 2016. 19-80.
  20. © DeNA Co., Ltd. Contraction Hierarchy - 今回我々が実装したのはContraction Hierarchy -

    OSRM(Open Source Routing Machine) でも採用 - 事前計算の時間、Query に要する時間がバランスが取れていると判断 - 事前計算が短いと、渋滞の反映がやりやすくなる 34 source: https://movingai.com/IJCAI18-HS/ijcai-hs-harabor.pdf
  21. © DeNA Co., Ltd. Contraction Hierarchy - 原論文: Geisberger, Robert,

    et al. "Contraction Hierarchies: Faster and Simpler Hierarchical Routing in Road Networks." - ざっくり説明 - Bidirectional Dijkstraベース - 隣り合っていないノード間の最短経路をショートカットとして、事前にエッジとして 追加しておく - 経路検索時には、ショートカットを利用することで、ターゲットにより早く着くように なる 35 source: https://movingai.com/IJCAI18-HS/ijcai-hs-harabor.pdf
  22. Priority Queue 優先度付きキュー(ゆうせんどつき -、英: priority queue)は、以下の4つの操作をサポートする抽象 データ型である。 • キューに対して要素を優先度付きで追加する。 •

    最も高い優先度を持つ要素をキューから取り除き、それを返す。 • (オプション) 最も高い優先度を持つ要素を取り除くことなく参照する。 • (オプション) 指定した要素を取り除くことなく優先度を変更する source: 優先度付きキュー - Wikipedia 40 queue := NewMinPQ(0) queue.Push("プリン", 100) queue.Push("うまい棒", 10) queue.Push("ケーキ", 3000) fmt.Print(queue.Pop()) fmt.Print(queue.Pop()) fmt.Print(queue.Pop()) // Output: // うまい棒: 10 // プリン: 100 // ケーキ: 3000 優先度付き Push 安い順に Pop
  23. Priority Queue の実装方法 - Priority Queueをgoで実装するなら、標準ライブラリーのcontainer/heapを 使うのが恐らく最も簡単 - godoc の

    Exampleに Priority Queue の実装が載っている - https://godoc.org/container/heap#example-package--PriorityQueue 41
  24. container/heap 紹介 42 利用手順 - heap.Interface を満たす実装を用意 これはアイテムを保存するもの - heap.Push(),

    heap.Pop()経由でアイテムを 追加・削除することで、2分木としてメンテナ ンスされる type Interface interface { Push(x interface{}) // add x as element Len() Pop() interface{} // remove and return element Len() - 1. Len() int // sort.Interface Less(i, j int) bool // sort.Interface Swap(i, j int) // sort.Interface } source: https://golang.org/pkg/container/heap/
  25. heap.Interface 実装例 43 type Item struct { value string priority

    int } type PriorityQueue []*Item func (pq PriorityQueue) Len() int { return len(pq) } func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority } func (pq PriorityQueue) Swap(i, j int) { pq[i], pq[j] = pq[j], pq[i]} func (pq *PriorityQueue) Push(x interface{}) { n := len(*pq) item := x.(*Item) *pq = append(*pq, item) } func (pq *PriorityQueue) Pop() interface{} { old := *pq n := len(old) item := old[n-1] old[n-1] = nil // avoid memory leak *pq = old[0 : n-1] return item } container/heap の Example から最小構成要素を抜き出し heap.Interface を実装 source: https://golang.org/pkg/container/heap/#exam ple__priorityQueue
  26. container/heap 利用例 44 type PriorityQueue []*Item func main() { pq

    := PriorityQueue{} heap.Init(&pq) item := &Item{ value: "orange", priority: 1, } heap.Push(&pq, item) heap.Pop(&pq) } source: https://golang.org/pkg/container/heap/#exam ple__priorityQueue
  27. Priority Queue の実装を高速化しよう - Push のたびに Item が allocate されている

    - ダイクストラのように、Push の回数が極めて多いと問題となる - ダイクストラ自体は、ほとんど巨大なfor文が回る処理 - これの実装を改良して、より処理速度が早くなるようにしてみましょう 45 type PriorityQueue []*Item func main() { pq := PriorityQueue{} heap.Init(&pq) item := &Item{ value: "orange", priority: 1, } heap.Push(&pq, item) } source: https://golang.org/pkg/container/heap/#exam ple__priorityQueue
  28. Slice の仕組みのおさらい - まずは前提知識の整理 - ゴール - sliceとarrayの関係 - slice

    の lengthとcapacityについて - appendの処理内容 (The Go Blog の Go Slices: usage and internalsが詳しい) 46
  29. sliceとarrayについて - array - 固定長 - slice - 大きさを後から変更可能 -

    sliceは内部に、ベースとなる arrayへのポインタを持っている - このarrayは基底配列と呼ばれる 47 var a [4]int var s []int
  30. sliceとarrayについて slice の構成要素 1. 基底配列 (array) へのポインター 2. length ⁃

    実際にsliceの要素が何個存在するか 3. capacity ⁃ 基底配列の容量 48 (source: Go Slices: usage and internals )

  31. slice の拡張 appendの模擬コード 53 func AppendByte(slice []byte, data ...byte) []byte

    { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice } (source: Go Slices: usage and internals )
  32. slice の拡張 appendの模擬コード 54 func AppendByte(slice []byte, data ...byte) []byte

    { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice } (source: Go Slices: usage and internals ) slice 拡張しないのなら、copyするだけ
  33. slice の拡張 appendの模擬コード 55 func AppendByte(slice []byte, data ...byte) []byte

    { m := len(slice) n := m + len(data) if n > cap(slice) { // if necessary, reallocate // allocate double what's needed, for future growth. newSlice := make([]byte, (n+1)*2) copy(newSlice, slice) slice = newSlice } slice = slice[0:n] copy(slice[m:n], data) return slice } (source: Go Slices: usage and internals ) slice 拡張しないのなら、copyするだけ slice 拡張が必要なケース
  34. sliceの拡張 - 実験 - sliceをcapacityを指定せず作成 - 要素を一つずつ、20回append - arrayの長さが1,2,4,8,16,32となるsliceの拡張が発生する -

    無駄に発生したコスト - 複数回のallocateのコスト、そしてメモリコピーのコスト - 長さが20のarrayで十分なはずが、32の長さで確保されている 57 var s []int for i:=0; i<20; i++ { s = append(s, 10) fmt.Println(cap(s)) } 1 2 4 4 8 8 8 8 16 16 16 16 16 16 16 16 32 32 32 32
  35. Slice の仕組み - ゴール - sliceとarrayの関係 - slice の lengthとcapacityについて

    - appendの処理内容 以上を踏まえた上で、sliceのベストプラクティスを見ていきましょう 58
  36. sliceのベストプラクティス1 length や capacity を指定して slice を作成する - 事前に必要な長さの目安を capacityに明示しよう

    - appendを使えば、それ以上に要素を追加してもOK 59 s1 := make([]int, length) // capacity = length s2 := make([]int, length, capacity)
  37. sliceのベストプラクティス3 length を延長し allocate 済みの領域を使おう 例:構造体のsliceに要素を一つ追加する 62 for i:=0; i<E;

    i++ { if len(s)+1 < cap(s) { s=s[:len(s)+1] InitBigStruct(&s[len(s)]) } } s[0] ... s[len(s)-1] s[len(s)] ... s[cap(s)-1] s s[0:len(s)] すでに要素が入っていて、利 用済みの領域 s[len(s):cap(s)] アロケート済みだが、 まだ利用されていない領域 s[:len(s)+1] slice の length を延長
  38. sliceのベストプラクティス3 length を延長し allocate 済みの領域を使おう 例:構造体のsliceに要素を一つ追加する 63 for i:=0; i<E;

    i++ { if len(s)+1 < cap(s) { s=s[:len(s)+1] // slice の延長 InitBigStruct(&s[len(s)]) } else if cap(s) < len(s)+1 { s = append(s, NewBigStruct()) } } capacityが足りなくなったら、appendで 基底配列を拡張
  39. sliceのベストプラクティス まとめ 1. length や capacity を指定して slice を作成する 2.

    メモリアロケートの回数は少なくしよう 3. length を延長し allocate 済みの領域を使う 64
  40. Priority Queue の実装を高速化しよう(再掲) 65 type PriorityQueue []*Item func main() {

    pq := PriorityQueue{} heap.Init(&pq) item := &Item{ value: "orange", priority: 1, } heap.Push(&pq, item) }
  41. Priority Queue の実装を高速化しよう 66 type PriorityQueue []*Item func main() {

    pq := make(PriorityQueue, 0, N) heap.Init(&pq) item := &Item{ value: "orange", priority: 1, } heap.Push(&pq, item) } 1. length や capacity を指定して slice を作成する
  42. Priority Queue の実装を高速化しよう 67 type PriorityQueue []Item func main() {

    pq := make(PriorityQueue, 0, N) heap.Init(&pq) pq.PushFast("orange", 1) } 2. メモリアロケートの回数は少なく しよう (Item はまとめてallocate)
  43. Priority Queue の実装を高速化しよう 68 func (q *PriorityQueue) PushFast(value string, priority

    float64) { l := len(q) if l < cap(q) { q = q[:l+1] q[l].value = value q[l].priority = priority } else { q = append(q, Item{ value: value, priority: priority, }) } heap.Push(p.p, &q[l]) } 3. length を延長し allocate 済みの 領域を使う
  44. Priority Queue の実装を高速化しよう PQのPush()をtestingパッケージでベンチマーク allocateがなくなった! 速度も2倍高速化! 69 $ go test

    -bench . -benchmem goos: darwin goarch: amd64 BenchmarkTheirs-8 30000000 49.8 ns/op 16 B/op 1 allocs/op BenchmarkOurs-8 100000000 20.4 ns/op 0 B/op 0 allocs/op PASS ok const benchmarkSize = 10000 func BenchmarkTheirs(b *testing.B) { b.StopTimer() for i := 0; i < b.N; { p := NewMinPQ(benchmarkSize) b.StartTimer() for j := 0; j < benchmarkSize; j++ { p.Push(0, 0) i++ if i >= b.N { return } } b.StopTimer() } }
  45. グラフライブラリーとは? - グラフ(データ構造)を扱うライブラリー - ダイクストラをするのに必要なもの - エッジの追加 - あるノードの、近隣ノードの取得 72

    0 1 3 2 1 2 4 var g Graph g.SetEdge(0, 1, 2.0) g.SetEdge(0, 2, 4.0) g.SetEdge(0, 3, 1.0) fmt.Println(g.From(0)) // Output: // [{1, 2.0}, {2, 4.0}, {3, 1.0}]
  46. type SimpleGraph struct { from map[int64]map[int64]float64 } func (g *SimpleGraph)

    SetEdge(from, to int64, weight float64) { if _, ok := g.from[from]; !ok { g.from[from] = map[int64]float64{} } g.from[from][to] = weight } グラフライブラリーの単純な実装 73 from To weight Edge を map の map として表現
  47. type SimpleGraph struct { from map[int64]map[int64]float64 } func (g *SimpleGraph)

    SetEdge(from, to int64, weight float64) { if _, ok := g.from[from]; !ok { g.from[from] = map[int64]float64{} } g.from[from][to] = weight } グラフライブラリーの単純な実装 74 from To weight Edge を map の map として表現
  48. type SimpleGraph struct { from map[int64]map[int64]float64 } func (g *SimpleGraph)

    From(id int64) (nodes []int64, costs []float64) { for node, cost := range g.from[id] { nodes = append(nodes, node) costs = append(costs, cost) } return } グラフライブラリーの実装例 75 from To weight 近隣 Node の取得
  49. go の map の仕組み - go の map はただの ハッシュテーブル

    cf.https://github.com/golang/go/blob/master/src/runtime/map.go#L9 - ハッシュテーブルとは - ハッシュテーブルはキーをもとに生成されたハッシュ値を添え字とした配列である。通常、配列の添え字には非負整数し か扱えない。そこで、キーを要約する値であるハッシュ値を添え字として値を管理することで、検索や追加を要素数によら ず定数時間O(1)で実現する。 77 source: ハッシュテーブル - Wikipedia
  50. go の map の仕組み 謝辞:この解説は Macro View of Map Internals

    In Go に大きくよります。2013年の記事ですが、現在のruntimeのコード と比較しても、大きな違いはないようです。 - goのmapは内部的に「Bucket」の配列となる - 1つのBucket には、8つのキー/値ペアを保存 - Bucketの個数は常に2の累乗 - マップのキーはハッシュ値に変換され、ハッシュ値の LOB(low order bits; 下位ビット) がBucketの選択に用いられる 78 keys “orange” ハッシュ値 ハッシュ関数 Bucket Bucket Bucket ... LOB key: value 8個分
  51. go の map の仕組み - Bucket の構成 - ハッシュ値のHOB (top

    8 high order bits)の配列 - key/valueのペアを格納するバイトの配列 - LOBが衝突して、同じBucketに9個以上入れる場合は? - Overflow Bucket が作成され、各Bucket から参照される 79 source:Macro View of Map Internals In Go https://www.ardanlabs.com/blog/2013/12/macro-view-of-map-internals-in-go.html
  52. go の map の仕組み - map に key/value の追加を繰り返すと、Overflow Bucketが増えてゆき、

    性能が下がる - スレッショルドを超えると map の拡張が走る - 既存 Bucket の2倍の Bucket の配列が allocate される - key/value が追加または削除のタイミングで、古い Bucket配列から新しい Bucket 配列へ「Evacuate(退避)」してゆく 80
  53. go の map の仕組み - 事前にmapの目安がわかるなら、”capacity hint ” を指定してmap を作る

    ことで、 map の拡張が最小限に押さえられる - slice 同様、初期の capacity を超えて要素を追加することができる - benchmark とってみると、capacity を指定したほうが、key/valueの追加は 早い 81 make(map[string]int) make(map[string]int, 100) cf. https://golang.org/ref/spec#Map_types goos: darwin goarch: amd64 BenchmarkMapAssignNoCap-8 10000000 176 ns/op 87 B/op 0 allocs/op BenchmarkMapAssignWithCap-8 20000000 107 ns/op 0 B/op 0 allocs/op
  54. go の map の仕組み まとめ - go の map はハッシュテーブル

    - slice 同様、内部的に capacity や、mapの拡張がある - 事前に要素数の目安がわかるなら、”capacity hint ” を指定してmap を作 ることで、 map の拡張が最小限に押さえられる 82 make(map[string]int) make(map[string]int, 100)
  55. グラフライブラリーの高速化 - キーアイディア:map access はやめて代わりに slice access にしよう - node

    ID は飛び飛びの値でありえる → node ID を全部sliceにつめる → node ID の代わりに、slice で何番目かのindexを使う - 繋がっている所を辿っていくダイクストラの用途だと、これで充分だった 83 type SimpleGraph struct { fromCost [][]float64 fromIndex [][]int nodes []int64 indexOf map[int64]int } func (g *SimpleGraph) From(index int) (nodeIndex []int, costs []float64) { return g.fromIndex[index], g.fromCost[index] } 注意:模式的に簡略化しています
  56. まとめ - 経路探索と、代表的アルゴリズムを紹介 - ダイクストラ法をgoで実装時に役立つ知識を紹介 - sliceのしくみ - mapのしくみ -

    slice を作るときは、capacity, length を指定しよう - map を作るときは、capacity hintを指定しよう 85
  57. 参考文献 ▪ Bast, Hannah, et al. "Route planning in transportation

    networks." Algorithm engineering. Springer, Cham, 2016. 19-80. ▪ Geisberger, Robert, et al. "Contraction hierarchies: Faster and simpler hierarchical routing in road networks." International Workshop on Experimental and Efficient Algorithms. Springer, Berlin, Heidelberg, 2008. ▪ 最短経路探査アルゴリズムの実装 ▪ An Introduction to Contraction Hierarchies ▪ The Go Blog Go Slices: usage and internals ▪ Macro View of Map Internals In Go