A search page receives URLs like https://stays.example/search?city=Lisbon&guests=2#map and needs the parameters as a dictionary.
Implement:
def parse_query(url: str) -> dict[str, str | list[str]]
Follow these rules, in this order:
- Fragment. Everything from the first
#in the URL onwards is ignored. - Query string. The query string is everything after the first
?in what's left. If there is no?, return{}. Later?characters are ordinary text. - Pieces. Split the query string on
&. Empty pieces (from&&, or a leading or trailing&) are skipped. - Key and value. Split each piece at its first
=: the part before is the key, the rest is the value (it may contain more=). A piece with no=has the value"". - Decoding. Decode the key and the value separately, after splitting, so an encoded
&or=doesn't split anything:+becomes a space;%followed by two hex digits (either case) becomes the character with that code, e.g.%20is a space and%3Dis=(inputs only encode codes below0x80);- any other
%is kept as it is, e.g.100%or%zz.
- Empty keys. A piece whose decoded key is empty (like
=5) is skipped. - Repeated keys. A key seen once maps to its value string. A key seen more than once maps to a list of all its values, in the order they appear.
parse_query("https://stays.example/search?city=Lisbon&guests=2#map")
# {"city": "Lisbon", "guests": "2"}
parse_query("/s?amenity=wifi&amenity=pool&q=sea+view¬e=a%3Db%26c&flex")
# {"amenity": ["wifi", "pool"], "q": "sea view", "note": "a=b&c", "flex": ""}
parse_query("/s?&&=oops&x=1=2&disc=100%&y")
# {"x": "1=2", "disc": "100%", "y": ""}
parse_query("/about#faq?x=1") # {} the ? is inside the fragment
URLs can be up to 500,000 characters with tens of thousands of pieces, many of them sharing one key. Keep the work linear: don't rebuild a key's list every time you add a value.
Show hint
str.partition does "split at the first occurrence" and returns the separator too, so you can tell "a" (no =) apart from "a=". Decode with one left-to-right pass over the characters.