Skip to content

Commit 90aed78

Browse files
committed
In MemoryDB
1 parent 7ca43aa commit 90aed78

3 files changed

Lines changed: 196 additions & 113 deletions

File tree

src/main/java/com/codingsignaltest/inMemoryDB/InMemoryDB.java

Lines changed: 101 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -3,155 +3,143 @@
33
import java.util.*;
44

55
public class InMemoryDB {
6-
Map<String, Record> records = new HashMap<>();
7-
TreeMap<Long, Map<String, Record>> backups = new TreeMap<>();
86

9-
void set(long timestamp, String key, String field, int value) {
10-
records.computeIfAbsent(key, k -> new Record())
11-
.data.put(field, new Value(value, timestamp, null));
12-
}
7+
private Map<String,Map<String,Value>> store;
138

14-
void setAt(String key, String field, int value, long timestampToSet) {
15-
records.computeIfAbsent(key, k -> new Record())
16-
.data.put(field, new Value(value, timestampToSet, null));
9+
public InMemoryDB() {
10+
this.store = new HashMap<>();
1711
}
1812

19-
void setWithTTL(long timestamp, String key, String field, int value, int ttl) {
20-
records.computeIfAbsent(key, k -> new Record())
21-
.data.put(field, new Value(value, timestamp, ttl));
13+
static class Value {
14+
String value;
15+
int expiry;
16+
17+
Value(String value, int expiry) {
18+
this.value = value;
19+
this.expiry = expiry;
20+
}
2221
}
2322

24-
void setAtWithTTL(String key, String field, int value, long timestampToSet, int ttl) {
25-
records.computeIfAbsent(key, k -> new Record())
26-
.data.put(field, new Value(value, timestampToSet, ttl));
23+
private boolean isKeyPresent(String key) {
24+
return store.containsKey(key);
2725
}
2826

29-
boolean compareAndSet(long timestamp, String key, String field, int expected, int newValue) {
30-
Record record = records.get(key);
31-
if (record != null) {
32-
Value val = record.data.get(field);
33-
if (val != null && val.value == expected) {
34-
record.data.put(field, new Value(newValue, timestamp, null));
35-
return true;
36-
}
37-
}
38-
return false;
27+
private boolean isFieldPresent(String key,String field){
28+
return isKeyPresent(key) && store.get(key).containsKey(field);
3929
}
4030

41-
boolean compareAndSetWithTTL(long timestamp, String key, String field, int expected, int newValue, int ttl) {
42-
Record record = records.get(key);
43-
if (record != null) {
44-
Value val = record.data.get(field);
45-
if (val != null && val.value == expected) {
46-
record.data.put(field, new Value(newValue, timestamp, ttl));
47-
return true;
48-
}
49-
}
50-
return false;
31+
// Level 1
32+
// Set(key, field, value string) - Should insert a field-value pair to the record associated with key. If the field in the record already exists, replace the existing value with the specified value. If record doesn't exist, create a new one.
33+
//
34+
// Get(key, field string) *string - Should return the value contained within field of record associated with key. If record or field doesn't exist, should return nil
35+
//
36+
// Delete(key, field string) bool - Should remove the field from the record associated with key. Returns true if the field was successfully deleted, and false if the key or the field do not exist in the database
37+
38+
public void set(String key,String field, String value){
39+
store.putIfAbsent(key,new HashMap<>());
40+
store.get(key).put(field,new Value(value,Integer.MAX_VALUE));
5141
}
5242

53-
boolean compareAndDelete(long timestamp, String key, String field, int expected) {
54-
Record record = records.get(key);
55-
if (record != null) {
56-
Value val = record.data.get(field);
57-
if (val != null && val.value == expected) {
58-
record.data.put(field, new Value(expected, timestamp, 0));
59-
return true;
60-
}
43+
public String get(String key,String field){
44+
if(!isFieldPresent(key,field)){
45+
return null;
6146
}
62-
return false;
47+
return store.get(key).get(field).value;
6348
}
6449

65-
boolean deleteAt(String key, String field, long timestampToDelete) {
66-
Record record = records.get(key);
67-
if (record != null && record.data.containsKey(field)) {
68-
Value val = record.data.get(field);
69-
val.lastSetAt = timestampToDelete;
70-
val.ttl = 0;
71-
return true;
50+
public boolean delete(String key, String field){
51+
if(!isFieldPresent(key,field)){
52+
return false;
7253
}
73-
return false;
54+
store.get(key).remove(field);
55+
return true;
7456
}
7557

76-
Integer get(long timestamp, String key, String field) {
77-
Record record = records.get(key);
78-
if (record == null) return null;
79-
Value val = record.data.get(field);
80-
if (val == null || val.isExpired(timestamp)) return null;
81-
return val.value;
82-
}
8358

84-
List<String> scan(long timestamp, String key) {
85-
Record record = records.get(key);
86-
if (record == null) return new ArrayList<>();
59+
// Level 2
60+
// Scan(key string) []string - Should return a list of strings representing the fields of a record associated with the key. The returned list should be in the following format ["<field1>(<value1>)" , "<field2>(<value2>)", ...] where the fields are lexicographically sorted. If specified record doesn't exist, return empty list.
61+
//
62+
//ScanByPrefix(key, prefix string) []string - Should return a list of strings representing some fields of a records associated with the key. Specifically, only fields that starts with the prefix should be included. The returned list should be the same format as the Scan operation with the fields sorted in lexicographical order.
63+
64+
public List<String> scan(String key) {
65+
if (!isKeyPresent(key)) return new ArrayList<>();
8766
List<String> res = new ArrayList<>();
88-
for (Map.Entry<String, Value> e : record.data.entrySet()) {
89-
if (!e.getValue().isExpired(timestamp)) {
90-
res.add(e.getKey() + "(" + e.getValue().value + ")");
91-
}
67+
for (Map.Entry<String, Value> entry : store.get(key).entrySet()) {
68+
res.add(entry.getKey() + "(" + entry.getValue().value + ")");
9269
}
70+
Collections.sort(res);
9371
return res;
9472
}
9573

96-
List<String> scanAt(String key, long timestamp) {
97-
return scan(timestamp, key);
98-
}
99-
100-
List<String> scanByPrefix(long timestamp, String key, String prefix) {
74+
public List<String> scanByPrefix(String key, String prefix) {
75+
if (!isKeyPresent(key)) return new ArrayList<>();
10176
List<String> res = new ArrayList<>();
102-
Record record = records.get(key);
103-
if (record == null) return res;
104-
for (Map.Entry<String, Value> e : record.data.entrySet()) {
105-
if (e.getKey().startsWith(prefix)) {
106-
if (!e.getValue().isExpired(timestamp)) {
107-
res.add(e.getKey() + "(" + e.getValue().value + ")");
108-
}
109-
} else if (e.getKey().compareTo(prefix) > 0) {
110-
break;
77+
for (Map.Entry<String, Value> entry : store.get(key).entrySet()) {
78+
if (entry.getKey().startsWith(prefix)) {
79+
res.add(entry.getKey() + "(" + entry.getValue().value + ")");
11180
}
11281
}
82+
Collections.sort(res);
11383
return res;
11484
}
11585

116-
int backup(long timestamp) {
117-
int count = 0;
118-
Map<String, Record> deepCopy = new HashMap<>();
119-
for (Map.Entry<String, Record> e : records.entrySet()) {
120-
Record newRecord = new Record();
121-
for (Map.Entry<String, Value> fv : e.getValue().data.entrySet()) {
122-
Value v = fv.getValue();
123-
if (!v.isExpired(timestamp)) {
124-
Integer newTTL = v.ttl == null ? null : v.ttl - (int)(timestamp - v.lastSetAt);
125-
newRecord.data.put(fv.getKey(), new Value(v.value, timestamp, newTTL));
126-
}
127-
}
128-
if (!newRecord.data.isEmpty()) {
129-
deepCopy.put(e.getKey(), newRecord);
130-
count++;
131-
}
132-
}
133-
backups.put(timestamp, deepCopy);
134-
return count;
86+
// Level 3
87+
/*SetAt(key, field, value string, timestamp int) []string - Should insert a field-value pair or update the value of the field in the record associated with key
88+
89+
SetAtWithTtl(key, field, value string, timestamp, ttl int) []string - Should insert a field-value pair or update the value of the field in the record associated with key. Also sets its Time-to-Live starting at timestamp to be ttl. The ttl is the amount of time that this field-value pair should exist in the database, meaning it will be avaialble during the interval: [timestamp, timestamp + ttl]
90+
91+
DeleteAt(key, field string, timestamp int) bool The same as Delete, but with timestamp of the operation specified. Should return true if the field existed and was successfully deleted and false if the key didn't exist.
92+
93+
GetAt(key, field string, timestamp int) *string The same as Get, but with timestamp of the operation specified
94+
95+
ScanAt(key string, timestamp int) []string The same Scan but with the timestamp of the operation specified
96+
97+
ScanPrefixAt(key, prefix string, timestamp int) []string The same as ScanPrefix but with the timestamp of the operation specified.*/
98+
99+
public void setAt(String key, String field, String value, int timestamp) {
100+
store.putIfAbsent(key, new HashMap<>());
101+
store.get(key).put(field, new Value(value, Integer.MAX_VALUE));
102+
}
103+
104+
public void setAtWithTtl(String key, String field, String value, int timestamp, int ttl) {
105+
store.putIfAbsent(key, new HashMap<>());
106+
store.get(key).put(field, new Value(value, timestamp + ttl));
135107
}
136108

137-
void restore(long timestamp, long timestampToRestore) {
138-
Map.Entry<Long, Map<String, Record>> entry = backups.floorEntry(timestampToRestore);
139-
if (entry != null) {
140-
Map<String, Record> snapshot = deepCopyRecords(entry.getValue(), timestamp);
141-
records = snapshot;
109+
public boolean deleteAt(String key, String field, int timestamp) {
110+
if (!isFieldPresent(key, field)) return false;
111+
Value val = store.get(key).get(field);
112+
store.get(key).remove(field);
113+
return timestamp <= val.expiry;
114+
}
115+
116+
public String getAt(String key, String field, int timestamp) {
117+
if (!isFieldPresent(key, field)) return null;
118+
Value val = store.get(key).get(field);
119+
return (timestamp <= val.expiry) ? val.value : null;
120+
}
121+
122+
public List<String> scanAt(String key, int timestamp) {
123+
if (!isKeyPresent(key)) return new ArrayList<>();
124+
List<String> res = new ArrayList<>();
125+
for (Map.Entry<String, Value> entry : store.get(key).entrySet()) {
126+
if (timestamp <= entry.getValue().expiry) {
127+
res.add(entry.getKey() + "(" + entry.getValue().value + ")");
128+
}
142129
}
130+
Collections.sort(res);
131+
return res;
143132
}
144133

145-
private Map<String, Record> deepCopyRecords(Map<String, Record> original, long newTimestamp) {
146-
Map<String, Record> copy = new HashMap<>();
147-
for (Map.Entry<String, Record> e : original.entrySet()) {
148-
Record newRecord = new Record();
149-
for (Map.Entry<String, Value> fv : e.getValue().data.entrySet()) {
150-
Value v = fv.getValue();
151-
newRecord.data.put(fv.getKey(), new Value(v.value, newTimestamp, v.ttl));
134+
public List<String> scanPrefixAt(String key, String prefix, int timestamp) {
135+
if (!isKeyPresent(key)) return new ArrayList<>();
136+
List<String> res = new ArrayList<>();
137+
for (Map.Entry<String, Value> entry : store.get(key).entrySet()) {
138+
if (entry.getKey().startsWith(prefix) && timestamp <= entry.getValue().expiry) {
139+
res.add(entry.getKey() + "(" + entry.getValue().value + ")");
152140
}
153-
copy.put(e.getKey(), newRecord);
154141
}
155-
return copy;
142+
Collections.sort(res);
143+
return res;
156144
}
157145
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.codingsignaltest.inMemoryDB;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.util.Arrays;
6+
import java.util.List;
7+
8+
import static org.junit.Assert.*;
9+
10+
public class InMemoryDBTest {
11+
12+
@Test
13+
public void testLevel1() {
14+
InMemoryDB db = new InMemoryDB();
15+
16+
db.set("user1", "name", "Alice");
17+
db.set("user1", "age", "30");
18+
19+
assertEquals("Alice", db.get("user1", "name"));
20+
assertEquals("30", db.get("user1", "age"));
21+
22+
db.set("user1", "age", "31");
23+
assertEquals("31", db.get("user1", "age"));
24+
25+
assertNull(db.get("user1", "address"));
26+
27+
assertTrue(db.delete("user1", "age"));
28+
assertNull(db.get("user1", "age"));
29+
30+
assertFalse(db.delete("user1", "age"));
31+
assertFalse(db.delete("user2", "name"));
32+
}
33+
34+
@Test
35+
public void testLevel2() {
36+
InMemoryDB db = new InMemoryDB();
37+
38+
db.set("user1", "name", "Alice");
39+
db.set("user1", "age", "30");
40+
db.set("user1", "address", "Wonderland");
41+
42+
List<String> expected = Arrays.asList("address(Wonderland)", "age(30)", "name(Alice)");
43+
assertEquals(expected, db.scan("user1"));
44+
45+
expected = Arrays.asList("address(Wonderland)");
46+
assertEquals(expected, db.scanByPrefix("user1", "add"));
47+
48+
assertEquals(Arrays.asList(), db.scan("user2"));
49+
assertEquals(Arrays.asList(), db.scanByPrefix("user2", "name"));
50+
assertEquals(Arrays.asList(), db.scanByPrefix("user1", "xyz"));
51+
}
52+
53+
@Test
54+
public void testLevel3() {
55+
InMemoryDB db = new InMemoryDB();
56+
57+
db.setAt("user1", "name", "Alice", 1);
58+
db.setAt("user1", "age", "30", 2);
59+
db.setAt("user1", "address", "Wonderland", 3);
60+
db.setAtWithTtl("user1", "tempField", "tempValue", 4, 2);
61+
62+
assertEquals("Alice", db.getAt("user1", "name", 1));
63+
assertEquals("30", db.getAt("user1", "age", 2));
64+
65+
assertNull(db.getAt("user1", "tempField", 7));
66+
67+
assertTrue(db.deleteAt("user1", "age", 2));
68+
assertNull(db.getAt("user1", "age", 3));
69+
70+
assertFalse(db.deleteAt("user1", "tempField", 8));
71+
72+
List<String> expected = Arrays.asList("address(Wonderland)", "name(Alice)");
73+
assertEquals(expected, db.scanAt("user1", 2));
74+
75+
expected = Arrays.asList("address(Wonderland)");
76+
assertEquals(expected, db.scanPrefixAt("user1", "add", 3));
77+
}
78+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.codingsignaltest.inMemoryDB;
2+
3+
public class Main {
4+
public static void main(String[] args) {
5+
InMemoryDB db = new InMemoryDB();
6+
7+
db.set("user1", "name", "Alice");
8+
db.set("user1", "age", "30");
9+
10+
System.out.println(db.get("user1", "name")); // Alice
11+
System.out.println(db.scan("user1")); // [age(30), name(Alice)]
12+
13+
db.setAtWithTtl("user1", "session", "xyz", 100, 10);
14+
System.out.println(db.getAt("user1", "session", 105)); // xyz
15+
System.out.println(db.getAt("user1", "session", 120)); // null (expired)
16+
}
17+
}

0 commit comments

Comments
 (0)