1
0
mirror of https://github.com/robonen/tools.git synced 2026-03-20 10:54:44 +00:00

refactor: change separate tools by category

This commit is contained in:
2025-05-19 17:43:42 +07:00
parent d55737df2f
commit 78fb4da82a
158 changed files with 32 additions and 24 deletions

View File

@@ -0,0 +1,26 @@
import { describe, it, expect } from 'vitest';
import { flagsGenerator } from '.';
describe('flagsGenerator', () => {
it('generate unique flags', () => {
const generateFlag = flagsGenerator();
const flag1 = generateFlag();
const flag2 = generateFlag();
const flag3 = generateFlag();
expect(flag1).toBe(1);
expect(flag2).toBe(2);
expect(flag3).toBe(4);
});
it('throw an error if more than 31 flags are created', () => {
const generateFlag = flagsGenerator();
for (let i = 0; i < 31; i++) {
generateFlag();
}
expect(() => generateFlag()).toThrow(new RangeError('Cannot create more than 31 flags'));
});
});

View File

@@ -0,0 +1,22 @@
/**
* @name flagsGenerator
* @category Bits
* @description Create a function that generates unique flags
*
* @returns {Function} A function that generates unique flags
* @throws {RangeError} If more than 31 flags are created
*
* @since 0.0.2
*/
export function flagsGenerator() {
let lastFlag = 0;
return () => {
// 31 flags is the maximum number of flags that can be created
// (without zero) because of the 32-bit integer limit in bitwise operations
if (lastFlag & 0x40000000)
throw new RangeError('Cannot create more than 31 flags');
return (lastFlag = lastFlag === 0 ? 1 : lastFlag << 1);
};
}

View File

@@ -0,0 +1,95 @@
import { describe, it, expect } from 'vitest';
import { and, or, not, has, is, unset, toggle } from '.';
describe('flagsAnd', () => {
it('no effect on zero flags', () => {
const result = and();
expect(result).toBe(-1);
});
it('source flag is returned if no flags are provided', () => {
const result = and(0b1010);
expect(result).toBe(0b1010);
});
it('perform bitwise AND operation on flags', () => {
const result = and(0b1111, 0b1010, 0b1100);
expect(result).toBe(0b1000);
});
});
describe('flagsOr', () => {
it('no effect on zero flags', () => {
const result = or();
expect(result).toBe(0);
});
it('source flag is returned if no flags are provided', () => {
const result = or(0b1010);
expect(result).toBe(0b1010);
});
it('perform bitwise OR operation on flags', () => {
const result = or(0b1111, 0b1010, 0b1100);
expect(result).toBe(0b1111);
});
});
describe('flagsNot', () => {
it('perform bitwise NOT operation on a flag', () => {
const result = not(0b101);
expect(result).toBe(-0b110);
});
});
describe('flagsHas', () => {
it('check if a flag has a specific bit set', () => {
const result = has(0b1010, 0b1000);
expect(result).toBe(true);
});
it('check if a flag has a specific bit unset', () => {
const result = has(0b1010, 0b0100);
expect(result).toBe(false);
});
});
describe('flagsIs', () => {
it('check if a flag is set', () => {
const result = is(0b1010);
expect(result).toBe(true);
});
it('check if a flag is unset', () => {
const result = is(0);
expect(result).toBe(false);
});
});
describe('flagsUnset', () => {
it('unset a flag', () => {
const result = unset(0b1010, 0b1000);
expect(result).toBe(0b0010);
});
});
describe('flagsToggle', () => {
it('toggle a flag', () => {
const result = toggle(0b1010, 0b1000);
expect(result).toBe(0b0010);
});
});

View File

@@ -0,0 +1,100 @@
/**
* @name and
* @category Bits
* @description Function to combine multiple flags using the AND operator
*
* @param {number[]} flags - The flags to combine
* @returns {number} The combined flags
*
* @since 0.0.2
*/
export function and(...flags: number[]) {
return flags.reduce((acc, flag) => acc & flag, -1);
}
/**
* @name or
* @category Bits
* @description Function to combine multiple flags using the OR operator
*
* @param {number[]} flags - The flags to combine
* @returns {number} The combined flags
*
* @since 0.0.2
*/
export function or(...flags: number[]) {
return flags.reduce((acc, flag) => acc | flag, 0);
}
/**
* @name not
* @category Bits
* @description Function to combine multiple flags using the XOR operator
*
* @param {number} flag - The flag to apply the NOT operator to
* @returns {number} The result of the NOT operator
*
* @since 0.0.2
*/
export function not(flag: number) {
return ~flag;
}
/**
* @name has
* @category Bits
* @description Function to make sure a flag has a specific bit set
*
* @param {number} flag - The flag to check
* @param {number} other - Flag to check
* @returns {boolean} Whether the flag has the bit set
*
* @since 0.0.2
*/
export function has(flag: number, other: number) {
return (flag & other) === other;
}
/**
* @name is
* @category Bits
* @description Function to check if a flag is set
*
* @param {number} flag - The flag to check
* @returns {boolean} Whether the flag is set
*
* @since 0.0.2
*/
export function is(flag: number) {
return flag !== 0;
}
/**
* @name unset
* @category Bits
* @description Function to unset a flag
*
* @param {number} flag - Source flag
* @param {number} other - Flag to unset
* @returns {number} The new flag
*
* @since 0.0.2
*/
export function unset(flag: number, other: number) {
return flag & ~other;
}
/**
* @name toggle
* @category Bits
* @description Function to toggle (xor) a flag
*
* @param {number} flag - Source flag
* @param {number} other - Flag to toggle
* @returns {number} The new flag
*
* @since 0.0.2
*/
export function toggle(flag: number, other: number) {
return flag ^ other;
}

View File

@@ -0,0 +1 @@
export * from './flags';

View File

@@ -0,0 +1,63 @@
import { describe, it, expect } from 'vitest';
import { BitVector } from '.';
describe('BitVector', () => {
it('initialize with the correct size', () => {
const size = 16;
const expectedSize = Math.ceil(size / 8);
const bitVector = new BitVector(size);
expect(bitVector.length).toBe(expectedSize);
});
it('set and get bits correctly', () => {
const bitVector = new BitVector(16);
bitVector.setBit(5);
expect(bitVector.getBit(5)).toBe(true);
expect(bitVector.getBit(4)).toBe(false);
});
it('get out of bounds bits correctly', () => {
const bitVector = new BitVector(16);
expect(bitVector.getBit(155)).toBe(false);
});
it('clear bits correctly', () => {
const bitVector = new BitVector(16);
bitVector.setBit(5);
expect(bitVector.getBit(5)).toBe(true);
bitVector.clearBit(5);
expect(bitVector.getBit(5)).toBe(false);
});
it('find the previous bit correctly', () => {
const bitVector = new BitVector(100);
const indices = [99, 88, 66, 65, 64, 63, 15, 14, 1, 0];
const result = [];
indices.forEach(index => bitVector.setBit(index));
for (let i = bitVector.previousBit(100); i !== -1; i = bitVector.previousBit(i)) {
result.push(i);
}
expect(result).toEqual(indices);
});
it('return -1 when no previous bit is found', () => {
const bitVector = new BitVector(16);
expect(bitVector.previousBit(0)).toBe(-1);
});
it('throw RangeError when previousBit is called with an unreachable value', () => {
const bitVector = new BitVector(16);
bitVector.setBit(5);
expect(() => bitVector.previousBit(24)).toThrow(new RangeError('Unreachable value'));
});
});

View File

@@ -0,0 +1,61 @@
export interface BitVector {
getBit(index: number): boolean;
setBit(index: number): void;
clearBit(index: number): void;
previousBit(index: number): number;
}
/**
* @name BitVector
* @category Bits
* @description A bit vector is a vector of bits that can be used to store a collection of bits
*
* @since 0.0.3
*/
export class BitVector extends Uint8Array implements BitVector {
constructor(size: number) {
super(Math.ceil(size / 8));
}
getBit(index: number) {
const value = this[index >> 3]! & (1 << (index & 7));
return value !== 0;
}
setBit(index: number) {
this[index >> 3]! |= 1 << (index & 7);
}
clearBit(index: number): void {
this[index >> 3]! &= ~(1 << (index & 7));
}
previousBit(index: number): number {
while (index !== ((index >> 3) << 3)) {
--index;
if (this.getBit(index)) {
return index;
}
}
let byteIndex = (index >> 3) - 1;
while (byteIndex >= 0 && this[byteIndex] === 0)
--byteIndex;
if (byteIndex < 0)
return -1;
index = (byteIndex << 3) + 7;
while (index >= (byteIndex << 3)) {
if (this.getBit(index))
return index;
--index;
}
throw new RangeError('Unreachable value');
}
}