You are given a starting URL startUrl, a page limit maxPages, a worker limit maxWorkers, and a dictionary urlMap that models the web graph. The dictionary maps each URL to a list of its outgoing links (as if you had fetched and parsed that page).
Implement a thread‑safe concurrent crawler that explores pages starting from startUrl and obeys these rules:
startUrl are crawled.maxPages distinct URLs have been scheduled (or visited). Once that many URLs are in the work queue, no further URLs may be added.maxWorkers worker threads.urlMap may be slow; a single slow URL must not stall other workers.def crawl(startUrl: str, maxPages: int, maxWorkers: int, urlMap: dict[str, list[str]]) -> list[str]:
startUrl: the entry point URL.maxPages: maximum number of distinct URLs that may be crawled.maxWorkers: maximum number of concurrent worker threads.urlMap: a dictionary where each key is a URL and the associated value is a list of outgoing links found on that page. The dictionary contains the entire known graph; you do not need to call external fetch/parse functions.1 <= len(urlMap) <= 10^51 <= maxPages <= 10^51 <= maxWorkers <= 64urlMap is at most 2 * 10^5.startUrl = "https://a.com/"
maxPages = 10
maxWorkers = 2
urlMap = {
"https://a.com/": ["https://a.com/x", "https://a.com/y"],
"https://a.com/x": ["https://a.com/z"],
"https://a.com/y": ["https://a.com/z"]
}
All four URLs share the same hostname. The crawler visits https://a.com/, discovers x and y, then visits those and discovers z. No URL is visited more than once. The returned set, when sorted lexicographically, is:
https://a.com/
https://a.com/x
https://a.com/y
https://a.com/z