-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlib.ts
150 lines (124 loc) · 4.37 KB
/
lib.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
Approach
I wanted to build a simple encoder that follows a similar format to JSON
serialization, but with a LZW-inspired pattern to compress repeated values.
I used a Map to dictionary the values on the way in.
One thing I found pretty challenging was dealing with nested arrays. Even now
my deep nested array test is failing - I think this is because the deserialization
is not truly recursive, but in the time allotment I couldn't quite figure out how
to make it work efficiently.
The algorithm is fairly simple, we iterate over the to_send array and serialize each
value. If the value exists in the dictionary we store a pointer instead of the value.
In the case that there are repeated values, especially if they are larger, this does
a good job at compression the payload.
I did some manual benchmarking and on thing is for sure - JSON.parse and JSON.stringify
are much faster than my implementation (although payload size was generally larger), but
I would imagine this is probably a better job for c++ or go.
*/
export type DataElement = number | string | Data;
export type Data = DataElement[];
type Dict = Map<string, number>;
// In general the time complexity should be O(n) for flat values, but O(n^2) when we have nested values.
export const encode = (to_send: DataElement[]): string => {
validateInput(to_send);
const parts = to_send;
const partDictionary: Dict = new Map();
let payload = '';
let delimiter = ",";
for(let i = 0; i < parts.length; i++) {
const part = parts[i];
const encodedPart = serializeValue(part);
if(i === parts.length - 1) delimiter = "";
if(partDictionary.has(encodedPart)) {
const finalPart = `<${partDictionary.get(encodedPart)}>${delimiter}`;
payload += finalPart;
} else {
partDictionary.set(encodedPart, i);
payload += `${encodedPart}${delimiter}`;
}
}
payload = `[${payload}]`;
return payload;
};
export const decode = (recieved: string): Data => {
const arrayString = recieved.slice(1, -1).split(",");
const deserialized = arrayString.map((value) => {
if(value.startsWith("<") && value.endsWith(">")) {
const pointer = parseInt(value.slice(1, -1));
return deserializeValue(arrayString[pointer]);
} else {
return deserializeValue(value);
};
});
return deserialized;
}
const serializeValue = (value: DataElement): string => {
if(typeof value === 'string') {
if(value.length > 1000000) {
throw new Error('String length must be less than or equal to 1,000,000');
}
value = value
.replace(/,/g, "0x2c")
.replace(/"/g, "0x22")
.replace(/'/g, "0x27")
.replace(/</g, "0x3c")
.replace(/>/g, "0x3e")
.replace(/;/g, "0x3b");
return `"${value}"`;
}
if(typeof value === "number") {
if(value > 2147483647 || value < -2147483648) {
throw new Error('Integers must be of type int32');
}
return `${value}`;
}
if(Array.isArray(value)) {
let arrayString = '';
let delimiter = ";";
for(let i = 0; i < value.length; i++) {
if(i === value.length - 1) {
delimiter = "";
}
arrayString += serializeValue(value[i]) + delimiter;
}
return `[${arrayString}]`;
}
// No string, no number, no array, throw an error
throw new Error('Unsupported data type');
}
const deserializeValue = (value: string): DataElement => {
if(typeof value === "string" && value.startsWith('"') && value.endsWith('"')) {
value = value
.slice(1, -1)
.replace(/0x2c/g, ",")
.replace(/0x22/g, "\"")
.replace(/0x27/g, "'")
.replace(/0x3c/g, "<")
.replace(/0x3e/g, ">")
.replace(/0x3b/g, ";");
return value;
} else if(typeof value === "string" && value.startsWith("[") && value.endsWith("]")) {
const arrayString = value.slice(1, -1).split(";");
const deserializedArray = arrayString.map((value) => {
return deserializeValue(value);
});
return deserializedArray;
} else {
return parseInt(value);
}
}
const validateInput = (to_send: DataElement[]): void => {
if(!Array.isArray(to_send)) {
throw new Error("to_send should be an array.");
}
if(to_send.length > 1000) {
throw new Error("to_send max size is 1000");
}
}
const to_send: Data = ["foo", "foo", 23, ["bar", 42], 23, ["bar", 42]];
const encoded = encode(to_send);
const decoded = decode(encoded);
console.log({
encoded,
decoded,
})