示例代碼(含測試)在這裏git
在甘特圖的場景下,咱們常常會遇到這種狀況,五位員工A, B, C, D, E,可能他們的工做都是並行的,咱們須要計算 某段時間內他們總的工做時長。github
咱們不能簡單得把五我的的工做時間都加起來,由於當中會有重疊的部分。 因此這時候咱們就須要一個計算時間交併集的工具。數組
將一組離散的時間段按照開始時間,從小到大排序。像這樣bash
[{2 7} {4 11} {10 19} {10 30} {16 18} {19 29} {23 35} {24 42} {25 30} {27 49}]
複製代碼
我這裏將時間用十分小的秒來代替,方便理解。app
循環排序後的數組,若是下一個時間段開始時間介於上個時間段的開始時間和結束時間之間,那麼就進行合併
,不然就分離
。 能夠看到咱們這裏有兩個關鍵動做,合併
,分離
,而這個就是咱們要實現的核心代碼。工具
一段連續的工做時間都會有兩個點,開始時間
和結束時間
。 因此咱們能夠把這個時間結構設計成:測試
type T struct {
Start int64
End int64
}
複製代碼
而一我的的工做時間是由多個 T 組成的,因此咱們在定義一個切片類型ui
type TSlice []T
複製代碼
爲了能順序合併時間,咱們須要將TSlice
進行排序。 咱們知道 Go 中有個 sort 包,咱們只須要實現 sort 類型的接口,就能實現 TSlice 的排序了。 咱們實現下:spa
func (t TSlice) Len() int { return len(t) }
func (t TSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t TSlice) Less(i, j int) bool { return t[i].Start < t[j].Start }
複製代碼
三個方法分別是,長度、交換位置、比小。設計
這樣一來,咱們就能直接用 sort.Stable()
穩定排序,對咱們的時間段切片排序了。
好,接下來咱們實現並集的方法,咱們取名爲 Union
:
func (t TSlice) Union() TSlice {
// 新建一個空的時間切片
var s TSlice
// 若是有至少兩個是時間段,咱們才排序,不然直接返回
if len(t) > 1 {
// @todo 合併邏輯
}
return s
}
複製代碼
Union 方法將會返回一個一樣的 TSlice
時間切片,只不過是通過並集處理的。
一旦 t 中的時間段個數大於1,咱們就要執行處理邏輯了:
if len(t) > 1 {
sort.Stable(t)
s = append(s, t[0])
// @todo 循環比較合併
}
複製代碼
咱們先對時間切片進行排序,而後把第一個時間段做爲第一個元素放進咱們的結果 TSlice 中,好讓咱們開始進行循壞的比較。
if len(t) > 1 {
sort.Stable(t)
s = append(s, t[0])
for k, v := range t {
// 若是開始時間大於結束時間,那實際上是錯誤數據,可是咱們這裏正常返回
// 你能夠根據本身的須要定製錯誤處理邏輯
if v.Start > v.End {
return s
}
// 第一組元素咱們不作任何操做
if k == 0 {
continue
}
// 當開始時間介於上一個時間段的開始時間和結束時間之間
if v.Start >= s[len(s)-1].Start && v.Start <= s[len(s)-1].End {
// 合併
if v.End > s[len(s)-1].End {
s[len(s)-1].End = v.End
}
// 若是大於上一個時間段的結束時間
} else if v.Start > s[len(s)-1].End {
// 分離
inner := T{Start: v.Start, End: v.End}
s = append(s, inner)
}
}
}
複製代碼
來張圖其實就清楚了:
能夠看到最後輸出的也是一個 TSlice
類型。 上面就是 union,求並集的過程,那交集的?
其實交集也很簡單,若是兩個時間段相交,咱們只要判斷:開始時間取最大的那個,結束時間取兩個時間段中最小的那個。
func (t TSlice) Intersect() TSlice {
var s TSlice
if len(t) > 1 {
sort.Stable(t)
s = append(s, t[0])
for k, v := range t {
if v.Start > v.End {
return s
}
if k == 0 {
continue
}
// 兩個時間段相交
if v.Start >= s[0].Start && v.Start <= s[0].End {
// 開始時間取最大的那個
s[0].Start = v.Start
// 結束時間取最小的那個
if v.End <= s[0].End {
s[0].End = v.End
}
} else {
return s[:0]
}
}
}
return s
}
複製代碼
同樣,咱們來個圖:
須要注意的是,這個求交集的結果是全相交--只有當全部時間段都有共同時間纔會有結果。 這樣的需求在實際過程當中用到的是否是不太多??因此我想是否是可以實現:一次相交,兩次相交...的條件篩選。
咱們隨機生成了一組時間切片
func makeTimes(t int) TSlice {
var set TSlice
rand.Seed(time.Now().Unix())
for i := 0; i < t; i++ {
randStart := rand.Int63n(50)
randEnd := randStart + rand.Int63n(25) + 1
set = append(set, T{Start: randStart, End: randEnd})
}
return set
}
testSet := makeTimes(10) // 生成10個時間段的時間切片
res := testSet.Union() // 直接調用 Union() 或者 Intersect()
複製代碼
輸入數據爲:
[{10 21} {34 52} {49 54} {18 31} {26 44} {24 27} {43 51} {41 53} {20 41} {48 67}]
複製代碼
輸出結果:
[{10 67}]
複製代碼
結果還行~
我發如今求並集的過程當中,會要求求最終的時間之和,因此咱們爲 TSlice 加一個 Sum() 方法, 就是簡單的循環求和:
func (t TSlice) Sum() (sum int64) {
for i := 0; i < len(t); i++ {
sum += t[i].End - t[i].Start
}
return sum
}
複製代碼
調用方法:TSlice.Union().Sum()