Titles versus copies, a hold shelf that gives a returned book to the member who waited, fines from the dates at return, and a word index for search.
The library round, as in the lesson. A title (Book) has many copies (BookCopy, with a barcode): a loan points at a copy, a reservation at a title. The data classes, the fine policy (PerDayFine), the notifier and a recording notifier for tests are given. You build the search index and the Library. Time is always a parameter (today).
CatalogIndex: an inverted index per field (title, author, subject), a map from each word to the ids of the titles that contain it. add(book) indexes the title, the authors joined by spaces and the subjects joined by spaces with the given words(); search(query, field=None) returns a set of book ids: every word of the query must match (AND), in the field asked for or in any field; whole words only ("art" does not match "Earth"); no words or no hits gives an empty set.
Library(fines, notifier, loan_days=14, max_loans=5, hold_days=3, fine_block=100) keeps books, copies (barcode to copy), members, loans (barcode to the active loan), queue (book id to the member ids waiting, first come first served) and an index.
| Method | Does |
|---|---|
add_book(book, barcodes) |
adds the title, one available copy per barcode, and indexes it |
search(query, field=None) |
the matching titles, sorted |
borrow(member_id, book_id, today) |
returns a Loan due loan_days later (ids L1, L2...). Refused with LibraryError: fines of fine_block or more ("Ishaan owes ₹300: pay first"), or max_loans loans already, or the same title already on loan to this member. The member's own held copy comes first, otherwise any AVAILABLE copy; none gives NoCopyAvailable("no free copy of Godan"). A copy on hold for someone else is not available |
return_copy(barcode, today) |
the loan closes, the fine is fines.fine(days late) added to the member's fines_due and returned. A barcode not on loan raises LibraryError. The copy becomes AVAILABLE, then in the same step goes to the head of the title's queue if there is one: status ON_HOLD, held_for them, hold_until = today + hold_days, and they are notified "Godan is held for you until 23 Nov" |
reserve(member_id, book_id) |
joins the queue and returns the position (1-based). Refused when a copy is AVAILABLE ("a copy is on the shelf: borrow it"), when already queued, or when this member has a copy of the title on loan |
renew(barcode, today) |
extends the due date by loan_days, once. Refused when renewed already, overdue (today after the due date) or someone is waiting |
expire_holds(today) |
a nightly job: a hold whose hold_until is before today is released ("your hold on Godan has expired" to its member) and passed on to the next in the queue (notified as above), or back to the shelf |
remind(today) |
"Godan is due tomorrow" to every borrower whose due date is today + 1 day |
Messages go to notifier.send(member, message), collected during the operation and sent at the end (in a threaded library, after the lock is released).
JavaScript and TypeScript: camelCase (addBook, returnCopy, expireHolds, loanDays, maxLoans, holdDays, fineBlock, finesDue, heldFor, holdUntil); books, copies, members and loans are Maps, queue is a Map of book id to array, search on the index returns a Set, dates are UTC Dates (helpers D(y, m, d), addDays, daysBetween and fmtDay are given), and the index keeps byField ({ title: Map word -> Set of ids, ... }).
Write your code, then run 11 example tests and 3 hidden tests. Each run is limited to 8 seconds: an infinite loop gets stopped and the runtime restarts, your code stays.