I have a database object that has a field called workdays
, it's a array boolean that contains 7 elements representing whether the user is active on the corresponding day.
I need to save this array into the database, I thought the bets way would be to convert the boolean array into a integer (bits) array and convert that into an integer which can be saved in the DB.
this is what I came up with, but I feel like I'm doing it the unnecessarily long way.
How can this be improved upon ?
function serializeDays(days: boolean[]): number {
// use (days || []) to avoid null pointer exception
// concat it with Array(7).fill(false) to fill in missing values
const d1 = (days || []).concat(Array(7).fill(false)).slice(0, 7);
// map the boolean values to int values
const d2 = d1.map(d => (d ? 1 : 0));
// join the array to string
const d3 = d2.join("");
// parse the string into base 10 from base 2
const n = parseInt(d3, 2);
return n;
}
function parseDays(days: number): boolean[] {
// use (days || 0) to avoid null pointer exception
// convert the base 10 integer into base 2
const d1 = (days || 0).toString(2);
// split the string into an array
// reverse it, concat it with Array(7).fill(0), reverse it again
// this is just to zerofill the array from the left side
const d2 = d1.split("").reverse().concat(Array(7).fill(0)).slice(0, 7).reverse();
// parse the values from strings to actual integers to boolean
const d3 = d2.map(d => (parseInt(d, 10) ? true : false));
return d3;
}
// monday to friday
serializeDays([true, true, true, true, true, false, false]); // outputs 124
parseDays(124); // outputs [true, true, true, true, true, false, false]
```
2 Answers 2
Unnecessary alternation You're using TypeScript, which is great - TypeScript will forbid calling functions with improperly typed parameters. That is, you don't have to worry about:
serializeDays();
because one parameter which is an array of booleans is required. The array will always exist, so:
// use (days || []) to avoid null pointer exception
isn't necessary - using days
alone is sufficient. (This same thing applies to the parseDays
argument)
Use meaningful variable names - d1
, d2
, and d3
aren't as informative as they could be. Give them names that accurately represent what they contain, and (when it doesn't make things hard to read) reduce the number of declared variables and chain off of previous expressions instead (see below).
parseDays argument name is days: number
. But serializeDays
takes an argument of days: boolean[]
. Consider using a different variable name, maybe binaryDaysFromDB
.
Array construction can be done more concisely by creating an array of length 7, and then mapping it to 0 or 1 depending on days[i]
. If the item exists, its boolean will be used; if the item doesn't exist, it'll be undefined
, and false
will be used.
Since both serializeDays
and parseDays
require the construction of an array of length 7 which then gets filled based on its index, abstract that part into a function:
const makeArrOf7 = mapper => Array.from(
{ length: 7 },
(_, i) => mapper(i)
)
function serializeDays(days: boolean[]) {
const binaryDays = makeArrOf7(i => Number(days[i] || 0))
.join('');
return parseInt(binaryDays, 2);
}
When turning a number from the database back into an array of booleans, reversing, filling, then reversing again isn't necessary. The logic being implemented is to change, eg, '110'
into [0, 0, 0, 0, 1, 1, 0]
- this can be achieved very easily by using padStart
to pad the start of the string until it's length 7.
const makeArrOf7 = mapper => Array.from(
{ length: 7 },
(_, i) => mapper(i)
)
function serializeDays(days) {
const binaryDays = makeArrOf7(i => Number(days[i] || 0))
.join('');
return parseInt(binaryDays, 2);
}
function parseDays(binaryDaysFromDB) {
const daysStr = binaryDaysFromDB
.toString(2)
.padStart(7, '0')
.split('');
return makeArrOf7(i => daysStr[i] === '1');
}
console.log(serializeDays([true, true, true, true, true, false, false])); // outputs 124
console.log(parseDays(124).join(',')); // outputs [true, true, true, true, true, false, false]
console.log(serializeDays([false, true, true]));
console.log(parseDays(48).join(','));
All that said:
This is a very strange thing to want to do. Why not just save the arrays directly, without any transformation? Unless you have a really good reason to save numbers instead of arrays, I'd prefer using the original arrays, they make a whole lot more sense, have no chance of bugs introduced via calculations, and require no additional code.
-
\$\begingroup\$ Thank you for the great answer, I do agree with what you said, as for why I chose to do it this way: honestly I don't know why :p, I guess I felt it was cleaner this way ¯\_(ツ)_/¯ \$\endgroup\$Benjie Wheeler– Benjie Wheeler2020年12月04日 17:21:08 +00:00Commented Dec 4, 2020 at 17:21
Binary Math
Using string to do integer maths is very slow. You can use bitwise operators to encode and decode bit fields.
Encode bool array to int
The first function sets the low bit depending on the boolean. Each iteration the bits set are shifted to the left. The result is that the first array item is the highest bit (bit 7) and the last day is the lowest bit (bit 1)
Decode int to bool array
The second uses bitwise AND &
to mask out all but the bit position required and adds a boolean to an array depending on the state of the bit.
// high bit is first item in days eg 0b1000000 is day[0]
function serializeDays(days) {
return days.reduce((bin, day) => (bin << 1) + (!!day), 0);
}
function parseDays(bitField) {
var bit = 7, days = [];
while (bit) { days.push((bitField & (1 << --bit)) !== 0) }
return days;
}
console.log(serializeDays([true,false,true,false,true,false,true]).toString(2));
console.log(parseDays(0b1010101).join(","));
-
\$\begingroup\$ Thank you for the feedback, the reason I didn't bitwise operators is because it's one of my weak points, since I don't have a strong (any actually) background in CS \$\endgroup\$Benjie Wheeler– Benjie Wheeler2020年12月04日 17:13:21 +00:00Commented Dec 4, 2020 at 17:13