Library System
Build the core of a library: books with several copies, members with limits, a fair waiting queue, and fines. The rules differ by kind of member and the fine rule is likely to change, so the point of the exercise is to keep each rule in one place. A new kind of member must not mean an if (tier === ...) in five methods. Two ideas carry the design: a copy has a small lifecycle (available, loaned, held), and a fine is a policy you look up, not a branch.
Implement the class Library. Time is always a whole day number passed in; there is no clock.
addBook(isbn, title, copies): addscopiesnew copies (at least 1) of the bookisbn. If the ISBN already exists, the new copies are added to it and its title is unchanged.join(member, tier): registers a member.tieris"standard"or"premium".borrow(member, isbn, day): returns the new loan id ("L1","L2", ... numbered in the order loans are made), or one of these strings, checked in this order:"UNKNOWN"(no such member or ISBN),"LIMIT_REACHED"(the member already has the maximum number of open loans),"NO_COPY"(no copy this member may take). A member may take a copy that is held for them, or any available copy. A copy held for someone else is not available to them.returnBook(loanId, day): closes the loan and returns the fine in cents, or-1if the loan id is unknown or already closed. The returned copy goes to the first member in the book's waiting queue, if any (seereserve); otherwise it becomes available.reserve(member, isbn): puts the member at the back of the book's waiting queue and returns their 1-based place (1means next in line). It returns0and changes nothing if: the member or ISBN is unknown, any copy is available (just borrow it), the member is already in the queue, or a copy is already held for the member.renew(loanId, day): extends the loan and returns the new due day, or-1if it is refused. It is refused when the loan is unknown or closed, whendayis after the due day, when the loan has already been renewed twice, or when anyone is waiting in that book's queue. A renewal adds the member's loan period to the current due day.sweep(day): call once a day. Every held copy whose hold has lapsed (dayis later than its hold deadline) is passed on: to the next member in the queue, or back to available if nobody waits. Returns how many holds lapsed in this call. A member whose hold lapsed loses their place and is not put back in the queue.available(isbn): the number of copies that are available right now, or-1for an unknown ISBN.queue(isbn): the member ids waiting, in order ([]if none or unknown).holds(isbn): the member ids a copy is currently held for, sorted alphabetically.search(text): the ISBNs of books whose title containstext, ignoring case, sorted alphabetically.
The rules by tier
| Tier | Max open loans | Loan period | Fine per day late | Fine cap |
|---|---|---|---|---|
standard | 3 | 14 days | 25 cents | 1000 cents |
premium | 5 | 21 days | 10 cents | 500 cents |
A loan made on day is due on day + loan period. The days late are returnDay - due; the fine is min(cap, max(0, daysLate) * rate). A book returned on its due day is not late.
Holds. When a returned copy goes to a waiting member it is held for them, with a deadline of the return day plus 3, inclusive: a hold made on day 2 can be collected through day 5 and lapses on sweep(6). Holding a copy removes that member from the queue.
Notes
- A held copy is neither available nor loaned. It becomes loaned when the member it is held for borrows it, and the limit check still applies first: a member at their limit gets
"LIMIT_REACHED"and the hold stays. - Rejected calls change nothing.
- The queue is per book, not per copy.
- Return values must be exactly the types above: a string, a number, or an array.
Examples
Example 1
Input: ["Library","addBook","join","join","join","available","borrow","available","returnBook","available"]
[[],["111","Dune",1],["ann","standard"],["bob","standard"],["cy","standard"],["111"],["ann","111",1],["111"],["L1",15],["111"]]
Output: [null,null,null,null,null,1,"L1",0,0,1]A standard loan on day 1 is due on day 15, so returning on day 15 is on time and costs nothing. The copy is available again.
Example 2
Input: ["Library","addBook","join","join","join","borrow","borrow","borrow","borrow","available"]
[[],["111","Dune",5],["ann","standard"],["bob","standard"],["cy","standard"],["ann","111",1],["ann","111",1],["ann","111",1],["ann","111",1],["111"]]
Output: [null,null,null,null,null,"L1","L2","L3","LIMIT_REACHED",2]A standard member may have 3 open loans. The fourth is refused and takes no copy.
Example 3
Input: ["Library","addBook","join","join","join","borrow","borrow","borrow","borrow","returnBook"]
[[],["111","Dune",1],["ann","standard"],["bob","standard"],["cy","standard"],["zed","111",1],["ann","999",1],["ann","111",1],["bob","111",1],["L9",5]]
Output: [null,null,null,null,null,"UNKNOWN","UNKNOWN","L1","NO_COPY",-1]Unknown member and unknown ISBN are UNKNOWN. With the only copy out, the next borrow is NO_COPY, and an unknown loan id returns -1.
Each test creates the class with the first operation, then calls the methods in order. Output lists what each call returns; null marks the constructor and methods that return nothing, and their return values aren't checked.
class Library {
constructor() {
}
/** Add copies of a book. */
addBook(isbn, title, copies) {
}
/** @param {"standard"|"premium"} tier */
join(member, tier) {
}
/** @return {string} a loan id such as "L1", or "UNKNOWN", "LIMIT_REACHED" or "NO_COPY" */
borrow(member, isbn, day) {
}
/** @return {number} the fine in cents, or -1 for an unknown or closed loan */
returnBook(loanId, day) {
}
/** @return {number} the 1-based place in the queue, or 0 if refused */
reserve(member, isbn) {
}
/** @return {number} the new due day, or -1 if refused */
renew(loanId, day) {
}
/** Pass on lapsed holds. @return {number} how many holds lapsed */
sweep(day) {
}
/** @return {number} copies available now, or -1 for an unknown ISBN */
available(isbn) {
}
/** @return {string[]} members waiting, in order */
queue(isbn) {
}
/** @return {string[]} members a copy is held for, sorted */
holds(isbn) {
}
/** @return {string[]} ISBNs whose title contains the text (ignoring case), sorted */
search(text) {
}
}
Ctrl/⌘ + ' runs the examples, Ctrl/⌘ + Enter submits. Use console.log freely: output shows under Console. In the editor, press Esc then Tab to move focus out.