From 0a0d12b1fa6fd4d2ff3fd67ce6c7cec111d34280 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 14 Jul 2025 16:51:41 +0545 Subject: [PATCH 01/24] add: redis geospatial stages --- course-definition.yml | 67 +++++++++++++++++++++ stage_descriptions/geospatial_1_zt4.md | 35 +++++++++++ stage_descriptions/geospatial_2_wq4.md | 25 ++++++++ stage_descriptions/geospatial_3_ck3.md | 46 +++++++++++++++ stage_descriptions/geospatial_4_xg4.md | 70 ++++++++++++++++++++++ stage_descriptions/geospatial_5_ia8.md | 46 +++++++++++++++ stage_descriptions/geospatial_6_ek6.md | 64 ++++++++++++++++++++ stage_descriptions/geospatial_7_ra4.md | 27 +++++++++ stage_descriptions/geospatial_8_rm9.md | 53 +++++++++++++++++ stage_descriptions/geospatial_9_hv8.md | 81 ++++++++++++++++++++++++++ 10 files changed, 514 insertions(+) create mode 100644 stage_descriptions/geospatial_1_zt4.md create mode 100644 stage_descriptions/geospatial_2_wq4.md create mode 100644 stage_descriptions/geospatial_3_ck3.md create mode 100644 stage_descriptions/geospatial_4_xg4.md create mode 100644 stage_descriptions/geospatial_5_ia8.md create mode 100644 stage_descriptions/geospatial_6_ek6.md create mode 100644 stage_descriptions/geospatial_7_ra4.md create mode 100644 stage_descriptions/geospatial_8_rm9.md create mode 100644 stage_descriptions/geospatial_9_hv8.md diff --git a/course-definition.yml b/course-definition.yml index cb03bb28..2de2d2c3 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -117,6 +117,18 @@ extensions: [redis-persistence]: https://redis.io/docs/manual/persistence/ [rdb-file-format]: https://github.com/sripathikrishnan/redis-rdb-tools/blob/548b11ec3c81a603f5b321228d07a61a0b940159/docs/RDB_File_Format.textile + - slug: "geospatial" + name: "Geospatial Commands" + description_markdown: | + In this challenge extension you'll add support for [Geospatial Commands][geospatial-commands] to your Redis implementation. + + Along the way, you'll learn commands like [GEOADD][geoadd-command], [GEOSEARCH][geosearch-command], and more. + + [geospatial-commands]: https://redis.io/docs/latest/develop/data-types/geospatial/ + [geoadd-command]: https://redis.io/docs/latest/commands/geoadd/ + [geosearch-command]: https://redis.io/docs/latest/commands/geosearch/ + + stages: - slug: "jm1" concept_slugs: @@ -3878,3 +3890,58 @@ stages: name: "Blocking retrieval with timeout" difficulty: medium marketing_md: In this stage, you will add support for a non-zero timeout duration for the `BLPOP` command. + + # Geospatial Commands + - slug: "zt4" + primary_extension_slug: "geospatial" + name: "Add a location" + difficulty: easy + marketing_md: In this stage, you'll add support for adding a single location under a key using the `GEOADD` command. + + - slug: "wq4" + primary_extension_slug: "geospatial" + name: "Add multiple locations" + difficulty: easy + marketing_md: In this stage, you'll add support for inserting multiple locations in one `GEOADD` command. + + - slug: "ck3" + primary_extension_slug: "geospatial" + name: "Validate Coordinates" + difficulty: easy + marketing_md: In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. + + - slug: "xg4" + primary_extension_slug: "geospatial" + name: "Retrieve a location" + difficulty: easy + marketing_md: In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. + + - slug: "ia8" + primary_extension_slug: "geospatial" + name: "Retrieve multiple locations" + difficulty: easy + marketing_md: In this stage, you'll add support for retrieving multiple locations using a single `GEOPOS` command. + + - slug: "ek6" + primary_extension_slug: "geospatial" + name: "Measure Distance" + difficulty: medium + marketing_md: In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. + + - slug: "ra4" + primary_extension_slug: "geospatial" + name: "Unit Conversion" + difficulty: easy + marketing_md: In this stage, you'll add support for different units of measure in the `GEODIST` command. + + - slug: "rm9" + primary_extension_slug: "geospatial" + name: "Search within radius" + difficulty: hard + marketing_md: In this stage, you'll add support for searching locations near a coordinate within a given radius. + + - slug: "hv8" + primary_extension_slug: "geospatial" + name: "Search within box" + difficulty: hard + marketing_md: In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. \ No newline at end of file diff --git a/stage_descriptions/geospatial_1_zt4.md b/stage_descriptions/geospatial_1_zt4.md new file mode 100644 index 00000000..caa0b6bc --- /dev/null +++ b/stage_descriptions/geospatial_1_zt4.md @@ -0,0 +1,35 @@ +In this stage, you'll add support for adding a single location under a key using the `GEOADD` command. + +### The `GEOADD` command +The `GEOADD` command adds one or more location (with longitude, latitude, and name) to a key. If the key doesn’t exist, it is created. If the key exists, the locations are appended under that key. +The syntax for `GEOADD` command is + +``` +GEOADD +``` + +Example usage: + +``` +> GEOADD places 13.361389 38.115556 "Palermo" +(integer) 1 + +> GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +(integer) 2 +``` + +It returns the count of elements added, encoded as a RESP Integer. + +### Tests +The tester will execute your program like this: + +``` +./your_program.sh +``` + + +It will then send a `GEOADD` command specifying a key, random values of latitude and longitude, and also a random name for that co-ordinate. +The tester will verify that the response to the command is `:1\r\n`, which is 1 (the number of locations added), encoded as a RESP Integer. + +### Notes +In this stage, you'll only handle adding one location per GEOADD command. We'll get to adding multiple locations in a single GEOADD command in the next stage. diff --git a/stage_descriptions/geospatial_2_wq4.md b/stage_descriptions/geospatial_2_wq4.md new file mode 100644 index 00000000..7f6ed5f5 --- /dev/null +++ b/stage_descriptions/geospatial_2_wq4.md @@ -0,0 +1,25 @@ +In this stage, you'll add support for inserting multiple locations in one `GEOADD` command. + + +### Tests +The tester will execute your program like this: +It will then send multiple `GEOADD` commands specifying one or more locations with random coordinates. + +``` +./your_program.sh +``` + +It will then send multiple GEOADD commands specifying one or more locations with random coordinates. + +``` +$ redis-cli +> GEOADD location_key 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +# Expect: 2 (Resp Encoded Integer) + +> GEOADD location_key 10.0 20.0 "Foo" +# Expect: 1 (Resp Encoded Integer) + +> GEOADD location_key 20.0 10.0 "Bar" +# Expect: 1 (Resp Encoded Integer) +``` + diff --git a/stage_descriptions/geospatial_3_ck3.md b/stage_descriptions/geospatial_3_ck3.md new file mode 100644 index 00000000..7c72f62d --- /dev/null +++ b/stage_descriptions/geospatial_3_ck3.md @@ -0,0 +1,46 @@ +In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. + +### The `GEOADD` command (Validating Coordinates) +The latitude and longitudes used in the GEOADD command should be in a certain range. Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. +Example use case: + +``` +# Invalid latitude +> GEOADD places 180 90 test +(error) ERR invalid longitude,latitude pair 180.000000,90.000000 + +# Invalid longitude +> geoadd places 181 0.3 test +(error) ERR invalid longitude,latitude pair 181.000000,0.300000 +``` + +In the response, both latitude and longitude values are rounded to 6 digits after the decimal point. + +### Tests +The tester will execute your program like this: +``` +./your_program.sh +``` +It will then send multiple GEOADD commands specifying one or more location to add. For valid co-ordinates, the tester will expect the response to be the usual response of the GEOADD command. For invalid co-ordinate values, it will expect an error. + + +For example, the tester will expect the response of the following command + +``` +$ redis-cli +> GEOADD location_key 200 100 foo +``` + +to be +``` +(error) ERR invalid longitude,latitude pair 180.000000,90.000000 +``` + +which is RESP-encoded as + +``` +-ERR invalid longitude,latitude pair 180.000000,90.000000\r\n +``` + +### Notes +If a GEOADD command specifies multiple locations, and if the co-ordinates of any one of those locations is invalid, it returns the error message and all the locations are discarded. \ No newline at end of file diff --git a/stage_descriptions/geospatial_4_xg4.md b/stage_descriptions/geospatial_4_xg4.md new file mode 100644 index 00000000..98b8798c --- /dev/null +++ b/stage_descriptions/geospatial_4_xg4.md @@ -0,0 +1,70 @@ +In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. + +### The `GEOPOS` command + +The GEOPOS command returns the longitude and latitude of the specified locations. Its syntax is + +``` +GEOPOS … +``` + +Example usage: +``` +> GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" + +> GEOPOS places Catania Rome non_existent +1) 1) "15.087267458438873" + 2) "37.50266842333162" +2) 1) "12.496366202831268" + 2) "41.90278213378984" +3) (nil) +``` + +It returns an array with one entry for each location requested. + +- If a location exists under the key, its entry is an array of two items: + - Longitude (Encoded as a Bulk string) + - Latitude (Encoded as a Bulk string) +- If a location doesn’t exist, the entry is a null bulk string `($-1\r\n)`. +- If the key does not exist, an empty array is returned `(*0\r\n)` + +### Tests +The tester will execute your program like this: +``` +./your_program.sh +``` + +It will add multiple locations using the `GEOADD` command. + +``` +$ redis-cli +> GEOADD location_key 19.0872 33.5026 "Foo" 49.125 72.991 "Bar" +> GEOADD location_key 10.0872 34.5026 "Baz" 41.125 73.991 "Caz" +``` + +The tester will then send multiple GEOPOS commands, each specifying a single location that may or may not have been added. For example, for the following command + +``` +> GEOPOS places Foo +``` + +It will expect the response to be the following array + +``` +1) 1) "19.0872" + 2) "33.5026" +``` + +which is RESP-encoded as +``` +*1\r\n +*2\r\n +$7\r\n +19.0872\r\n +$7\r\n +33.5026\r\n +``` + +### Notes +- If the location key does not exist, you should return an empty array. +- In this stage, you will only implement retrieving a single location using the `GEOPOS` command. We'll get to retrieving multiple locations in a single `GEOPOS` command in the next stage. diff --git a/stage_descriptions/geospatial_5_ia8.md b/stage_descriptions/geospatial_5_ia8.md new file mode 100644 index 00000000..174e2282 --- /dev/null +++ b/stage_descriptions/geospatial_5_ia8.md @@ -0,0 +1,46 @@ +In this stage, you'll add support for retrieving multiple locations using a single `GEOPOS` command. + +### Tests +The tester will execute your program like this: + +``` +./your_program.sh +``` + +It will add multiple locations using the `GEOADD` command. + +``` +$ redis-cli +> GEOADD location_key 19.0872 33.5026 "Foo" 49.125 72.991 "Bar" +> GEOADD location_key 10.0872 34.5026 "Baz" 41.125 73.991 "Caz" +``` + +The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, for the following command + +``` +> GEOPOS location_key Foo Caz non_existent +``` + +``` +1) 1) "19.0872" + 2) "33.5026" +2) 1) "41.125" + 2) "73.991" +3) (nil) +``` + +which is RESP-encoded as + +``` +*2\r\n +*2\r\n +$18\r\n +19.087197482585907\r\n +$17\r\n +33.50259961456723\r\n +*2\r\n +$18\r\n +41.12499922513962\r\n +$17\r\n +73.99100100464304\r\n +``` \ No newline at end of file diff --git a/stage_descriptions/geospatial_6_ek6.md b/stage_descriptions/geospatial_6_ek6.md new file mode 100644 index 00000000..485652f7 --- /dev/null +++ b/stage_descriptions/geospatial_6_ek6.md @@ -0,0 +1,64 @@ +In this stage, you'll add support for calculating the distance between two locations. + +### The `GEODIST` command +The `GEODIST` command returns the distance between two members. It can also take a optional last parameter which is the unit in which to express the distance in. The default unit is meters. The valid units are meters(m), kilometers(km), miles(mi), or feet(ft). +The syntax for `GEODIST` command is: +``` +GEODIST [unit] +``` +Example usage: + +``` +# Distance in meters +> GEODIST places Catania Rome m +"537215.1152" + +# Distance in kilometers +> GEODIST places Catania Rome km +"537.2151" + +# Distance in feet +> GEODIST places Catania Rome ft +"1762516.7822" + +# Distance in miles +> GEODIST places Catania Rome mi +"333.8108" + +# Default is meters + GEODIST places Catania Rome +"537215.1152" +``` + +It returns the distance as a string, encoded as a RESP Bulk String. The precision of the response is up to 4 digits after the decimal. + +Calculating the distance when two latitude and longitude pairs are given is not as straightforward as using Pythagorean theorem. Since the earth is a sphere, we have to account for its curvature as well. The [Haversine's Formula](https://en.wikipedia.org/wiki/Haversine_formula#Example) is used to calculate distance in such cases. See how redis implements it [here](https://github.com/redis/redis/blob/4322cebc1764d433b3fce3b3a108252648bf59e7/src/geohash_helper.c#L228C1-L228C72). + + +### Tests +The tester will execute your program like this: +``` +./your_program.sh +``` + +It will then add multiple locations using the `GEOADD` command. +``` +GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +``` + +The tester will then send multiple `GEODIST` commands specifying two locations. For example, in this case, + +``` +GEODIST places Catania Rome +``` + +it will expect the response to be "166.2742", which is RESP encoded as a bulk string as: + +``` +$11\r\n +537215.1152\r\n +``` + +### Notes +- If one or both of the location specified in the `GEODIST` command does not exist, it should return a null bulk string `($-1\r\n)`. +- In this stage, you will only implement returning the distance in meters. We will get to using different units in the next stage. \ No newline at end of file diff --git a/stage_descriptions/geospatial_7_ra4.md b/stage_descriptions/geospatial_7_ra4.md new file mode 100644 index 00000000..3a9808d0 --- /dev/null +++ b/stage_descriptions/geospatial_7_ra4.md @@ -0,0 +1,27 @@ +In this stage, you'll add support for different units of measure in the `GEODIST` command. + +### Tests +The tester will execute your program like this: +``` +./your_program.sh +``` + +It will then add multiple locations using the `GEOADD` command. + +``` +GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +GEODIST places Catania Catania km +``` + +The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, in this case, + +``` +GEODIST places Palermo Catania km +``` + +It will expect the response to be "537.2151", which is RESP bulk string encoded as + +``` +$8\r\n +537.2151\r\n +``` \ No newline at end of file diff --git a/stage_descriptions/geospatial_8_rm9.md b/stage_descriptions/geospatial_8_rm9.md new file mode 100644 index 00000000..723d36c0 --- /dev/null +++ b/stage_descriptions/geospatial_8_rm9.md @@ -0,0 +1,53 @@ +In this stage, you'll add support for searching locations near a coordinate within a given radius. + +### The GEOSEARCH command + +The `GEOSEARCH` command lets you search for locations near a given coordinate within a specified area. + +It supports several search modes, but in our implementation, we'll focus only on the `FROMLONLAT` option, which allows searching by directly specifying longitude and latitude. The syntax for this is: + +``` +GEOSEARCH key FROMLONLAT longitude latitude BYRADIUS distance [km|ft|m|mi] +``` + +For example, to search for locations within 200 kilometers of the point (longitude: 15, latitude: 37) stored in the places key, you can use: + +``` +> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 200 km +1) "Palermo" +2) "Catania" +``` + +It returns a RESP Array of member names, where each member's name is a encoded as a bulk string. + +### Tests +The tester will execute your program like this: + +``` +./your_program.sh +``` + +It will add multiple locations using the `GEOADD` command. + +``` +GEOADD places 13.361389 38.115556 Palermo 15.087269 37.502669 Catania +``` + +The tester will then send multiple `GEOSEARCH` commands specifying a latitude and longitude pair with `BYRADIUS` option specifying the distance and unit. For example, it may send the following command. + +``` +GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 200 km +``` + +It will expect the response to be (["Palermo", "Catania"]) which is RESP Encoded as: + +``` +*2\r\n +$7\r\n +Palermo\r\n +$7\r\n +Catania\r\n +``` + +### Notes +In this stage, you will only implement the BYRADIUS option. We'll get to implementing the BYBOX option in the next stage. \ No newline at end of file diff --git a/stage_descriptions/geospatial_9_hv8.md b/stage_descriptions/geospatial_9_hv8.md new file mode 100644 index 00000000..ca4f7045 --- /dev/null +++ b/stage_descriptions/geospatial_9_hv8.md @@ -0,0 +1,81 @@ +In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. + +### The GEOSEARCH command (Box Search) +The GEOSEARCH command can also be used to find all points within a box centered at a given pair of latitude and longitude within a rectangle of certain width and height. +The syntax for this is: +``` +GEOSEARCH key FROMLONLAT longitude latitude BYBOX width height [km|ft|m|mi] +``` + +Example usage: + +``` +> GEOADD cities 13.361389 38.115556 "Palermo" +(integer) 1 +> GEOADD cities 15.087269 37.502669 "Catania" +(integer) 1 +> GEOADD cities 12.496365 41.902783 "Rome" +(integer) 1 + +> GEOSEARCH cities FROMLONLAT 15 37 BYBOX 300 300 km +1) "Palermo" +2) "Catania" +``` + +### Tests +The tester will execute your program like this: +``` +./your_program.sh +``` + +It will add multiple locations with random co-ordinates using the `GEOADD` command. +``` +> GEOADD test 0 0 "center" +> GEOADD test 0.5 0 "east" +> GEOADD test -0.5 0 "west" +> GEOADD test 0 0.5 "north" +> GEOADD test 0 -0.5 "south" +> GEOADD test 1 1 "outside" +``` + +The tester will then send multiple `GEOSEARCH` commands with `FROMLONLAT` and `BYBOX` option specifying random longitude and latitude and bounding box size. For example, it may send the following: + + +``` +> GEOSEARCH test fromlonlat 0 0 bybox 300 300 km +1) "center" +2) "east" +3) "north" +4) "outside" +5) "south" +6) "west" +``` + +It will expect the response to be + +``` +[ + "center", + "east", + "north", + "outside", + "south", + "west", +] +``` +which is RESP-Encoded as +``` +*6\r\n +$6\r\n +center\r\n +$4\r\n +east\r\n +$5\r\n +north\r\n +$7\r\n +outside\r\n +$5\r\n +south\r\n +$4\r\n +west\r\n +``` From e7f7f51ff1d0a6a793cc564898b36dbe2ff00257 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Wed, 16 Jul 2025 12:20:33 +0545 Subject: [PATCH 02/24] - Initial proposition for redis/geospatial challenge --- stage_descriptions/geospatial_1_zt4.md | 5 +---- stage_descriptions/geospatial_2_wq4.md | 9 +++++++++ stage_descriptions/geospatial_3_ck3.md | 18 ++++++++++------- stage_descriptions/geospatial_4_xg4.md | 16 +++++++++++++-- stage_descriptions/geospatial_5_ia8.md | 27 ++++++++++++++------------ stage_descriptions/geospatial_6_ek6.md | 15 ++++++++++++++ stage_descriptions/geospatial_7_ra4.md | 6 ++++-- 7 files changed, 69 insertions(+), 27 deletions(-) diff --git a/stage_descriptions/geospatial_1_zt4.md b/stage_descriptions/geospatial_1_zt4.md index caa0b6bc..9c8566d5 100644 --- a/stage_descriptions/geospatial_1_zt4.md +++ b/stage_descriptions/geospatial_1_zt4.md @@ -13,9 +13,6 @@ Example usage: ``` > GEOADD places 13.361389 38.115556 "Palermo" (integer) 1 - -> GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" -(integer) 2 ``` It returns the count of elements added, encoded as a RESP Integer. @@ -32,4 +29,4 @@ It will then send a `GEOADD` command specifying a key, random values of latitude The tester will verify that the response to the command is `:1\r\n`, which is 1 (the number of locations added), encoded as a RESP Integer. ### Notes -In this stage, you'll only handle adding one location per GEOADD command. We'll get to adding multiple locations in a single GEOADD command in the next stage. +- In this stage, you'll only handle adding only one location per `GEOADD` command. We'll get to adding multiple locations in single `GEOADD` command in the next stage. diff --git a/stage_descriptions/geospatial_2_wq4.md b/stage_descriptions/geospatial_2_wq4.md index 7f6ed5f5..a95b7d64 100644 --- a/stage_descriptions/geospatial_2_wq4.md +++ b/stage_descriptions/geospatial_2_wq4.md @@ -1,5 +1,14 @@ In this stage, you'll add support for inserting multiple locations in one `GEOADD` command. +### The `GEOADD` command (Multiple Locations) +The `GEOADD` command also supports adding multiple locations at once. + +Example usage: + +``` +> GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +(integer) 2 +``` ### Tests The tester will execute your program like this: diff --git a/stage_descriptions/geospatial_3_ck3.md b/stage_descriptions/geospatial_3_ck3.md index 7c72f62d..52821df2 100644 --- a/stage_descriptions/geospatial_3_ck3.md +++ b/stage_descriptions/geospatial_3_ck3.md @@ -6,15 +6,20 @@ Example use case: ``` # Invalid latitude -> GEOADD places 180 90 test +> GEOADD places 180 90 test1 (error) ERR invalid longitude,latitude pair 180.000000,90.000000 # Invalid longitude -> geoadd places 181 0.3 test +> GEOADD places 181 0.3 test2 (error) ERR invalid longitude,latitude pair 181.000000,0.300000 + +# Roundinf off to 6 digits in case of errors +> GEOADD loc 122 90.23454393 test3 +(error) ERR invalid longitude,latitude pair 122.000000,90.234544 + ``` -In the response, both latitude and longitude values are rounded to 6 digits after the decimal point. +In the response, both latitude and longitude values are rounded to 6 digits after the decimal point. ### Tests The tester will execute your program like this: @@ -23,7 +28,6 @@ The tester will execute your program like this: ``` It will then send multiple GEOADD commands specifying one or more location to add. For valid co-ordinates, the tester will expect the response to be the usual response of the GEOADD command. For invalid co-ordinate values, it will expect an error. - For example, the tester will expect the response of the following command ``` @@ -33,14 +37,14 @@ $ redis-cli to be ``` -(error) ERR invalid longitude,latitude pair 180.000000,90.000000 +(error) ERR invalid longitude,latitude pair 200.000000,100.000000 ``` which is RESP-encoded as ``` --ERR invalid longitude,latitude pair 180.000000,90.000000\r\n +-ERR invalid longitude,latitude pair 200.000000,100.000000\r\n ``` ### Notes -If a GEOADD command specifies multiple locations, and if the co-ordinates of any one of those locations is invalid, it returns the error message and all the locations are discarded. \ No newline at end of file +If a `GEOADD` command specifies multiple locations, and if the co-ordinates of any one of those locations is invalid, it returns the error message and all the locations are discarded. \ No newline at end of file diff --git a/stage_descriptions/geospatial_4_xg4.md b/stage_descriptions/geospatial_4_xg4.md index 98b8798c..a72ee299 100644 --- a/stage_descriptions/geospatial_4_xg4.md +++ b/stage_descriptions/geospatial_4_xg4.md @@ -22,11 +22,11 @@ Example usage: It returns an array with one entry for each location requested. +- If the key does not exist, an empty array is returned `(*0\r\n)` - If a location exists under the key, its entry is an array of two items: - Longitude (Encoded as a Bulk string) - Latitude (Encoded as a Bulk string) - If a location doesn’t exist, the entry is a null bulk string `($-1\r\n)`. -- If the key does not exist, an empty array is returned `(*0\r\n)` ### Tests The tester will execute your program like this: @@ -42,7 +42,7 @@ $ redis-cli > GEOADD location_key 10.0872 34.5026 "Baz" 41.125 73.991 "Caz" ``` -The tester will then send multiple GEOPOS commands, each specifying a single location that may or may not have been added. For example, for the following command +The tester will then send multiple GEOPOS commands, each specifying a single location that may or may not have been added. For example, for the following command ``` > GEOPOS places Foo @@ -66,5 +66,17 @@ $7\r\n ``` ### Notes + +- The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded-off. + + * For example, if a location was added using `GEOADD key 20.123456 30.123001`, any of the following will be accepted: + + * `20.1235 30.1230` + * `20.123456 30.123000` + * `20.123490 30.123045` + * `20.123459 30.123001` + * `20.1235001 30.122999` + - If the location key does not exist, you should return an empty array. + - In this stage, you will only implement retrieving a single location using the `GEOPOS` command. We'll get to retrieving multiple locations in a single `GEOPOS` command in the next stage. diff --git a/stage_descriptions/geospatial_5_ia8.md b/stage_descriptions/geospatial_5_ia8.md index 174e2282..0a49270d 100644 --- a/stage_descriptions/geospatial_5_ia8.md +++ b/stage_descriptions/geospatial_5_ia8.md @@ -11,18 +11,20 @@ It will add multiple locations using the `GEOADD` command. ``` $ redis-cli -> GEOADD location_key 19.0872 33.5026 "Foo" 49.125 72.991 "Bar" +> GEOADD location_key 19.08729 33.5026 "Foo" 49.125 72.991 "Bar" > GEOADD location_key 10.0872 34.5026 "Baz" 41.125 73.991 "Caz" ``` -The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, for the following command +The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, for the following command, ``` > GEOPOS location_key Foo Caz non_existent ``` +it will expect the response to be + ``` -1) 1) "19.0872" +1) 1) "19.08729" 2) "33.5026" 2) 1) "41.125" 2) "73.991" @@ -32,15 +34,16 @@ The tester will then send multiple `GEOPOS` commands, each specifying a multiple which is RESP-encoded as ``` +*3\r\n *2\r\n +$8\r\n +19.08729\r\n +$7\r\n +33.5026\r\n *2\r\n -$18\r\n -19.087197482585907\r\n -$17\r\n -33.50259961456723\r\n -*2\r\n -$18\r\n -41.12499922513962\r\n -$17\r\n -73.99100100464304\r\n +$6\r\n +41.125\r\n +$6\r\n +73.991\r\n +$-1\r\n ``` \ No newline at end of file diff --git a/stage_descriptions/geospatial_6_ek6.md b/stage_descriptions/geospatial_6_ek6.md index 485652f7..973e78d9 100644 --- a/stage_descriptions/geospatial_6_ek6.md +++ b/stage_descriptions/geospatial_6_ek6.md @@ -60,5 +60,20 @@ $11\r\n ``` ### Notes +- The tester will be lenient when validating the distance returned by the `GEODIST` command. The distance should match the actual distance between the provided locations with a precision of **up to 2 decimal places after rounding**. + + * This means that minor floating-point differences are acceptable as long as the values, when rounded to two decimal places, are the same. + + * For example, if the expected distance is `12345.67`, any of the following returned values will be accepted: + + * `12345.67001` + * `12345.674` + * `12345.6659` + * `12345.6666` + * `12345.669` + + * However, values like `12345.64`, `12345.70`, or `12345.61` would be considered incorrect. + + - If one or both of the location specified in the `GEODIST` command does not exist, it should return a null bulk string `($-1\r\n)`. - In this stage, you will only implement returning the distance in meters. We will get to using different units in the next stage. \ No newline at end of file diff --git a/stage_descriptions/geospatial_7_ra4.md b/stage_descriptions/geospatial_7_ra4.md index 3a9808d0..76ea64a9 100644 --- a/stage_descriptions/geospatial_7_ra4.md +++ b/stage_descriptions/geospatial_7_ra4.md @@ -10,7 +10,6 @@ It will then add multiple locations using the `GEOADD` command. ``` GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" -GEODIST places Catania Catania km ``` The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, in this case, @@ -24,4 +23,7 @@ It will expect the response to be "537.2151", which is RESP bulk string encoded ``` $8\r\n 537.2151\r\n -``` \ No newline at end of file +``` + +### Notes +- For greatest accuracy, it is recommended to calculate the distance in feet, since it is the unit with smallest precision, and convert that into other units. \ No newline at end of file From ea695bb94b99f8942ca09e38bd4aadc9a33eba91 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Thu, 17 Jul 2025 14:05:22 +0545 Subject: [PATCH 03/24] - Apply requested changes - Rename stage desription files to match the latest format --- course-definition.yml | 8 +--- ...ospatial_1_zt4.md => geospatial-01-zt4.md} | 3 +- ...ospatial_3_ck3.md => geospatial-02-ck3.md} | 20 +++++----- ...ospatial_4_xg4.md => geospatial-03-xg4.md} | 9 +++-- ...ospatial_5_ia8.md => geospatial-04-ia8.md} | 6 ++- ...ospatial_6_ek6.md => geospatial-05-ek6.md} | 31 ++++----------- stage_descriptions/geospatial-06-ra4.md | 39 +++++++++++++++++++ ...ospatial_8_rm9.md => geospatial-07-rm9.md} | 3 +- ...ospatial_9_hv8.md => geospatial-08-hv8.md} | 0 stage_descriptions/geospatial_2_wq4.md | 34 ---------------- stage_descriptions/geospatial_7_ra4.md | 29 -------------- 11 files changed, 69 insertions(+), 113 deletions(-) rename stage_descriptions/{geospatial_1_zt4.md => geospatial-01-zt4.md} (84%) rename stage_descriptions/{geospatial_3_ck3.md => geospatial-02-ck3.md} (61%) rename stage_descriptions/{geospatial_4_xg4.md => geospatial-03-xg4.md} (89%) rename stage_descriptions/{geospatial_5_ia8.md => geospatial-04-ia8.md} (81%) rename stage_descriptions/{geospatial_6_ek6.md => geospatial-05-ek6.md} (60%) create mode 100644 stage_descriptions/geospatial-06-ra4.md rename stage_descriptions/{geospatial_8_rm9.md => geospatial-07-rm9.md} (94%) rename stage_descriptions/{geospatial_9_hv8.md => geospatial-08-hv8.md} (100%) delete mode 100644 stage_descriptions/geospatial_2_wq4.md delete mode 100644 stage_descriptions/geospatial_7_ra4.md diff --git a/course-definition.yml b/course-definition.yml index 5dc3ec71..8219d90b 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -699,15 +699,9 @@ stages: difficulty: easy marketing_md: In this stage, you'll add support for adding a single location under a key using the `GEOADD` command. - - slug: "wq4" - primary_extension_slug: "geospatial" - name: "Add multiple locations" - difficulty: easy - marketing_md: In this stage, you'll add support for inserting multiple locations in one `GEOADD` command. - - slug: "ck3" primary_extension_slug: "geospatial" - name: "Validate Coordinates" + name: "Validate coordinates" difficulty: easy marketing_md: In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. diff --git a/stage_descriptions/geospatial_1_zt4.md b/stage_descriptions/geospatial-01-zt4.md similarity index 84% rename from stage_descriptions/geospatial_1_zt4.md rename to stage_descriptions/geospatial-01-zt4.md index 9c8566d5..02b50ec2 100644 --- a/stage_descriptions/geospatial_1_zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -24,9 +24,8 @@ The tester will execute your program like this: ./your_program.sh ``` - It will then send a `GEOADD` command specifying a key, random values of latitude and longitude, and also a random name for that co-ordinate. The tester will verify that the response to the command is `:1\r\n`, which is 1 (the number of locations added), encoded as a RESP Integer. ### Notes -- In this stage, you'll only handle adding only one location per `GEOADD` command. We'll get to adding multiple locations in single `GEOADD` command in the next stage. +- You will only implement adding one location per `GEOADD` command. diff --git a/stage_descriptions/geospatial_3_ck3.md b/stage_descriptions/geospatial-02-ck3.md similarity index 61% rename from stage_descriptions/geospatial_3_ck3.md rename to stage_descriptions/geospatial-02-ck3.md index 52821df2..d2172f63 100644 --- a/stage_descriptions/geospatial_3_ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -1,6 +1,6 @@ In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. -### The `GEOADD` command (Validating Coordinates) +### The `GEOADD` command (Validate coordinates) The latitude and longitudes used in the GEOADD command should be in a certain range. Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. Example use case: @@ -12,15 +12,8 @@ Example use case: # Invalid longitude > GEOADD places 181 0.3 test2 (error) ERR invalid longitude,latitude pair 181.000000,0.300000 - -# Roundinf off to 6 digits in case of errors -> GEOADD loc 122 90.23454393 test3 -(error) ERR invalid longitude,latitude pair 122.000000,90.234544 - ``` -In the response, both latitude and longitude values are rounded to 6 digits after the decimal point. - ### Tests The tester will execute your program like this: ``` @@ -37,14 +30,19 @@ $ redis-cli to be ``` -(error) ERR invalid longitude,latitude pair 200.000000,100.000000 +(error) ERR invalid longitude,latitude pair 200,100 ``` which is RESP-encoded as ``` --ERR invalid longitude,latitude pair 200.000000,100.000000\r\n +-ERR invalid longitude,latitude pair 200,100\r\n ``` ### Notes -If a `GEOADD` command specifies multiple locations, and if the co-ordinates of any one of those locations is invalid, it returns the error message and all the locations are discarded. \ No newline at end of file +- In case of invalid co-ordinates, the tester is lenient in checking error messages so you don't have to stick to the exact format Redis uses. The exact format it checks for is `invalid` (case-insensitive). Examples of error message strings that will pass the tests: + - `invalid longitude,latitude pair 200,100` + - `Invalid longitude,latitude` + - `invalid` + +- If a `GEOADD` command specifies multiple locations, and if the co-ordinates of any one of those locations is invalid, it returns the error message and all the locations are discarded. diff --git a/stage_descriptions/geospatial_4_xg4.md b/stage_descriptions/geospatial-03-xg4.md similarity index 89% rename from stage_descriptions/geospatial_4_xg4.md rename to stage_descriptions/geospatial-03-xg4.md index a72ee299..cf52c688 100644 --- a/stage_descriptions/geospatial_4_xg4.md +++ b/stage_descriptions/geospatial-03-xg4.md @@ -10,7 +10,8 @@ GEOPOS … Example usage: ``` -> GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +> GEOADD places 15.087269 37.502669 "Catania" +> GEOADD places 12.496365 41.902783 "Rome" > GEOPOS places Catania Rome non_existent 1) 1) "15.087267458438873" @@ -38,8 +39,10 @@ It will add multiple locations using the `GEOADD` command. ``` $ redis-cli -> GEOADD location_key 19.0872 33.5026 "Foo" 49.125 72.991 "Bar" -> GEOADD location_key 10.0872 34.5026 "Baz" 41.125 73.991 "Caz" +> GEOADD location_key 19.0872 33.5026 "Foo" +> GEOADD location_key 49.125 72.991 "Bar" +> GEOADD location_key 10.0872 34.5026 "Baz" +> GEOADD location_key 41.125 73.991 "Caz" ``` The tester will then send multiple GEOPOS commands, each specifying a single location that may or may not have been added. For example, for the following command diff --git a/stage_descriptions/geospatial_5_ia8.md b/stage_descriptions/geospatial-04-ia8.md similarity index 81% rename from stage_descriptions/geospatial_5_ia8.md rename to stage_descriptions/geospatial-04-ia8.md index 0a49270d..eb117ae2 100644 --- a/stage_descriptions/geospatial_5_ia8.md +++ b/stage_descriptions/geospatial-04-ia8.md @@ -11,8 +11,10 @@ It will add multiple locations using the `GEOADD` command. ``` $ redis-cli -> GEOADD location_key 19.08729 33.5026 "Foo" 49.125 72.991 "Bar" -> GEOADD location_key 10.0872 34.5026 "Baz" 41.125 73.991 "Caz" +> GEOADD location_key 19.08729 33.5026 "Foo" +> GEOADD location_key 49.125 72.991 "Bar" +> GEOADD location_key 10.0872 34.5026 "Baz" +> GEOADD location_key 41.125 73.991 "Caz" ``` The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, for the following command, diff --git a/stage_descriptions/geospatial_6_ek6.md b/stage_descriptions/geospatial-05-ek6.md similarity index 60% rename from stage_descriptions/geospatial_6_ek6.md rename to stage_descriptions/geospatial-05-ek6.md index 973e78d9..376583c0 100644 --- a/stage_descriptions/geospatial_6_ek6.md +++ b/stage_descriptions/geospatial-05-ek6.md @@ -1,39 +1,21 @@ In this stage, you'll add support for calculating the distance between two locations. ### The `GEODIST` command -The `GEODIST` command returns the distance between two members. It can also take a optional last parameter which is the unit in which to express the distance in. The default unit is meters. The valid units are meters(m), kilometers(km), miles(mi), or feet(ft). +The `GEODIST` command returns the distance between two members. The default unit of the distance which is returned, is meters. The syntax for `GEODIST` command is: ``` -GEODIST [unit] +GEODIST ``` Example usage: ``` -# Distance in meters -> GEODIST places Catania Rome m -"537215.1152" - -# Distance in kilometers -> GEODIST places Catania Rome km -"537.2151" - -# Distance in feet -> GEODIST places Catania Rome ft -"1762516.7822" - -# Distance in miles -> GEODIST places Catania Rome mi -"333.8108" - -# Default is meters - GEODIST places Catania Rome +> GEODIST places Catania Rome "537215.1152" ``` It returns the distance as a string, encoded as a RESP Bulk String. The precision of the response is up to 4 digits after the decimal. -Calculating the distance when two latitude and longitude pairs are given is not as straightforward as using Pythagorean theorem. Since the earth is a sphere, we have to account for its curvature as well. The [Haversine's Formula](https://en.wikipedia.org/wiki/Haversine_formula#Example) is used to calculate distance in such cases. See how redis implements it [here](https://github.com/redis/redis/blob/4322cebc1764d433b3fce3b3a108252648bf59e7/src/geohash_helper.c#L228C1-L228C72). - +Redis uses the [Haversine's Formula](https://en.wikipedia.org/wiki/Haversine_formula#Example) to calculate the distance between two points. You can see how this is done in the Redis source code [here](https://github.com/redis/redis/blob/4322cebc1764d433b3fce3b3a108252648bf59e7/src/geohash_helper.c#L228C1-L228C72). ### Tests The tester will execute your program like this: @@ -43,13 +25,14 @@ The tester will execute your program like this: It will then add multiple locations using the `GEOADD` command. ``` -GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" +> GEOADD places 15.087269 37.502669 "Catania" +> GEOADD places 12.496365 41.902783 "Rome" ``` The tester will then send multiple `GEODIST` commands specifying two locations. For example, in this case, ``` -GEODIST places Catania Rome +> GEODIST places Catania Rome ``` it will expect the response to be "166.2742", which is RESP encoded as a bulk string as: diff --git a/stage_descriptions/geospatial-06-ra4.md b/stage_descriptions/geospatial-06-ra4.md new file mode 100644 index 00000000..6c6ade69 --- /dev/null +++ b/stage_descriptions/geospatial-06-ra4.md @@ -0,0 +1,39 @@ +In this stage, you'll add support for different units of measure in the `GEODIST` command. + +### Unit Conversion +The `GEODIST` command supports an optional fourth argument, which is the unit in which the distance should be returned. The valid units are meters(`m`), kilometers(`km`), miles(`mi`) and feet(`ft`). If the unit is specified, the syntax for `GEODIST` is: +``` +GEODIST key location1 location2 +``` + +Example Usage: +``` +> GEODIST places Catania Rome mi +"333.8108" +``` + +### Tests +The tester will execute your program like this: +``` +./your_program.sh +``` + +It will then add multiple locations using the `GEOADD` command. + +``` +> GEOADD places 15.087269 37.502669 "Catania" +> GEOADD places 12.496365 41.902783 "Rome" +``` + +The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, in this case, + +``` +> GEODIST places Catania Rome km +``` + +It will expect the response to be "537.2151", which is RESP bulk string encoded as + +``` +$8\r\n +537.2151\r\n +``` \ No newline at end of file diff --git a/stage_descriptions/geospatial_8_rm9.md b/stage_descriptions/geospatial-07-rm9.md similarity index 94% rename from stage_descriptions/geospatial_8_rm9.md rename to stage_descriptions/geospatial-07-rm9.md index 723d36c0..91cb8223 100644 --- a/stage_descriptions/geospatial_8_rm9.md +++ b/stage_descriptions/geospatial-07-rm9.md @@ -30,7 +30,8 @@ The tester will execute your program like this: It will add multiple locations using the `GEOADD` command. ``` -GEOADD places 13.361389 38.115556 Palermo 15.087269 37.502669 Catania +> GEOADD places 13.361389 38.115556 Palermo +> GEOADD places 15.087269 37.502669 Catania ``` The tester will then send multiple `GEOSEARCH` commands specifying a latitude and longitude pair with `BYRADIUS` option specifying the distance and unit. For example, it may send the following command. diff --git a/stage_descriptions/geospatial_9_hv8.md b/stage_descriptions/geospatial-08-hv8.md similarity index 100% rename from stage_descriptions/geospatial_9_hv8.md rename to stage_descriptions/geospatial-08-hv8.md diff --git a/stage_descriptions/geospatial_2_wq4.md b/stage_descriptions/geospatial_2_wq4.md deleted file mode 100644 index a95b7d64..00000000 --- a/stage_descriptions/geospatial_2_wq4.md +++ /dev/null @@ -1,34 +0,0 @@ -In this stage, you'll add support for inserting multiple locations in one `GEOADD` command. - -### The `GEOADD` command (Multiple Locations) -The `GEOADD` command also supports adding multiple locations at once. - -Example usage: - -``` -> GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" -(integer) 2 -``` - -### Tests -The tester will execute your program like this: -It will then send multiple `GEOADD` commands specifying one or more locations with random coordinates. - -``` -./your_program.sh -``` - -It will then send multiple GEOADD commands specifying one or more locations with random coordinates. - -``` -$ redis-cli -> GEOADD location_key 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" -# Expect: 2 (Resp Encoded Integer) - -> GEOADD location_key 10.0 20.0 "Foo" -# Expect: 1 (Resp Encoded Integer) - -> GEOADD location_key 20.0 10.0 "Bar" -# Expect: 1 (Resp Encoded Integer) -``` - diff --git a/stage_descriptions/geospatial_7_ra4.md b/stage_descriptions/geospatial_7_ra4.md deleted file mode 100644 index 76ea64a9..00000000 --- a/stage_descriptions/geospatial_7_ra4.md +++ /dev/null @@ -1,29 +0,0 @@ -In this stage, you'll add support for different units of measure in the `GEODIST` command. - -### Tests -The tester will execute your program like this: -``` -./your_program.sh -``` - -It will then add multiple locations using the `GEOADD` command. - -``` -GEOADD places 15.087269 37.502669 "Catania" 12.496365 41.902783 "Rome" -``` - -The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, in this case, - -``` -GEODIST places Palermo Catania km -``` - -It will expect the response to be "537.2151", which is RESP bulk string encoded as - -``` -$8\r\n -537.2151\r\n -``` - -### Notes -- For greatest accuracy, it is recommended to calculate the distance in feet, since it is the unit with smallest precision, and convert that into other units. \ No newline at end of file From 974c1322e7788faa1611dd4752b35e6ab653c5f8 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Thu, 17 Jul 2025 14:09:31 +0545 Subject: [PATCH 04/24] Remove merge conflict --- course-definition.yml | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 8219d90b..52b22bdd 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -117,7 +117,17 @@ extensions: [redis-persistence]: https://redis.io/docs/manual/persistence/ [rdb-file-format]: https://github.com/sripathikrishnan/redis-rdb-tools/blob/548b11ec3c81a603f5b321228d07a61a0b940159/docs/RDB_File_Format.textile -<<<<<<< HEAD + - slug: "pub-sub" + name: "Pub/Sub" + description_markdown : |- + In this challenge extension you'll add support for [Publish/Subscribe (Pub/Sub)][redis-pub-sub] to your Redis implementation. + + Along the way, you'll learn commands like [SUBSCRIBE][subscribe-command], [PUBLISH][publish-command], and more. + + [redis-pub-sub]: https://redis.io/docs/latest/develop/pubsub/ + [subscribe-command]: https://redis.io/docs/latest/commands/subscribe/ + [publish-command]: https://redis.io/docs/latest/commands/publish/ + - slug: "geospatial" name: "Geospatial Commands" description_markdown: | @@ -129,18 +139,6 @@ extensions: [geoadd-command]: https://redis.io/docs/latest/commands/geoadd/ [geosearch-command]: https://redis.io/docs/latest/commands/geosearch/ -======= - - slug: "pub-sub" - name: "Pub/Sub" - description_markdown : |- - In this challenge extension you'll add support for [Publish/Subscribe (Pub/Sub)][redis-pub-sub] to your Redis implementation. - - Along the way, you'll learn commands like [SUBSCRIBE][subscribe-command], [PUBLISH][publish-command], and more. - - [redis-pub-sub]: https://redis.io/docs/latest/develop/pubsub/ - [subscribe-command]: https://redis.io/docs/latest/commands/subscribe/ - [publish-command]: https://redis.io/docs/latest/commands/publish/ ->>>>>>> main stages: - slug: "jm1" From 98b4c6e80508a3a65e4b1e26a67a930931dd70af Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 12:39:26 +0545 Subject: [PATCH 05/24] fix: update requested changes " --- course-definition.yml | 4 +-- stage_descriptions/geospatial-01-zt4.md | 9 +++++-- stage_descriptions/geospatial-02-ck3.md | 34 ++++++++++++++----------- stage_descriptions/geospatial-03-xg4.md | 15 ++++------- stage_descriptions/geospatial-04-ia8.md | 9 +++---- stage_descriptions/geospatial-05-ek6.md | 10 +++++--- stage_descriptions/geospatial-06-ra4.md | 6 +++-- stage_descriptions/geospatial-07-rm9.md | 9 ++++--- stage_descriptions/geospatial-08-hv8.md | 15 +++++------ 9 files changed, 59 insertions(+), 52 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 52b22bdd..020b1b2e 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -717,13 +717,13 @@ stages: - slug: "ek6" primary_extension_slug: "geospatial" - name: "Measure Distance" + name: "Measure distance" difficulty: medium marketing_md: In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. - slug: "ra4" primary_extension_slug: "geospatial" - name: "Unit Conversion" + name: "Unit conversion" difficulty: easy marketing_md: In this stage, you'll add support for different units of measure in the `GEODIST` command. diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index 02b50ec2..945c62db 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -1,7 +1,7 @@ In this stage, you'll add support for adding a single location under a key using the `GEOADD` command. ### The `GEOADD` command -The `GEOADD` command adds one or more location (with longitude, latitude, and name) to a key. If the key doesn’t exist, it is created. If the key exists, the locations are appended under that key. +The `GEOADD` command adds a location (with longitude, latitude, and name) to a key. If the key doesn’t exist, it is created. If the key exists, the location is appended to that key. The syntax for `GEOADD` command is ``` @@ -24,7 +24,12 @@ The tester will execute your program like this: ./your_program.sh ``` -It will then send a `GEOADD` command specifying a key, random values of latitude and longitude, and also a random name for that co-ordinate. +It will then send a `GEOADD` command specifying a key, random values of latitude and longitude, and also a random name for that coordinate. + +``` +$ redis-cli GEOADD places 15.09 37.50 Catania +``` + The tester will verify that the response to the command is `:1\r\n`, which is 1 (the number of locations added), encoded as a RESP Integer. ### Notes diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index d2172f63..1d07bea4 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -1,7 +1,7 @@ In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. ### The `GEOADD` command (Validate coordinates) -The latitude and longitudes used in the GEOADD command should be in a certain range. Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. +Latitudes and longitudes used in the `GEOADD` command should be in a certain range as per [EPSG:3857](https://epsg.io/3857). Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. Example use case: ``` @@ -19,30 +19,34 @@ The tester will execute your program like this: ``` ./your_program.sh ``` -It will then send multiple GEOADD commands specifying one or more location to add. For valid co-ordinates, the tester will expect the response to be the usual response of the GEOADD command. For invalid co-ordinate values, it will expect an error. +It will then send multiple `GEOADD` commands specifying one or more location to add. For valid coordinates, the tester will expect the response to be the usual response of the `GEOADD` command. For invalid coordinates values, it will expect an error. -For example, the tester will expect the response of the following command +For example, the tester might send your program a command like this: ``` -$ redis-cli -> GEOADD location_key 200 100 foo -``` +$ redis-cli GEOADD location_key 200 100 foo -to be -``` +# Expecting error (error) ERR invalid longitude,latitude pair 200,100 ``` -which is RESP-encoded as +The value is RESP simple error, which is RESP-encoded as ``` -ERR invalid longitude,latitude pair 200,100\r\n ``` -### Notes -- In case of invalid co-ordinates, the tester is lenient in checking error messages so you don't have to stick to the exact format Redis uses. The exact format it checks for is `invalid` (case-insensitive). Examples of error message strings that will pass the tests: - - `invalid longitude,latitude pair 200,100` - - `Invalid longitude,latitude` - - `invalid` +The tester will also send your program a `GEOADD` command specifying a non-numeric value in the latitude/longitude field. In this case, it will expect an error. For example, in case of the following command, it will expect an error. + +``` +$ redis-cli GEOADD location_key foo bar location_name + +# Expecting RESP simple error +(error) ERR value is not a valid float +``` -- If a `GEOADD` command specifies multiple locations, and if the co-ordinates of any one of those locations is invalid, it returns the error message and all the locations are discarded. +### Notes +- In case of out-of-bounds values of latitude or longitude, the tester is lenient in checking error messages so you don't have to stick to the exact format Redis uses. The exact format it checks for is `ERR invalid` (case-insensitive). Examples of error message strings that will pass the tests: + - `ERR invalid longitude,latitude pair 200,100` + - `ERR Invalid longitude,latitude` + - `ERR invalid` diff --git a/stage_descriptions/geospatial-03-xg4.md b/stage_descriptions/geospatial-03-xg4.md index cf52c688..009fc2de 100644 --- a/stage_descriptions/geospatial-03-xg4.md +++ b/stage_descriptions/geospatial-03-xg4.md @@ -2,7 +2,7 @@ In this stage, you'll add support for retrieving the coordinates of a location u ### The `GEOPOS` command -The GEOPOS command returns the longitude and latitude of the specified locations. Its syntax is +The `GEOPOS` command returns the longitude and latitude of the specified locations. Its syntax is ``` GEOPOS … @@ -45,20 +45,15 @@ $ redis-cli > GEOADD location_key 41.125 73.991 "Caz" ``` -The tester will then send multiple GEOPOS commands, each specifying a single location that may or may not have been added. For example, for the following command +The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: ``` > GEOPOS places Foo -``` - -It will expect the response to be the following array -``` -1) 1) "19.0872" - 2) "33.5026" +# Expecting [["19.0872", "33.5026"]] ``` -which is RESP-encoded as +The value is a RESP array, which is encoded as ``` *1\r\n *2\r\n @@ -70,7 +65,7 @@ $7\r\n ### Notes -- The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded-off. +- The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded off. * For example, if a location was added using `GEOADD key 20.123456 30.123001`, any of the following will be accepted: diff --git a/stage_descriptions/geospatial-04-ia8.md b/stage_descriptions/geospatial-04-ia8.md index eb117ae2..3a0847af 100644 --- a/stage_descriptions/geospatial-04-ia8.md +++ b/stage_descriptions/geospatial-04-ia8.md @@ -17,15 +17,12 @@ $ redis-cli > GEOADD location_key 41.125 73.991 "Caz" ``` -The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, for the following command, +The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, the tester might send your program a command like this: ``` > GEOPOS location_key Foo Caz non_existent -``` - -it will expect the response to be -``` +# Expecting RESP Array 1) 1) "19.08729" 2) "33.5026" 2) 1) "41.125" @@ -33,7 +30,7 @@ it will expect the response to be 3) (nil) ``` -which is RESP-encoded as +The value is a RESP array, which is encoded as ``` *3\r\n diff --git a/stage_descriptions/geospatial-05-ek6.md b/stage_descriptions/geospatial-05-ek6.md index 376583c0..87880055 100644 --- a/stage_descriptions/geospatial-05-ek6.md +++ b/stage_descriptions/geospatial-05-ek6.md @@ -25,21 +25,23 @@ The tester will execute your program like this: It will then add multiple locations using the `GEOADD` command. ``` +$ redis-cli > GEOADD places 15.087269 37.502669 "Catania" > GEOADD places 12.496365 41.902783 "Rome" ``` -The tester will then send multiple `GEODIST` commands specifying two locations. For example, in this case, +The tester will then send multiple `GEODIST` commands specifying two locations. For example, the tester might send your program a command like this: ``` > GEODIST places Catania Rome +# Expecting "166.2742" ``` -it will expect the response to be "166.2742", which is RESP encoded as a bulk string as: +The value is a RESP bulk string encoded as: ``` -$11\r\n -537215.1152\r\n +$8\r\n +166.2742\r\n ``` ### Notes diff --git a/stage_descriptions/geospatial-06-ra4.md b/stage_descriptions/geospatial-06-ra4.md index 6c6ade69..9516c1f7 100644 --- a/stage_descriptions/geospatial-06-ra4.md +++ b/stage_descriptions/geospatial-06-ra4.md @@ -21,17 +21,19 @@ The tester will execute your program like this: It will then add multiple locations using the `GEOADD` command. ``` +$ redis-cli > GEOADD places 15.087269 37.502669 "Catania" > GEOADD places 12.496365 41.902783 "Rome" ``` -The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, in this case, +The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, the tester might send your program command like this: ``` > GEODIST places Catania Rome km +# Expecting "537.2151" ``` -It will expect the response to be "537.2151", which is RESP bulk string encoded as +The value is a RESP bulk string, which is encoded as ``` $8\r\n diff --git a/stage_descriptions/geospatial-07-rm9.md b/stage_descriptions/geospatial-07-rm9.md index 91cb8223..b7b9846f 100644 --- a/stage_descriptions/geospatial-07-rm9.md +++ b/stage_descriptions/geospatial-07-rm9.md @@ -30,6 +30,7 @@ The tester will execute your program like this: It will add multiple locations using the `GEOADD` command. ``` +$ redis-cli > GEOADD places 13.361389 38.115556 Palermo > GEOADD places 15.087269 37.502669 Catania ``` @@ -37,10 +38,12 @@ It will add multiple locations using the `GEOADD` command. The tester will then send multiple `GEOSEARCH` commands specifying a latitude and longitude pair with `BYRADIUS` option specifying the distance and unit. For example, it may send the following command. ``` -GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 200 km +> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 200 km + +# Expecting ["Palermo", "Catania"] ``` -It will expect the response to be (["Palermo", "Catania"]) which is RESP Encoded as: +The value is a RESP array, which is encoded as: ``` *2\r\n @@ -51,4 +54,4 @@ Catania\r\n ``` ### Notes -In this stage, you will only implement the BYRADIUS option. We'll get to implementing the BYBOX option in the next stage. \ No newline at end of file +In this stage, you will only implement the `BYRADIUS` option. We'll get to implementing the BYBOX option in the next stage. \ No newline at end of file diff --git a/stage_descriptions/geospatial-08-hv8.md b/stage_descriptions/geospatial-08-hv8.md index ca4f7045..df038f6d 100644 --- a/stage_descriptions/geospatial-08-hv8.md +++ b/stage_descriptions/geospatial-08-hv8.md @@ -28,8 +28,10 @@ The tester will execute your program like this: ./your_program.sh ``` -It will add multiple locations with random co-ordinates using the `GEOADD` command. +It will add multiple locations with random coordinates using the `GEOADD` command. + ``` +$ redis-cli > GEOADD test 0 0 "center" > GEOADD test 0.5 0 "east" > GEOADD test -0.5 0 "west" @@ -38,8 +40,7 @@ It will add multiple locations with random co-ordinates using the `GEOADD` comma > GEOADD test 1 1 "outside" ``` -The tester will then send multiple `GEOSEARCH` commands with `FROMLONLAT` and `BYBOX` option specifying random longitude and latitude and bounding box size. For example, it may send the following: - +The tester will then send multiple `GEOSEARCH` commands with `FROMLONLAT` and `BYBOX` option specifying random longitude and latitude and bounding box size. For example, the tester may send your program the following command: ``` > GEOSEARCH test fromlonlat 0 0 bybox 300 300 km @@ -49,11 +50,8 @@ The tester will then send multiple `GEOSEARCH` commands with `FROMLONLAT` and `B 4) "outside" 5) "south" 6) "west" -``` -It will expect the response to be - -``` +# Expecting [ "center", "east", @@ -63,7 +61,8 @@ It will expect the response to be "west", ] ``` -which is RESP-Encoded as + +The value is a RESP array, which is encoded as ``` *6\r\n $6\r\n From fabccb89591d82e89ea3b92dc00fa10c30fb25fd Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 12:41:35 +0545 Subject: [PATCH 06/24] fix: grammar --- stage_descriptions/geospatial-04-ia8.md | 2 +- stage_descriptions/geospatial-05-ek6.md | 2 +- stage_descriptions/geospatial-06-ra4.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/stage_descriptions/geospatial-04-ia8.md b/stage_descriptions/geospatial-04-ia8.md index 3a0847af..fbae905f 100644 --- a/stage_descriptions/geospatial-04-ia8.md +++ b/stage_descriptions/geospatial-04-ia8.md @@ -17,7 +17,7 @@ $ redis-cli > GEOADD location_key 41.125 73.991 "Caz" ``` -The tester will then send multiple `GEOPOS` commands, each specifying a multiple locations that may or may not have been added. For example, the tester might send your program a command like this: +The tester will then send multiple `GEOPOS` commands, each specifying multiple locations that may or may not have been added. For example, the tester might send your program a command like this: ``` > GEOPOS location_key Foo Caz non_existent diff --git a/stage_descriptions/geospatial-05-ek6.md b/stage_descriptions/geospatial-05-ek6.md index 87880055..e3f995b9 100644 --- a/stage_descriptions/geospatial-05-ek6.md +++ b/stage_descriptions/geospatial-05-ek6.md @@ -23,7 +23,7 @@ The tester will execute your program like this: ./your_program.sh ``` -It will then add multiple locations using the `GEOADD` command. +It will add multiple locations using the `GEOADD` command. ``` $ redis-cli > GEOADD places 15.087269 37.502669 "Catania" diff --git a/stage_descriptions/geospatial-06-ra4.md b/stage_descriptions/geospatial-06-ra4.md index 9516c1f7..785a81fd 100644 --- a/stage_descriptions/geospatial-06-ra4.md +++ b/stage_descriptions/geospatial-06-ra4.md @@ -18,7 +18,7 @@ The tester will execute your program like this: ./your_program.sh ``` -It will then add multiple locations using the `GEOADD` command. +It will add multiple locations using the `GEOADD` command. ``` $ redis-cli From 78d1c0fc73d290f650ac7bd236d9519f6282bd56 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 13:01:28 +0545 Subject: [PATCH 07/24] fix: typos --- course-definition.yml | 3 +-- stage_descriptions/geospatial-05-ek6.md | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 020b1b2e..695ceeac 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -737,5 +737,4 @@ stages: primary_extension_slug: "geospatial" name: "Search within box" difficulty: hard - marketing_md: In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. - \ No newline at end of file + marketing_md: In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. \ No newline at end of file diff --git a/stage_descriptions/geospatial-05-ek6.md b/stage_descriptions/geospatial-05-ek6.md index e3f995b9..5b98de7e 100644 --- a/stage_descriptions/geospatial-05-ek6.md +++ b/stage_descriptions/geospatial-05-ek6.md @@ -34,14 +34,14 @@ The tester will then send multiple `GEODIST` commands specifying two locations. ``` > GEODIST places Catania Rome -# Expecting "166.2742" +# Expecting "537215.1152" ``` The value is a RESP bulk string encoded as: ``` -$8\r\n -166.2742\r\n +$11\r\n +537215.1152\r\n ``` ### Notes From e5ff7a848080bc2b3cd8159193f7f6a8b706fb7c Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 13:13:03 +0545 Subject: [PATCH 08/24] fix: description --- course-definition.yml | 3 ++- stage_descriptions/geospatial-05-ek6.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 695ceeac..020b1b2e 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -737,4 +737,5 @@ stages: primary_extension_slug: "geospatial" name: "Search within box" difficulty: hard - marketing_md: In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. \ No newline at end of file + marketing_md: In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. + \ No newline at end of file diff --git a/stage_descriptions/geospatial-05-ek6.md b/stage_descriptions/geospatial-05-ek6.md index 5b98de7e..268863c3 100644 --- a/stage_descriptions/geospatial-05-ek6.md +++ b/stage_descriptions/geospatial-05-ek6.md @@ -13,7 +13,7 @@ Example usage: "537215.1152" ``` -It returns the distance as a string, encoded as a RESP Bulk String. The precision of the response is up to 4 digits after the decimal. +It returns the distance as a string, encoded as a RESP Bulk String. Redis uses the [Haversine's Formula](https://en.wikipedia.org/wiki/Haversine_formula#Example) to calculate the distance between two points. You can see how this is done in the Redis source code [here](https://github.com/redis/redis/blob/4322cebc1764d433b3fce3b3a108252648bf59e7/src/geohash_helper.c#L228C1-L228C72). From db308e31423798f4b3301b9421a32f254b868a11 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 13:17:18 +0545 Subject: [PATCH 09/24] fix: typo --- stage_descriptions/geospatial-03-xg4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stage_descriptions/geospatial-03-xg4.md b/stage_descriptions/geospatial-03-xg4.md index 009fc2de..3802358c 100644 --- a/stage_descriptions/geospatial-03-xg4.md +++ b/stage_descriptions/geospatial-03-xg4.md @@ -48,7 +48,7 @@ $ redis-cli The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: ``` -> GEOPOS places Foo +> GEOPOS location_key Foo # Expecting [["19.0872", "33.5026"]] ``` From f613c8f92ecf4b5af928a1d03bdc9704c9bbf3c6 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 13:43:45 +0545 Subject: [PATCH 10/24] fix: update tester description for ck3 --- stage_descriptions/geospatial-02-ck3.md | 33 +++++++++++++++---------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index 1d07bea4..ff8bfb80 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -36,17 +36,24 @@ The value is RESP simple error, which is RESP-encoded as -ERR invalid longitude,latitude pair 200,100\r\n ``` -The tester will also send your program a `GEOADD` command specifying a non-numeric value in the latitude/longitude field. In this case, it will expect an error. For example, in case of the following command, it will expect an error. - -``` -$ redis-cli GEOADD location_key foo bar location_name - -# Expecting RESP simple error -(error) ERR value is not a valid float -``` - ### Notes -- In case of out-of-bounds values of latitude or longitude, the tester is lenient in checking error messages so you don't have to stick to the exact format Redis uses. The exact format it checks for is `ERR invalid` (case-insensitive). Examples of error message strings that will pass the tests: - - `ERR invalid longitude,latitude pair 200,100` - - `ERR Invalid longitude,latitude` - - `ERR invalid` + +- In case of out-of-bounds values for latitude or longitude, the tester is lenient but structured in checking error messages. + - The error response must: + - Start with the phrase "ERR invalid" (case-insensitive) + - Contain the word latitude if the latitude value is invalid + - Contain the word longitude if the longitude value is invalid + - Contain both "latitude" and "longitude" if both the latitudes and longitudes are invalid. + + - You do not need to match Redis’s exact error message format, just ensure the right keywords are included. + + - Examples that will pass: + - ERR Invalid latitude value (In case of invalid latitude and valid longitude) + - ERR invalid longitude: must be between -180 and 180 (In case of invalid longitude but valid latitude value) + - ERR invalid longitude,latitude pair (In case of one or both invalid values) + - ERR invalid longitude and latitude range (In case of one or both invalid values) + + - Examples that will not pass: + - ERR invalid (In case of one or both invalid values) + - ERR invalid longitude (In case of invalid latitude but valid longitude) + - ERR invalid latitude (In case of invalid longitude but valid latitude) From eac6dc433ba4bfa98d609e806035002211eeb568 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 28 Jul 2025 13:50:22 +0545 Subject: [PATCH 11/24] Fix: update instructions; fix grammar --- stage_descriptions/geospatial-02-ck3.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index ff8bfb80..3fd95c9a 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -39,14 +39,13 @@ The value is RESP simple error, which is RESP-encoded as ### Notes - In case of out-of-bounds values for latitude or longitude, the tester is lenient but structured in checking error messages. + - The error response does not need to match Redis’s exact error message format. - The error response must: - Start with the phrase "ERR invalid" (case-insensitive) - Contain the word latitude if the latitude value is invalid - Contain the word longitude if the longitude value is invalid - Contain both "latitude" and "longitude" if both the latitudes and longitudes are invalid. - - You do not need to match Redis’s exact error message format, just ensure the right keywords are included. - - Examples that will pass: - ERR Invalid latitude value (In case of invalid latitude and valid longitude) - ERR invalid longitude: must be between -180 and 180 (In case of invalid longitude but valid latitude value) From 7a6f6b0800d0e054d5fecd304590dd5b9e11a18b Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Fri, 1 Aug 2025 12:20:48 +0545 Subject: [PATCH 12/24] Reorganize and reorder stages --- course-definition.yml | 37 ++++----- stage_descriptions/geospatial-01-zt4.md | 16 ++-- stage_descriptions/geospatial-02-ck3.md | 13 +-- stage_descriptions/geospatial-03-tn5.md | 40 ++++++++++ stage_descriptions/geospatial-04-cr3.md | 1 + stage_descriptions/geospatial-04-ia8.md | 48 ----------- ...spatial-03-xg4.md => geospatial-05-xg4.md} | 36 ++++----- ...spatial-05-ek6.md => geospatial-06-ek6.md} | 6 +- stage_descriptions/geospatial-06-ra4.md | 41 ---------- stage_descriptions/geospatial-07-rm9.md | 31 ++++--- stage_descriptions/geospatial-08-hv8.md | 80 ------------------- 11 files changed, 104 insertions(+), 245 deletions(-) create mode 100644 stage_descriptions/geospatial-03-tn5.md create mode 100644 stage_descriptions/geospatial-04-cr3.md delete mode 100644 stage_descriptions/geospatial-04-ia8.md rename stage_descriptions/{geospatial-03-xg4.md => geospatial-05-xg4.md} (68%) rename stage_descriptions/{geospatial-05-ek6.md => geospatial-06-ek6.md} (93%) delete mode 100644 stage_descriptions/geospatial-06-ra4.md delete mode 100644 stage_descriptions/geospatial-08-hv8.md diff --git a/course-definition.yml b/course-definition.yml index 020b1b2e..039494c6 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -693,9 +693,9 @@ stages: # Geospatial Commands - slug: "zt4" primary_extension_slug: "geospatial" - name: "Add a location" + name: "Respond to GEOADD" difficulty: easy - marketing_md: In this stage, you'll add support for adding a single location under a key using the `GEOADD` command. + marketing_md: In this stage, you'll add support for responding to the `GEOADD` command. - slug: "ck3" primary_extension_slug: "geospatial" @@ -703,17 +703,23 @@ stages: difficulty: easy marketing_md: In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. - - slug: "xg4" + - slug: "tn5" primary_extension_slug: "geospatial" - name: "Retrieve a location" + name: "Store a location" difficulty: easy - marketing_md: In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. + marketing_md: In this stage, you'll add support for storing a location in a sorted set. - - slug: "ia8" + - slug: "cr3" primary_extension_slug: "geospatial" - name: "Retrieve multiple locations" + name: "Calculate location score" difficulty: easy - marketing_md: In this stage, you'll add support for retrieving multiple locations using a single `GEOPOS` command. + marketing_md: In this stage, you'll add support for calculating the score of a location. + + - slug: "xg4" + primary_extension_slug: "geospatial" + name: "Retrieve a location" + difficulty: easy + marketing_md: In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. - slug: "ek6" primary_extension_slug: "geospatial" @@ -721,21 +727,8 @@ stages: difficulty: medium marketing_md: In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. - - slug: "ra4" - primary_extension_slug: "geospatial" - name: "Unit conversion" - difficulty: easy - marketing_md: In this stage, you'll add support for different units of measure in the `GEODIST` command. - - slug: "rm9" primary_extension_slug: "geospatial" name: "Search within radius" difficulty: hard - marketing_md: In this stage, you'll add support for searching locations near a coordinate within a given radius. - - - slug: "hv8" - primary_extension_slug: "geospatial" - name: "Search within box" - difficulty: hard - marketing_md: In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. - \ No newline at end of file + marketing_md: In this stage, you'll add support for searching locations near a coordinate within a given radius. \ No newline at end of file diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index 945c62db..a4adbfde 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -1,4 +1,4 @@ -In this stage, you'll add support for adding a single location under a key using the `GEOADD` command. +In this stage, you'll add support for responding to the `GEOADD` command. ### The `GEOADD` command The `GEOADD` command adds a location (with longitude, latitude, and name) to a key. If the key doesn’t exist, it is created. If the key exists, the location is appended to that key. @@ -10,7 +10,7 @@ GEOADD Example usage: -``` +```bash > GEOADD places 13.361389 38.115556 "Palermo" (integer) 1 ``` @@ -20,17 +20,15 @@ It returns the count of elements added, encoded as a RESP Integer. ### Tests The tester will execute your program like this: -``` -./your_program.sh +```bash +$ ./your_program.sh ``` -It will then send a `GEOADD` command specifying a key, random values of latitude and longitude, and also a random name for that coordinate. +It will then send a `GEOADD` command specifying a key, laitude, longitude, and location name. -``` +```bash $ redis-cli GEOADD places 15.09 37.50 Catania ``` -The tester will verify that the response to the command is `:1\r\n`, which is 1 (the number of locations added), encoded as a RESP Integer. - ### Notes -- You will only implement adding one location per `GEOADD` command. +- In this stage, you will only implement responding the the `GEOADD` command. You don't need to store the locations yet. We'll get to storing the locations in the later stages. \ No newline at end of file diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index 3fd95c9a..afdc9f13 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -4,7 +4,7 @@ In this stage, you'll add support for validating the latitude and longitude prov Latitudes and longitudes used in the `GEOADD` command should be in a certain range as per [EPSG:3857](https://epsg.io/3857). Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. Example use case: -``` +```bash # Invalid latitude > GEOADD places 180 90 test1 (error) ERR invalid longitude,latitude pair 180.000000,90.000000 @@ -16,14 +16,15 @@ Example use case: ### Tests The tester will execute your program like this: -``` -./your_program.sh + +```bash +$ ./your_program.sh ``` It will then send multiple `GEOADD` commands specifying one or more location to add. For valid coordinates, the tester will expect the response to be the usual response of the `GEOADD` command. For invalid coordinates values, it will expect an error. For example, the tester might send your program a command like this: -``` +```bash $ redis-cli GEOADD location_key 200 100 foo # Expecting error @@ -32,7 +33,7 @@ $ redis-cli GEOADD location_key 200 100 foo The value is RESP simple error, which is RESP-encoded as -``` +```bash -ERR invalid longitude,latitude pair 200,100\r\n ``` @@ -56,3 +57,5 @@ The value is RESP simple error, which is RESP-encoded as - ERR invalid (In case of one or both invalid values) - ERR invalid longitude (In case of invalid latitude but valid longitude) - ERR invalid latitude (In case of invalid longitude but valid latitude) + +- If you're wondering why the boundary of latitude are clipped at -85.05112878 and 85.05112878 degrees instead of -90 and 90 degrees respectively. It is because of the Web Mercator projection used by Redis. You can read more about it on [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator_projection). \ No newline at end of file diff --git a/stage_descriptions/geospatial-03-tn5.md b/stage_descriptions/geospatial-03-tn5.md new file mode 100644 index 00000000..763d9e9d --- /dev/null +++ b/stage_descriptions/geospatial-03-tn5.md @@ -0,0 +1,40 @@ +In this stage, you'll add support for storing locations in a sorted set. + +### Storing locations in sorted set + +The locations added using the `GEOADD` command are stored in a sorted set. After a `GEOADD` command is issued, Redis internally calculates a score for the specified location using its latitude and longitude. For example, if you send the following command to Redis. +```bash +$ redis-cli GEOADD places_key location +``` + +This is equivalent to sending the following command: +```bash +$ redis-cli ZADD places_key location +``` + +where is calculated using the location's and values using a special algorithm. We'll get to implementing this algorithm in the later stage. + +For now, you can hardcode a location's score to be 0 for all the locations. + + +### Tests +The tester will execute your program like this: + +```bash +$ ./your_program.sh +``` + +It will then send a `GEOADD` command specifying a key, laitude, longitude, and location name. + +```bash +$ redis-cli GEOADD places 15.09 37.50 Catania +``` + +The tester will then send a `ZRANGE` command to your program specifying the key used in `GEOADD` command. +```bash +$ redis-cli ZRANGE places 0 -1 +# Expect RESP Array: ["Catania"] +``` + +### Notes +- In this stage, you can hardcode the score of the location to be 0. We'll get to calculating the value of score using latitude and longitude in the next stage. \ No newline at end of file diff --git a/stage_descriptions/geospatial-04-cr3.md b/stage_descriptions/geospatial-04-cr3.md new file mode 100644 index 00000000..82cb2a75 --- /dev/null +++ b/stage_descriptions/geospatial-04-cr3.md @@ -0,0 +1 @@ +In this stage, you'll add support for calculating the score of a location. \ No newline at end of file diff --git a/stage_descriptions/geospatial-04-ia8.md b/stage_descriptions/geospatial-04-ia8.md deleted file mode 100644 index fbae905f..00000000 --- a/stage_descriptions/geospatial-04-ia8.md +++ /dev/null @@ -1,48 +0,0 @@ -In this stage, you'll add support for retrieving multiple locations using a single `GEOPOS` command. - -### Tests -The tester will execute your program like this: - -``` -./your_program.sh -``` - -It will add multiple locations using the `GEOADD` command. - -``` -$ redis-cli -> GEOADD location_key 19.08729 33.5026 "Foo" -> GEOADD location_key 49.125 72.991 "Bar" -> GEOADD location_key 10.0872 34.5026 "Baz" -> GEOADD location_key 41.125 73.991 "Caz" -``` - -The tester will then send multiple `GEOPOS` commands, each specifying multiple locations that may or may not have been added. For example, the tester might send your program a command like this: - -``` -> GEOPOS location_key Foo Caz non_existent - -# Expecting RESP Array -1) 1) "19.08729" - 2) "33.5026" -2) 1) "41.125" - 2) "73.991" -3) (nil) -``` - -The value is a RESP array, which is encoded as - -``` -*3\r\n -*2\r\n -$8\r\n -19.08729\r\n -$7\r\n -33.5026\r\n -*2\r\n -$6\r\n -41.125\r\n -$6\r\n -73.991\r\n -$-1\r\n -``` \ No newline at end of file diff --git a/stage_descriptions/geospatial-03-xg4.md b/stage_descriptions/geospatial-05-xg4.md similarity index 68% rename from stage_descriptions/geospatial-03-xg4.md rename to stage_descriptions/geospatial-05-xg4.md index 3802358c..e4b27157 100644 --- a/stage_descriptions/geospatial-03-xg4.md +++ b/stage_descriptions/geospatial-05-xg4.md @@ -35,14 +35,14 @@ The tester will execute your program like this: ./your_program.sh ``` -It will add multiple locations using the `GEOADD` command. +It will add multiple locations using the `ZADD` command. It will use a score which will be equivalent to a latitude and longitude. ``` $ redis-cli -> GEOADD location_key 19.0872 33.5026 "Foo" -> GEOADD location_key 49.125 72.991 "Bar" -> GEOADD location_key 10.0872 34.5026 "Baz" -> GEOADD location_key 41.125 73.991 "Caz" +> ZADD location_key 3477108430792699 "Foo" +> ZADD location_key 3876464048901851 "Bar" +> ZADD location_key 3468915414364476 "Baz" +> ZADD location_key 3781709020344510 "Caz" ``` The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: @@ -50,31 +50,29 @@ The tester will then send multiple `GEOPOS` commands, each specifying a single l ``` > GEOPOS location_key Foo -# Expecting [["19.0872", "33.5026"]] +# Expecting [["19.087197482585907", "33.50259961456723"]] ``` The value is a RESP array, which is encoded as ``` *1\r\n *2\r\n -$7\r\n -19.0872\r\n -$7\r\n -33.5026\r\n +$18\r\n +19.087197482585907\r\n +$17\r\n +33.50259961456723\r\n ``` ### Notes - The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded off. - * For example, if a location was added using `GEOADD key 20.123456 30.123001`, any of the following will be accepted: + - For example, for the response of the example shown above, any of the following will be accepted: - * `20.1235 30.1230` - * `20.123456 30.123000` - * `20.123490 30.123045` - * `20.123459 30.123001` - * `20.1235001 30.122999` + - `19.0872 33.5026` + - `19.087197 33.502599` + - `19.08719 33.50260` + - `19.087199 33.5025996` + - `19.0872001 33.5026001` -- If the location key does not exist, you should return an empty array. - -- In this stage, you will only implement retrieving a single location using the `GEOPOS` command. We'll get to retrieving multiple locations in a single `GEOPOS` command in the next stage. +- If the location key does not exist, you should return an empty array. \ No newline at end of file diff --git a/stage_descriptions/geospatial-05-ek6.md b/stage_descriptions/geospatial-06-ek6.md similarity index 93% rename from stage_descriptions/geospatial-05-ek6.md rename to stage_descriptions/geospatial-06-ek6.md index 268863c3..504fedd8 100644 --- a/stage_descriptions/geospatial-05-ek6.md +++ b/stage_descriptions/geospatial-06-ek6.md @@ -1,4 +1,4 @@ -In this stage, you'll add support for calculating the distance between two locations. +In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. ### The `GEODIST` command The `GEODIST` command returns the distance between two members. The default unit of the distance which is returned, is meters. @@ -59,6 +59,4 @@ $11\r\n * However, values like `12345.64`, `12345.70`, or `12345.61` would be considered incorrect. - -- If one or both of the location specified in the `GEODIST` command does not exist, it should return a null bulk string `($-1\r\n)`. -- In this stage, you will only implement returning the distance in meters. We will get to using different units in the next stage. \ No newline at end of file +- If one or both of the location specified in the `GEODIST` command does not exist, it should return a null bulk string `($-1\r\n)`. \ No newline at end of file diff --git a/stage_descriptions/geospatial-06-ra4.md b/stage_descriptions/geospatial-06-ra4.md deleted file mode 100644 index 785a81fd..00000000 --- a/stage_descriptions/geospatial-06-ra4.md +++ /dev/null @@ -1,41 +0,0 @@ -In this stage, you'll add support for different units of measure in the `GEODIST` command. - -### Unit Conversion -The `GEODIST` command supports an optional fourth argument, which is the unit in which the distance should be returned. The valid units are meters(`m`), kilometers(`km`), miles(`mi`) and feet(`ft`). If the unit is specified, the syntax for `GEODIST` is: -``` -GEODIST key location1 location2 -``` - -Example Usage: -``` -> GEODIST places Catania Rome mi -"333.8108" -``` - -### Tests -The tester will execute your program like this: -``` -./your_program.sh -``` - -It will add multiple locations using the `GEOADD` command. - -``` -$ redis-cli -> GEOADD places 15.087269 37.502669 "Catania" -> GEOADD places 12.496365 41.902783 "Rome" -``` - -The tester will then send multiple `GEODIST` commands specifying two locations and a unit of measure. For example, the tester might send your program command like this: - -``` -> GEODIST places Catania Rome km -# Expecting "537.2151" -``` - -The value is a RESP bulk string, which is encoded as - -``` -$8\r\n -537.2151\r\n -``` \ No newline at end of file diff --git a/stage_descriptions/geospatial-07-rm9.md b/stage_descriptions/geospatial-07-rm9.md index b7b9846f..0fa63225 100644 --- a/stage_descriptions/geospatial-07-rm9.md +++ b/stage_descriptions/geospatial-07-rm9.md @@ -4,17 +4,16 @@ In this stage, you'll add support for searching locations near a coordinate with The `GEOSEARCH` command lets you search for locations near a given coordinate within a specified area. -It supports several search modes, but in our implementation, we'll focus only on the `FROMLONLAT` option, which allows searching by directly specifying longitude and latitude. The syntax for this is: +It supports several search modes. In our implementation, we'll focus only on the `FROMLONLAT` mode with distance unit in meters. The `FROMLONLAT` mode searching by directly specifying longitude and latitude. The syntax for this ``` -GEOSEARCH key FROMLONLAT longitude latitude BYRADIUS distance [km|ft|m|mi] +GEOSEARCH FROMLONLAT BYRADIUS m ``` -For example, to search for locations within 200 kilometers of the point (longitude: 15, latitude: 37) stored in the places key, you can use: +For example, to search for locations within 100,000 meters of the point (longitude: 15, latitude: 37) stored in the places key, you can use: -``` -> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 200 km -1) "Palermo" +```bash +> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 100000 m 2) "Catania" ``` @@ -23,13 +22,13 @@ It returns a RESP Array of member names, where each member's name is a encoded a ### Tests The tester will execute your program like this: -``` -./your_program.sh +```bash +$ ./your_program.sh ``` It will add multiple locations using the `GEOADD` command. -``` +```bash $ redis-cli > GEOADD places 13.361389 38.115556 Palermo > GEOADD places 15.087269 37.502669 Catania @@ -37,21 +36,19 @@ $ redis-cli The tester will then send multiple `GEOSEARCH` commands specifying a latitude and longitude pair with `BYRADIUS` option specifying the distance and unit. For example, it may send the following command. -``` -> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 200 km +```bash +> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 100000 m -# Expecting ["Palermo", "Catania"] +# Expecting ["Catania"] ``` The value is a RESP array, which is encoded as: -``` -*2\r\n -$7\r\n -Palermo\r\n +```bash +*1\r\n $7\r\n Catania\r\n ``` ### Notes -In this stage, you will only implement the `BYRADIUS` option. We'll get to implementing the BYBOX option in the next stage. \ No newline at end of file +- The tester will only test using the `meters` unit. \ No newline at end of file diff --git a/stage_descriptions/geospatial-08-hv8.md b/stage_descriptions/geospatial-08-hv8.md deleted file mode 100644 index df038f6d..00000000 --- a/stage_descriptions/geospatial-08-hv8.md +++ /dev/null @@ -1,80 +0,0 @@ -In this stage, you'll add support for searching locations near a coordinate within a rectangular bounding box. - -### The GEOSEARCH command (Box Search) -The GEOSEARCH command can also be used to find all points within a box centered at a given pair of latitude and longitude within a rectangle of certain width and height. -The syntax for this is: -``` -GEOSEARCH key FROMLONLAT longitude latitude BYBOX width height [km|ft|m|mi] -``` - -Example usage: - -``` -> GEOADD cities 13.361389 38.115556 "Palermo" -(integer) 1 -> GEOADD cities 15.087269 37.502669 "Catania" -(integer) 1 -> GEOADD cities 12.496365 41.902783 "Rome" -(integer) 1 - -> GEOSEARCH cities FROMLONLAT 15 37 BYBOX 300 300 km -1) "Palermo" -2) "Catania" -``` - -### Tests -The tester will execute your program like this: -``` -./your_program.sh -``` - -It will add multiple locations with random coordinates using the `GEOADD` command. - -``` -$ redis-cli -> GEOADD test 0 0 "center" -> GEOADD test 0.5 0 "east" -> GEOADD test -0.5 0 "west" -> GEOADD test 0 0.5 "north" -> GEOADD test 0 -0.5 "south" -> GEOADD test 1 1 "outside" -``` - -The tester will then send multiple `GEOSEARCH` commands with `FROMLONLAT` and `BYBOX` option specifying random longitude and latitude and bounding box size. For example, the tester may send your program the following command: - -``` -> GEOSEARCH test fromlonlat 0 0 bybox 300 300 km -1) "center" -2) "east" -3) "north" -4) "outside" -5) "south" -6) "west" - -# Expecting -[ - "center", - "east", - "north", - "outside", - "south", - "west", -] -``` - -The value is a RESP array, which is encoded as -``` -*6\r\n -$6\r\n -center\r\n -$4\r\n -east\r\n -$5\r\n -north\r\n -$7\r\n -outside\r\n -$5\r\n -south\r\n -$4\r\n -west\r\n -``` From bbf188fa6f5249177a91cbba9edc51568a842c4c Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 4 Aug 2025 17:29:54 +0545 Subject: [PATCH 13/24] - Restructured stages - Re-wrote instructions in detail including the encode/decode algorithm --- stage_descriptions/geospatial-01-zt4.md | 3 +- stage_descriptions/geospatial-02-ck3.md | 4 +- stage_descriptions/geospatial-03-tn5.md | 10 +- stage_descriptions/geospatial-04-cr3.md | 198 ++++++++++++++- stage_descriptions/geospatial-05-xg4.md | 49 ++-- stage_descriptions/geospatial-06-hb5.md | 229 ++++++++++++++++++ ...spatial-06-ek6.md => geospatial-07-ek6.md} | 10 +- ...spatial-07-rm9.md => geospatial-08-rm9.md} | 0 8 files changed, 462 insertions(+), 41 deletions(-) create mode 100644 stage_descriptions/geospatial-06-hb5.md rename stage_descriptions/{geospatial-06-ek6.md => geospatial-07-ek6.md} (97%) rename stage_descriptions/{geospatial-07-rm9.md => geospatial-08-rm9.md} (100%) diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index a4adbfde..a2cd6a8d 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -1,7 +1,8 @@ In this stage, you'll add support for responding to the `GEOADD` command. ### The `GEOADD` command -The `GEOADD` command adds a location (with longitude, latitude, and name) to a key. If the key doesn’t exist, it is created. If the key exists, the location is appended to that key. +The `GEOADD` command adds a location (with longitude, latitude, and name) to a key. It stores the location as a [sorted set](https://redis.io/docs/latest/develop/data-types/sorted-sets/) under the specified key. If the key doesn’t exist, a new sorted set is created under the specified name and the location is inserted to it. If the key exists, the location is inserted in the sorted set. + The syntax for `GEOADD` command is ``` diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index afdc9f13..c4eecac6 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -39,6 +39,8 @@ The value is RESP simple error, which is RESP-encoded as ### Notes +- In this stage, you'll only extend responding to `GEOADD` in case of invalid values of latitude and longitude. You do not need to implement the storage mechanism yet. + - In case of out-of-bounds values for latitude or longitude, the tester is lenient but structured in checking error messages. - The error response does not need to match Redis’s exact error message format. - The error response must: @@ -58,4 +60,4 @@ The value is RESP simple error, which is RESP-encoded as - ERR invalid longitude (In case of invalid latitude but valid longitude) - ERR invalid latitude (In case of invalid longitude but valid latitude) -- If you're wondering why the boundary of latitude are clipped at -85.05112878 and 85.05112878 degrees instead of -90 and 90 degrees respectively. It is because of the Web Mercator projection used by Redis. You can read more about it on [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator_projection). \ No newline at end of file +- The booundary of latitude are clipped at -85.05112878 and 85.05112878 degrees instead of -90 and 90 degrees respectively. It is because of the Web Mercator projection used by Redis. You can read more about it on [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator_projection). \ No newline at end of file diff --git a/stage_descriptions/geospatial-03-tn5.md b/stage_descriptions/geospatial-03-tn5.md index 763d9e9d..6cd35f64 100644 --- a/stage_descriptions/geospatial-03-tn5.md +++ b/stage_descriptions/geospatial-03-tn5.md @@ -2,13 +2,14 @@ In this stage, you'll add support for storing locations in a sorted set. ### Storing locations in sorted set -The locations added using the `GEOADD` command are stored in a sorted set. After a `GEOADD` command is issued, Redis internally calculates a score for the specified location using its latitude and longitude. For example, if you send the following command to Redis. +The locations added using the `GEOADD` command are stored in a sorted set. Redis internally calculates a score for the specified location using a location's latitude and longitude. + +For example, the following two commands are equivalent in Redis. ```bash +# Adding a location $ redis-cli GEOADD places_key location -``` -This is equivalent to sending the following command: -```bash +# This command is equivalent to the command above $ redis-cli ZADD places_key location ``` @@ -16,7 +17,6 @@ where is calculated using the location's and valu For now, you can hardcode a location's score to be 0 for all the locations. - ### Tests The tester will execute your program like this: diff --git a/stage_descriptions/geospatial-04-cr3.md b/stage_descriptions/geospatial-04-cr3.md index 82cb2a75..2ae9e876 100644 --- a/stage_descriptions/geospatial-04-cr3.md +++ b/stage_descriptions/geospatial-04-cr3.md @@ -1 +1,197 @@ -In this stage, you'll add support for calculating the score of a location. \ No newline at end of file +In this stage, you'll add support for calculating the score of a location. + +### Calculating location score + +The algorithm for calculating the score from latitude and longitude involves two major steps: + +1. Normalizing the latitude and longitude values +2. Interleaving the bits of normalized values of latitude and longitude + +#### Normalization + +1. The values of latitude and longitude are converted in the range of [0, 1). This is done using the following formula: + +``` +normalized_latitude = (latitude - LATITUDE_MIN) / (LATITUDE_MAX - LATITUDE_MIN) +normalized_longitude = (longitude - LONGITUDE_MIN) / (LONGITUDE_MAX - LONGITUDE_MIN) +``` + +The values of the constants are the boundary values for the latitude and longitude. Specifically, + +``` +LATITUDE_MIN = -85.05112878 +LATITUDE_MAX = 85.05112878 +LONGITUDE_MIN = -180 +LONGITUDE_MAX = 180 +``` + +You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L141) + +2. The normalized values are then scaled in the range of [0, 2^26). This can be achieved by multiplying the normalized values by 2^26. + +``` +scaled_latitude = scaled_latitude * (2^26) +scaled_longitude = scaled_longitude * (2^26) +``` + +You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L147) + +#### Interleaving bits + +1. The scaled values of latitude and longitude are converted to unsigned 32 bit unsigned integers. This is equivalent to dropping off the digits after the decimal point so that these values are now integers instead of floating point numbers. + +In the official Redis implementation, the scaled values of latitude and longitude (stored as `double`) are casted implicitly to `uint32_t` while calling `interleave64(uint32_t, uint32_t)`. You can view how this is done [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L149). + + +2. The bits of latitude and longitude are interleaved to form a 64 bit unsigned integer. + +Let's consider the bits of latitude are `X31 X30 X29 ... X2 X1 X0` (32 bit integer), and the bits of longitude are `Y31 Y30 Y29 ... Y2 Y1 Y0` (32 bit integer). The end goal here is to construct a 64 bit integer whose digits will look like this: + +``` +score = Y31 X31 Y30 X30 Y29 X29 ... Y2 X2 Y1 X1 Y0 X0 +``` + +The pseudocode below shows how this is done. + +``` +function interleave_bits(lat: uint32, lon: uint32) -> uint64: + # Cast the values to 64 bits unsigned integers + lat = uint_64(lat) + lon = uint_64(lon) + + # Interleave-16 + lat = (lat | (lat << 16)) & 0x0000FFFF0000FFFF + lon = (lon | (lon << 16)) & 0x0000FFFF0000FFFF + + # Interleave-8 + lat = (lat | (lat << 8)) & 0x00FF00FF00FF00FF + lon = (lon | (lon << 8)) & 0x00FF00FF00FF00FF + + # Interleave-4 + lat = (lat | (lat << 4)) & 0x0F0F0F0F0F0F0F0F + lon = (lon | (lon << 4)) & 0x0F0F0F0F0F0F0F0F + + # Interleave-2 + lat = (lat | (lat << 2)) & 0x3333333333333333 + lon = (lon | (lon << 2)) & 0x3333333333333333 + + # Interleave-1 + lat = (lat | (lat << 1)) & 0x5555555555555555 + lon = (lon | (lon << 1)) & 0x5555555555555555 + + # Interleave latitude and lonngitude: latitude in even bits, longitude in odd bits + return lat | (lon << 1) +``` + +Let's focus only on the latitude value for now and call it x. This is a 32-bit integer made up of bits like X31, X30, ..., X0. The end goal here is to spread these bits out so that each one is separated by a zero. In other words, the goal here is to turn x into a 64-bit value: 0 X31 0 X30 0 X29 ... 0 X1 0 X0. + +This step prepares the data for interleaving with longitude. Once the same spreading for longitude is done, the longitude bits are shifted to the left once, so its bits move to the odd positions. Then both are combined using bitwise OR. The result is a 64-bit integer where longitude bits are in the odd positions, and latitude bits are in the even positions, achieving bit interleaving. This is done in the last step of the pseudocode above. + +##### Interleave-16 + +`x` is a 64 bit unsigned integer after casting. It is made up of four 16-bit blocks. Since the number was originally a 32 bit integer before being casted to 64 bit integer, the 32 most significant bits are all zeros. The 32 least significant bits are the original bits of the number. Let's call the two 16 bits blocks which make up that number P and Q. + +Consider x = 48348958 after normalization. Here's the application of first step on the integer. +``` + P Q +x = 0000000000000000 0000000000000000 0000001011100001 1011111100011110 + P Q +x << 16 = 0000000000000000 0000001011100001 1011111100011110 0000000000000000 + +x | (x << 16) = 0000000000000000 0000001011100001 1011111111111111 1011111100011110 +0x0000FFFF0000FFFF = 0000000000000000 1111111111111111 0000000000000000 1111111111111111 +----------------------------------------------------------------------------------------- + P Q +x (after bitwise &) = 0000000000000000 0000001011100001 0000000000000000 1011111100011110 +``` + +This step spreads out the digits of `x` (P and Q) in two different 32 bit groups. The pattern that appears after the first step is `0P0Q`, where each digit in the pattern is a 16-bit block, and P and Q are the original digits of `x`. If they're arranged in the order `PQ`, they'll form `x`. + +Let's re-write for clarity. Consider that P and Q are made up of (W, X), and (Y, Z) respectively. + +##### Interleave-8 +``` W X Y Z +x = 00000000 00000000 00000010 11100001 00000000 00000000 10111111 00011110 + W X Y Z +x << 8 = 00000000 00000010 11100001 00000000 00000000 10111111 00011110 00000000 + +x | x << 8 = 00000000 00000010 11100011 11100001 00000000 10111111 10111111 00011110 +0x00FF00FF00FF00FF = 00000000 11111111 00000000 11111111 00000000 11111111 00000000 11111111 +---------------------------------------------------------------------------------------------- + W X Y Z +x (after bitwise &) = 00000000 00000010 00000000 11100001 00000000 10111111 00000000 00011110 +``` + +After step 2, the bits of `x` are spread out in 8 8-bit groups. The pattern that appears after this step is `0W0X0Y0Z`, where each digit in the pattern is a 8-bit block. `W`, `X`, `Y`, and `Z` are the original digits that make up `x`. If they're arranged in the order `WXYZ`, they'll form `x`. + +Let's re-write for clarity. Consider that W, X, Y and Z are made up of (A,B), (C,D), (E,F) and (G,H) respectively. + +##### Interleave-4 +``` + A B C D E F G H +x = 0000 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 + A B C D E F G H +x << 4 = 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 0000 + +x | (x << 4) = 0000 0000 0010 0010 0000 1110 1111 0001 0000 1011 1111 1111 0000 0001 1111 1110 +0x0F0F0F0F0F0F0F0F = 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 +----------------------------------------------------------------------------------------------------- + A B C D E F G H +x (after bitwise &) = 0000 0000 0000 0010 0000 1110 0000 0001 0000 1011 0000 1111 0000 0001 0000 1110 +``` + +After this step, the digits of the original number have been spread out in 16 4-bit groups. The pattern that appears after this step is `0A0B0C0D0E0F0G0H`, where each digit in the pattern is a 4-bit block. If the blocks are arranged as `ABCDEFGH`, they'll for the orignal number `x`. + +##### Interleave-2 +A asterisk above a bit means that it is a part of the original bit sequence of `x`. + +``` + ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +x = 00 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 + ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +x << 2 = 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 00 + +x | (x << 2) = 00 00 00 00 00 00 10 10 00 11 11 10 00 00 01 01 00 10 11 11 00 11 11 11 00 00 01 01 00 11 11 10 +0x3333333333333333 = 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 +--------------------------------------------------------------------------------------------------------------------- + ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +x (after bitwise &) = 00 00 00 00 00 00 00 10 00 00 00 11 00 10 00 01 00 00 00 10 00 11 00 11 00 00 00 00 00 01 00 10 +``` + +After this step, the result is be 32 2-bit blocks, where every second block constitute of the digits of `x`. + +After one more step, the block size will be 1, which means, every second bit is the orignal bit in `x`. If the zeros in between are ignored, those bits form the orignal number `x`. + + +- The left shift (<<) operations are used to create space between the bits of the original number. Each shift duplicates the bits and moves them into higher positions, effectively spreading them apart. + +- The bitwise AND (&) with a constant is then used to mask out unwanted bits and retain only the original bits of `x`. These constants (eg. `0x00FF00FF00FF00FF`) are carefully chosen masks that preserve every n-th bit group after the shift, ensuring bits from the original value are positioned correctly for final interleaving. + + +This step is done for both `latitude` and `longitude` and at last, the longitude is shifted by 1 bit to the right so that its digits take up all the odd places. After bitwise OR operation between the interleaved latitude and interleaved longitude, you get the final result. + +You can view the official Redis implementation of this step [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L52). + + +### Tests + +The tester will execute your program like this: + +```bash +$ ./your_program.sh +``` + +It will then send multiple `GEOADD` commands each specifying a key, laitude, longitude, and location name. + +For example, the tester might send your program a command like this. + +```bash +$ redis-cli GEOADD places 15.09 37.50 Catania +``` + +The tester will then send multiple `ZSCORE` commands to your program specifying the key and locations used in `GEOADD` command. For example, the tester might send your program a command like this. + +```bash +$ redis-cli ZSCORE places Catania +# Expecting bulk string: "3479447193062238" +``` \ No newline at end of file diff --git a/stage_descriptions/geospatial-05-xg4.md b/stage_descriptions/geospatial-05-xg4.md index e4b27157..5faf29cf 100644 --- a/stage_descriptions/geospatial-05-xg4.md +++ b/stage_descriptions/geospatial-05-xg4.md @@ -1,15 +1,15 @@ -In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. +In this stage, you'll add support for responding to the `GEOPOS` command. ### The `GEOPOS` command -The `GEOPOS` command returns the longitude and latitude of the specified locations. Its syntax is +The `GEOPOS` command returns the longitude and latitude of the specified location. Its syntax is ``` -GEOPOS … +GEOPOS ``` Example usage: -``` +```bash > GEOADD places 15.087269 37.502669 "Catania" > GEOADD places 12.496365 41.902783 "Rome" @@ -29,15 +29,18 @@ It returns an array with one entry for each location requested. - Latitude (Encoded as a Bulk string) - If a location doesn’t exist, the entry is a null bulk string `($-1\r\n)`. + +Since the latitude and longitude values are calculated from the score of each location, an extra step is required. You will implement this in the next stage. For now, you only need to respond with a valid floating point numbers for latitude and longitude. For example, you may hardcode every latitude and longitude to be 0. + ### Tests The tester will execute your program like this: -``` -./your_program.sh +```bash +$ ./your_program.sh ``` It will add multiple locations using the `ZADD` command. It will use a score which will be equivalent to a latitude and longitude. -``` +```bash $ redis-cli > ZADD location_key 3477108430792699 "Foo" > ZADD location_key 3876464048901851 "Bar" @@ -47,32 +50,22 @@ $ redis-cli The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: -``` +```bash > GEOPOS location_key Foo - -# Expecting [["19.087197482585907", "33.50259961456723"]] +# Expecting [["0", "0"]] ``` -The value is a RESP array, which is encoded as -``` -*1\r\n -*2\r\n -$18\r\n -19.087197482585907\r\n -$17\r\n -33.50259961456723\r\n -``` +It will expect the value to be a RESP array, which contains another RESP array. The elements of the inner array should be the two bulk strings. Each bulk string should be a valid floating point number when parsed. -### Notes +The tester will also send a `GEOPOS` command using a non-existent key. -- The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded off. +```bash +> GEOPOS non_existent_key Foo +# Expecting: *0\r\n +``` - - For example, for the response of the example shown above, any of the following will be accepted: +It will expect the response to be a RESP nil array, which is encoded as `*0\r\n`. - - `19.0872 33.5026` - - `19.087197 33.502599` - - `19.08719 33.50260` - - `19.087199 33.5025996` - - `19.0872001 33.5026001` +### Notes -- If the location key does not exist, you should return an empty array. \ No newline at end of file +- In this stage, you will only implement responding to the `GEOPOS` command using valid floating point numbers. We'll get to responding with actual values of latitude and longitude in the next stage. diff --git a/stage_descriptions/geospatial-06-hb5.md b/stage_descriptions/geospatial-06-hb5.md new file mode 100644 index 00000000..e646bce5 --- /dev/null +++ b/stage_descriptions/geospatial-06-hb5.md @@ -0,0 +1,229 @@ +In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. + +### Calculating latitude and longitude from score + +The algorithm to get back the latitude and longitude is essentially the reverse of the one used to compute the score from them. In other words, we undo the bit interleaving process to extract the original latitude and longitude values. This involves two major steps: + +1. De-interleaving the bits of score +2. De-normalizing the latitude and longitude + + +#### De-interleaving bits + +The `score` is a 64-bit unsigned integer, whose bits are the interleaved bits of two 32-bit unsigned integers, which represent latitude and longitude. The digits at the odd place are the digits of longitude and those at the even place are the digits of latitude. + +For example, if `x` is the score, it is made up of + +``` +x = B63 B62 B61 .... B3 B2 B1 B0 +``` + +The bits `B63 B61 B59 ... B5 B3 B1` make up the longitude and the bits `B62 B60 B58 ... B4 B2 B0` make up the latitude. The end goal here is to de-interleave the bits so that the final result looks like this: + +``` +longitude = B63 B61 B59 ... B5 B3 B1 +latitude = B62 B60 B58 ... B4 B2 B0 +``` + +The pseudocode below shows how this is done. + +``` +function deinterleave_bits(score: uint64) -> (uint64, uint64): + # Right shift + uint64_t lat = score; + uint64_t lon = score >> 1; + + # Step 1 + lat = (lat | (lat >> 0)) & 0x5555555555555555ULL; + lon = (lon | (lon >> 0)) & 0x5555555555555555ULL; + + # Step 2 + lat = (lat | (lat >> 1)) & 0x3333333333333333ULL; + lon = (lon | (lon >> 1)) & 0x3333333333333333ULL; + + # Step 3 + lat = (lat | (lat >> 2)) & 0x0F0F0F0F0F0F0F0FULL; + lon = (lon | (lon >> 2)) & 0x0F0F0F0F0F0F0F0FULL; + + lat = (lat | (lat >> 4)) & 0x00FF00FF00FF00FFULL; + lon = (lon | (lon >> 4)) & 0x00FF00FF00FF00FFULL; + + lat = (lat | (lat >> 8)) & 0x0000FFFF0000FFFFULL; + lon = (lon | (lon >> 8)) & 0x0000FFFF0000FFFFULL; + + lat = (lat | (lat >> 16)) & 0x00000000FFFFFFFFULL; + lon = (lon | (lon >> 16)) & 0x00000000FFFFFFFFULL; + + return lat, lon +``` + +Let's see how the bits of latitude are extracted. Suppose the score is `3781709020344510`. Let us refer the latitude as `x` for simplicity. + +##### Step-1 +Let us see the application of the first step + +``` +x = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 +x >> 0 = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 + +x | (x >> 0) = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 +0x5555555555555555ULL = 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 +----------------------------------------------------------------------------------------------------------------------- +BITWISE AND = 00 00 00 00 00 00 01 01 01 00 01 01 01 01 00 01 00 01 01 00 01 00 01 01 00 01 00 00 00 01 01 00 +``` + +In the first step, `x` is right shifted by 0, which means up until the stage `x | (x >> 0)`, the original number stays the same. Now, the constant `0x5555555555555555ULL` is carefully picked so that all of its bits at the even position are 1 and the odd position are 0. So, when `x` is operated with this constant using bitwise `AND`, only the bits of even position(bits of latitude) are retained. The bits of latitude haven't changed their position, but the bits of longitude has been set to 0 after this step. + +##### Step-2 +``` +Let's re-write this in groups of 4 to make it easier, and see the application of the second step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. + + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +x = 0000 0000 0000 0101 0100 0101 0101 0001 0001 0100 0100 0101 0001 0000 0001 0100 + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +x >> 1 = 0000 0000 0000 0010 1010 0010 1010 1000 1000 1010 0010 0010 1000 1000 0000 1010 + +x | (x >> 1) = 0000 0000 0000 0111 1110 0111 1111 1001 1001 1110 0110 0111 1001 1000 0001 1110 +0x3333333333333333ULL = 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 +------------------------------------------------------------------------------------------------------- + ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +BITWISE AND = 0000 0000 0000 0011 0010 0011 0011 0001 0001 0010 0010 0011 0001 0000 0001 0010 +``` + +In this step, `x` is right-shifted by 1 and operated with bitwisse OR with the original value of `x`. This ensures that bits which were originally one positions apart (i.e., bit 0 and bit 2, bit 1 and bit 3, etc.) are grouped together. This is because `(x | 0) = x`. This applies to every odd positioned bit. It ensure that the signifcant bit after that is moved to its position because of the right shift operator. The constant has been carefully chosen to convinently ignore values which are not the original bits of `x`. + +Then, bitwise AND is applied with 0x3333333333333333ULL. This constant has the pattern 0011 0011... 0011, which isolates pairs of bits at every 4-bit block by setting other bits to 0. + +The result of this steps consists of 4 16-bit blocks. The 2 least significant bits of each 4-bit block are the digits of latitude. The result is one step closer to the full re-construction. + +##### Step-3 + +Let's re-write the result into 8 8-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. + +``` + ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +x = 00000000 00000011 00100011 00110001 00010010 00100011 00010000 00010010 + ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +x >> 2 = 00000000 00000000 11001000 11001100 01000100 10001000 11000100 00000100 + +(x | (x >> 2)) = 00000000 00000011 11101011 11111101 01010110 10101011 11010100 00010110 +0x0F0F0F0F0F0F0F0FULL = 00001111 00001111 00001111 00001111 00001111 00001111 00001111 00001111 +------------------------------------------------------------------------------------------------ + **** **** **** **** **** **** **** **** +BITWISE AND = 00000000 00000000 00001000 00001100 00000100 00001000 00000100 00000100 +``` + +In this step, the process of collapsing the scattered bits of latitude closer to their original positions continues. The bits that were originally two positions apart are re-grouped. To do this, `x` is right-shift by 2 and operated using bitwise OR with the original value of `x`. Here, the constant `0x0F0F0F0F0F0F0F0FULL` has the pattern `00001111 00001111 .... 00001111` repeated across all bytes. This selects only the lowest 4 bits of each 8-bit block and masks out the rest. Since two bits at a time were grouped previously, this step now gathers 4 bits that originally belonged together into a single nibble (4-bit group). + +As a result, the bits of latitude are now packed one nibble per byte, with the lowest significant nibble of each 8 bit-block representing the digits of latitude. + +##### Step-4 + +Let's re-write the result into 4 16-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. + +``` + **** **** **** **** **** **** **** **** +x = 0000000000000011 0010001100110001 0001001000100011 0001000000010010 + **** **** **** **** **** **** **** +x >> 4 = 0000000000000000 0011001000110011 0001000100100010 0011000100000001 + +(x | (x >> 4)) = 0000000000000011 0011001100110011 0001001100100011 0011000100010011 +0x0F0F0F0F0F0F0F0FULL = 0000000011111111 0000000011111111 0000000011111111 0000000011111111 +-------------------------------------------------------------------------------------------- + ******** ******** ******** ******** +BITWISE AND = 0000000000000011 0000000000110011 0000000000100011 0000000000010011 +``` + +In this step the bits that were originally four positions apart are grouped into a single 8-bit block. To do this, `x` is right right-shift by 4 and operated using bitwise OR with the original value of `x`. Here, the constant `0x0F0F0F0F0F0F0F0FULL` has the pattern `0000000011111111 ... 0000000011111111` repeated across all bytes. This selects only the lowest 8 bits of each 16-bit block and masks out the rest. Since four bits were grouped at a time previously, this step now gathers 8 bits that originally belonged together into a 16 bit block. As a result, the bits of latitude are now packed in 8-bit block per 16-bit block, with the lowest significant 8 bits of each 16 bit-block representing the digits of latitude, and the upper 8 bits all being 0. + +Each step shifts and combines progressively wider chunks, essentially "unzipping" the interleaved bits by peeling off odd bits and regrouping even ones back into their original layout. + +After two more steps, the result will essentially be a 64 bit integer, whose 32 most significant bits are all zeros and 32 least significant digits are the digits of the latitude. + +The same steps are repeated for longitude, except that at the very first step, the digits of longitude are shifted by 1 because the digits of longitude are stored in odd positions. Shifting it to the right by 1 brings all the digits of longitude in the even position. This implies that the same steps can be applied to longitude as that are applied for the latitude. + +You can see how this step is implemented in the official Redis source [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L82). + + +#### De-normalizing the latitude and longitude + +After the computation of the previous step, both latitude and longitude will have been obtained. But, they are in the range of [0, 2^26). To convert them to valid values, they should be normalized to the range of [0, 1) and multiplied by their respective ranges, and added with respective offsets to obtain the actual values of latitude and longitude. + +The pseudocode below shows how this is done: + +``` +func convert_to_lat_lon(latitude: uint64, longitude: uint64) -> () + double lat_scale = LATITUDE_MAX - LATITUDE_MIN; + double long_scale = LONGITUDE_MAX - LONGITUDE_MIN; + + latitude_min = LATITUDE_MIN + (latitude * 1.0 / (1ULL << step)) * lat_scale; + latitude_max = LATITUDE_MAX + ((latitude + 1) * 1.0 / (1ULL << step)) * lat_scale; + + longitude_min = LONGITUDE_MIN + (longitude * 1.0 / (1ULL << step)) * long_scale; + longitude_max = LONGITUDE_MAX + ((longitude + 1) * 1.0 / (1ULL << step)) * long_scale; + + lat = (latitude_min + latitude_max) / 2; + lon = (longitude_min + longitude_max) / 2; + +``` + +The values of the constants are given below: + +``` +LATITUDE_MIN = -85.05112878 +LATITUDE_MAX = 85.05112878 +LONGITUDE_MIN = -180 +LONGITUDE_MAX = 180 +``` + +The minimum and maximum values of latitude and longitude are averaged because the encoded score represents a rectangular region on the Earth's surface, not a single point. This region is a cell in a grid formed by subdividing the full geographic coordinate space. Averaging the boundaries gives the center of that cell — the best approximation of the original coordinate that was encoded. You can learn more about this visually by using the [GeoHash Explorer](https://geohash.softeng.co/). + + +You an see how this is implemented in the official Redis source [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L181). + + +### Tests +The tester will execute your program like this: +```bash +$ ./your_program.sh +``` + +It will add multiple locations using the `ZADD` command. It will use a score which will be equivalent to a latitude and longitude. + +```bash +$ redis-cli +> ZADD location_key 3477108430792699 "Foo" +> ZADD location_key 3876464048901851 "Bar" +> ZADD location_key 3468915414364476 "Baz" +> ZADD location_key 3781709020344510 "Caz" +``` + +The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: + +```bash +> GEOPOS location_key Foo + +# Expecting [["19.087197482585907", "33.50259961456723"]] +``` + +The value is a RESP array, which is encoded as +``` +*1\r\n +*2\r\n +$18\r\n +19.087197482585907\r\n +$17\r\n +33.50259961456723\r\n +``` + +### Notes + +- The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded off. + + - For example, for the response of the example shown above, any of the following will be accepted: + + - `19.0872 33.5026` + - `19.087197 33.502599` + - `19.08719 33.50260` + - `19.087199 33.5025996` + - `19.0872001 33.5026001` \ No newline at end of file diff --git a/stage_descriptions/geospatial-06-ek6.md b/stage_descriptions/geospatial-07-ek6.md similarity index 97% rename from stage_descriptions/geospatial-06-ek6.md rename to stage_descriptions/geospatial-07-ek6.md index 504fedd8..75bb970a 100644 --- a/stage_descriptions/geospatial-06-ek6.md +++ b/stage_descriptions/geospatial-07-ek6.md @@ -8,7 +8,7 @@ GEODIST ``` Example usage: -``` +```bash > GEODIST places Catania Rome "537215.1152" ``` @@ -19,12 +19,12 @@ Redis uses the [Haversine's Formula](https://en.wikipedia.org/wiki/Haversine_for ### Tests The tester will execute your program like this: -``` -./your_program.sh +```bash +$ ./your_program.sh ``` It will add multiple locations using the `GEOADD` command. -``` +```bash $ redis-cli > GEOADD places 15.087269 37.502669 "Catania" > GEOADD places 12.496365 41.902783 "Rome" @@ -32,7 +32,7 @@ $ redis-cli The tester will then send multiple `GEODIST` commands specifying two locations. For example, the tester might send your program a command like this: -``` +```bash > GEODIST places Catania Rome # Expecting "537215.1152" ``` diff --git a/stage_descriptions/geospatial-07-rm9.md b/stage_descriptions/geospatial-08-rm9.md similarity index 100% rename from stage_descriptions/geospatial-07-rm9.md rename to stage_descriptions/geospatial-08-rm9.md From f4af75c32f38d711ae87019df751c6331288c484 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Mon, 4 Aug 2025 20:07:45 +0545 Subject: [PATCH 14/24] fix: typos and add context --- stage_descriptions/geospatial-04-cr3.md | 4 ++-- stage_descriptions/geospatial-06-hb5.md | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/stage_descriptions/geospatial-04-cr3.md b/stage_descriptions/geospatial-04-cr3.md index 2ae9e876..abc6007a 100644 --- a/stage_descriptions/geospatial-04-cr3.md +++ b/stage_descriptions/geospatial-04-cr3.md @@ -30,8 +30,8 @@ You can see how the official Redis implementation does this [here](https://githu 2. The normalized values are then scaled in the range of [0, 2^26). This can be achieved by multiplying the normalized values by 2^26. ``` -scaled_latitude = scaled_latitude * (2^26) -scaled_longitude = scaled_longitude * (2^26) +scaled_latitude = normalized_latitude * (2^26) +scaled_longitude = normalized_longitude * (2^26) ``` You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L147) diff --git a/stage_descriptions/geospatial-06-hb5.md b/stage_descriptions/geospatial-06-hb5.md index e646bce5..68abf2fe 100644 --- a/stage_descriptions/geospatial-06-hb5.md +++ b/stage_descriptions/geospatial-06-hb5.md @@ -156,11 +156,11 @@ func convert_to_lat_lon(latitude: uint64, longitude: uint64) -> () double lat_scale = LATITUDE_MAX - LATITUDE_MIN; double long_scale = LONGITUDE_MAX - LONGITUDE_MIN; - latitude_min = LATITUDE_MIN + (latitude * 1.0 / (1ULL << step)) * lat_scale; - latitude_max = LATITUDE_MAX + ((latitude + 1) * 1.0 / (1ULL << step)) * lat_scale; + latitude_min = LATITUDE_MIN + (latitude * 1.0 / (1ULL << 26)) * lat_scale; + latitude_max = LATITUDE_MAX + ((latitude + 1) * 1.0 / (1ULL << 26)) * lat_scale; - longitude_min = LONGITUDE_MIN + (longitude * 1.0 / (1ULL << step)) * long_scale; - longitude_max = LONGITUDE_MAX + ((longitude + 1) * 1.0 / (1ULL << step)) * long_scale; + longitude_min = LONGITUDE_MIN + (longitude * 1.0 / (1ULL << 26)) * long_scale; + longitude_max = LONGITUDE_MAX + ((longitude + 1) * 1.0 / (1ULL << 26)) * long_scale; lat = (latitude_min + latitude_max) / 2; lon = (longitude_min + longitude_max) / 2; From 29e33a1ff4aac8fbcafa567752ffdabfcd4579c8 Mon Sep 17 00:00:00 2001 From: Udeshya Dhungana <075bct095.udeshya@pcampus.edu.np> Date: Fri, 8 Aug 2025 13:48:56 +0545 Subject: [PATCH 15/24] apply suggsted changes --- course-definition.yml | 20 ++- stage_descriptions/geospatial-01-zt4.md | 15 +- stage_descriptions/geospatial-02-ck3.md | 35 ++-- stage_descriptions/geospatial-03-tn5.md | 12 +- stage_descriptions/geospatial-04-cr3.md | 170 +++++++++---------- stage_descriptions/geospatial-05-xg4.md | 26 ++- stage_descriptions/geospatial-06-hb5.md | 208 +++++++++++------------- stage_descriptions/geospatial-07-ek6.md | 21 +-- stage_descriptions/geospatial-08-rm9.md | 46 ++++-- 9 files changed, 267 insertions(+), 286 deletions(-) diff --git a/course-definition.yml b/course-definition.yml index 039494c6..1e74bdba 100644 --- a/course-definition.yml +++ b/course-definition.yml @@ -706,29 +706,35 @@ stages: - slug: "tn5" primary_extension_slug: "geospatial" name: "Store a location" - difficulty: easy + difficulty: medium marketing_md: In this stage, you'll add support for storing a location in a sorted set. - slug: "cr3" primary_extension_slug: "geospatial" name: "Calculate location score" - difficulty: easy + difficulty: hard marketing_md: In this stage, you'll add support for calculating the score of a location. - slug: "xg4" primary_extension_slug: "geospatial" - name: "Retrieve a location" + name: "Respond to GEOPOS" difficulty: easy - marketing_md: In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. + marketing_md: In this stage, you'll add support for responding to the `GEOPOS` command. + + - slug: "hb5" + primary_extension_slug: "geospatial" + name: "Decode coordinates" + difficulty: hard + marketing_md: In this stage, you'll add support for decoding the coordinates of a location. - slug: "ek6" primary_extension_slug: "geospatial" - name: "Measure distance" + name: "Calculate distance" difficulty: medium marketing_md: In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. - slug: "rm9" primary_extension_slug: "geospatial" name: "Search within radius" - difficulty: hard - marketing_md: In this stage, you'll add support for searching locations near a coordinate within a given radius. \ No newline at end of file + difficulty: easy + marketing_md: In this stage, you'll add support for searching locations near a coordinate within a given radius using the `GEOSEARCH` command. \ No newline at end of file diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index a2cd6a8d..dc0832be 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -3,18 +3,13 @@ In this stage, you'll add support for responding to the `GEOADD` command. ### The `GEOADD` command The `GEOADD` command adds a location (with longitude, latitude, and name) to a key. It stores the location as a [sorted set](https://redis.io/docs/latest/develop/data-types/sorted-sets/) under the specified key. If the key doesn’t exist, a new sorted set is created under the specified name and the location is inserted to it. If the key exists, the location is inserted in the sorted set. -The syntax for `GEOADD` command is - -``` -GEOADD -``` - Example usage: ```bash -> GEOADD places 13.361389 38.115556 "Palermo" +> GEOADD places -0.0884948 51.506479 "London" (integer) 1 ``` +The argument order of `GEOADD` argument – key, longitude, latitude, member (longitude first, then latitude). It returns the count of elements added, encoded as a RESP Integer. @@ -25,11 +20,13 @@ The tester will execute your program like this: $ ./your_program.sh ``` -It will then send a `GEOADD` command specifying a key, laitude, longitude, and location name. +It will then send a `GEOADD` command specifying a key, latitude, longitude, and location name. ```bash -$ redis-cli GEOADD places 15.09 37.50 Catania +$ redis-cli GEOADD places 11.5030378 48.164271 Munich ``` +The tester will expect the response to be `:1\r\n`, which is 1(number of locations added) encoded as a RESP integer. + ### Notes - In this stage, you will only implement responding the the `GEOADD` command. You don't need to store the locations yet. We'll get to storing the locations in the later stages. \ No newline at end of file diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index c4eecac6..14bef10f 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -2,7 +2,8 @@ In this stage, you'll add support for validating the latitude and longitude prov ### The `GEOADD` command (Validate coordinates) Latitudes and longitudes used in the `GEOADD` command should be in a certain range as per [EPSG:3857](https://epsg.io/3857). Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. -Example use case: + +If either of these values aren't within the appropriate range, `GEOADD` returns an error. Examples: ```bash # Invalid latitude @@ -20,7 +21,8 @@ The tester will execute your program like this: ```bash $ ./your_program.sh ``` -It will then send multiple `GEOADD` commands specifying one or more location to add. For valid coordinates, the tester will expect the response to be the usual response of the `GEOADD` command. For invalid coordinates values, it will expect an error. + +It will then send multiple `GEOADD` commands. If the coordinates supplied are valid, the tester will expect the response to be `:1\r\n`, the usual response of the `GEOADD` command. For invalid coordinates values, it will expect an error response. For example, the tester might send your program a command like this: @@ -41,23 +43,14 @@ The value is RESP simple error, which is RESP-encoded as - In this stage, you'll only extend responding to `GEOADD` in case of invalid values of latitude and longitude. You do not need to implement the storage mechanism yet. -- In case of out-of-bounds values for latitude or longitude, the tester is lenient but structured in checking error messages. - - The error response does not need to match Redis’s exact error message format. +- The tester is lenient in checking error messages, it doesn't require that errors match Redis's exact format. - The error response must: - - Start with the phrase "ERR invalid" (case-insensitive) - - Contain the word latitude if the latitude value is invalid - - Contain the word longitude if the longitude value is invalid - - Contain both "latitude" and "longitude" if both the latitudes and longitudes are invalid. - - - Examples that will pass: - - ERR Invalid latitude value (In case of invalid latitude and valid longitude) - - ERR invalid longitude: must be between -180 and 180 (In case of invalid longitude but valid latitude value) - - ERR invalid longitude,latitude pair (In case of one or both invalid values) - - ERR invalid longitude and latitude range (In case of one or both invalid values) - - - Examples that will not pass: - - ERR invalid (In case of one or both invalid values) - - ERR invalid longitude (In case of invalid latitude but valid longitude) - - ERR invalid latitude (In case of invalid longitude but valid latitude) - -- The booundary of latitude are clipped at -85.05112878 and 85.05112878 degrees instead of -90 and 90 degrees respectively. It is because of the Web Mercator projection used by Redis. You can read more about it on [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator_projection). \ No newline at end of file + - Start with "ERR invalid" (case-insensitive) + - Include "latitude" if latitude is invalid. + - Include "longitude" if longitude is invalid. + + - Examples: + - ✅ ERR invalid latitude value + - ❌ ERR invalid + +- The boundary of latitude are clipped at -85.05112878 and 85.05112878 degrees instead of -90 and 90 degrees respectively. It is because of the Web Mercator projection used by Redis. You can read more about it on [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator_projection). \ No newline at end of file diff --git a/stage_descriptions/geospatial-03-tn5.md b/stage_descriptions/geospatial-03-tn5.md index 6cd35f64..f096c521 100644 --- a/stage_descriptions/geospatial-03-tn5.md +++ b/stage_descriptions/geospatial-03-tn5.md @@ -5,15 +5,16 @@ In this stage, you'll add support for storing locations in a sorted set. The locations added using the `GEOADD` command are stored in a sorted set. Redis internally calculates a score for the specified location using a location's latitude and longitude. For example, the following two commands are equivalent in Redis. + ```bash # Adding a location -$ redis-cli GEOADD places_key location +$ redis-cli GEOADD places_key 2.2944692 48.8584625 location # This command is equivalent to the command above -$ redis-cli ZADD places_key location +$ redis-cli ZADD places_key 3663832614298053 location ``` -where is calculated using the location's and values using a special algorithm. We'll get to implementing this algorithm in the later stage. +where score is calculated using the location's latitude and longitude values using an algorithm. We'll get to implementing this algorithm in the later stage. For now, you can hardcode a location's score to be 0 for all the locations. @@ -27,13 +28,14 @@ $ ./your_program.sh It will then send a `GEOADD` command specifying a key, laitude, longitude, and location name. ```bash -$ redis-cli GEOADD places 15.09 37.50 Catania +$ redis-cli GEOADD places 2.2944692 48.8584625 Paris +# Expect: (integer) 1 ``` The tester will then send a `ZRANGE` command to your program specifying the key used in `GEOADD` command. ```bash $ redis-cli ZRANGE places 0 -1 -# Expect RESP Array: ["Catania"] +# Expect RESP Array: ["Paris"] ``` ### Notes diff --git a/stage_descriptions/geospatial-04-cr3.md b/stage_descriptions/geospatial-04-cr3.md index abc6007a..977004c6 100644 --- a/stage_descriptions/geospatial-04-cr3.md +++ b/stage_descriptions/geospatial-04-cr3.md @@ -11,7 +11,7 @@ The algorithm for calculating the score from latitude and longitude involves two 1. The values of latitude and longitude are converted in the range of [0, 1). This is done using the following formula: -``` +```python normalized_latitude = (latitude - LATITUDE_MIN) / (LATITUDE_MAX - LATITUDE_MIN) normalized_longitude = (longitude - LONGITUDE_MIN) / (LONGITUDE_MAX - LONGITUDE_MIN) ``` @@ -25,9 +25,9 @@ LONGITUDE_MIN = -180 LONGITUDE_MAX = 180 ``` -You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L141) +You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L141). -2. The normalized values are then scaled in the range of [0, 2^26). This can be achieved by multiplying the normalized values by 2^26. +2. The normalized values are then scaled in the range of \[0, 2^26\). This can be achieved by multiplying the normalized values by 2^26. ``` scaled_latitude = normalized_latitude * (2^26) @@ -53,122 +53,109 @@ score = Y31 X31 Y30 X30 Y29 X29 ... Y2 X2 Y1 X1 Y0 X0 The pseudocode below shows how this is done. -``` +```python function interleave_bits(lat: uint32, lon: uint32) -> uint64: - # Cast the values to 64 bits unsigned integers + +# Let's focus only on the latitude value for now and call it x. This is a 32-bit integer made up of bits like X31, X30, ..., X0. The goal here is to spread these bits out so that each one is separated by a zero. In other words, the goal here is to turn x into a 64-bit value: '0 X31 0 X30 0 X29 ... 0 X1 0 X0'. + +# Once the same steps are applied for longitude (call it y), the longitude bits will be '0 Y31 0 Y30 0 Y29 ... 0 Y1 0 Y0', the longitude bits are shifted to the left once (check the return statement of this pseudocode), so its bits move to the odd positions. Then both are combined using bitwise OR. The result is a 64-bit integer where longitude bits are in the odd positions, and latitude bits are in the even positions, achieving bit interleaving. This is done in the last step of the pseudocode above. + +# Cast the values to 64 bits unsigned integers lat = uint_64(lat) lon = uint_64(lon) - # Interleave-16 + lat = (lat | (lat << 16)) & 0x0000FFFF0000FFFF lon = (lon | (lon << 16)) & 0x0000FFFF0000FFFF +# Interleave-16 +# x is a 64 bit unsigned integer after casting. It is made up of four 16-bit blocks. Since the number was originally a 32 bit integer before being casted to 64 bit integer, the 32 most significant bits are all zeros. The 32 least significant bits are the original bits of the number. Let's call the two 16 bits blocks which make up that number P and Q. - # Interleave-8 - lat = (lat | (lat << 8)) & 0x00FF00FF00FF00FF - lon = (lon | (lon << 8)) & 0x00FF00FF00FF00FF - - # Interleave-4 - lat = (lat | (lat << 4)) & 0x0F0F0F0F0F0F0F0F - lon = (lon | (lon << 4)) & 0x0F0F0F0F0F0F0F0F +# Consider x = 48348958 after normalization. Here's the application of first step on the integer. +# P Q +# x = 0000000000000000 0000000000000000 0000001011100001 1011111100011110 +# P Q +# x << 16 = 0000000000000000 0000001011100001 1011111100011110 0000000000000000 - # Interleave-2 - lat = (lat | (lat << 2)) & 0x3333333333333333 - lon = (lon | (lon << 2)) & 0x3333333333333333 +# x | (x << 16) = 0000000000000000 0000001011100001 1011111111111111 1011111100011110 +# 0x0000FFFF0000FFFF = 0000000000000000 1111111111111111 0000000000000000 1111111111111111 +# ----------------------------------------------------------------------------------------- +# P Q +# x (after bitwise &) = 0000000000000000 0000001011100001 0000000000000000 1011111100011110 - # Interleave-1 - lat = (lat | (lat << 1)) & 0x5555555555555555 - lon = (lon | (lon << 1)) & 0x5555555555555555 +# This step spreads out the digits of x (P and Q) in two different 32 bit groups. The pattern that appears after the first step is 0P0Q, where each digit in the pattern is a 16-bit block, and P and Q are the original digits of x. If they're arranged in the order PQ, they'll form x. - # Interleave latitude and lonngitude: latitude in even bits, longitude in odd bits - return lat | (lon << 1) -``` -Let's focus only on the latitude value for now and call it x. This is a 32-bit integer made up of bits like X31, X30, ..., X0. The end goal here is to spread these bits out so that each one is separated by a zero. In other words, the goal here is to turn x into a 64-bit value: 0 X31 0 X30 0 X29 ... 0 X1 0 X0. + lat = (lat | (lat << 8)) & 0x00FF00FF00FF00FF + lon = (lon | (lon << 8)) & 0x00FF00FF00FF00FF +# Interleave-8 +# Let's re-write for clarity. Consider that P and Q are made up of (W, X), and (Y, Z) respectively. +# W X Y Z +# x = 00000000 00000000 00000010 11100001 00000000 00000000 10111111 00011110 +# W X Y Z +# x << 8 = 00000000 00000010 11100001 00000000 00000000 10111111 00011110 00000000 -This step prepares the data for interleaving with longitude. Once the same spreading for longitude is done, the longitude bits are shifted to the left once, so its bits move to the odd positions. Then both are combined using bitwise OR. The result is a 64-bit integer where longitude bits are in the odd positions, and latitude bits are in the even positions, achieving bit interleaving. This is done in the last step of the pseudocode above. +# x | x << 8 = 00000000 00000010 11100011 11100001 00000000 10111111 10111111 00011110 +# 0x00FF00FF00FF00FF = 00000000 11111111 00000000 11111111 00000000 11111111 00000000 11111111 +# ---------------------------------------------------------------------------------------------- +# W X Y Z +# x (after bitwise &) = 00000000 00000010 00000000 11100001 00000000 10111111 00000000 00011110 -##### Interleave-16 +# After this, the bits of x are spread out in 8 8-bit groups. The pattern that appears after this step is 0W0X0Y0Z, where each digit in the pattern is a 8-bit block. W, X, Y, and Z are the original digits that make up x. If they're arranged in the order WXYZ, they'll form x. -`x` is a 64 bit unsigned integer after casting. It is made up of four 16-bit blocks. Since the number was originally a 32 bit integer before being casted to 64 bit integer, the 32 most significant bits are all zeros. The 32 least significant bits are the original bits of the number. Let's call the two 16 bits blocks which make up that number P and Q. -Consider x = 48348958 after normalization. Here's the application of first step on the integer. -``` - P Q -x = 0000000000000000 0000000000000000 0000001011100001 1011111100011110 - P Q -x << 16 = 0000000000000000 0000001011100001 1011111100011110 0000000000000000 - -x | (x << 16) = 0000000000000000 0000001011100001 1011111111111111 1011111100011110 -0x0000FFFF0000FFFF = 0000000000000000 1111111111111111 0000000000000000 1111111111111111 ------------------------------------------------------------------------------------------ - P Q -x (after bitwise &) = 0000000000000000 0000001011100001 0000000000000000 1011111100011110 -``` + lat = (lat | (lat << 4)) & 0x0F0F0F0F0F0F0F0F + lon = (lon | (lon << 4)) & 0x0F0F0F0F0F0F0F0F +# Interleave-4 +# Let's re-write for clarity. Consider that W, X, Y and Z are made up of (A,B), (C,D), (E,F) and (G,H) respectively. +# A B C D E F G H +# x = 0000 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 +# A B C D E F G H +# x << 4 = 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 0000 -This step spreads out the digits of `x` (P and Q) in two different 32 bit groups. The pattern that appears after the first step is `0P0Q`, where each digit in the pattern is a 16-bit block, and P and Q are the original digits of `x`. If they're arranged in the order `PQ`, they'll form `x`. +# x | (x << 4) = 0000 0000 0010 0010 0000 1110 1111 0001 0000 1011 1111 1111 0000 0001 1111 1110 +# 0x0F0F0F0F0F0F0F0F = 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 +# ----------------------------------------------------------------------------------------------------- +# A B C D E F G H +# x (after bitwise &) = 0000 0000 0000 0010 0000 1110 0000 0001 0000 1011 0000 1111 0000 0001 0000 1110 -Let's re-write for clarity. Consider that P and Q are made up of (W, X), and (Y, Z) respectively. +# After this step, the digits of the original number have been spread out in 16 4-bit groups. The pattern that appears after this step is 0A0B0C0D0E0F0G0H, where each digit in the pattern is a 4-bit block. If the blocks are arranged as ABCDEFGH, they'll for the orignal number x. -##### Interleave-8 -``` W X Y Z -x = 00000000 00000000 00000010 11100001 00000000 00000000 10111111 00011110 - W X Y Z -x << 8 = 00000000 00000010 11100001 00000000 00000000 10111111 00011110 00000000 -x | x << 8 = 00000000 00000010 11100011 11100001 00000000 10111111 10111111 00011110 -0x00FF00FF00FF00FF = 00000000 11111111 00000000 11111111 00000000 11111111 00000000 11111111 ----------------------------------------------------------------------------------------------- - W X Y Z -x (after bitwise &) = 00000000 00000010 00000000 11100001 00000000 10111111 00000000 00011110 -``` + lat = (lat | (lat << 2)) & 0x3333333333333333 + lon = (lon | (lon << 2)) & 0x3333333333333333 +# Interleave-2 +# Let's re-write these bits in groups of 2 for clarity. +# A asterisk above a bit means that it is a part of the original bit sequence of x. +# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +# x = 00 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 +# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +# x << 2 = 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 00 -After step 2, the bits of `x` are spread out in 8 8-bit groups. The pattern that appears after this step is `0W0X0Y0Z`, where each digit in the pattern is a 8-bit block. `W`, `X`, `Y`, and `Z` are the original digits that make up `x`. If they're arranged in the order `WXYZ`, they'll form `x`. +# x | (x << 2) = 00 00 00 00 00 00 10 10 00 11 11 10 00 00 01 01 00 10 11 11 00 11 11 11 00 00 01 01 00 11 11 10 +# 0x3333333333333333 = 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 +# --------------------------------------------------------------------------------------------------------------------- +# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +# x (after bitwise &) = 00 00 00 00 00 00 00 10 00 00 00 11 00 10 00 01 00 00 00 10 00 11 00 11 00 00 00 00 00 01 00 10 -Let's re-write for clarity. Consider that W, X, Y and Z are made up of (A,B), (C,D), (E,F) and (G,H) respectively. +# After this step, the result is be 32 2-bit blocks, where every second block constitute of the digits of x. -##### Interleave-4 -``` - A B C D E F G H -x = 0000 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 - A B C D E F G H -x << 4 = 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 0000 - -x | (x << 4) = 0000 0000 0010 0010 0000 1110 1111 0001 0000 1011 1111 1111 0000 0001 1111 1110 -0x0F0F0F0F0F0F0F0F = 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 ------------------------------------------------------------------------------------------------------ - A B C D E F G H -x (after bitwise &) = 0000 0000 0000 0010 0000 1110 0000 0001 0000 1011 0000 1111 0000 0001 0000 1110 -``` -After this step, the digits of the original number have been spread out in 16 4-bit groups. The pattern that appears after this step is `0A0B0C0D0E0F0G0H`, where each digit in the pattern is a 4-bit block. If the blocks are arranged as `ABCDEFGH`, they'll for the orignal number `x`. + lat = (lat | (lat << 1)) & 0x5555555555555555 + lon = (lon | (lon << 1)) & 0x5555555555555555 +# Interleave-1 +# After this step, the block size will be 1, which means, every second bit is the orignal bit in x. If the zeros in between are ignored, those bits form the orignal number x. -##### Interleave-2 -A asterisk above a bit means that it is a part of the original bit sequence of `x`. -``` - ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -x = 00 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 - ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -x << 2 = 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 00 - -x | (x << 2) = 00 00 00 00 00 00 10 10 00 11 11 10 00 00 01 01 00 10 11 11 00 11 11 11 00 00 01 01 00 11 11 10 -0x3333333333333333 = 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 ---------------------------------------------------------------------------------------------------------------------- - ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -x (after bitwise &) = 00 00 00 00 00 00 00 10 00 00 00 11 00 10 00 01 00 00 00 10 00 11 00 11 00 00 00 00 00 01 00 10 +# Interleave latitude and lonngitude: latitude in even bits, longitude in odd bits + return lat | (lon << 1) ``` -After this step, the result is be 32 2-bit blocks, where every second block constitute of the digits of `x`. - -After one more step, the block size will be 1, which means, every second bit is the orignal bit in `x`. If the zeros in between are ignored, those bits form the orignal number `x`. - - +To summarize: - The left shift (<<) operations are used to create space between the bits of the original number. Each shift duplicates the bits and moves them into higher positions, effectively spreading them apart. - The bitwise AND (&) with a constant is then used to mask out unwanted bits and retain only the original bits of `x`. These constants (eg. `0x00FF00FF00FF00FF`) are carefully chosen masks that preserve every n-th bit group after the shift, ensuring bits from the original value are positioned correctly for final interleaving. - -This step is done for both `latitude` and `longitude` and at last, the longitude is shifted by 1 bit to the right so that its digits take up all the odd places. After bitwise OR operation between the interleaved latitude and interleaved longitude, you get the final result. +- The steps in the algorithm are repeated for both `latitude` and `longitude`, and at last, the longitude is shifted by 1 bit to the right so that its digits take up all the odd places. After bitwise OR operation between the interleaved latitude and interleaved longitude, the score is obtained. You can view the official Redis implementation of this step [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L52). @@ -186,12 +173,13 @@ It will then send multiple `GEOADD` commands each specifying a key, laitude, lon For example, the tester might send your program a command like this. ```bash -$ redis-cli GEOADD places 15.09 37.50 Catania +$ redis-cli GEOADD places 2.2944692 48.8584625 Paris +# Expect: (integer) 1 ``` The tester will then send multiple `ZSCORE` commands to your program specifying the key and locations used in `GEOADD` command. For example, the tester might send your program a command like this. ```bash -$ redis-cli ZSCORE places Catania -# Expecting bulk string: "3479447193062238" +$ redis-cli ZSCORE places Paris +# Expecting bulk string: "3663832614298053" ``` \ No newline at end of file diff --git a/stage_descriptions/geospatial-05-xg4.md b/stage_descriptions/geospatial-05-xg4.md index 5faf29cf..360d3351 100644 --- a/stage_descriptions/geospatial-05-xg4.md +++ b/stage_descriptions/geospatial-05-xg4.md @@ -2,23 +2,20 @@ In this stage, you'll add support for responding to the `GEOPOS` command. ### The `GEOPOS` command -The `GEOPOS` command returns the longitude and latitude of the specified location. Its syntax is - -``` -GEOPOS -``` +The `GEOPOS` command returns the longitude and latitude of the specified location. Example usage: ```bash -> GEOADD places 15.087269 37.502669 "Catania" -> GEOADD places 12.496365 41.902783 "Rome" - -> GEOPOS places Catania Rome non_existent -1) 1) "15.087267458438873" - 2) "37.50266842333162" -2) 1) "12.496366202831268" - 2) "41.90278213378984" -3) (nil) +> GEOADD places -0.0884948 51.506479 "London" +> GEOADD places 11.5030378 48.164271 "Munich" + +> GEOPOS places London +1) 1) "-0.08849412202835083" + 2) "51.50647814139934" + +> GEOPOS places Munich +1) 1) "11.503036916255951" + 2) "48.16427086232978" ``` It returns an array with one entry for each location requested. @@ -69,3 +66,4 @@ It will expect the response to be a RESP nil array, which is encoded as `*0\r\n` ### Notes - In this stage, you will only implement responding to the `GEOPOS` command using valid floating point numbers. We'll get to responding with actual values of latitude and longitude in the next stage. +- You can specify any floating point value for latitude and longitude. diff --git a/stage_descriptions/geospatial-06-hb5.md b/stage_descriptions/geospatial-06-hb5.md index 68abf2fe..38ec2c1c 100644 --- a/stage_descriptions/geospatial-06-hb5.md +++ b/stage_descriptions/geospatial-06-hb5.md @@ -1,4 +1,4 @@ -In this stage, you'll add support for retrieving the coordinates of a location using the `GEOPOS` command. +In this stage, you'll add support for decoding the coordinates of a location. ### Calculating latitude and longitude from score @@ -27,144 +27,136 @@ latitude = B62 B60 B58 ... B4 B2 B0 The pseudocode below shows how this is done. -``` -function deinterleave_bits(score: uint64) -> (uint64, uint64): - # Right shift - uint64_t lat = score; - uint64_t lon = score >> 1; +```python +def deinterleave_bits(score: uint64) -> (uint64, uint64): - # Step 1 - lat = (lat | (lat >> 0)) & 0x5555555555555555ULL; - lon = (lon | (lon >> 0)) & 0x5555555555555555ULL; +# Right shift + uint64_t lat = score + uint64_t lon = score >> 1 - # Step 2 - lat = (lat | (lat >> 1)) & 0x3333333333333333ULL; - lon = (lon | (lon >> 1)) & 0x3333333333333333ULL; +# Let's see how the bits of latitude are extracted. Suppose the score is 3781709020344510. Let us refer the latitude as x for simplicity. - # Step 3 - lat = (lat | (lat >> 2)) & 0x0F0F0F0F0F0F0F0FULL; - lon = (lon | (lon >> 2)) & 0x0F0F0F0F0F0F0F0FULL; - lat = (lat | (lat >> 4)) & 0x00FF00FF00FF00FFULL; - lon = (lon | (lon >> 4)) & 0x00FF00FF00FF00FFULL; + lat = (lat | (lat >> 0)) & 0x5555555555555555 + lon = (lon | (lon >> 0)) & 0x5555555555555555 +# Step-1 +# Let us see the application of the first step - lat = (lat | (lat >> 8)) & 0x0000FFFF0000FFFFULL; - lon = (lon | (lon >> 8)) & 0x0000FFFF0000FFFFULL; +# x = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 +# x >> 0 = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 - lat = (lat | (lat >> 16)) & 0x00000000FFFFFFFFULL; - lon = (lon | (lon >> 16)) & 0x00000000FFFFFFFFULL; +# x | (x >> 0) = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 +# 0x5555555555555555 = 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 +# ----------------------------------------------------------------------------------------------------------------------- +# BITWISE AND = 00 00 00 00 00 00 01 01 01 00 01 01 01 01 00 01 00 01 01 00 01 00 01 01 00 01 00 00 00 01 01 00 - return lat, lon -``` +# In the first step, x is right shifted by 0, which means up until the stage x | (x >> 0), the original number stays the same. Now, the constant 0x5555555555555555 is carefully picked so that all of its bits at the even position are 1 and the odd position are 0. So, when x is operated with this constant using bitwise AND, only the bits of even position(bits of latitude) are retained. The bits of latitude haven't changed their position, but the bits of longitude has been set to 0 after this step. -Let's see how the bits of latitude are extracted. Suppose the score is `3781709020344510`. Let us refer the latitude as `x` for simplicity. -##### Step-1 -Let us see the application of the first step + lat = (lat | (lat >> 1)) & 0x3333333333333333 + lon = (lon | (lon >> 1)) & 0x3333333333333333 +# Step-2 +# Let's re-write this in groups of 4 to make it easier, and see the application of the second step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. -``` -x = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 -x >> 0 = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 +# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +# x = 0000 0000 0000 0101 0100 0101 0101 0001 0001 0100 0100 0101 0001 0000 0001 0100 +# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +# x >> 1 = 0000 0000 0000 0010 1010 0010 1010 1000 1000 1010 0010 0010 1000 1000 0000 1010 -x | (x >> 0) = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 -0x5555555555555555ULL = 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 ------------------------------------------------------------------------------------------------------------------------ -BITWISE AND = 00 00 00 00 00 00 01 01 01 00 01 01 01 01 00 01 00 01 01 00 01 00 01 01 00 01 00 00 00 01 01 00 -``` +# x | (x >> 1) = 0000 0000 0000 0111 1110 0111 1111 1001 1001 1110 0110 0111 1001 1000 0001 1110 +# 0x3333333333333333 = 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 +# ------------------------------------------------------------------------------------------------------- +# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +# BITWISE AND = 0000 0000 0000 0011 0010 0011 0011 0001 0001 0010 0010 0011 0001 0000 0001 0010 -In the first step, `x` is right shifted by 0, which means up until the stage `x | (x >> 0)`, the original number stays the same. Now, the constant `0x5555555555555555ULL` is carefully picked so that all of its bits at the even position are 1 and the odd position are 0. So, when `x` is operated with this constant using bitwise `AND`, only the bits of even position(bits of latitude) are retained. The bits of latitude haven't changed their position, but the bits of longitude has been set to 0 after this step. +# In this step, x is right-shifted by 1 and operated with bitwisse OR with the original value of x. This ensures that bits which were originally one positions apart (i.e., bit 0 and bit 2, bit 1 and bit 3, etc.) are grouped together. This is because (x | 0) = x. This applies to every odd positioned bit. It ensure that the signifcant bit after that is moved to its position because of the right shift operator. The constant has been carefully chosen to convinently ignore values which are not the original bits of x. -##### Step-2 -``` -Let's re-write this in groups of 4 to make it easier, and see the application of the second step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. - - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -x = 0000 0000 0000 0101 0100 0101 0101 0001 0001 0100 0100 0101 0001 0000 0001 0100 - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -x >> 1 = 0000 0000 0000 0010 1010 0010 1010 1000 1000 1010 0010 0010 1000 1000 0000 1010 - -x | (x >> 1) = 0000 0000 0000 0111 1110 0111 1111 1001 1001 1110 0110 0111 1001 1000 0001 1110 -0x3333333333333333ULL = 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 -------------------------------------------------------------------------------------------------------- - ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -BITWISE AND = 0000 0000 0000 0011 0010 0011 0011 0001 0001 0010 0010 0011 0001 0000 0001 0010 -``` - -In this step, `x` is right-shifted by 1 and operated with bitwisse OR with the original value of `x`. This ensures that bits which were originally one positions apart (i.e., bit 0 and bit 2, bit 1 and bit 3, etc.) are grouped together. This is because `(x | 0) = x`. This applies to every odd positioned bit. It ensure that the signifcant bit after that is moved to its position because of the right shift operator. The constant has been carefully chosen to convinently ignore values which are not the original bits of `x`. +# Then, bitwise AND is applied with 0x3333333333333333. This constant has the pattern 0011 0011... 0011, which isolates pairs of bits at every 4-bit block by setting other bits to 0. -Then, bitwise AND is applied with 0x3333333333333333ULL. This constant has the pattern 0011 0011... 0011, which isolates pairs of bits at every 4-bit block by setting other bits to 0. +# The result of this steps consists of 4 16-bit blocks. The 2 least significant bits of each 4-bit block are the digits of latitude. The result is one step closer to the full re-construction. -The result of this steps consists of 4 16-bit blocks. The 2 least significant bits of each 4-bit block are the digits of latitude. The result is one step closer to the full re-construction. + lat = (lat | (lat >> 2)) & 0x0F0F0F0F0F0F0F0F + lon = (lon | (lon >> 2)) & 0x0F0F0F0F0F0F0F0F ##### Step-3 +# Let's re-write the result into 8 8-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. -Let's re-write the result into 8 8-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. +# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +# x = 00000000 00000011 00100011 00110001 00010010 00100011 00010000 00010010 +# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** +# x >> 2 = 00000000 00000000 11001000 11001100 01000100 10001000 11000100 00000100 -``` - ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -x = 00000000 00000011 00100011 00110001 00010010 00100011 00010000 00010010 - ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -x >> 2 = 00000000 00000000 11001000 11001100 01000100 10001000 11000100 00000100 - -(x | (x >> 2)) = 00000000 00000011 11101011 11111101 01010110 10101011 11010100 00010110 -0x0F0F0F0F0F0F0F0FULL = 00001111 00001111 00001111 00001111 00001111 00001111 00001111 00001111 ------------------------------------------------------------------------------------------------- - **** **** **** **** **** **** **** **** -BITWISE AND = 00000000 00000000 00001000 00001100 00000100 00001000 00000100 00000100 -``` +# (x | (x >> 2)) = 00000000 00000011 11101011 11111101 01010110 10101011 11010100 00010110 +# 0x0F0F0F0F0F0F0F0F = 00001111 00001111 00001111 00001111 00001111 00001111 00001111 00001111 +# ------------------------------------------------------------------------------------------------ +# **** **** **** **** **** **** **** **** +# BITWISE AND = 00000000 00000000 00001000 00001100 00000100 00001000 00000100 00000100 -In this step, the process of collapsing the scattered bits of latitude closer to their original positions continues. The bits that were originally two positions apart are re-grouped. To do this, `x` is right-shift by 2 and operated using bitwise OR with the original value of `x`. Here, the constant `0x0F0F0F0F0F0F0F0FULL` has the pattern `00001111 00001111 .... 00001111` repeated across all bytes. This selects only the lowest 4 bits of each 8-bit block and masks out the rest. Since two bits at a time were grouped previously, this step now gathers 4 bits that originally belonged together into a single nibble (4-bit group). +# In this step, the process of collapsing the scattered bits of latitude closer to their original positions continues. The bits that were originally two positions apart are re-grouped. To do this, x is right-shift by 2 and operated using bitwise OR with the original value of x. Here, the constant 0x0F0F0F0F0F0F0F0F has the pattern 00001111 00001111 .... 00001111 repeated across all bytes. This selects only the lowest 4 bits of each 8-bit block and masks out the rest. Since two bits at a time were grouped previously, this step now gathers 4 bits that originally belonged together into a single nibble (4-bit group). -As a result, the bits of latitude are now packed one nibble per byte, with the lowest significant nibble of each 8 bit-block representing the digits of latitude. +# As a result, the bits of latitude are now packed one nibble per byte, with the lowest significant nibble of each 8 bit-block representing the digits of latitude. -##### Step-4 -Let's re-write the result into 4 16-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. + lat = (lat | (lat >> 4)) & 0x00FF00FF00FF00FF + lon = (lon | (lon >> 4)) & 0x00FF00FF00FF00FF +# Step-4 +# Let's re-write the result into 4 16-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. -``` - **** **** **** **** **** **** **** **** -x = 0000000000000011 0010001100110001 0001001000100011 0001000000010010 - **** **** **** **** **** **** **** -x >> 4 = 0000000000000000 0011001000110011 0001000100100010 0011000100000001 - -(x | (x >> 4)) = 0000000000000011 0011001100110011 0001001100100011 0011000100010011 -0x0F0F0F0F0F0F0F0FULL = 0000000011111111 0000000011111111 0000000011111111 0000000011111111 --------------------------------------------------------------------------------------------- - ******** ******** ******** ******** -BITWISE AND = 0000000000000011 0000000000110011 0000000000100011 0000000000010011 -``` +# **** **** **** **** **** **** **** **** +# x = 0000000000000011 0010001100110001 0001001000100011 0001000000010010 +# **** **** **** **** **** **** **** +# x >> 4 = 0000000000000000 0011001000110011 0001000100100010 0011000100000001 + +# (x | (x >> 4)) = 0000000000000011 0011001100110011 0001001100100011 0011000100010011 +# 0x0F0F0F0F0F0F0F0F = 0000000011111111 0000000011111111 0000000011111111 0000000011111111 +# -------------------------------------------------------------------------------------------- +# ******** ******** ******** ******** +# BITWISE AND = 0000000000000011 0000000000110011 0000000000100011 0000000000010011 + +# In this step the bits that were originally four positions apart are grouped into a single 8-bit block. To do this, x is right right-shift by 4 and operated using bitwise OR with the original value of x. Here, the constant 0x0F0F0F0F0F0F0F0F has the pattern 0000000011111111 ... 0000000011111111 repeated across all bytes. This selects only the lowest 8 bits of each 16-bit block and masks out the rest. Since four bits were grouped at a time previously, this step now gathers 8 bits that originally belonged together into a 16 bit block. As a result, the bits of latitude are now packed in 8-bit block per 16-bit block, with the lowest significant 8 bits of each 16 bit-block representing the digits of latitude, and the upper 8 bits all being 0. -In this step the bits that were originally four positions apart are grouped into a single 8-bit block. To do this, `x` is right right-shift by 4 and operated using bitwise OR with the original value of `x`. Here, the constant `0x0F0F0F0F0F0F0F0FULL` has the pattern `0000000011111111 ... 0000000011111111` repeated across all bytes. This selects only the lowest 8 bits of each 16-bit block and masks out the rest. Since four bits were grouped at a time previously, this step now gathers 8 bits that originally belonged together into a 16 bit block. As a result, the bits of latitude are now packed in 8-bit block per 16-bit block, with the lowest significant 8 bits of each 16 bit-block representing the digits of latitude, and the upper 8 bits all being 0. -Each step shifts and combines progressively wider chunks, essentially "unzipping" the interleaved bits by peeling off odd bits and regrouping even ones back into their original layout. +# Each step shifts and combines progressively wider chunks, essentially "unzipping" the interleaved bits by peeling off odd bits and regrouping even ones back into their original layout. -After two more steps, the result will essentially be a 64 bit integer, whose 32 most significant bits are all zeros and 32 least significant digits are the digits of the latitude. +# After two more steps, the result will essentially be a 64 bit integer, whose 32 most significant bits are all zeros and 32 least significant digits are the digits of the latitude. -The same steps are repeated for longitude, except that at the very first step, the digits of longitude are shifted by 1 because the digits of longitude are stored in odd positions. Shifting it to the right by 1 brings all the digits of longitude in the even position. This implies that the same steps can be applied to longitude as that are applied for the latitude. +# The same steps are repeated for longitude, except that at the very first step, the digits of longitude are shifted by 1 because the digits of longitude are stored in odd positions. Shifting it to the right by 1 brings all the digits of longitude in the even position. This implies that the same steps can be applied to longitude as that are applied for the latitude. + lat = (lat | (lat >> 8)) & 0x0000FFFF0000FFFF + lon = (lon | (lon >> 8)) & 0x0000FFFF0000FFFF + + lat = (lat | (lat >> 16)) & 0x00000000FFFFFFFF + lon = (lon | (lon >> 16)) & 0x00000000FFFFFFFF + + return lat, lon +``` You can see how this step is implemented in the official Redis source [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L82). #### De-normalizing the latitude and longitude -After the computation of the previous step, both latitude and longitude will have been obtained. But, they are in the range of [0, 2^26). To convert them to valid values, they should be normalized to the range of [0, 1) and multiplied by their respective ranges, and added with respective offsets to obtain the actual values of latitude and longitude. +After the computation of the previous step, both latitude and longitude will have been obtained. But, they are in the range of \[0, 2^26\). To convert them to valid values, they should be normalized to the range of \[0, 1\) and multiplied by their respective ranges, and added with respective offsets to obtain the actual values of latitude and longitude. The pseudocode below shows how this is done: -``` -func convert_to_lat_lon(latitude: uint64, longitude: uint64) -> () - double lat_scale = LATITUDE_MAX - LATITUDE_MIN; - double long_scale = LONGITUDE_MAX - LONGITUDE_MIN; +```python +def convert_to_lat_lon(latitude: uint64, longitude: uint64) + double lat_scale = LATITUDE_MAX - LATITUDE_MIN + double long_scale = LONGITUDE_MAX - LONGITUDE_MIN - latitude_min = LATITUDE_MIN + (latitude * 1.0 / (1ULL << 26)) * lat_scale; - latitude_max = LATITUDE_MAX + ((latitude + 1) * 1.0 / (1ULL << 26)) * lat_scale; + # Calculate lower boundary of grid + latitude_min = LATITUDE_MIN + (latitude * 1.0 / (1 << 26)) * lat_scale + latitude_max = LATITUDE_MAX + ((latitude + 1) * 1.0 / (1 << 26)) * lat_scale - longitude_min = LONGITUDE_MIN + (longitude * 1.0 / (1ULL << 26)) * long_scale; - longitude_max = LONGITUDE_MAX + ((longitude + 1) * 1.0 / (1ULL << 26)) * long_scale; + # Calculate upper boundary of grid + longitude_min = LONGITUDE_MIN + (longitude * 1.0 / (1 << 26)) * long_scale + longitude_max = LONGITUDE_MAX + ((longitude + 1) * 1.0 / (1 << 26)) * long_scale - lat = (latitude_min + latitude_max) / 2; - lon = (longitude_min + longitude_max) / 2; + # Calculate average for best approximation + lat = (latitude_min + latitude_max) / 2 + lon = (longitude_min + longitude_max) / 2 + return lat, lon ``` The values of the constants are given below: @@ -192,7 +184,7 @@ It will add multiple locations using the `ZADD` command. It will use a score whi ```bash $ redis-cli -> ZADD location_key 3477108430792699 "Foo" +> ZADD location_key 3663832614298053 "Foo" > ZADD location_key 3876464048901851 "Bar" > ZADD location_key 3468915414364476 "Baz" > ZADD location_key 3781709020344510 "Caz" @@ -202,18 +194,17 @@ The tester will then send multiple `GEOPOS` commands, each specifying a single l ```bash > GEOPOS location_key Foo - -# Expecting [["19.087197482585907", "33.50259961456723"]] +# Expecting [["2.294471561908722", "48.85846255040141"]] ``` The value is a RESP array, which is encoded as ``` *1\r\n *2\r\n -$18\r\n -19.087197482585907\r\n $17\r\n -33.50259961456723\r\n +2.294471561908722\r\n +$17\r\n +48.85846255040141\r\n ``` ### Notes @@ -222,8 +213,7 @@ $17\r\n - For example, for the response of the example shown above, any of the following will be accepted: - - `19.0872 33.5026` - - `19.087197 33.502599` - - `19.08719 33.50260` - - `19.087199 33.5025996` - - `19.0872001 33.5026001` \ No newline at end of file + - `2.2945 48.8585` + - `2.2945001 48.8585002` + - `2.29447156190 48.858462550` + - `2.29449 48.858453` \ No newline at end of file diff --git a/stage_descriptions/geospatial-07-ek6.md b/stage_descriptions/geospatial-07-ek6.md index 75bb970a..cbee6023 100644 --- a/stage_descriptions/geospatial-07-ek6.md +++ b/stage_descriptions/geospatial-07-ek6.md @@ -1,16 +1,13 @@ In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. ### The `GEODIST` command -The `GEODIST` command returns the distance between two members. The default unit of the distance which is returned, is meters. -The syntax for `GEODIST` command is: -``` -GEODIST -``` +The `GEODIST` command returns the distance between two members of a key. The default unit of the distance which is returned, is meters. + Example usage: ```bash -> GEODIST places Catania Rome -"537215.1152" +> GEODIST places Munich Paris +"682477.7582" ``` It returns the distance as a string, encoded as a RESP Bulk String. @@ -26,22 +23,22 @@ $ ./your_program.sh It will add multiple locations using the `GEOADD` command. ```bash $ redis-cli -> GEOADD places 15.087269 37.502669 "Catania" -> GEOADD places 12.496365 41.902783 "Rome" +> GEOADD places 11.5030378 48.164271 "Munich" +> GEOADD places 2.2944692 48.8584625 "Paris" ``` The tester will then send multiple `GEODIST` commands specifying two locations. For example, the tester might send your program a command like this: ```bash -> GEODIST places Catania Rome -# Expecting "537215.1152" +> GEODIST places Munich Paris +# Expecting "682477.7582" ``` The value is a RESP bulk string encoded as: ``` $11\r\n -537215.1152\r\n +682477.7582\r\n ``` ### Notes diff --git a/stage_descriptions/geospatial-08-rm9.md b/stage_descriptions/geospatial-08-rm9.md index 0fa63225..55a01465 100644 --- a/stage_descriptions/geospatial-08-rm9.md +++ b/stage_descriptions/geospatial-08-rm9.md @@ -1,22 +1,23 @@ -In this stage, you'll add support for searching locations near a coordinate within a given radius. +In this stage, you'll add support for searching locations near a coordinate within a given radius using the `GEOSEARCH` command. ### The GEOSEARCH command The `GEOSEARCH` command lets you search for locations near a given coordinate within a specified area. -It supports several search modes. In our implementation, we'll focus only on the `FROMLONLAT` mode with distance unit in meters. The `FROMLONLAT` mode searching by directly specifying longitude and latitude. The syntax for this +It supports several search modes. In our implementation, we'll focus only on the `FROMLONLAT` mode with distance unit in meters. The `FROMLONLAT` mode searching by directly specifying longitude and latitude. -``` -GEOSEARCH FROMLONLAT BYRADIUS m -``` - -For example, to search for locations within 100,000 meters of the point (longitude: 15, latitude: 37) stored in the places key, you can use: +For example, to search for locations within 100000 meters of the point (longitude: 2, latitude: 48) stored in the `places` key, you can use: ```bash -> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 100000 m -2) "Catania" +> GEOSEARCH places FROMLONLAT 2 48 BYRADIUS 100000 m +1) "Paris" ``` +- `FROMLONLAT ` — This option specifies the center point for the search. +- `BYRADIUS ` — This option searches within a circular area of the given radius and unit (m, km, mi, etc.). + +Redis supports other values for search origin option and shape option, but here we'll only use `FROMLONLAT` and `BYRADIUS`. + It returns a RESP Array of member names, where each member's name is a encoded as a bulk string. ### Tests @@ -30,25 +31,34 @@ It will add multiple locations using the `GEOADD` command. ```bash $ redis-cli -> GEOADD places 13.361389 38.115556 Palermo -> GEOADD places 15.087269 37.502669 Catania +> GEOADD places 11.5030378 48.164271 "Munich" +> GEOADD places 2.2944692 48.8584625 "Paris" +> GEOADD places -0.0884948 51.506479 "London" ``` The tester will then send multiple `GEOSEARCH` commands specifying a latitude and longitude pair with `BYRADIUS` option specifying the distance and unit. For example, it may send the following command. ```bash -> GEOSEARCH places FROMLONLAT 15 37 BYRADIUS 100000 m +> GEOSEARCH places FROMLONLAT 2 48 BYRADIUS 100000 m +# Expecting ["Paris"] + +> GEOSEARCH places FROMLONLAT 2 48 BYRADIUS 500000 m +# Expecting ["Paris, "London"] (Any order) -# Expecting ["Catania"] +> GEOSEARCH places FROMLONLAT 11 50 BYRADIUS 300000 m +# Expecting ["Munich"] ``` The value is a RESP array, which is encoded as: -```bash -*1\r\n -$7\r\n -Catania\r\n +``` +*2\r\n +$5\r\n +Paris\r\n +$6\r\n +London\r\n ``` ### Notes -- The tester will only test using the `meters` unit. \ No newline at end of file +- The tester will only test using the `meters` unit. +- The locations returned can be in any order since we are not implementing `ASC` or `DESC` option. \ No newline at end of file From 9a6ec4c4a6901f73a20d0a426597f13895fa6e34 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 14:07:36 -0700 Subject: [PATCH 16/24] Update geospatial stage description for the GEOADD command and its usage. --- stage_descriptions/geospatial-01-zt4.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index dc0832be..4b766634 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -1,7 +1,8 @@ In this stage, you'll add support for responding to the `GEOADD` command. ### The `GEOADD` command -The `GEOADD` command adds a location (with longitude, latitude, and name) to a key. It stores the location as a [sorted set](https://redis.io/docs/latest/develop/data-types/sorted-sets/) under the specified key. If the key doesn’t exist, a new sorted set is created under the specified name and the location is inserted to it. If the key exists, the location is inserted in the sorted set. + +The [`GEOADD` command](https://redis.io/docs/latest/commands/geoadd/) adds a location (with longitude, latitude, and name) to a key. It stores the location as a [sorted set](https://redis.io/docs/latest/develop/data-types/sorted-sets/) under the specified key. Example usage: @@ -9,24 +10,34 @@ Example usage: > GEOADD places -0.0884948 51.506479 "London" (integer) 1 ``` -The argument order of `GEOADD` argument – key, longitude, latitude, member (longitude first, then latitude). + +The arguments `GEOADD` accepts are: + +1. `key`: The key to store the location in. +2. `longitude`: The longitude of the location. +3. `latitude`: The latitude of the location. +4. `member`: The name of the location. It returns the count of elements added, encoded as a RESP Integer. +In this stage, you'll only implement the response to the `GEOADD` command. We'll get to validating arguments and storing locations in later stages. + ### Tests + The tester will execute your program like this: ```bash $ ./your_program.sh ``` -It will then send a `GEOADD` command specifying a key, latitude, longitude, and location name. +It will then send a `GEOADD` command: ```bash $ redis-cli GEOADD places 11.5030378 48.164271 Munich ``` -The tester will expect the response to be `:1\r\n`, which is 1(number of locations added) encoded as a RESP integer. +The tester will expect the response to be `:1\r\n`, which is 1 (number of locations added) encoded as a RESP integer. ### Notes -- In this stage, you will only implement responding the the `GEOADD` command. You don't need to store the locations yet. We'll get to storing the locations in the later stages. \ No newline at end of file + +- In this stage, you only need to implement responding to the `GEOADD` command. We'll get to validating arguments and storing locations in later stages. From 87c039a6719ef8caa5064a3829a55118ae6328cf Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 14:23:20 -0700 Subject: [PATCH 17/24] Update geospatial stage to clarify latitude and longitude validation details. --- stage_descriptions/geospatial-02-ck3.md | 51 ++++++++++++++----------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/stage_descriptions/geospatial-02-ck3.md b/stage_descriptions/geospatial-02-ck3.md index 14bef10f..fbaf494d 100644 --- a/stage_descriptions/geospatial-02-ck3.md +++ b/stage_descriptions/geospatial-02-ck3.md @@ -1,7 +1,14 @@ -In this stage, you'll add support for validating the latitude and longitude provided in the `GEOADD` command. +In this stage, you'll add support for validating the latitude and longitude values provided in a `GEOADD` command. -### The `GEOADD` command (Validate coordinates) -Latitudes and longitudes used in the `GEOADD` command should be in a certain range as per [EPSG:3857](https://epsg.io/3857). Valid longitudes are from -180 to 180 degrees. Valid latitudes are from -85.05112878 to 85.05112878 degrees. Both of these limits are inclusive. +### Validating latitude and longitude values + +The latitude and longitude values used in the `GEOADD` command should be in a certain range as per [EPSG:3857](https://epsg.io/3857). + +- Valid longitudes are from -180° to +180° + - Both these limits are inclusive, so -180° and +180° are both valid. +- Valid latitudes are from -85.05112878° to +85.05112878° + - Both of these limits are inclusive, so -85.05112878° and +85.05112878° are both valid. + - The reason these limits are not -/+90° is because of the [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) that Redis uses. If either of these values aren't within the appropriate range, `GEOADD` returns an error. Examples: @@ -15,42 +22,42 @@ If either of these values aren't within the appropriate range, `GEOADD` returns (error) ERR invalid longitude,latitude pair 181.000000,0.300000 ``` +In this stage, you'll implement validating latitude and longitude values and returning error messages as shown above. The tester is lenient with error message formats, so you don't have to use the exact error message format mentioned above. + ### Tests + The tester will execute your program like this: ```bash $ ./your_program.sh ``` -It will then send multiple `GEOADD` commands. If the coordinates supplied are valid, the tester will expect the response to be `:1\r\n`, the usual response of the `GEOADD` command. For invalid coordinates values, it will expect an error response. +It will then send multiple `GEOADD` commands. -For example, the tester might send your program a command like this: +If the coordinates are invalid, it will expect an error response. For example: ```bash -$ redis-cli GEOADD location_key 200 100 foo - # Expecting error +$ redis-cli GEOADD location_key 200 100 foo (error) ERR invalid longitude,latitude pair 200,100 ``` -The value is RESP simple error, which is RESP-encoded as +The returned value must: + +- Be a [RESP simple error](https://redis.io/docs/latest/develop/reference/protocol-spec/#simple-errors) i.e. start with `-` and end with `\r\n` +- The error message must start with `ERR`, like standard Redis error messages +- The error message must contain the word "latitude" if the latitude is invalid +- The error message must contain the word "longitude" if the longitude is invalid + +For example, if the latitude is invalid, valid error messages that the tester will accept are: ```bash --ERR invalid longitude,latitude pair 200,100\r\n +-ERR invalid latitude argument\r\n +-ERR invalid latitude value\r\n +-ERR latitude value (200.0) is invalid\r\n ``` ### Notes -- In this stage, you'll only extend responding to `GEOADD` in case of invalid values of latitude and longitude. You do not need to implement the storage mechanism yet. - -- The tester is lenient in checking error messages, it doesn't require that errors match Redis's exact format. - - The error response must: - - Start with "ERR invalid" (case-insensitive) - - Include "latitude" if latitude is invalid. - - Include "longitude" if longitude is invalid. - - - Examples: - - ✅ ERR invalid latitude value - - ❌ ERR invalid - -- The boundary of latitude are clipped at -85.05112878 and 85.05112878 degrees instead of -90 and 90 degrees respectively. It is because of the Web Mercator projection used by Redis. You can read more about it on [Wikipedia](https://en.wikipedia.org/wiki/Web_Mercator_projection). \ No newline at end of file +- You don't need to implement storing locations yet, we'll get to that in later stages. +- The boundary of latitude are clipped at -/+85.05112878° instead of -/+90°. This is because of the [Web Mercator projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) that Redis uses. From 43bd4693e8e92e12c37b12db93d2ece99b719998 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 14:24:02 -0700 Subject: [PATCH 18/24] Update geospatial-01-zt4.md to remove reference to sorted set in GEOADD command. --- stage_descriptions/geospatial-01-zt4.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index 4b766634..505f426f 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -2,7 +2,7 @@ In this stage, you'll add support for responding to the `GEOADD` command. ### The `GEOADD` command -The [`GEOADD` command](https://redis.io/docs/latest/commands/geoadd/) adds a location (with longitude, latitude, and name) to a key. It stores the location as a [sorted set](https://redis.io/docs/latest/develop/data-types/sorted-sets/) under the specified key. +The [`GEOADD` command](https://redis.io/docs/latest/commands/geoadd/) adds a location (with longitude, latitude, and name) to a key. Example usage: From af148c659c6bec8fede801360eb2ede670f7d6d2 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 14:55:07 -0700 Subject: [PATCH 19/24] Update geospatial stage descriptions with prerequisites and implementation details. --- stage_descriptions/geospatial-01-zt4.md | 4 ++++ stage_descriptions/geospatial-03-tn5.md | 14 +++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/stage_descriptions/geospatial-01-zt4.md b/stage_descriptions/geospatial-01-zt4.md index 505f426f..7bd939ef 100644 --- a/stage_descriptions/geospatial-01-zt4.md +++ b/stage_descriptions/geospatial-01-zt4.md @@ -1,5 +1,9 @@ In this stage, you'll add support for responding to the `GEOADD` command. +### Extension prerequisites + +This stage depends on the [**Sorted Sets**](https://redis.io/docs/latest/data-types/sorted-sets/) extension. Before attempting this extension, please make sure you've completed the Sorted Sets extension. + ### The `GEOADD` command The [`GEOADD` command](https://redis.io/docs/latest/commands/geoadd/) adds a location (with longitude, latitude, and name) to a key. diff --git a/stage_descriptions/geospatial-03-tn5.md b/stage_descriptions/geospatial-03-tn5.md index f096c521..535c7116 100644 --- a/stage_descriptions/geospatial-03-tn5.md +++ b/stage_descriptions/geospatial-03-tn5.md @@ -14,29 +14,33 @@ $ redis-cli GEOADD places_key 2.2944692 48.8584625 location $ redis-cli ZADD places_key 3663832614298053 location ``` -where score is calculated using the location's latitude and longitude values using an algorithm. We'll get to implementing this algorithm in the later stage. +In this stage, you'll implement adding locations to a sorted set when a `GEOADD` command is run. -For now, you can hardcode a location's score to be 0 for all the locations. +You can hardcode the score to be 0 for now, we'll get to calculating the score in later stages. ### Tests + The tester will execute your program like this: ```bash $ ./your_program.sh ``` -It will then send a `GEOADD` command specifying a key, laitude, longitude, and location name. +It will then send a `GEOADD` command: ```bash $ redis-cli GEOADD places 2.2944692 48.8584625 Paris # Expect: (integer) 1 ``` -The tester will then send a `ZRANGE` command to your program specifying the key used in `GEOADD` command. +The tester will then send a `ZRANGE` command to validate that the location was added to the sorted set: + ```bash $ redis-cli ZRANGE places 0 -1 # Expect RESP Array: ["Paris"] ``` ### Notes -- In this stage, you can hardcode the score of the location to be 0. We'll get to calculating the value of score using latitude and longitude in the next stage. \ No newline at end of file + +- In this stage, you can hardcode the score of the location to be 0. We'll get to calculating the value of score using latitude and longitude in later stages. +- The implementation of the `ZRANGE` command is covered in the sorted sets extension. From bccffba691f59fca4e53213c99a7dbe2cb804c12 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 15:02:58 -0700 Subject: [PATCH 20/24] Refactor location scoring section and update algorithm description in README. --- stage_descriptions/geospatial-04-cr3.md | 176 ++---------------------- 1 file changed, 15 insertions(+), 161 deletions(-) diff --git a/stage_descriptions/geospatial-04-cr3.md b/stage_descriptions/geospatial-04-cr3.md index 977004c6..29e43658 100644 --- a/stage_descriptions/geospatial-04-cr3.md +++ b/stage_descriptions/geospatial-04-cr3.md @@ -1,164 +1,16 @@ In this stage, you'll add support for calculating the score of a location. -### Calculating location score +### Location scores -The algorithm for calculating the score from latitude and longitude involves two major steps: +To store locations in a sorted set, Redis converts latitude and longitude values to a single "score". -1. Normalizing the latitude and longitude values -2. Interleaving the bits of normalized values of latitude and longitude +We've created a [GitHub repository](https://github.com/codecrafters-io/redis-geocoding-algorithm) that explains how this conversion is done. It includes: -#### Normalization - -1. The values of latitude and longitude are converted in the range of [0, 1). This is done using the following formula: - -```python -normalized_latitude = (latitude - LATITUDE_MIN) / (LATITUDE_MAX - LATITUDE_MIN) -normalized_longitude = (longitude - LONGITUDE_MIN) / (LONGITUDE_MAX - LONGITUDE_MIN) -``` - -The values of the constants are the boundary values for the latitude and longitude. Specifically, - -``` -LATITUDE_MIN = -85.05112878 -LATITUDE_MAX = 85.05112878 -LONGITUDE_MIN = -180 -LONGITUDE_MAX = 180 -``` - -You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L141). - -2. The normalized values are then scaled in the range of \[0, 2^26\). This can be achieved by multiplying the normalized values by 2^26. - -``` -scaled_latitude = normalized_latitude * (2^26) -scaled_longitude = normalized_longitude * (2^26) -``` - -You can see how the official Redis implementation does this [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L147) - -#### Interleaving bits - -1. The scaled values of latitude and longitude are converted to unsigned 32 bit unsigned integers. This is equivalent to dropping off the digits after the decimal point so that these values are now integers instead of floating point numbers. - -In the official Redis implementation, the scaled values of latitude and longitude (stored as `double`) are casted implicitly to `uint32_t` while calling `interleave64(uint32_t, uint32_t)`. You can view how this is done [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L149). - - -2. The bits of latitude and longitude are interleaved to form a 64 bit unsigned integer. - -Let's consider the bits of latitude are `X31 X30 X29 ... X2 X1 X0` (32 bit integer), and the bits of longitude are `Y31 Y30 Y29 ... Y2 Y1 Y0` (32 bit integer). The end goal here is to construct a 64 bit integer whose digits will look like this: - -``` -score = Y31 X31 Y30 X30 Y29 X29 ... Y2 X2 Y1 X1 Y0 X0 -``` - -The pseudocode below shows how this is done. - -```python -function interleave_bits(lat: uint32, lon: uint32) -> uint64: - -# Let's focus only on the latitude value for now and call it x. This is a 32-bit integer made up of bits like X31, X30, ..., X0. The goal here is to spread these bits out so that each one is separated by a zero. In other words, the goal here is to turn x into a 64-bit value: '0 X31 0 X30 0 X29 ... 0 X1 0 X0'. - -# Once the same steps are applied for longitude (call it y), the longitude bits will be '0 Y31 0 Y30 0 Y29 ... 0 Y1 0 Y0', the longitude bits are shifted to the left once (check the return statement of this pseudocode), so its bits move to the odd positions. Then both are combined using bitwise OR. The result is a 64-bit integer where longitude bits are in the odd positions, and latitude bits are in the even positions, achieving bit interleaving. This is done in the last step of the pseudocode above. - -# Cast the values to 64 bits unsigned integers - lat = uint_64(lat) - lon = uint_64(lon) - - - lat = (lat | (lat << 16)) & 0x0000FFFF0000FFFF - lon = (lon | (lon << 16)) & 0x0000FFFF0000FFFF -# Interleave-16 -# x is a 64 bit unsigned integer after casting. It is made up of four 16-bit blocks. Since the number was originally a 32 bit integer before being casted to 64 bit integer, the 32 most significant bits are all zeros. The 32 least significant bits are the original bits of the number. Let's call the two 16 bits blocks which make up that number P and Q. - -# Consider x = 48348958 after normalization. Here's the application of first step on the integer. -# P Q -# x = 0000000000000000 0000000000000000 0000001011100001 1011111100011110 -# P Q -# x << 16 = 0000000000000000 0000001011100001 1011111100011110 0000000000000000 - -# x | (x << 16) = 0000000000000000 0000001011100001 1011111111111111 1011111100011110 -# 0x0000FFFF0000FFFF = 0000000000000000 1111111111111111 0000000000000000 1111111111111111 -# ----------------------------------------------------------------------------------------- -# P Q -# x (after bitwise &) = 0000000000000000 0000001011100001 0000000000000000 1011111100011110 - -# This step spreads out the digits of x (P and Q) in two different 32 bit groups. The pattern that appears after the first step is 0P0Q, where each digit in the pattern is a 16-bit block, and P and Q are the original digits of x. If they're arranged in the order PQ, they'll form x. - - - lat = (lat | (lat << 8)) & 0x00FF00FF00FF00FF - lon = (lon | (lon << 8)) & 0x00FF00FF00FF00FF -# Interleave-8 -# Let's re-write for clarity. Consider that P and Q are made up of (W, X), and (Y, Z) respectively. -# W X Y Z -# x = 00000000 00000000 00000010 11100001 00000000 00000000 10111111 00011110 -# W X Y Z -# x << 8 = 00000000 00000010 11100001 00000000 00000000 10111111 00011110 00000000 - -# x | x << 8 = 00000000 00000010 11100011 11100001 00000000 10111111 10111111 00011110 -# 0x00FF00FF00FF00FF = 00000000 11111111 00000000 11111111 00000000 11111111 00000000 11111111 -# ---------------------------------------------------------------------------------------------- -# W X Y Z -# x (after bitwise &) = 00000000 00000010 00000000 11100001 00000000 10111111 00000000 00011110 - -# After this, the bits of x are spread out in 8 8-bit groups. The pattern that appears after this step is 0W0X0Y0Z, where each digit in the pattern is a 8-bit block. W, X, Y, and Z are the original digits that make up x. If they're arranged in the order WXYZ, they'll form x. - - - lat = (lat | (lat << 4)) & 0x0F0F0F0F0F0F0F0F - lon = (lon | (lon << 4)) & 0x0F0F0F0F0F0F0F0F -# Interleave-4 -# Let's re-write for clarity. Consider that W, X, Y and Z are made up of (A,B), (C,D), (E,F) and (G,H) respectively. -# A B C D E F G H -# x = 0000 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 -# A B C D E F G H -# x << 4 = 0000 0000 0010 0000 0000 1110 0001 0000 0000 1011 1111 0000 0000 0001 1110 0000 - -# x | (x << 4) = 0000 0000 0010 0010 0000 1110 1111 0001 0000 1011 1111 1111 0000 0001 1111 1110 -# 0x0F0F0F0F0F0F0F0F = 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 0000 1111 -# ----------------------------------------------------------------------------------------------------- -# A B C D E F G H -# x (after bitwise &) = 0000 0000 0000 0010 0000 1110 0000 0001 0000 1011 0000 1111 0000 0001 0000 1110 - -# After this step, the digits of the original number have been spread out in 16 4-bit groups. The pattern that appears after this step is 0A0B0C0D0E0F0G0H, where each digit in the pattern is a 4-bit block. If the blocks are arranged as ABCDEFGH, they'll for the orignal number x. - - - lat = (lat | (lat << 2)) & 0x3333333333333333 - lon = (lon | (lon << 2)) & 0x3333333333333333 -# Interleave-2 -# Let's re-write these bits in groups of 2 for clarity. -# A asterisk above a bit means that it is a part of the original bit sequence of x. -# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -# x = 00 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 -# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -# x << 2 = 00 00 00 00 00 00 10 00 00 11 10 00 00 00 01 00 00 10 11 00 00 11 11 00 00 00 01 00 00 11 10 00 - -# x | (x << 2) = 00 00 00 00 00 00 10 10 00 11 11 10 00 00 01 01 00 10 11 11 00 11 11 11 00 00 01 01 00 11 11 10 -# 0x3333333333333333 = 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 00 11 -# --------------------------------------------------------------------------------------------------------------------- -# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -# x (after bitwise &) = 00 00 00 00 00 00 00 10 00 00 00 11 00 10 00 01 00 00 00 10 00 11 00 11 00 00 00 00 00 01 00 10 - -# After this step, the result is be 32 2-bit blocks, where every second block constitute of the digits of x. - - - lat = (lat | (lat << 1)) & 0x5555555555555555 - lon = (lon | (lon << 1)) & 0x5555555555555555 -# Interleave-1 -# After this step, the block size will be 1, which means, every second bit is the orignal bit in x. If the zeros in between are ignored, those bits form the orignal number x. - - -# Interleave latitude and lonngitude: latitude in even bits, longitude in odd bits - return lat | (lon << 1) -``` - -To summarize: -- The left shift (<<) operations are used to create space between the bits of the original number. Each shift duplicates the bits and moves them into higher positions, effectively spreading them apart. - -- The bitwise AND (&) with a constant is then used to mask out unwanted bits and retain only the original bits of `x`. These constants (eg. `0x00FF00FF00FF00FF`) are carefully chosen masks that preserve every n-th bit group after the shift, ensuring bits from the original value are positioned correctly for final interleaving. - -- The steps in the algorithm are repeated for both `latitude` and `longitude`, and at last, the longitude is shifted by 1 bit to the right so that its digits take up all the odd places. After bitwise OR operation between the interleaved latitude and interleaved longitude, the score is obtained. - -You can view the official Redis implementation of this step [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L52). +- A description of the algorithm used, along with pseudocode +- Python code that implements the algorithm +- A set of locations & scores to test against +Here's the [repository link](https://github.com/codecrafters-io/redis-geocoding-algorithm). ### Tests @@ -168,18 +20,20 @@ The tester will execute your program like this: $ ./your_program.sh ``` -It will then send multiple `GEOADD` commands each specifying a key, laitude, longitude, and location name. - -For example, the tester might send your program a command like this. +It will then send multiple `GEOADD` commands: ```bash $ redis-cli GEOADD places 2.2944692 48.8584625 Paris # Expect: (integer) 1 +$ redis-cli GEOADD places -0.127758 51.507351 London +# Expect: (integer) 1 ``` -The tester will then send multiple `ZSCORE` commands to your program specifying the key and locations used in `GEOADD` command. For example, the tester might send your program a command like this. +The tester will validate that scores are calculated correctly by sending multiple `ZSCORE` commands: ```bash $ redis-cli ZSCORE places Paris -# Expecting bulk string: "3663832614298053" -``` \ No newline at end of file +# Expecting bulk string: "3663832614298053" +``` + +The calculated scores must match the expected values as described in [this repository](https://github.com/codecrafters-io/redis-geocoding-algorithm). From 21b4c0ce1857f874c84679832d003436cec78875 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 15:12:54 -0700 Subject: [PATCH 21/24] Update geospatial stage description with clearer RESP string references and examples. --- stage_descriptions/geospatial-05-xg4.md | 51 +++++++++++++++---------- 1 file changed, 31 insertions(+), 20 deletions(-) diff --git a/stage_descriptions/geospatial-05-xg4.md b/stage_descriptions/geospatial-05-xg4.md index 360d3351..22b60cb8 100644 --- a/stage_descriptions/geospatial-05-xg4.md +++ b/stage_descriptions/geospatial-05-xg4.md @@ -5,6 +5,7 @@ In this stage, you'll add support for responding to the `GEOPOS` command. The `GEOPOS` command returns the longitude and latitude of the specified location. Example usage: + ```bash > GEOADD places -0.0884948 51.506479 "London" > GEOADD places 11.5030378 48.164271 "Munich" @@ -22,48 +23,58 @@ It returns an array with one entry for each location requested. - If the key does not exist, an empty array is returned `(*0\r\n)` - If a location exists under the key, its entry is an array of two items: - - Longitude (Encoded as a Bulk string) - - Latitude (Encoded as a Bulk string) -- If a location doesn’t exist, the entry is a null bulk string `($-1\r\n)`. - + - Longitude (Encoded as a [RESP Bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#bulk-strings)) + - Latitude (Encoded as a [RESP Bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#bulk-strings)) +- If a location doesn’t exist, the entry is a [null bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#null-bulk-strings) `($-1\r\n)`. -Since the latitude and longitude values are calculated from the score of each location, an extra step is required. You will implement this in the next stage. For now, you only need to respond with a valid floating point numbers for latitude and longitude. For example, you may hardcode every latitude and longitude to be 0. +To return the latitude and longitude values, Redis decodes the "score" back to latitude and longitude values. We'll cover this process in later stages, for now you can hardcode the returned latitude and longitude values to be 0 (or any number). ### Tests + The tester will execute your program like this: + ```bash $ ./your_program.sh ``` -It will add multiple locations using the `ZADD` command. It will use a score which will be equivalent to a latitude and longitude. +It will then add multiple locations using the `GEOADD` command. ```bash $ redis-cli -> ZADD location_key 3477108430792699 "Foo" -> ZADD location_key 3876464048901851 "Bar" -> ZADD location_key 3468915414364476 "Baz" -> ZADD location_key 3781709020344510 "Caz" +> GEOADD location_key -0.0884948 51.506479 "London" +# Expect: (integer) 1 +> GEOADD location_key 11.5030378 48.164271 "Munich" +# Expect: (integer) 1 ``` -The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: +The tester will then send multiple `GEOPOS` commands: ```bash -> GEOPOS location_key Foo -# Expecting [["0", "0"]] +> GEOPOS location_key London Munich +# Expecting: [["0", "0"], ["0", "0"]], encoded as "*2\r\n$1\r\n0\r\n$1\r\n0\r\n$1\r\n0\r\n$1\r\n0\r\n" +> GEOPOS location_key missing_location +# Expecting: [nil], encoded as "*1\r\n$-1\r\n" ``` -It will expect the value to be a RESP array, which contains another RESP array. The elements of the inner array should be the two bulk strings. Each bulk string should be a valid floating point number when parsed. +The tester will assert that: + +- The response is a RESP array that contains as many elements as the number of locations requested +- For each location requested: + - If the location exists: + - The corresponding element is a RESP array with two elements (i.e. longitude and latitude) + - Both elements are "0" (or any other valid floating point number), encoded as a RESP bulk string + - If the location doesn't exist: + - The corresponding element is a [null bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#null-bulk-strings) `($-1\r\n)`. -The tester will also send a `GEOPOS` command using a non-existent key. +The tester will also send a `GEOPOS` command using a key that doesn't exist: ```bash -> GEOPOS non_existent_key Foo -# Expecting: *0\r\n +> GEOPOS missing_key Foo +# Expecting: [], encoded as "*0\r\n" ``` -It will expect the response to be a RESP nil array, which is encoded as `*0\r\n`. +It will expect the response to be an empty RESP array, which is encoded as `*0\r\n`. ### Notes -- In this stage, you will only implement responding to the `GEOPOS` command using valid floating point numbers. We'll get to responding with actual values of latitude and longitude in the next stage. -- You can specify any floating point value for latitude and longitude. +- In this stage, you can hardcode the returned latitude and longitude values to be 0 (or any other valid floating point number). We'll get to testing actual latitude and longitude values in later stages. From 182e0d24f58a7521e6d045313d84d0f7aac5aa0e Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 15:16:48 -0700 Subject: [PATCH 22/24] Refactor geospatial stage description for clarity and conciseness. --- stage_descriptions/geospatial-06-hb5.md | 198 +++--------------------- 1 file changed, 18 insertions(+), 180 deletions(-) diff --git a/stage_descriptions/geospatial-06-hb5.md b/stage_descriptions/geospatial-06-hb5.md index 38ec2c1c..86b51ee1 100644 --- a/stage_descriptions/geospatial-06-hb5.md +++ b/stage_descriptions/geospatial-06-hb5.md @@ -1,186 +1,26 @@ In this stage, you'll add support for decoding the coordinates of a location. -### Calculating latitude and longitude from score +### Decoding latitude and longitude from score -The algorithm to get back the latitude and longitude is essentially the reverse of the one used to compute the score from them. In other words, we undo the bit interleaving process to extract the original latitude and longitude values. This involves two major steps: +The algorithm to get back the latitude and longitude is essentially the reverse of the one used to compute the score from them. -1. De-interleaving the bits of score -2. De-normalizing the latitude and longitude +The [GitHub repository](https://github.com/codecrafters-io/redis-geocoding-algorithm) we referenced earlier explains how this conversion is done. It includes: +- A description of the algorithm used, along with pseudocode +- Python code that implements the algorithm +- A set of locations & scores to test against -#### De-interleaving bits - -The `score` is a 64-bit unsigned integer, whose bits are the interleaved bits of two 32-bit unsigned integers, which represent latitude and longitude. The digits at the odd place are the digits of longitude and those at the even place are the digits of latitude. - -For example, if `x` is the score, it is made up of - -``` -x = B63 B62 B61 .... B3 B2 B1 B0 -``` - -The bits `B63 B61 B59 ... B5 B3 B1` make up the longitude and the bits `B62 B60 B58 ... B4 B2 B0` make up the latitude. The end goal here is to de-interleave the bits so that the final result looks like this: - -``` -longitude = B63 B61 B59 ... B5 B3 B1 -latitude = B62 B60 B58 ... B4 B2 B0 -``` - -The pseudocode below shows how this is done. - -```python -def deinterleave_bits(score: uint64) -> (uint64, uint64): - -# Right shift - uint64_t lat = score - uint64_t lon = score >> 1 - -# Let's see how the bits of latitude are extracted. Suppose the score is 3781709020344510. Let us refer the latitude as x for simplicity. - - - lat = (lat | (lat >> 0)) & 0x5555555555555555 - lon = (lon | (lon >> 0)) & 0x5555555555555555 -# Step-1 -# Let us see the application of the first step - -# x = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 -# x >> 0 = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 - -# x | (x >> 0) = 00 00 00 00 00 00 11 01 01 10 11 11 01 11 00 01 10 11 11 10 11 00 11 01 00 11 00 00 10 11 11 10 -# 0x5555555555555555 = 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 -# ----------------------------------------------------------------------------------------------------------------------- -# BITWISE AND = 00 00 00 00 00 00 01 01 01 00 01 01 01 01 00 01 00 01 01 00 01 00 01 01 00 01 00 00 00 01 01 00 - -# In the first step, x is right shifted by 0, which means up until the stage x | (x >> 0), the original number stays the same. Now, the constant 0x5555555555555555 is carefully picked so that all of its bits at the even position are 1 and the odd position are 0. So, when x is operated with this constant using bitwise AND, only the bits of even position(bits of latitude) are retained. The bits of latitude haven't changed their position, but the bits of longitude has been set to 0 after this step. - - - lat = (lat | (lat >> 1)) & 0x3333333333333333 - lon = (lon | (lon >> 1)) & 0x3333333333333333 -# Step-2 -# Let's re-write this in groups of 4 to make it easier, and see the application of the second step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. - -# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -# x = 0000 0000 0000 0101 0100 0101 0101 0001 0001 0100 0100 0101 0001 0000 0001 0100 -# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * -# x >> 1 = 0000 0000 0000 0010 1010 0010 1010 1000 1000 1010 0010 0010 1000 1000 0000 1010 - -# x | (x >> 1) = 0000 0000 0000 0111 1110 0111 1111 1001 1001 1110 0110 0111 1001 1000 0001 1110 -# 0x3333333333333333 = 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 -# ------------------------------------------------------------------------------------------------------- -# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -# BITWISE AND = 0000 0000 0000 0011 0010 0011 0011 0001 0001 0010 0010 0011 0001 0000 0001 0010 - -# In this step, x is right-shifted by 1 and operated with bitwisse OR with the original value of x. This ensures that bits which were originally one positions apart (i.e., bit 0 and bit 2, bit 1 and bit 3, etc.) are grouped together. This is because (x | 0) = x. This applies to every odd positioned bit. It ensure that the signifcant bit after that is moved to its position because of the right shift operator. The constant has been carefully chosen to convinently ignore values which are not the original bits of x. - -# Then, bitwise AND is applied with 0x3333333333333333. This constant has the pattern 0011 0011... 0011, which isolates pairs of bits at every 4-bit block by setting other bits to 0. - -# The result of this steps consists of 4 16-bit blocks. The 2 least significant bits of each 4-bit block are the digits of latitude. The result is one step closer to the full re-construction. - - - lat = (lat | (lat >> 2)) & 0x0F0F0F0F0F0F0F0F - lon = (lon | (lon >> 2)) & 0x0F0F0F0F0F0F0F0F -##### Step-3 -# Let's re-write the result into 8 8-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. - -# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -# x = 00000000 00000011 00100011 00110001 00010010 00100011 00010000 00010010 -# ** ** ** ** ** ** ** ** ** ** ** ** ** ** ** -# x >> 2 = 00000000 00000000 11001000 11001100 01000100 10001000 11000100 00000100 - -# (x | (x >> 2)) = 00000000 00000011 11101011 11111101 01010110 10101011 11010100 00010110 -# 0x0F0F0F0F0F0F0F0F = 00001111 00001111 00001111 00001111 00001111 00001111 00001111 00001111 -# ------------------------------------------------------------------------------------------------ -# **** **** **** **** **** **** **** **** -# BITWISE AND = 00000000 00000000 00001000 00001100 00000100 00001000 00000100 00000100 - -# In this step, the process of collapsing the scattered bits of latitude closer to their original positions continues. The bits that were originally two positions apart are re-grouped. To do this, x is right-shift by 2 and operated using bitwise OR with the original value of x. Here, the constant 0x0F0F0F0F0F0F0F0F has the pattern 00001111 00001111 .... 00001111 repeated across all bytes. This selects only the lowest 4 bits of each 8-bit block and masks out the rest. Since two bits at a time were grouped previously, this step now gathers 4 bits that originally belonged together into a single nibble (4-bit group). - -# As a result, the bits of latitude are now packed one nibble per byte, with the lowest significant nibble of each 8 bit-block representing the digits of latitude. - - - lat = (lat | (lat >> 4)) & 0x00FF00FF00FF00FF - lon = (lon | (lon >> 4)) & 0x00FF00FF00FF00FF -# Step-4 -# Let's re-write the result into 4 16-bit groups for clarity and see the application of the third step. A asterisk above a bit indicates that it is the digit of latitude, and a empty space means the bit below it is zero. - -# **** **** **** **** **** **** **** **** -# x = 0000000000000011 0010001100110001 0001001000100011 0001000000010010 -# **** **** **** **** **** **** **** -# x >> 4 = 0000000000000000 0011001000110011 0001000100100010 0011000100000001 - -# (x | (x >> 4)) = 0000000000000011 0011001100110011 0001001100100011 0011000100010011 -# 0x0F0F0F0F0F0F0F0F = 0000000011111111 0000000011111111 0000000011111111 0000000011111111 -# -------------------------------------------------------------------------------------------- -# ******** ******** ******** ******** -# BITWISE AND = 0000000000000011 0000000000110011 0000000000100011 0000000000010011 - -# In this step the bits that were originally four positions apart are grouped into a single 8-bit block. To do this, x is right right-shift by 4 and operated using bitwise OR with the original value of x. Here, the constant 0x0F0F0F0F0F0F0F0F has the pattern 0000000011111111 ... 0000000011111111 repeated across all bytes. This selects only the lowest 8 bits of each 16-bit block and masks out the rest. Since four bits were grouped at a time previously, this step now gathers 8 bits that originally belonged together into a 16 bit block. As a result, the bits of latitude are now packed in 8-bit block per 16-bit block, with the lowest significant 8 bits of each 16 bit-block representing the digits of latitude, and the upper 8 bits all being 0. - - -# Each step shifts and combines progressively wider chunks, essentially "unzipping" the interleaved bits by peeling off odd bits and regrouping even ones back into their original layout. - -# After two more steps, the result will essentially be a 64 bit integer, whose 32 most significant bits are all zeros and 32 least significant digits are the digits of the latitude. - -# The same steps are repeated for longitude, except that at the very first step, the digits of longitude are shifted by 1 because the digits of longitude are stored in odd positions. Shifting it to the right by 1 brings all the digits of longitude in the even position. This implies that the same steps can be applied to longitude as that are applied for the latitude. - lat = (lat | (lat >> 8)) & 0x0000FFFF0000FFFF - lon = (lon | (lon >> 8)) & 0x0000FFFF0000FFFF - - lat = (lat | (lat >> 16)) & 0x00000000FFFFFFFF - lon = (lon | (lon >> 16)) & 0x00000000FFFFFFFF - - return lat, lon -``` - -You can see how this step is implemented in the official Redis source [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L82). - - -#### De-normalizing the latitude and longitude - -After the computation of the previous step, both latitude and longitude will have been obtained. But, they are in the range of \[0, 2^26\). To convert them to valid values, they should be normalized to the range of \[0, 1\) and multiplied by their respective ranges, and added with respective offsets to obtain the actual values of latitude and longitude. - -The pseudocode below shows how this is done: - -```python -def convert_to_lat_lon(latitude: uint64, longitude: uint64) - double lat_scale = LATITUDE_MAX - LATITUDE_MIN - double long_scale = LONGITUDE_MAX - LONGITUDE_MIN - - # Calculate lower boundary of grid - latitude_min = LATITUDE_MIN + (latitude * 1.0 / (1 << 26)) * lat_scale - latitude_max = LATITUDE_MAX + ((latitude + 1) * 1.0 / (1 << 26)) * lat_scale - - # Calculate upper boundary of grid - longitude_min = LONGITUDE_MIN + (longitude * 1.0 / (1 << 26)) * long_scale - longitude_max = LONGITUDE_MAX + ((longitude + 1) * 1.0 / (1 << 26)) * long_scale - - # Calculate average for best approximation - lat = (latitude_min + latitude_max) / 2 - lon = (longitude_min + longitude_max) / 2 - - return lat, lon -``` - -The values of the constants are given below: - -``` -LATITUDE_MIN = -85.05112878 -LATITUDE_MAX = 85.05112878 -LONGITUDE_MIN = -180 -LONGITUDE_MAX = 180 -``` - -The minimum and maximum values of latitude and longitude are averaged because the encoded score represents a rectangular region on the Earth's surface, not a single point. This region is a cell in a grid formed by subdividing the full geographic coordinate space. Averaging the boundaries gives the center of that cell — the best approximation of the original coordinate that was encoded. You can learn more about this visually by using the [GeoHash Explorer](https://geohash.softeng.co/). - - -You an see how this is implemented in the official Redis source [here](https://github.com/redis/redis/blob/ff2f0b092c24d5cc590ff1eb596fc0865e0fb721/src/geohash.c#L181). - +Here's the [repository link](https://github.com/codecrafters-io/redis-geocoding-algorithm). ### Tests + The tester will execute your program like this: + ```bash $ ./your_program.sh ``` -It will add multiple locations using the `ZADD` command. It will use a score which will be equivalent to a latitude and longitude. +It will add multiple locations using the `ZADD` command. The scores used are valid scores that can be converted back to latitude and longitude values. ```bash $ redis-cli @@ -190,14 +30,15 @@ $ redis-cli > ZADD location_key 3781709020344510 "Caz" ``` -The tester will then send multiple `GEOPOS` commands, each specifying a single location that may or may not have been added. For example, the tester might send your program a command like this: +The tester will then send multiple `GEOPOS` commands: ```bash > GEOPOS location_key Foo # Expecting [["2.294471561908722", "48.85846255040141"]] ``` -The value is a RESP array, which is encoded as +The tester will validate that the response is a RESP array, which is encoded as: + ``` *1\r\n *2\r\n @@ -209,11 +50,8 @@ $17\r\n ### Notes -- The tester will be lenient in checking the coordinates provided. The latitude and longitude returned by the server should match the values provided in the `GEOADD` command with a precision of up to 4 decimal places when rounded off. - - - For example, for the response of the example shown above, any of the following will be accepted: - - - `2.2945 48.8585` - - `2.2945001 48.8585002` - - `2.29447156190 48.858462550` - - `2.29449 48.858453` \ No newline at end of file +- The conversion from latitude/longitude to score and back is lossy, so the tester will be lenient in checking the coordinates provided - it will accept any coordinates that are within 4 decimal places of the original values. For example, for the example shown above, any of the following values will be accepted: + - `["2.2945", "48.8585"]` + - `["2.2945001", "48.8585002"]` + - `["2.29447156190", "48.858462550"]` + - `["2.29449", "48.858453"]` From ae848128e3f2caca194b16e2600be2e8710627d9 Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 15:19:12 -0700 Subject: [PATCH 23/24] Fix typos and improve clarity in geospatial stage description for GEODIST. --- stage_descriptions/geospatial-07-ek6.md | 41 +++++++++++-------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/stage_descriptions/geospatial-07-ek6.md b/stage_descriptions/geospatial-07-ek6.md index cbee6023..58183dea 100644 --- a/stage_descriptions/geospatial-07-ek6.md +++ b/stage_descriptions/geospatial-07-ek6.md @@ -1,7 +1,8 @@ -In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` comamnd. +In this stage, you'll add support for calculating the distance between two locations using the `GEODIST` command. ### The `GEODIST` command -The `GEODIST` command returns the distance between two members of a key. The default unit of the distance which is returned, is meters. + +The `GEODIST` command returns the distance between two members of a key. Example usage: @@ -10,50 +11,44 @@ Example usage: "682477.7582" ``` -It returns the distance as a string, encoded as a RESP Bulk String. +The distance is returned in meters, encoded as a [RESP bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#bulk-strings). Redis uses the [Haversine's Formula](https://en.wikipedia.org/wiki/Haversine_formula#Example) to calculate the distance between two points. You can see how this is done in the Redis source code [here](https://github.com/redis/redis/blob/4322cebc1764d433b3fce3b3a108252648bf59e7/src/geohash_helper.c#L228C1-L228C72). ### Tests + The tester will execute your program like this: + ```bash $ ./your_program.sh ``` -It will add multiple locations using the `GEOADD` command. +It will then add multiple locations using the `GEOADD` command: + ```bash $ redis-cli > GEOADD places 11.5030378 48.164271 "Munich" > GEOADD places 2.2944692 48.8584625 "Paris" ``` -The tester will then send multiple `GEODIST` commands specifying two locations. For example, the tester might send your program a command like this: +The tester will then send multiple `GEODIST` commands specifying two locations: ```bash > GEODIST places Munich Paris # Expecting "682477.7582" ``` -The value is a RESP bulk string encoded as: +The tester will validate that the response is a RESP bulk string that contains the distance between the two locations, for example: -``` -$11\r\n -682477.7582\r\n +```bash +$11\r\n682477.7582\r\n ``` ### Notes -- The tester will be lenient when validating the distance returned by the `GEODIST` command. The distance should match the actual distance between the provided locations with a precision of **up to 2 decimal places after rounding**. - - * This means that minor floating-point differences are acceptable as long as the values, when rounded to two decimal places, are the same. - - * For example, if the expected distance is `12345.67`, any of the following returned values will be accepted: - - * `12345.67001` - * `12345.674` - * `12345.6659` - * `12345.6666` - * `12345.669` - - * However, values like `12345.64`, `12345.70`, or `12345.61` would be considered incorrect. -- If one or both of the location specified in the `GEODIST` command does not exist, it should return a null bulk string `($-1\r\n)`. \ No newline at end of file +- The distance should match the actual distance between the provided locations with a precision of **up to 2 decimal places after rounding**. For example, if the expected distance is `12345.67`, any of the following returned values will be accepted: + - `12345.67001` + - `12345.674` + - `12345.6659` + - `12345.6666` + - `12345.669` From c1e00ac995ea60228716d83f0a54f94b6d112cab Mon Sep 17 00:00:00 2001 From: Paul Kuruvilla Date: Wed, 13 Aug 2025 15:23:53 -0700 Subject: [PATCH 24/24] Update geospatial stage description for GEOSEARCH command usage and examples. --- stage_descriptions/geospatial-08-rm9.md | 31 +++++++++++++++---------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/stage_descriptions/geospatial-08-rm9.md b/stage_descriptions/geospatial-08-rm9.md index 55a01465..b421ebc1 100644 --- a/stage_descriptions/geospatial-08-rm9.md +++ b/stage_descriptions/geospatial-08-rm9.md @@ -1,26 +1,31 @@ -In this stage, you'll add support for searching locations near a coordinate within a given radius using the `GEOSEARCH` command. +In this stage, you'll add support for searching locations within a given radius using the `GEOSEARCH` command. ### The GEOSEARCH command -The `GEOSEARCH` command lets you search for locations near a given coordinate within a specified area. +The `GEOSEARCH` command lets you search for locations within a given radius. -It supports several search modes. In our implementation, we'll focus only on the `FROMLONLAT` mode with distance unit in meters. The `FROMLONLAT` mode searching by directly specifying longitude and latitude. +It supports several search modes. In our implementation, we'll focus only on the `FROMLONLAT` mode. The `FROMLONLAT` mode searches by directly specifying longitude and latitude. -For example, to search for locations within 100000 meters of the point (longitude: 2, latitude: 48) stored in the `places` key, you can use: +Example usage: ```bash -> GEOSEARCH places FROMLONLAT 2 48 BYRADIUS 100000 m +> GEOSEARCH places FROMLONLAT 2 48 BYRADIUS 100 m 1) "Paris" ``` +The example command above searches for locations in the `places` key that are within 100 meters of the point (longitude: 2, latitude: 48). + +The response is a RESP array containing the names of the locations that match the search criteria, each encoded as a [RESP bulk string](https://redis.io/docs/latest/develop/reference/protocol-spec/#bulk-strings). + +Note that there are two options we passed to the command: + - `FROMLONLAT ` — This option specifies the center point for the search. - `BYRADIUS ` — This option searches within a circular area of the given radius and unit (m, km, mi, etc.). -Redis supports other values for search origin option and shape option, but here we'll only use `FROMLONLAT` and `BYRADIUS`. - -It returns a RESP Array of member names, where each member's name is a encoded as a bulk string. +Redis supports other such options, but in this challenge we'll only use `FROMLONLAT` and `BYRADIUS`. ### Tests + The tester will execute your program like this: ```bash @@ -36,7 +41,7 @@ $ redis-cli > GEOADD places -0.0884948 51.506479 "London" ``` -The tester will then send multiple `GEOSEARCH` commands specifying a latitude and longitude pair with `BYRADIUS` option specifying the distance and unit. For example, it may send the following command. +The tester will then send multiple `GEOSEARCH` commands: ```bash > GEOSEARCH places FROMLONLAT 2 48 BYRADIUS 100000 m @@ -49,7 +54,7 @@ The tester will then send multiple `GEOSEARCH` commands specifying a latitude an # Expecting ["Munich"] ``` -The value is a RESP array, which is encoded as: +The tester will validate that the response is a RESP array, for example ``` *2\r\n @@ -59,6 +64,8 @@ $6\r\n London\r\n ``` +Locations can be returned in any order. + ### Notes -- The tester will only test using the `meters` unit. -- The locations returned can be in any order since we are not implementing `ASC` or `DESC` option. \ No newline at end of file + +- The tester will always use the `FROMLONLAT` and `BYRADIUS` options when sending a `GEOSEARCH` command.