Your task is to implement a simple cloud storage system that maps objects (files) to their metainformation. Specifically, the storage system should maintain files along with some information about them (name, size, etc.).
Note that this system should be in-memory, you do not need to work with the real filesystem.
Plan your design according to the level specifications below:
Level 1: The cloud storage system should support adding new files, retrieving, and copying files
Level 2: The cloud storage system should support finding files by matching prefixes and suffixes
Level 3: The cloud storage system should support adding users with various capacity limits
Level 4: The cloud storage system should support compressing and decompressing files
To move to the next level, you need to pass all the tests at this level.
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.
Input: You will receive a list of queries (array of string arrays) to the system.
Output: Return an array of strings representing the returned values of all queries.
Note:
Each query will only call one operation
It is guaranteed that the given queries will never call operations that result in collisions between file and directory names
The cloud storage system should support operations to add files, copy files, and get files stored on the system.
ADD_FILE
ADD_FILE <name> <size>
Adds a new file name to the storage
size is the amount of memory required in bytes
The operation fails if a file with the same name already exists
Returns "true" if the file was added successfully, or "false" otherwise
COPY_FILE
COPY_FILE <nameFrom> <nameTo>
Copies the file at nameFrom to nameTo
The operation fails if:
nameFrom points to a file that does not exist
nameFrom points to a directory
The specified file already exists at nameTo
Returns "true" if the file was copied successfully, or "false" otherwise
GET_FILE_SIZE
GET_FILE_SIZE <name>
Returns a string representing the size of the file name if it exists
Returns an empty string "" if the file does not exist
Queries:
[
["ADD_FILE", "/dir1/dir2/file.txt", "10"],
["COPY_FILE", "/root-existing_file.txt", "/dir1/file.txt"],
["COPY_FILE", "/dir1/dir2/file.txt", "/dir1/file.txt"],
["ADD_FILE", "/dir1/file.txt", "15"],
["COPY_FILE", "/dir1/file.txt", "/dir1/dir2/file.txt"],
["GET_FILE_SIZE", "/dir1/file.txt"],
["GET_FILE_SIZE", "/not-existing.file"]
]
Execution:
Output: ["true", "false", "true", "false", "false", "10", ""]
Implement support for retrieving file names by searching directories via prefixes and suffixes.
FIND_FILE
FIND_FILE <prefix> <suffix>
Searches for files with names starting with prefix and ending with suffix
Returns a string representing all matching files in this format:
"<name1>(<size1>), <name2>(<size2>), ..."
Output should be sorted in descending order of file sizes
In case of ties (same file size), sort lexicographically by file name
If no files match the required properties, returns an empty string ""
Queries:
[
["ADD_FILE", "/root/dir/another_dir/file.mp3", "10"],
["ADD_FILE", "/root/file.mp3", "5"],
["ADD_FILE", "/root/music/file.mp3", "7"],
["COPY_FILE", "/root/music/file.mp3", "/root/dir/file.mp3"],
["FIND_FILE", "/root", ".mp3"],
["FIND_FILE", "/root", "file.txt"],
["FIND_FILE", "/dir", "file.mp3"]
]
Execution:
Output: ["true", "true", "true", "true", "/root/dir/another_dir/file.mp3(10), /root/dir/file.mp3(7), /root/music/file.mp3(7), /root/file.mp3(5)", "", ""]
Implement support for different users sending queries to the system. All users share a common filesystem in the cloud storage, but each user is assigned an individual storage capacity limit.
ADD_USER
ADD_USER <userId> <capacity>
Adds a new user to the system with userId and capacity as their storage limit in bytes
The total size of all files owned by userId cannot exceed capacity
The operation fails if a user with userId already exists
Returns "true" if a user is successfully created, or "false" otherwise
ADD_FILE_BY
ADD_FILE_BY <userId> <name> <size>
Behaves the same way as the ADD_FILE operation from Level 1
The added file should be owned by the user with userId
A new file cannot be added to the storage if doing so will exceed the user's capacity limit
Returns a string representing the remaining storage capacity for userId if the file is successfully added
Returns an empty string "" otherwise
Note: All queries calling the ADD_FILE operation implemented during Level 1 are run by the user with userId = "admin", who has unlimited storage capacity. Also assume that the COPY_FILE operation preserves the ownership of the original file, and copying a file owned by a non-admin user is allowed only if that user has enough remaining capacity for the copy.
UPDATE_CAPACITY
UPDATE_CAPACITY <userId> <capacity>
Changes the maximum storage capacity for the user with userId
If the total size of all user's files exceeds the new capacity, the largest files (sorted lexicographically in case of a tie) should be removed from the storage until the total size of all remaining files no longer exceeds the new capacity
Returns a string representing the number of removed files
Returns an empty string "" if a user with userId does not exist
Queries:
[
["ADD_USER", "user1", "125"],
["ADD_USER", "user1", "100"],
["ADD_USER", "user2", "100"],
["ADD_FILE_BY", "user1", "/dir/file.big", "50"],
["ADD_FILE_BY", "user1", "/file.med", "30"],
["ADD_FILE_BY", "user2", "/file.med", "40"],
["COPY_FILE", "/file.med", "/dir/another/file.med"],
["COPY_FILE", "/file.med", "/dir/another/another/file.med"],
["ADD_FILE_BY", "user1", "/dir/file.small", "10"],
["ADD_FILE", "/dir/admin_file", "200"],
["ADD_FILE_BY", "user1", "/dir/file.small", "5"],
["ADD_FILE_BY", "user1", "/my_folder/file.huge", "100"],
["ADD_FILE_BY", "user3", "/my_folder/file.huge", "100"],
["UPDATE_CAPACITY", "user1", "300"],
["UPDATE_CAPACITY", "user1", "50"],
["UPDATE_CAPACITY", "user2", "1000"]
]
Execution:
Output: ["true", "false", "true", "75", "45", "", "true", "false", "5", "true", "", "", "", "0", "2", "0"]
Implement support for file compression.
COMPRESS_FILE
COMPRESS_FILE <userId> <name>
Compresses the file name if it belongs to userId
The compressed file is replaced with a new file named <name>.COMPRESSED
The size of the newly created file should be equal to half of the original file
The size of all files is guaranteed to be even, so there should be no fractional calculations
It is also guaranteed that name never ends with .COMPRESSED (for this operation)
Compressed files cannot be compressed again - i.e., it never ends with .COMPRESSED
Compressed files cannot be added via ADD_FILE (because file names can only contain lowercase letters)
Returns a string representing the remaining storage capacity for userId if the file was compressed successfully
Returns an empty string "" otherwise
Note: Because file names can only contain lowercase letters, compressed files cannot be added via ADD_FILE.
It is guaranteed that all COPY_FILE operations will preserve the suffix .COMPRESSED.
DECOMPRESS_FILE
DECOMPRESS_FILE <userId> <name>
Reverts the compression of the file name if it belongs to userId
It is guaranteed that name always ends with .COMPRESSED for this operation
If decompression results in the userId exceeding their storage capacity limit OR a decompressed version of the file with the given name already exists, the operation fails
Returns a string representing the remaining capacity of userId if the file was decompressed successfully
Returns an empty string "" otherwise
Queries:
[
["ADD_USER", "user1", "1000"],
["ADD_USER", "user2", "5000"],
["ADD_FILE_BY", "user1", "/dir/file.mp4", "500"],
["COMPRESS_FILE", "user2", "/dir/file.mp4"],
["COMPRESS_FILE", "user3", "/dir/file.mp4"],
["COMPRESS_FILE", "user1", "/folder/non_existing_file"],
["COMPRESS_FILE", "user1", "/dir/file.mp4"],
["GET_FILE_SIZE", "/dir/file.mp4.COMPRESSED"],
["GET_FILE_SIZE", "/dir/file.mp4"],
["COPY_FILE", "/dir/file.mp4.COMPRESSED", "/file.mp4.COMPRESSED"],
["ADD_FILE_BY", "user1", "/dir/file.mp4", "500"],
["DECOMPRESS_FILE", "user1", "/dir/file.mp4.COMPRESSED"],
["UPDATE_CAPACITY", "user1", "2000"],
["DECOMPRESS_FILE", "user2", "/dir/file.mp4.COMPRESSED"],
["DECOMPRESS_FILE", "user3", "/dir/file.mp4.COMPRESSED"],
["DECOMPRESS_FILE", "user1", "/dir/file.mp4.COMPRESSED"],
["DECOMPRESS_FILE", "user1", "/file.mp4.COMPRESSED"]
]
Execution:
Output: ["true", "true", "500", "", "", "", "750", "250", "", "true", "0", "", "0", "", "", "", "750"]
✦ AI-Generated Solution · Coding (OA) · Comprehensive Full level-by-level reference implementation. Verified: this code reproduces every official example output in the problem statement (all 4 levels pass).
These CodeSignal-style multi-level OAs reward a clean, extensible data model over clever tricks. Pick state that survives all four levels up front so each new level is an additive method, not a rewrite:
files dict: name -> {size, owner}. (Directories are implicit — only files are stored, and the problem guarantees no name collisions.)users dict: userId -> {cap, used}, seeded with admin at infinite capacity so Level-1 ADD_FILE is just ADD_FILE_BY("admin", ...).Everything else (prefix/suffix search, capacity eviction, compression) reads or mutates those two structures.
class CloudStorage:
def __init__(self):
self.files = {} # name -> {"size", "owner"}
self.users = {"admin": {"cap": float("inf"), "used": 0}}
# ---------- Level 1: add / copy / size ----------
def add_file(self, name, size):
return self._add( "admin", name, int(size), level1=True)
def _add(self, user, name, size, level1=False):
if name in self.files:
return "false" if level1 else ""
u = self.users.get(user)
if u is None or u["used"] + size > u["cap"]:
return "false" if level1 else ""
self.files[name] = {"size": size, "owner": user}
u["used"] += size
return "true" if level1 else str(int(u["cap"] - u["used"]))
def copy_file(self, src, dst):
if src not in self.files or dst in self.files:
return "false"
f = self.files[src]; u = self.users[f["owner"]]
if u["used"] + f["size"] > u["cap"]: # copy keeps original owner
return "false"
self.files[dst] = {"size": f["size"], "owner": f["owner"]}
u["used"] += f["size"]
return "true"
def get_file_size(self, name):
return str(self.files[name]["size"]) if name in self.files else ""
# ---------- Level 2: prefix + suffix search ----------
def find_file(self, prefix, suffix):
hits = [(n, f["size"]) for n, f in self.files.items()
if n.startswith(prefix) and n.endswith(suffix)]
hits.sort(key=lambda x: (-x[1], x[0])) # size desc, then name asc
return ", ".join(f"{n}({s})" for n, s in hits)
# ---------- Level 3: users + capacity ----------
def add_user(self, user, cap):
if user in self.users:
return "false"
self.users[user] = {"cap": int(cap), "used": 0}
return "true"
def add_file_by(self, user, name, size):
return self._add(user, name, int(size))
def update_capacity(self, user, cap):
u = self.users.get(user)
if u is None:
return ""
u["cap"] = cap = int(cap)
if u["used"] <= cap:
return "0"
owned = [(n, f["size"]) for n, f in self.files.items() if f["owner"] == user]
owned.sort(key=lambda x: (-x[1], x[0])) # largest first; tie -> name asc
removed = 0
for n, s in owned:
if u["used"] <= cap:
break
del self.files[n]; u["used"] -= s; removed += 1
return str(removed)
# ---------- Level 4: compress / decompress ----------
def compress_file(self, user, name):
if name not in self.files or self.files[name]["owner"] != user:
return ""
comp = name + ".COMPRESSED"
if comp in self.files:
return ""
f = self.files.pop(name); u = self.users[user]
u["used"] -= f["size"]
half = f["size"] // 2 # sizes guaranteed even
self.files[comp] = {"size": half, "owner": user}
u["used"] += half
return str(int(u["cap"] - u["used"]))
def decompress_file(self, user, name):
if name not in self.files or self.files[name]["owner"] != user:
return ""
orig = name[:-len(".COMPRESSED")]
if orig in self.files: # decompressed twin already exists
return ""
f = self.files[name]; u = self.users[user]; full = f["size"] * 2
if u["used"] - f["size"] + full > u["cap"]: # would exceed capacity
return ""
del self.files[name]; u["used"] -= f["size"]
self.files[orig] = {"size": full, "owner": user}
u["used"] += full
return str(int(u["cap"] - u["used"]))
UPDATE_CAPACITY eviction order: remove largest files first; break ties by lexicographically smaller name. Return the count removed, "" only if the user doesn't exist (returning "0" when nothing is removed is correct).used field), not recomputed by scanning files each call — recomputation is correct but O(N) per query and risks TLE at Level 3/4 volumes.full - half), and also fails if the decompressed name already exists.ADD_FILE_BY returns remaining capacity as a string, while Level-1 ADD_FILE returns "true"/"false". Same internal path, different return contract — keep them separate.add/copy/get/compress/decompress: O(1) (dict ops).find_file: O(N log N) in the number of stored files (scan + sort of matches).update_capacity: O(N log N) to sort the user's owned files; eviction itself is O(k).Running the four official example query sequences from the prompt through CloudStorage reproduces every expected output exactly:
Level 1: PASS -> ["true","false","true","false","false","10",""]
Level 2: PASS -> [...,"/root/dir/another_dir/file.mp3(10), /root/dir/file.mp3(7), /root/music/file.mp3(7), /root/file.mp3(5)","",""]
Level 3: PASS -> ["true","false","true","75","45","","true","false","5","true","","","","0","2","0"]
Level 4: PASS -> ["true","true","500","","","","750","250","","true","0","","0","","","","750"]