In-Memory Database
Learn this problemProblem statement
Your task is to implement a simplified version of an in-memory database. All operations that should be supported by this database are described below.
Solving this task consists of several levels. Subsequent levels are opened when the current level is correctly solved. You always have access to the data for the current and all previous levels.
You are not required to provide the most efficient implementation. Any code that passes the unit tests is sufficient.
Level 1
The basic level of the in-memory database contains records. Each record can be accessed with a unique identifier key of string type. A record may contain several field-value pairs, both of which are of string type.
void set(String key, String field, String value)— should insert afield-valuepair to the record associated withkey. If thefieldin the record already exists, replace the existing value with the specifiedvalue. If the record does not exist, create a new one.Optional<String> get(String key, String field)— should return the value contained withinfieldof the record associated withkey. If the record or thefielddoes not exist, should returnOptional.empty().boolean delete(String key, String field)— should remove thefieldfrom the record associated withkey. Returnstrueif the field was successfully deleted, andfalseif thekeyor thefielddo not exist in the database.
Level 2
The database should support displaying data based on filters. Introduce an operation to support printing some fields of a record.
List<String> scan(String key)— should return a list of strings representing the fields of a record associated withkey. The returned list should be in the following format["<field_1>(<value_1>)", "<field_2>(<value_2>)", ...], where fields are sorted lexicographically. If the specified record does not exist, returns an empty list.List<String> scanByPrefix(String key, String prefix)— should return a list of strings representing some fields of a record associated withkey. Specifically, only fields that start withprefixshould be included. The returned list should be in the same format as in thescanoperation with fields sorted in lexicographical order.
Level 3
Support the timeline of operations and TTL (Time-To-Live) settings for records and fields. Each operation from previous levels now has an alternative version with a timestamp parameter to represent when the operation was executed. For each field-value pair in the database, the TTL determines how long that value will persist before being removed.
Time should always flow forward, so timestamps are guaranteed to strictly increase as operations are executed.
Each test cannot contain both versions of operations (with and without timestamp). However, you should maintain backward compatibility, so all previously defined methods should work in the same way as before.
void setAt(String key, String field, String value, int timestamp)— should insert afield-valuepair or update the value of thefieldin the record associated withkey.void setAtWithTtl(String key, String field, String value, int timestamp, int ttl)— should insert afield-valuepair or update the value of thefieldin the record associated withkey. Also sets its Time-To-Live starting attimestampto bettl. Thettlis the amount of time that thisfield-valuepair should exist in the database, meaning it will be available during this interval:[timestamp, timestamp + ttl).boolean deleteAt(String key, String field, int timestamp)— the same asdelete, but with timestamp of the operation specified.Optional<String> getAt(String key, String field, int timestamp)— the same asget, but with timestamp of the operation specified.List<String> scanAt(String key, int timestamp)— the same asscan, but with timestamp of the operation specified.List<String> scanByPrefixAt(String key, String prefix, int timestamp)— the same asscanByPrefix, but with timestamp of the operation specified.
Level 4
The database should be backed up from time to time. Introduce operations to support backing up and restoring the database state based on timestamps. When restoring, ttl expiration times should be recalculated accordingly.
int backup(int timestamp)— should save the database state at the specified timestamp, including the remainingttlfor all records and fields. Remainingttlis the difference between their initialttland their current lifespan. Returns the number of non-empty non-expired records in the database.void restore(int timestamp, int timestampToRestore)— should restore the database from the latest backup before or attimestampToRestore. It is guaranteed that a backup before or attimestampToRestorewill exist. Expiration times for restored records and fields should be recalculated according to the timestamp of this operation. Since the database timeline always flows forward, restored records and fields should expire after thetimestampof this operation, depending on their remainingttls at backup.
FastPrep interface
Complete runInMemoryDatabase. The operations parameter is an array of string arrays. Process the rows in order and return one result row for every operation.
["set", key, field, value]["get", key, field]["delete", key, field]["scan", key]["scanByPrefix", key, prefix]["setAt", key, field, value, timestamp]["setAtWithTtl", key, field, value, timestamp, ttl]["deleteAt", key, field, timestamp]["getAt", key, field, timestamp]["scanAt", key, timestamp]["scanByPrefixAt", key, prefix, timestamp]["backup", timestamp]["restore", timestamp, timestampToRestore]
Timestamps and TTLs are base-10 integer strings. Return an empty row for set, setAt, setAtWithTtl, and restore. Return a one-element row for get or getAt when a value exists, and an empty row otherwise. Return ["true"] or ["false"] for delete operations. Scan operations return their formatted fields directly. backup returns the record count as a one-element decimal-string row.
Function
runInMemoryDatabase(operations: String[][]) → String[][]Examples
Example 1
operations = [["set","A","B","E"],["set","A","C","F"],["get","A","B"],["get","A","D"],["delete","A","B"],["delete","A","D"]]return = [[],[],["E"],[],["true"],["false"]]The first two operations create fields B and C. The first get returns E, the second returns no value, deleting B returns true, and deleting missing field D returns false.
Example 2
operations = [["set","A","BC","E"],["set","A","BD","F"],["set","A","C","G"],["scanByPrefix","A","B"],["scan","A"],["scanByPrefix","B","B"]]return = [[],[],[],["BC(E)","BD(F)"],["BC(E)","BD(F)","C(G)"],[]]The prefix scan returns fields BC and BD. The full scan also returns C. Record B does not exist, so its scan result is empty.
Example 3
operations = [["setAtWithTtl","A","B","C","1","10"],["backup","3"],["setAt","A","D","E","4"],["backup","5"],["deleteAt","A","B","8"],["backup","9"],["restore","10","7"],["backup","11"],["scanAt","A","15"],["scanAt","A","16"]]return = [[],["1"],[],["1"],["true"],["1"],[],["1"],["B(C)","D(E)"],["D(E)"]]restore(10, 7) selects the backup at timestamp 5. Field B had 6 units of TTL remaining, so after restoration it expires at timestamp 16. Field D does not expire.
Constraints
- Time should always flow forward, so timestamps are guaranteed to strictly increase as operations are executed.
- Each test cannot contain both versions of operations (with and without timestamp).
- Every operation row uses one of the listed adapter formats.
- Keys, fields, values, and prefixes are strings.
- The source execution time limit is 3 seconds.
- The source memory limit is 4 GB.