Gopher面試中的Coding

從四月份下半月開始,陸陸續(xù)續(xù)面試了幾家公司,都是golang的崗位左驾。每一次面試,側(cè)重點都會有不同魂那,有的會直接給過來一道試題, 然后邊解題稠项,邊講述自己的思路涯雅,然后面試官根據(jù)你的思路和你交流溝通;有的呢展运,讓講述自己最近做過的項目活逆,遇到的難點精刷, 自己怎么解決的問題思路,而無獨有偶的呢划乖,這樣的面試中贬养,都要需要展示編碼能力。這篇文章就把自己最近面試中遇到的每一個編程問題琴庵, 分三步闡述出來:問題描述误算,解題思路,實際編程迷殿。

交替打印數(shù)字和字母

問題描述

使用兩個 goroutine 交替打印序列儿礼,一個 goroutinue 打印數(shù)字, 另外一個goroutine打印字母庆寺, 最終效果如下 12AB34CD56EF78GH910IJ 蚊夫。

解題思路

問題很簡單,使用 channel 來控制打印的進(jìn)度懦尝。使用兩個 channel 知纷,來分別控制數(shù)字和字母的打印序列, 數(shù)字打印完成后通過 channel 通知字母打印, 字母打印完成后通知數(shù)字打印陵霉,然后周而復(fù)始的工作琅轧。

實際編碼

runtime.GOMAXPROCS(runtime.NumCPU())
chan_n := make(chan bool)
chan_c := make(chan bool, 1)
done := make(chan struct{})

go func() {
  for i := 1; i < 11; i += 2 {
    <-chan_c
    fmt.Print(i)
    fmt.Print(i + 1)
    chan_n <- true
  }
}()

go func() {
  char_seq := []string{"A","B","C","D","E","F","G","H","I","J","K"}
  for i := 0; i < 10; i += 2 {
    <-chan_n
    fmt.Print(char_seq[i])
    fmt.Print(char_seq[i+1])
    chan_c <- true
  }
  done <- struct{}{}
}()

chan_c <- true
<-done

代碼執(zhí)行結(jié)果:

12AB34CD56EF78GH910IJ

看完上面的代碼,是不是會有些疑惑踊挠,為什么 chan_c 需要緩存乍桂,而 chan_n 不需要呢?
當(dāng)兩個打印 goroutine 無限交替運行時,沒有緩存是OK的效床, 但很明顯上面的示例不是睹酌,打印數(shù)字的 goroutine 先退出,也就沒有了 goroutine 來讀取 chan_c 中的內(nèi)容了剩檀, 而打印字母的 goroutine 就會阻塞在 chan_c <- true 憋沿,這樣就導(dǎo)致了死鎖。

隨機抽獎

問題描述

用戶隨機抽獎谨朝,數(shù)據(jù)結(jié)構(gòu)如下所示:

// map中卤妒,key代表名稱,value代表成交單數(shù)
var users map[string]int64 = map[string]int64{
  "a": 10,
  "b": 6,
  "c": 3,
  "d": 12,
  "f": 1,
}

解決思路

從map中選取隨機用戶字币,拿到這個編碼問題,有點懵逼,但仔細(xì)一想共缕,只需把關(guān)注用戶的區(qū)間洗出,轉(zhuǎn)變一下數(shù)據(jù)結(jié)構(gòu)即解題。 把map轉(zhuǎn)成array图谷,思考起來就簡單多了翩活,原有問題變成了從0至n-1中選取一個數(shù)字阱洪,數(shù)字對應(yīng)的用戶即中獎用戶。

實際編碼

package main

import (
  "fmt"
  "math/rand"
  "time"
)

func GetAwardUserName(users map[string]int64) (name string) {
  sizeOfUsers := len(users)
  award_index := rand.Intn(sizeOfUsers)

  var index int
  for u_name, _ := range users {
    if index == award_index {
      name = u_name
      return
    }
    index += 1
  }
  return
}

func main() {
  var users map[string]int64 = map[string]int64{
    "a": 10,
    "b": 6,
    "c": 3,
    "d": 12,
    "e": 20,
    "f": 1,
  }

  rand.Seed(time.Now().Unix())
  award_stat := make(map[string]int64)
  for i := 0; i < 1000; i += 1 {
    name := GetAwardUserName(users)
    if count, ok := award_stat[name]; ok {
      award_stat[name] = count + 1
    } else {
      award_stat[name] = 1
    }
  }

  for name, count := range award_stat {
    fmt.Printf("user: %s, award count: %d\n", name, count)
  }

  return
}

代碼執(zhí)行結(jié)果:

user: f, award count: 178
user: d, award count: 152
user: b, award count: 159
user: e, award count: 182
user: c, award count: 170
user: a, award count: 159

權(quán)重抽獎

問題描述

數(shù)據(jù)結(jié)構(gòu)和上面一致菠镇,只是問題發(fā)生變化冗荸,需要更加用戶的成單數(shù)來抽獎,用戶成單越多利耍,中獎概率越高蚌本,結(jié)構(gòu)如下所示:

// map中,key代表名稱隘梨,value代表成交單數(shù)
var users map[string]int64 = map[string]int64{
  "a": 10,
  "b": 6,
  "c": 3,
  "d": 12,
  "f": 1,
}

解決思路

這一題是上一題的延伸程癌,加了訂單數(shù)進(jìn)去,做為權(quán)重來為用戶抽獎轴猎。此題和上面的問題如此的相似嵌莉,可把上面的問題, 理解成所有的用戶權(quán)重都相同的抽獎捻脖,而此題是權(quán)重不同的抽獎锐峭。解決此問題,依舊是把map轉(zhuǎn)為數(shù)組來思考可婶, 把各用戶的權(quán)重沿癞,從前到后依次拼接到數(shù)軸上,數(shù)軸的起點到終點即時中獎區(qū)間扰肌,而隨機數(shù)落到的那個用戶的區(qū)間抛寝,那個用戶即為中獎用戶。

實際編碼

package main

import (
  "fmt"
  "math/rand"
  "time"
)

func GetAwardUserName(users map[string]int64) (name string) {
  type A_user struct {
    Name   string
    Offset int64
    Num    int64
  }

  a_user_arr := make([]*A_user, 0)
  var sum_num int64
  for name, num := range users {
    a_user := &A_user{
      Name:   name,
      Offset: sum_num,
      Num:    num,
    }
    a_user_arr = append(a_user_arr, a_user)
    sum_num += num
  }

  award_num := rand.Int63n(sum_num)

  for index, _ := range a_user_arr {
    a_user := a_user_arr[index]
    if a_user.Offset+a_user.Num > award_num {
      name = a_user.Name
      return
    }
  }
  return
}

func main() {
  var users map[string]int64 = map[string]int64{
    "a": 10,
    "b": 5,
    "c": 15,
    "d": 20,
    "e": 10,
    "f": 30,
  }

  rand.Seed(time.Now().Unix())
  award_stat := make(map[string]int64)
  for i := 0; i < 10000; i += 1 {
    name := GetAwardUserName(users)
    if count, ok := award_stat[name]; ok {
      award_stat[name] = count + 1
    } else {
      award_stat[name] = 1
    }
  }

  for name, count := range award_stat {
    fmt.Printf("user: %s, award count: %d\n", name, count)
  }

  return
}

代碼執(zhí)行結(jié)果:

user: c, award count: 1667
user: f, award count: 3310
user: e, award count: 1099
user: d, award count: 2276
user: b, award count: 549
user: a, award count: 1099

感謝各位的評論曙旭,讓我受益匪淺盗舰,上面代碼確實有太多的槽點,感謝吐槽桂躏,代碼更正如下:

func GetAwardUserName(users map[string]int64) (name string) {
  var sum_num int64
  for _, num := range users {
    sum_num += num
  }

  award_num := rand.Int63n(sum_num)

  var offset_num int64
  for _name, num := range a_user_arr {
    offset_num += num
    if award_num < offset_num {
      name = _name
      return
    }
  }
  return
}

由于一直以為Golang的map for range 是可重入的钻趋,但現(xiàn)實是前后兩輪遍歷到的 key 的順序居然是被隨機化的, 代碼示例如下:

n_map := make(map[int]bool)
for i := 1; i <= 10; i++ {
  n_map[i] = true
}

for num, _ := range n_map {
  fmt.Print(num)
}
fmt.Print("\n")
for num, _ := range n_map {
  fmt.Print(num)
}
91257103468
46810325791

由于map的不可重入性剂习, 以及 liguoqinjim 給出的 示例代碼運行結(jié)果 證明了map的 for range 的偽隨機性蛮位, 代碼修改如下(在Playground 中可查看完整代碼):

func GetAwardUserName(users map[string]int64) (name string) {
  var sum_num int64
  name_arr := make([]string, len(users))
  for u_name, num := range users {
    sum_num += num
    name_arr = append(name_arr, u_name)
  }

  award_num := rand.Int63n(sum_num)

  var offset_num int64
  for _, u_name := range name_arr {
    offset_num += users[u_name]
    if award_num < offset_num {
      name = u_name
      return
    }
  }
  return
}

上面代碼,對于多次調(diào)用會有性能問題鳞绕,每次都要重新計算 sum_num 和創(chuàng)建 name_arr, 使用閉包重新實現(xiàn)失仁, 代碼如下(在Playground 中可查看完整代碼):

func GetAwardGenerator(users map[string]int64) (generator func() string) {
  var sum_num int64
  name_arr := make([]string, len(users))
  for u_name, num := range users {
    sum_num += num
    name_arr = append(name_arr, u_name)
  }

  generator = func() string {
    award_num := rand.Int63n(sum_num)

    var offset_num int64
    for _, u_name := range name_arr {
      offset_num += users[u_name]
      if award_num < offset_num {
        return u_name
      }
    }
    // 缺省返回,正常情況下们何,不會運行到此處
    return name_arr[0]
  }
  return
}

上面代碼使用了閉包避免了多次抽獎時頻繁的初始化萄焦, 但每次抽獎的復(fù)雜度O(n),很明顯依舊有可優(yōu)化的空間,可使用二分搜索來使復(fù)雜度降到 O(log n), 代碼如下:

func GetAwardGenerator(users map[string]int64) (generator func() string) {
  var sum_num int64
  name_arr := make([]string, len(users))
  offset_arr := make([]int64, len(users))
  var index int
  for u_name, num := range users {
    name_arr[index] = u_name
    offset_arr[index] = sum_num
    sum_num += num
    index += 1
  }

  generator = func() string {
    award_num := rand.Int63n(sum_num)
    return name_arr[binary_search(offset_arr, award_num)]
  }
  return
}

func binary_search(nums []int64, target int64) int {
  start, end := 0, len(nums)-1
  for start <= end {
    mid := start + (end-start)/2
    if nums[mid] > target {
      end = mid - 1
    } else if nums[mid] < target {
      if mid+1 == len(nums) { // 最后一名中獎
        return mid
      }
      if nums[mid+1] > target {
        return mid
      }
      start = mid + 1
    } else {
      return mid
    }
  }

  return -1
}

總結(jié)

問題一來自一家公司 , 側(cè)重于語言特性拂封;問題二三來自另外一家公司 茬射,側(cè)重于解決問題的思路;本人更喜歡第二種冒签,很有啟發(fā)性在抛。 我之后會把其他自己認(rèn)為比較有趣的編程任務(wù),整理到此篇文章中萧恕,敬請期待刚梭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市廊鸥,隨后出現(xiàn)的幾起案子望浩,更是在濱河造成了極大的恐慌,老刑警劉巖惰说,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件磨德,死亡現(xiàn)場離奇詭異,居然都是意外死亡吆视,警方通過查閱死者的電腦和手機典挑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來啦吧,“玉大人您觉,你說我怎么就攤上這事∈谧遥” “怎么了琳水?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長般堆。 經(jīng)常有香客問我在孝,道長,這世上最難降的妖魔是什么淮摔? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任私沮,我火速辦了婚禮,結(jié)果婚禮上和橙,老公的妹妹穿的比我還像新娘仔燕。我一直安慰自己,他們只是感情好魔招,可當(dāng)我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布晰搀。 她就那樣靜靜地躺著,像睡著了一般办斑。 火紅的嫁衣襯著肌膚如雪厕隧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天俄周,我揣著相機與錄音吁讨,去河邊找鬼。 笑死峦朗,一個胖子當(dāng)著我的面吹牛建丧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播波势,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼翎朱,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了尺铣?” 一聲冷哼從身側(cè)響起拴曲,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凛忿,沒想到半個月后澈灼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡店溢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年叁熔,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片床牧。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡荣回,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戈咳,到底是詐尸還是另有隱情心软,我是刑警寧澤,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布著蛙,位于F島的核電站删铃,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏册踩。R本人自食惡果不足惜泳姐,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望暂吉。 院中可真熱鬧胖秒,春花似錦、人聲如沸慕的。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肮街。三九已至风题,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沛硅。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工眼刃, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摇肌。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓擂红,卻偏偏與公主長得像,于是被迫代替她去往敵國和親围小。 傳聞我的和親對象是個殘疾皇子昵骤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件肯适、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,093評論 4 62
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法变秦,類相關(guān)的語法,內(nèi)部類的語法框舔,繼承相關(guān)的語法蹦玫,異常的語法,線程的語...
    子非魚_t_閱讀 31,622評論 18 399
  • Goroutine是Go里的一種輕量級線程——協(xié)程雨饺。相對線程钳垮,協(xié)程的優(yōu)勢就在于它非常輕量級,進(jìn)行上下文切換的代價非...
    witchiman閱讀 4,834評論 0 9
  • 十一年前额港,那年參加高考前幾天饺窿,我們宿舍和隔壁宿舍的八九個同學(xué),一塊在學(xué)校附近的飯店吃了個飯移斩,那是我們的第一次聚餐肚医,...
    我心紅旗閱讀 363評論 0 0
  • 數(shù)著冬來春去流逝的光陰,寂寥無人的深夜里獨醉向瓷。 窗前前寒冷深冬的身影為誰消瘦肠套,月下唯有我的身影投。 酒入喉卻解不了...
    殘落閱讀 287評論 0 2