這道面試題是從 HarrisonHao 的一篇博文中看到的:原文鏈接
我看到之后,感覺此題十分有趣,遂自己用 node.js 以不同的思路實現了一遍,實現中使用了 lodash
原題比較長,而且是英文的,就不粘過來了,完整題目和代碼可見github
原題大意
你正在准備一場大型的開發者會議,但是有一點點麻煩……
這場會議為期兩天,每天上午從九點開始,上午的會議安排到中午12點之前必須結束;
中午12點到下午1點之間是午餐時間,下午1點開始進行下午的會議,到下午5點前必須結束;
現在你有一個清單,上面寫明了所有要安排的議題,和每個議題會占用的時間;
清單如下
Writing Fast Tests Against Enterprise Rails 60min
Overdoing it in Python 45min
Lua for the Masses 30min
Ruby Errors from Mismatched Gem Versions 45min
Common Ruby Errors 45min
Rails for Python Developers lightning
Communicating Over Distance 60min
Accounting-Driven Development 45min
Woah 30min
Sit Down and Write 30min
Pair Programming vs Noise 45min
Rails Magic 60min
Ruby on Rails: Why We Should Move On 60min
Clojure Ate Scala (on my project) 45min
Programming in the Boondocks of Seattle 30min
Ruby vs. Clojure for Back-End Development 30min
Ruby on Rails Legacy App Maintenance 60min
A World Without HackerNews 30min
User Interface CSS in Rails Apps 30min
清單中lightning
占用5分鍾,其他議題都各自注明了占用時間
現在你要寫一個程序把清單上的議題安排進四個時間段內。
用 HarrisonHao 的話說,就是要把19個給定大小的蘿卜放進4個給定大小的坑里,坑可以沒占滿,但蘿卜不能剩下。
基本的實現思路
先把清單中的議題按照占用時間排序,方便后續操作,然后開始往時間段中安排,每次拿出一個最耗時的議題,放入最空閑的時段中,不斷循環,直到所有議題都被安排進去;
首先,我們先看看有哪些蘿卜(讀取清單並排序)
本次是在 node.js 中實現的,使用 fs 模塊讀取文件,按行分割后,將每行內容轉為一個 Talk 對象,並按占用時間進行排序:
const fs = require('fs');
const _ = require('lodash');
//會議類
class Talk {
constructor (line) {
const words = _.compact(line.split(' '));
const duration = words.pop();
if (isNaN(duration[0])) {
words.push(duration)
this.duration = 5;
} else {
this.duration = duration.slice(0, -4);
}
this.name= words.join(' ')
}
}
//獲得輸入參數
const args = process.argv.splice(2);
const [inputPath, outputPath] = args;
//讀取輸入文件內容
let input = fs.readFileSync(inputPath, 'utf8')
//分割輸入文件內容
input = _.compact(input.split('\r').join().split('\n'));
//生成會議列表
let talks = input.map(line=>new Talk(line));
//按占用時間從小到大排序
talks = _.orderBy(talks, 'duration');
然后我們來看看有哪幾個坑……(生成時間段)
Slot 類實例化時接收一個總可用分鍾數,並包含三個方法:
add: 將一個議題添加進該時間段,
used: 返回已使用的分鍾數,
balance: 返回空閑的分鍾數
每天上午有3個小時會議時間即180分鍾,下午240分鍾,所以我們可以直接用這兩個分鍾數來生成時間段:
//時間段類
class Slot {
constructor (limit) {
this.limit = limit;
this.list = [];
};
//剩余可用分鍾數
balance () {
return this.limit - this.used()
};
//已安排掉的分鍾數
used () {
let used = 0;
this.list.forEach((talk)=>{
used += (talk.duration-0)
});
return used;
};
add (talk) {
this.list.push(talk)
}
}
//生成時間段列表
let slots = [180, 240, 180, 240].map(limit=>new Slot(limit));
核心部分
現在要把蘿卜往坑里扔了,之前提到思路是每次把耗時最長的議題扔進最空閑的時間段中,不斷循環,直到所有議題(蘿卜)都安排進時間段(坑)里;
獲取耗時最長的議題很簡單,因為我們之前已經給議題排序了,每次只要.pop()
彈出最后一條議題就可以了;
獲取最空閑的時間段也是一樣的原理,用上 Slot 對象的 balance 方法,按照空閑時間倒序排序,排序結果的第一條就是最空閑的時間段;
具體實現:
//分配會議
function arrange (talks, slots) {
//拿出一個占用時間最長的會議
const talk = talks.pop();
//放到時間最富裕的時間段內
slots = _.orderBy(slots, slot=>slot.balance(), 'desc');
slots[0].add(talk);
if (talks.length == 0) return slots;
return arrange(talks, slots);
};
//分配每個會議到各個時間段內
const result = arrange(talks, slots);
格式化輸出
反正原題似乎是要求按一定格式輸出的……不過格式這種東西就不細說了,上個代碼得了……
//格式化輸出
function print (slots) {
const formatNum = (num)=>('0'+num).slice(-2);
const am = {}, pm = {};
[am[1],am[2],pm[1],pm[2]] = _.orderBy(slots, 'limit');
[1,2].forEach((i)=>{
let minutes;
console.log('Track '+i);
minutes = 9*60
am[i].list.forEach((talk)=>{
const h = formatNum(Math.floor(minutes/60));
const m = formatNum(minutes%60);
const outputLine = [
h,':',m,'AM',' ',
talk.name,' ',talk.duration,'min'
].join('');
console.log(outputLine);
minutes += talk.duration-0;
})
console.log('12:00PM Lunch');
minutes = 1*60
pm[i].list.forEach((talk)=>{
const h = formatNum(parseInt(minutes/60));
const m = formatNum(minutes%60);
const outputLine = [
h,':',m,'PM',' ',
talk.name,' ',talk.duration,'min'
].join('');
console.log(outputLine);
minutes += talk.duration-0;
})
console.log('05:00PM Networking Event');
})
}
print(slots);
最終效果
λ node conference.js input.txt
Track 1
09:00AM Clojure Ate Scala (on my project) 45min
09:45AM Pair Programming vs Noise 45min
10:30AM Overdoing it in Python 45min
11:15AM Lua for the Masses 30min
11:45AM Rails for Python Developers lightning, 5min
12:00PM Lunch
01:00PM Ruby on Rails: Why We Should Move On 60min
02:00PM Rails Magic 60min
03:00PM Ruby Errors from Mismatched Gem Versions 45min
03:45PM User Interface CSS in Rails Apps 30min
04:15PM Woah 30min
05:00PM Networking Event
Track 2
09:00AM Writing Fast Tests Against Enterprise Rails 60min
10:00AM Accounting-Driven Development 45min
10:45AM Ruby vs. Clojure for Back-End Development 30min
11:15AM Programming in the Boondocks of Seattle 30min
12:00PM Lunch
01:00PM Ruby on Rails Legacy App Maintenance 60min
02:00PM Communicating Over Distance 60min
03:00PM Common Ruby Errors 45min
03:45PM A World Without HackerNews 30min
04:15PM Sit Down and Write 30min
05:00PM Networking Event
總結
雖然是實現了效果,但我感覺還不完善,現在這種算法還很簡單,原題本身就富裕了55分鍾的時間,所以才能用這種方式實現,如果占的更滿就不一定好使了,以后我會再繼續嘗試改進;
如果有更好的思路也歡迎告訴我,我也會試着實現一下的~