{"id":13626510,"url":"https://github.com/zandaqo/structurae","last_synced_at":"2025-05-16T09:03:40.792Z","repository":{"id":42041107,"uuid":"165816772","full_name":"zandaqo/structurae","owner":"zandaqo","description":"Data structures for high-performance JavaScript applications.","archived":false,"fork":false,"pushed_at":"2023-12-03T05:47:18.000Z","size":1157,"stargazers_count":716,"open_issues_count":15,"forks_count":22,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-13T19:24:56.625Z","etag":null,"topics":["array","bigint","binary","bitfield","data-structures","graphs","javascript","matrices","optimization","sorted","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zandaqo.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-01-15T08:49:51.000Z","updated_at":"2025-05-08T19:25:47.000Z","dependencies_parsed_at":"2024-01-14T07:17:46.254Z","dependency_job_id":"7c9fb229-9e74-42b1-9b85-a05cf42a69d1","html_url":"https://github.com/zandaqo/structurae","commit_stats":{"total_commits":171,"total_committers":3,"mean_commits":57.0,"dds":"0.011695906432748537","last_synced_commit":"024d358403ce0cb694040e41ace6b6fd8997afaa"},"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zandaqo%2Fstructurae","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zandaqo%2Fstructurae/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zandaqo%2Fstructurae/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zandaqo%2Fstructurae/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zandaqo","download_url":"https://codeload.github.com/zandaqo/structurae/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254501554,"owners_count":22081528,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["array","bigint","binary","bitfield","data-structures","graphs","javascript","matrices","optimization","sorted","typescript"],"created_at":"2024-08-01T21:02:20.982Z","updated_at":"2025-05-16T09:03:40.750Z","avatar_url":"https://github.com/zandaqo.png","language":"TypeScript","readme":"# Structurae\n\n[![Actions Status](https://github.com/zandaqo/structurae/workflows/ci/badge.svg)](https://github.com/zandaqo/structurae/actions)\n[![npm](https://img.shields.io/npm/v/structurae.svg?style=flat-square)](https://www.npmjs.com/package/structurae)\n\nA collection of data structures for high-performance JavaScript applications\nthat includes:\n\n- **[Binary Protocol](#binary-protocol)** - simple binary protocol based on\n  DataView and defined with JSONSchema\n- **[Bit Structures](#bit-structures)**:\n  - [BitField \u0026 BigBitField](#bitfield--bigbitfield) - stores and operates on\n    data in Numbers and BigInts treating them as bitfields.\n  - [BitArray](#bitArray) - an array of bits implemented with Uint32Array.\n  - [Pool](#pool) - manages availability of objects in object pools.\n  - [RankedBitArray](#rankedbitarray) - extends BitArray with O(1) time rank and\n    O(logN) select methods.\n- **[Graphs](#graphs)**:\n  - [Adjacency Structures](#adjacency-structures) - implement adjacency list \u0026\n    matrix data structures.\n  - [Graph](#graph) - extends an adjacency list/matrix structure and provides\n    methods for traversal (BFS, DFS), pathfinding (Dijkstra, Bellman-Ford),\n    spanning tree construction (BFS, Prim), etc.\n- **[Grids](#grids)**:\n  - [BinaryGrid](#binarygrid) - creates a grid or 2D matrix of bits.\n  - [Grid](#grid) - extends built-in indexed collections to handle 2 dimensional\n    data (e.g. nested arrays).\n  - [SymmetricGrid](#symmetricgrid) - a grid to handle symmetric or triangular\n    matrices using half the space required for a normal grid.\n- **[Sorted Structures](#sorted-structures)**:\n  - [BinaryHeap](#binaryheap) - extends Array to implement the Binary Heap data\n    structure.\n  - [SortedArray](#sortedarray) - extends Array to handle sorted data.\n\n## Usage\n\nNode.js:\n\n```shell\nnpm i structurae\n```\n\n```javascript\nimport {...} from \"structurae\";\n```\n\nDeno:\n\n```typescript\nimport {...} from \"https://deno.land/x/structurae/index.ts\"\n```\n\n## Documentation\n\n- Articles:\n  - [Structurae: Data Structures for Heigh-Performance\n    JavaScript](https://blog.usejournal.com/structurae-data-structures-for-high-performance-javascript-9b7da4c73f8)\n  - [Structurae 1.0: Graphs, Strings, and\n    WebAssembly](https://medium.com/@zandaqo/structurae-1-0-graphs-strings-and-webassembly-25dd964d5a70)\n  - [Binary Protocol for JavaScript](https://itnext.io/binary-protocol-for-javascript-cc409e144a3c)\n\n## Overview\n\n### Binary Protocol\n\nBinary data in JavaScript is represented by ArrayBuffer and accessed through\nTypedArrays and DataView. However, both of those interfaces are limited to\nworking with numbers. Structurae offers a set of classes that extend the\nDataView interface to support using ArrayBuffers for strings, objects, and\narrays. These classes (\"views\") form the basis for a simple binary protocol with\nthe following features:\n\n- smaller and faster than schema-less binary formats (e.g. BSON, MessagePack);\n- supports zero-copy operations, e.g. reading and changing object fields without\n  decoding the whole object;\n- supports static typing through TypeScript;\n- uses JSON Schema for schema definitions;\n- does not require compilation unlike most other schema-based formats (e.g.\n  FlatBuffers).\n\nThe protocol is operated through the `View` class that handles creation and\ncaching of necessary structures for a given JSON Schema as well as simplifying\nserialization of tagged objects.\n\n```typescript\nimport { View } from \"structurae\";\n\n// instantiate a view protocol\nconst view = new View();\n\n// define interface for out animal objects\ninterface Animal {\n  name: string;\n  age: number;\n}\n// create and return a view class (extension of DataView) that handles our Animal objects\nconst AnimalView = view.create\u003cAnimal\u003e({\n  $id: \"Pet\",\n  type: \"object\",\n  properties: {\n    name: { type: \"string\", maxLength: 10 },\n    // by default, type `number` is treated as int32, but can be further specified usin `btype`\n    age: { type: \"number\", btype: \"uint8\" },\n  },\n});\n// encode our animal object\nconst animal = AnimalView.from({ name: \"Gaspode\", age: 10 });\nanimal instanceof DataView; //=\u003e true\nanimal.byteLength; //=\u003e 14\nanimal.get(\"age\"); //=\u003e 10\nanimal.set(\"age\", 20);\nanimal.toJSON(); //=\u003e { name: \"Gaspode\", age: 20 }\n```\n\n#### Objects and Maps\n\nObjects by default are treated as C-like structs, the data is laid out\nsequentially with fixed sizes, all standard JavaScript values are supported,\ninluding other objects and arrays of fixed size:\n\n```typescript\ninterface Friend {\n  name: string;\n}\ninterface Person {\n  name: string;\n  fullName: Array\u003cstring\u003e;\n  bestFriend: Friend;\n  friends: Array\u003cFriend\u003e;\n}\nconst PersonView = view.create\u003cPerson\u003e({\n  // each object requires a unique id\n  $id: \"Person\",\n  type: \"object\",\n  properties: {\n    // the size of a string field is required and defined by maxLength\n    name: { type: \"string\", maxLength: 10 },\n    fullName: {\n      type: \"array\",\n      // the size of an array is required and defined by maxItems\n      maxItems: 2,\n      // all items have to be the same type\n      items: { type: \"string\", maxLength: 20 },\n    },\n    // objects can be referenced with $ref using their $id\n    bestFriend: { $ref: \"#Friend\" },\n    friends: {\n      type: \"array\",\n      maxItems: 3,\n      items: {\n        $id: \"Friend\",\n        type: \"object\",\n        properties: {\n          name: { type: \"string\", maxLength: 20 },\n        },\n      },\n    },\n  },\n});\nconst person = Person.from({\n  name: \"Carrot\",\n  fullName: [\"Carrot\", \"Ironfoundersson\"],\n  bestFriend: { name: \"Sam Vimes\" },\n  friends: [{ name: \"Sam Vimes\" }],\n});\nperson.get(\"name\"); //=\u003e Carrot\nperson.getView(\"name\"); //=\u003e StringView [10]\nperson.get(\"fullName\"); //=\u003e [\"Carrot\", \"Ironfoundersson\"]\nperson.toJSON();\n//=\u003e {\n//     name: \"Carrot\",\n//     fullName: [\"Carrot\", \"Ironfoundersson\"],\n//     bestFriend: { name: \"Sam Vimes\" },\n//     friends: [{ name: \"Sam Vimes\" }]\n//    }\n```\n\nObjects that support optional fields and fields of variable size (\"maps\") should\nadditionally specify `btype` `map` and list non-optional (fixed sized) fields as\n`required`:\n\n```typescript\ninterface Town {\n  name: string;\n  railstation: boolean;\n  clacks?: number;\n}\nconst TownView = view.create\u003cTown\u003e({\n    $id: \"Town\",\n    type: \"object\",\n    btype: \"map\",\n    properties: {\n      // notice that maxLength is not required for optional fields in maps\n      // however, if set, map with truncate longer strings to fit the maxLength\n      name: { type: \"string\" },\n      railstation: { type: \"boolean\" },\n      // optional, nullable field\n      clacks: { type: \"integer\" },\n    }\n    required: [\"railstation\"],\n  });\nconst lancre = TownView.from({ name: \"Lancre\", railstation: false });\nlancre.get(\"name\") //=\u003e Lancre\nlancre.get(\"clacks\") //=\u003e undefined\nlancre.byteLength //=\u003e 19\nconst stoLat = TownView.from({ name: \"Sto Lat\", railstation: true, clacks: 1 });\nstoLat.get(\"clacks\") //=\u003e 1\nstoLat.byteLength //=\u003e 24\n```\n\nThe size and layout of each map instance is calculated upon creation and stored\nwithin the instance (unlike fixed sized objects, where each instance have the\nsame size and layout). Maps are useful for densely packing objects and arrays\nwhose size my vary greatly. There is a limitation, though, since ArrayBuffers\ncannot be resized, optional fields that were absent upon creation of a map view\ncannot be set later, and those set cannot be resized, that is, assigned a value\nthat is greater than their current size.\n\nFor performance sake, all variable size views are encoded using single global\nArrayBuffer that is 8192 bytes long, if you expect to handle bigger views,\nsupply a bigger DataView when instantiating a view protocol:\n\n```ts\nimport { View } from \"structurae\";\n\n// instantiate a view protocol\nconst view = new View(new DataView(new ArrayBuffer(65536)));\n```\n\nThere are certain requirements for a JSON Schema used for fixed sized objects:\n\n- Each object should have a unique id defined with `$id` field. Upon\n  initialization, the view class is stored in `View.Views` and accessed with the\n  id used as the key. References made with `$ref` are also resolved against the\n  id.\n- For fixed sized objects, sizes of strings and arrays should be defined using\n  `maxLength` and `maxItems` properties respectfully.\n- `$ref` can be used to reference objects by their `$id`. The referenced object\n  should be defined either in the same schema or in a schema initialized\n  previously.\n- Type `number` by default resolves to `float64` and type `integer` to `int32`,\n  you can use any other type by specifying it in `btype` property.\n\nObjects and maps support setting default values of required fields. Default\nvalues are applied upon creation of a view:\n\n```typescript\ninterface House {\n  size: number;\n}\nconst House = view.create\u003cHouse\u003e({\n  $id: \"House\",\n  type: \"object\",\n  properties: {\n    size: { type: \"integer\", btype: \"uint32\", default: 100 },\n  },\n});\nconst house = House.from({} as House);\nhouse.get(\"size\"); //=\u003e 100\n```\n\nDefault values of an object can be overridden when it is nested inside another\nobject:\n\n```typescript\ninterface Neighborhood {\n  house: House;\n  biggerHouse: House;\n}\nconst Neighborhood = view.create\u003cNeighborhood\u003e({\n  $id: \"Neighborhood\",\n  type: \"object\",\n  properties: {\n    house: { $ref: \"#House\" },\n    biggerHouse: { $ref: \"#House\", default: { size: 200 } },\n  },\n});\nconst neighborhood = Neighborhood.from({} as Neighborhood);\nneighborhood.get(\"house\"); //=\u003e { size: 100 }\nneighborhood.get(\"biggerHouse\"); //=\u003e { size: 200 }\n```\n\n#### Dictionaries\n\nObjects and maps described above assume that all properties of encoded objects\nare known and defined beforehand, however, if the properties are not known, and\nwe are dealing with an object used as a lookup table (also called map, hash map,\nor records in TypeScript) with varying amount of properties and known type of\nvalues, we can use a dictionary view:\n\n```typescript\nconst NumberDict = view.create\u003cRecord\u003cnumber, string | undefined\u003e\u003e({\n  $id: \"NumberDict\",\n  type: \"object\",\n  btype: \"dict\", // dictionaries use btype dict\n  // the type of keys are defined in the `propertyNames` field of a schema\n  // the keys must be either fixed sized strings or numbers\n  propertyNames: { type: \"number\", btype: \"uint8\" },\n  // the type of values defined in `addtionalProperties` field\n  // values can be of any supported type\n  additionalProperties: { type: \"string\" },\n});\nconst dict = NumberDict.from({ 1: \"a\", 2: \"bcd\", 3: undefined });\ndict.get(1); //=\u003e \"a\"\ndict.get(3); //=\u003e undefined\ndict.get(10); //=\u003e undefined\ndict.get(2); //=\u003e \"bcd\"\n```\n\n#### Arrays and Vectors\n\nThe protocol supports arrays of non-nullable fixed sized values (numbers,\nstrings of fixed maximum size, objects) and vectors--arrays with nullable or\nvariable sized values. The type of items held by both \"container\" views is\ndefined in `items` field of the schema.\n\n```typescript\nconst Street = view.create\u003cArray\u003cHouse\u003e\u003e({\n  type: \"array\",\n  items: {\n    type: \"object\",\n    // we can also reference previously created class with $ref\n    $ref: \"#House\",\n  },\n});\nconst street = Street.from([{ size: 10 }, { size: 20 }]);\nstreet.byteLength; //=\u003e 8\nstreet.get(0); //=\u003e { size: 10 }\nstreet.getView(0).get(\"size\"); //=\u003e 10\nstreet.size; //=\u003e 2\nstreet.set(0, { size: 100 });\nstreet.get(0); //=\u003e { size: 100 }\n```\n\nFor vectors set `btype` to `vector`:\n\n```typescript\nconst Names = view.create\u003cArray\u003cstring | undefined\u003e\u003e({\n  type: \"array\",\n  btype: \"vector\",\n  items: {\n    type: \"string\",\n  },\n});\nconst witches = Names.from([\n  \"Granny Weatherwax\",\n  \"Nanny Ogg\",\n  undefined,\n  \"Magrat Garlick\",\n]);\nwitches.byteLength; //=\u003e 64\nwitches.get(0); //=\u003e \"Granny Weatherwax\"\nwitches.get(2); //=\u003e undefined\n```\n\nAs with maps, the layout of vectors is calculated upon creation and editing is\nlimited to the items present upon creation.\n\n#### Strings\n\nThe protocol handles strings through StringView, an extension of DataView that\nhandles string serialization. It also offers a handful of convenience methods to\noperate on encoded strings so that some common operations can be performed\nwithout decoding the string:\n\n```javascript\nimport { StringView } from \"structurae\";\n\nlet stringView = StringView.from(\"abc😀a\");\n//=\u003e StringView [ 97, 98, 99, 240, 159, 152, 128, 97 ]\nstringView.toString(); //=\u003e \"abc😀a\"\nstringView == \"abc😀a\"; //=\u003e true\n\nstringView = StringView.from(\"abc😀\");\n// length of the view in bytes\nstringView.length; //=\u003e 8\n// the amount of characters in the string\nstringView.size; //=\u003e 4\n// get the first character in the string\nstringView.charAt(0); //=\u003e \"a\"\n// get the fourth character in the string\nstringView.charAt(3); //=\u003e \"😀\"\n// iterate over characters\n[...stringView.characters()]; //=\u003e [\"a\", \"b\", \"c\", \"😀\"]\nstringView.substring(0, 4); //=\u003e \"abc😀\"\n\nstringView = StringView.from(\"abc😀a\");\nconst searchValue = StringView.from(\"😀\");\nstringView.indexOf(searchValue); //=\u003e 3\nconst replacement = StringView.from(\"d\");\nstringView.replace(searchValue, replacement).toString(); //=\u003e \"abcda\"\nstringView.reverse().toString(); //=\u003e \"adcba\"\n```\n\n#### Tagged Objects\n\nWhen transferring our buffers encoded with views we can often rely on meta\ninformation to know what kind of view to use in order to decode a received\nbuffer. However, sometimes we might want our views to carry that information\nwithin themselves. To do that, we can prepend or tag each view with a value\nindicating its class, i.e. add a field that defaults to a certain value for each\nview class. Now upon receiving a buffer we can read that field using the\nDataView and convert it into an appropriate view.\n\nThe `View` class offers a few convenience methods to simplify this process:\n\n```typescript\nimport { View } from \"structurae\";\ninterface Dog {\n  tag: 0;\n  name: string;\n}\ninterface Cat {\n  tag: 1;\n  name: string;\n}\nconst DogView = view.create\u003cDog\u003e({\n  type: \"object\",\n  $id: \"Dog\",\n  properties: {\n    // the tag field with default value\n    tag: { type: \"number\", btype: \"uint8\", default: 0 }\n    name: { type: \"string\", maxLength: 10 }\n  },\n});\nconst CatView = view.create\u003cCat\u003e({\n  type: \"object\",\n  $id: \"Cat\",\n  properties: {\n    // the tag field with default value\n    tag: { type: \"number\", btype: \"uint8\", default: 1 }\n    name: { type: \"string\", maxLength: 10 }\n  },\n});\n\n// now we can encode tagged objects without specifying views first:\nconst animal = view.encode({ tag: 0, name: \"Gaspode\" });\n// and decode them:\nview.decode(animal) //=\u003e { tag: 0, name: \"Gaspode\" }\n```\n\n#### Extending View Types\n\nThe view protocol is designed with extensibility in mind. While built-in view\ntypes are ample for most cases, creating a special type can reduce boilerplate\nin certain situations. You can check out a full example of creating and using a\ncustom view type for BitArray in\n[examples/bit-array-view](https://github.com/zandaqo/structurae/tree/master/examples/bit-array-view).\n\nTo create a new view type, first create a class extending DataView and\nimplementing one of the view type interfaces, for example `PrimitiveView`:\n\n```ts\nexport class BitArrayView extends DataView implements PrimitiveView\u003cBitArray\u003e {\n  ...\n}\n```\n\nTo let TypeScript know about our new type, we use\n[module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation)\nto add our new type name to `ViewSchemaTypeMap` interface:\n\n```ts\ndeclare module \"structurae\" {\n  interface ViewSchemaTypeMap {\n    bitarray: \"string\";\n  }\n}\n```\n\nThis way, it will be a binary subtype (or `btype`) of JSONSchema type `string`.\n\nAnd finally, we add the new class to the list of views used by our protocol\ninstance:\n\n```ts\nconst protocol = new View();\nprotocol.Views.set(\"bitarray\", BitArrayView);\n```\n\nNow we can use the new type in our schemas, for example:\n\n```ts\nclass UserSettings {\n  id = 0;\n  settings = new BitArray(3);\n}\n\nconst UserSettingsView = protocol.create\u003cUserSettings\u003e({\n  $id: \"UserSettings\",\n  type: \"object\",\n  properties: {\n    id: { type: \"integer\" },\n    settings: {\n      type: \"string\",\n      btype: \"bitarray\",\n      maxLength: 12,\n    },\n  },\n}, UserSettings);\n```\n\n### Bit Structures\n\n#### BitField \u0026 BigBitField\n\nBitField and BigBitField use JavaScript Numbers and BigInts respectively as\nbitfields to store and operate on data using bitwise operations. By default,\nBitField operates on 31 bit long bitfield where bits are indexed from least\nsignificant to most:\n\n```javascript\nimport { BitField } from \"structurae\";\n\nconst bitfield = new BitField(29); // 29 === 0b11101\nbitfield.get(0); //=\u003e 1\nbitfield.get(1); //=\u003e 0\nbitfield.has(2, 3, 4); //=\u003e true\n```\n\nYou can use BitFieldMixin or BigBitFieldMixin with your own schema by specifying\nfield names and their respective sizes in bits:\n\n```javascript\nconst Field = BitFieldMixin({ width: 8, height: 8 });\nconst field = new Field({ width: 100, height: 200 });\nfield.get(\"width\"); //=\u003e 100;\nfield.get(\"height\"); //=\u003e 200\nfield.set(\"width\", 18);\nfield.get(\"width\"); //=\u003e 18\nfield.toObject(); //=\u003e { width: 18, height: 200 }\n```\n\nIf the total size of your fields exceeds 31 bits, use BigBitFieldMixin that\ninternally uses a BigInt to represent the resulting number, however, you can\nstill use normal numbers to set each field and get their value as a number as\nwell:\n\n```javascript\nconst LargeField = BitFieldMixin({ width: 20, height: 20 });\nconst largeField = new LargeField([1048576, 1048576]);\nlargeField.value; //=\u003e 1099512676352n\nlargeField.set(\"width\", 1000).get(\"width\"); //=\u003e 1000\n```\n\nIf you have to add more fields to your schema later on, you do not have to\nre-encode your existing values, just add new fields at the end of your new\nschema:\n\n```javascript\nconst OldField = BitFieldMixin({ width: 8, height: 8 });\nconst oldField = OldField.encode([20, 1]);\n//=\u003e oldField === 276\n\nconst NewField = BitFieldMixin({ width: 8, height: 8, area: 10 });\nconst newField = new NewField(oldField);\nnewField.get(\"width\"); //=\u003e 20\nnewField.get(\"height\"); //=\u003e 1\nnewField.set(\"weight\", 100).get(\"weight\"); //=\u003e 100\n```\n\nIf you only want to encode or decode a set of field values without creating an\ninstance, you can do so by using static methods `BitField.encode` and\n`BitField.decode` respectively:\n\n```javascript\nconst Field = BitFieldMixin({ width: 7, height: 1 });\n\nField.encode([20, 1]); //=\u003e 41\nField.encode({ height: 1, width: 20 }); //=\u003e 41\nField.decode(41); //=\u003e { width: 20, height: 1 }\n```\n\nIf you don't know beforehand how many bits you need for your field, you can call\n`BitField.getMinSize` with the maximum possible value of your field to find out:\n\n```javascript\nBitField.getMinSize(100); //=\u003e 7\nconst Field = BitFieldMixin({ width: BitField.getMinSize(250), height: 8 });\n```\n\nFor performance sake, BitField doesn't check the size of values being set and\nsetting values that exceed the specified field size will lead to undefined\nbehavior. If you want to check whether values fit their respective fields, you\ncan use `BitField.isValid`:\n\n```javascript\nconst Field = BitFieldMixin({ width: 7, height: 1 });\n\nField.isValid({ width: 100 }); //=\u003e true\nField.isValid({ width: 100, height: 3 }); //=\u003e false\n```\n\n`BitField#match` (and its static variation `BitField.match`) can be used to\ncheck values of multiple fields at once:\n\n```javascript\nconst Field = BitFieldMixin({ width: 7, height: 1 });\nconst field = new Field([20, 1]);\nfield.match({ width: 20 }); //=\u003e true\nfield.match({ height: 1, width: 20 }); //=\u003e true\nfield.match({ height: 1, width: 19 }); //=\u003e false\nField.match(field.valueOf(), { height: 1, width: 20 }); //=\u003e true\n```\n\nIf you have to check multiple BitField instances for the same values, create a\nspecial matcher with `BitField.getMatcher` and use it in the match method, that\nway each check will require only one bitwise operation and a comparison:\n\n```javascript\nconst Field = BitFieldMixin({ width: 7, height: 1 });\nconst matcher = Field.getMatcher({ height: 1, width: 20 });\nField.match(new Field([20, 1]).valueOf(), matcher); //=\u003e true\nField.match(new Field([19, 1]).valueOf(), matcher); //=\u003e false\n```\n\n#### BitArray\n\nBitArray uses Uint32Array as an array or vector of bits. It's a simpler version\nof BitField that only sets and checks individual bits:\n\n```javascript\nconst array = new BitArray(10);\narray.getBit(0); //=\u003e 0\narray.setBit(0).getBit(0); //=\u003e 1\narray.size; //=\u003e 10\narray.length; //=\u003e 1\n```\n\nBitArray is the base class for\n[Pool](https://github.com/zandaqo/structurae#Pool) and\n[RankedBitArray](https://github.com/zandaqo/structurae#RankedBitArray) classes.\nIt's useful in cases where one needs more bits than can be stored in a number,\nbut doesn't want to use BigInts as it is done by\n[BitField](https://github.com/zandaqo/structurae#BitField).\n\n#### Pool\n\nImplements a fast algorithm to manage availability of objects in an object pool\nusing a BitArray.\n\n```javascript\nconst { Pool } = require(\"structurae\");\n\n// create a pool of 1600 indexes\nconst pool = Pool.create(100 * 16);\n\n// get the next available index and make it unavailable\npool.get(); //=\u003e 0\npool.get(); //=\u003e 1\n\n// set index available\npool.free(0);\npool.get(); //=\u003e 0\npool.get(); //=\u003e 2\n```\n\n#### RankedBitArray\n\nRankedBitArray is an extension of BitArray with methods to efficiently calculate\nrank and select. The rank is calculated in constant time where as select has\nO(logN) time complexity. This is often used as a basic element in implementing\nsuccinct data structures.\n\n```javascript\nconst array = new RankedBitArray(10);\narray.setBit(1).setBit(3).setBit(7);\narray.rank(2); //=\u003e 1\narray.rank(7); //=\u003e 2\narray.select(2); //=\u003e 3\n```\n\n### Graphs\n\nStructurae offers classes that implement adjacency list (`AdjacencyList`) and\nadjacency matrix (`AdjacencyMatrixUnweightedDirected`,\n`AdjacencyMatrixUnweightedUndirected`, `AdjacencyMatrixWeightedDirected`,\n`AdjacencyMatrixWeightedUnirected`) as basic primitives to represent graphs\nusing TypedArrays, and the `Graph` class that extends the adjacency structures\nto offer methods for traversing graphs (BFS, DFS), pathfinding (Dijkstra,\nBellman-Ford), and spanning tree construction (BFS, Prim).\n\n#### Adjacency Structures\n\n`AdjacencyList` implements adjacency list data structure extending a TypedArray\nclass. The adjacency list requires less storage space: number of vertices +\nnumber of edges * 2 (for a weighted list). However, adding and removing edges is\nmuch slower since it involves shifting/unshifting values in the underlying typed\narray.\n\n```javascript\nimport { AdjacencyListMixin } from \"structurae\";\n\nconst List = AdjacencyListMixin(Int32Array);\nconst graph = List.create(6, 6);\n\n// the length of a weighted graph is vertices + edges * 2 + 1\ngraph.length; //=\u003e 19\ngraph.addEdge(0, 1, 5);\ngraph.addEdge(0, 2, 1);\ngraph.addEdge(2, 4, 1);\ngraph.addEdge(2, 5, 2);\ngraph.hasEdge(0, 1); //=\u003e true\ngraph.hasEdge(0, 4); //=\u003e false\ngraph.outEdges(2); //=\u003e [4, 5]\ngraph.inEdges(2); //=\u003e [0]\ngraph.hasEdge(0, 1); //=\u003e true\ngraph.getEdge(0, 1); //=\u003e 5\n```\n\nSince the maximum amount of edges in AdjacencyList is limited to the number\nspecified at creation, adding edges can overflow throwing a RangeError. If\nthat's a possibility, use `AdjacencyList#isFull` method to check if the limit is\nreached before adding an edge.\n\n`AdjacencyMatrixUnweightedDirected` and `AdjacencyMatrixUnweightedUndirected`\nimplement adjacency matrix data structure for unweighted graphs representing\neach edge by a single bit in an underlying ArrayBuffer.\n\n`AdjacencyMatrixWeightedDirected` and `AdjacencyMatrixWeightedUnirected`\nimplement adjacency matrix for weighted graphs extending a given TypedArray to\nstore the weights akin to [Grid](https://github.com/zandaqo/structurae#Grid).\n\n```javascript\nimport {\n  AdjacencyMatrixUnweightedDirected,\n  AdjacencyMatrixWeightedDirectedMixin,\n} from \"structurae\";\n\n// creates a class for directed graphs that uses Int32Array for edge weights\nconst Matrix = AdjacencyMatrixWeightedDirectedMixin(Int32Array);\n\nconst unweightedMatrix = new AdjacencyMatrixUnweightedDirected.create(6);\nunweightedMatrix.addEdge(0, 1);\nunweightedMatrix.addEdge(0, 2);\nunweightedMatrix.addEdge(0, 3);\nunweightedMatrix.addEdge(2, 4);\nunweightedMatrix.addEdge(2, 5);\nunweightedMatrix.hasEdge(0, 1); //=\u003e true\nunweightedMatrix.hasEdge(0, 4); //=\u003e false\nunweightedMatrix.outEdges(2); //=\u003e [4, 5]\nunweightedMatrix.inEdges(2); //=\u003e [0]\n\nconst weightedMatrix = Matrix.create(6);\nweightedMatrix.addEdge(0, 1, 3);\nweightedMatrix.hasEdge(0, 1); //=\u003e true\nweightedMatrix.hasEdge(1, 0); //=\u003e false\nweightedMatrix.getEdge(1, 0); //=\u003e 3\n```\n\n#### Graph\n\n`Graph` extends a provided adjacency structure with methods for traversing,\npathfinding, and spanning tree construction that use various graph algorithms.\n\n```javascript\nimport {\n  AdjacencyMatrixUnweightedDirected,\n  AdjacencyMatrixWeightedDirectedMixin,\n  GraphMixin,\n} from \"structurae\";\n\n// create a graph for directed unweighted graphs that use adjacency list structure\nconst UnweightedGraph = GraphMixin(AdjacencyMatrixUnweightedDirected);\n\n// for directed weighted graphs that use adjacency matrix structure\nconst WeightedGraph = GraphMixin(\n  AdjacencyMatrixWeightedDirectedMixin(Int32Array),\n);\n```\n\nThe traversal is done by a generator function `Graph#traverse` that can be\nconfigured to use Breadth-First or Depth-First traversal, as well as returning\nvertices on various stages of processing, i.e. only return vertices that are\nfully processed (`black`), or being processed (`gray`), or just encountered\n(`white`):\n\n```javascript\nconst graph = WeightedGraph.create(6);\ngraph.addEdge(0, 1, 3);\ngraph.addEdge(0, 2, 2);\ngraph.addEdge(0, 3, 1);\ngraph.addEdge(2, 4, 8);\ngraph.addEdge(2, 5, 6);\n\n// a BFS traversal results\n[...graph.traverse()]; //=\u003e [0, 1, 2, 3, 4, 5]\n\n// DFS\n[...graph.traverse(true)]; //=\u003e [0, 3, 2, 5, 4, 1]\n\n// BFS yeilding only non-encountered (\"white\") vertices starting from 0\n[...graph.traverse(false, 0, false, true)]; //=\u003e [1, 2, 3, 4, 5]\n```\n\n`Graph#path` returns the list of vertices constituting the shortest path between\ntwo given vertices. By default, the class uses BFS based search for unweighted\ngraphs, and Bellman-Ford algorithm for weighted graphs. However, the method can\nbe configured to use other algorithms by specifying arguments of the function:\n\n```javascript\ngraph.path(0, 5); // uses Bellman-Ford by default\ngraph.path(0, 5, true); // the graph is acyclic, uses DFS\ngraph.path(0, 5, false, true); // the graph might have cycles, but has no negative edges, uses Dijkstra\n```\n\n### Grids\n\n#### BinaryGrid\n\nBinaryGrid creates a grid or 2D matrix of bits and provides methods to operate\non it:\n\n```javascript\nimport { BinaryGrid } from \"structurae\";\n\n// create a grid of 2 rows and 8 columns\nconst bitGrid = BinaryGrid.create(2, 8);\nbitGrid.setValue(0, 0).setValue(0, 2).setValue(0, 5);\nbitGrid.getValue(0, 0); //=\u003e 1\nbitGrid.getValue(0, 1); //=\u003e 0\nbitGrid.getValue(0, 2); //=\u003e 1\n```\n\nBinaryGrid packs bits into numbers like\n[BitField](https://github.com/zandaqo/structurae#BitField) and holds them in an\nArrayBuffer, thus occupying the smallest possible space.\n\n#### Grid\n\nGrid extends a provided Array or TypedArray class to efficiently handle 2\ndimensional data without creating nested arrays. Grid \"unrolls\" nested arrays\ninto a single array and pads its \"columns\" to the nearest power of 2 in order to\nemploy quick lookups with bitwise operations.\n\n```javascript\nimport { GridMixin } from \"structurae\";\n\nconst ArrayGrid = GridMixin(Array);\n\n// create a grid of 5 rows and 4 columns\nconst grid = ArrayGrid.create(5, 4);\ngrid.length; //=\u003e 20\ngrid[0]; //=\u003e 0\n\n// create a grid from existing data:\nconst dataGrid = new ArrayGrid([\n  1,\n  2,\n  3,\n  4,\n  5,\n  6,\n  7,\n  8,\n]);\n// set columns number:\ndataGrid.columns = 4;\ndataGrid.getValue(1, 0); //=\u003e 5\n\n// you can change dimensions of the grid by setting columns number at any time:\ndataGrid.columns = 2;\ndataGrid.getValue(1, 0); //=\u003e 3\n```\n\nYou can get and set elements using their row and column indexes:\n\n```javascript\n//=\u003e ArrayGrid [1, 2, 3, 4, 5, 6, 7, 8]\ngrid.getValue(0, 1); //=\u003e 2\ngrid.setValue(0, 1, 10);\ngrid.getValue(0, 1); //=\u003e 10\n\n// use `getIndex` to get an array index of an element at given coordinates\ngrid.getIndex(0, 1); //=\u003e 1\n\n// use `getCoordinates` to find out row and column indexes of a given element by its array index:\ngrid.getCoordinates(0); //=\u003e [0, 0]\ngrid.getCoordinates(1); //=\u003e [0, 1]\n```\n\nA grid can be turned to and from an array of nested arrays using respectively\n`Grid.fromArrays` and `Grid#toArrays` methods:\n\n```javascript\nconst grid = ArrayGrid.fromArrays([[1, 2], [3, 4]]);\n//=\u003e ArrayGrid [ 1, 2, 3, 4 ]\ngrid.getValue(1, 1); //=\u003e 4\n\n// if arrays are not the same size or their size is not equal to a power two, Grid will pad them with 0 by default\n// the value for padding can be specified as the second argument\nconst grid = ArrayGrid.fromArrays([[1, 2], [3, 4, 5]]);\n//=\u003e ArrayGrid [ 1, 2, 0, 0, 3, 4, 5, 0 ]\ngrid.getValue(1, 1); //=\u003e 4\ngrid.toArrays(); //=\u003e [ [1, 2], [3, 4, 5] ]\n\n// you can choose to keep the padding values\ngrid.toArrays(true); //=\u003e [ [1, 2, 0, 0], [3, 4, 5, 0] ]\n```\n\n#### SymmetricGrid\n\nSymmetricGrid is a Grid that offers a more compact way of encoding symmetric or\ntriangular square matrices using half as much space.\n\n```javascript\nimport { SymmetricGrid } from \"structurae\";\n\nconst grid = ArrayGrid.create(100, 100);\ngrid.length; //=\u003e 12800\nconst symmetricGrid = SymmetricGrid.create(100);\nsymmetricGrid.length; //=\u003e 5050\n```\n\nSince the grid is symmetric, it returns the same value for a given pair of\ncoordinates regardless of their position:\n\n```javascript\nsymmetricGrid.setValue(0, 5, 10);\nsymmetricGrid.getValue(0, 5); //=\u003e 10\nsymmetricGrid.getValue(5, 0); //=\u003e 10\n```\n\n### Sorted Structures\n\n#### BinaryHeap\n\nBinaryHeap extends built-in Array to implement the binary heap data structure.\nAll the mutating methods (push, shift, splice, etc.) do so while maintaining the\nvalid heap structure. By default, BinaryHeap implements min-heap, but it can be\nchanged by providing a different comparator function:\n\n```javascript\nimport { BinaryHeap } from \"structurae\";\n\nclass MaxHeap extends BinaryHeap {}\nMaxHeap.compare = (a, b) =\u003e a \u003e b;\n```\n\nIn addition to all array methods, BinaryHeap provides a few methods to traverse\nor change the heap:\n\n```javascript\nconst heap = new BinaryHeap(10, 1, 20, 3, 9, 8);\nheap[0]; //=\u003e 1\n// the left child of the first (minimal) element of the heap\nheap.left(0); //=\u003e 3\n// the right child of the first (minimal) element of the heap\nheap.right(0); //=\u003e 8\n// the parent of the second element of the heap\nheap.parent(1); //=\u003e 1\n// returns the first element and adds a new element in one operation\nheap.replace(4); //=\u003e 1\nheap[0]; //=\u003e 3\nheap[0] = 6;\n// BinaryHeap [ 6, 4, 8, 10, 9, 20 ]\nheap.update(0); // updates the position of an element in the heap\n// BinaryHeap [ 4, 6, 8, 10, 9, 20 ]\n```\n\n#### SortedArray\n\nSortedArray extends Array with methods to efficiently handle sorted data. The\nmethods that change the contents of an array do so while preserving the sorted\norder:\n\n```js\nimport { SortedArray } from \"structurae\";\n\nconst sortedArray = new SortedArray();\nsortedArray.push(1);\n//=\u003e SortedArray [ 1, 2, 3, 4, 5, 9 ]\nsortedArray.unshift(8);\n//=\u003e SortedArray [ 1, 2, 3, 4, 5, 8, 9 ]\nsortedArray.splice(0, 2, 6);\n//=\u003e SortedArray [ 3, 4, 5, 6, 8, 9 ]\n```\n\n`uniquify` can be used to remove duplicating elements from the array:\n\n```js\nconst a = SortedArray.from([1, 1, 2, 2, 3, 4]);\na.uniquify();\n//=\u003e SortedArray [ 1, 2, 3, 4 ]\n```\n\nIf the instance property `unique` of an array is set to `true`, the array will\nbehave as a set and avoid duplicating elements:\n\n```js\nconst a = new SortedArray();\na.unique = true;\na.push(1); //=\u003e 1\na.push(2); //=\u003e 2\na.push(1); //=\u003e 2\na; //=\u003e SortedArray [ 1, 2 ]\n```\n\nTo create a sorted collection from unsorted array-like objects or items use\n`from` and `of` static methods respectively:\n\n```js\nSortedArray.from([3, 2, 9, 5, 4]);\n//=\u003e SortedArray [ 2, 3, 4, 5, 9 ]\nSortedArray.of(8, 5, 6);\n//=\u003e SortedArray [ 5, 6, 8 ]\n```\n\n`new SortedArray` behaves the same way as `new Array` and should be used with\nalready sorted elements:\n\n```js\nnew SortedArray(...[1, 2, 3, 4, 8]);\n//=\u003e SortedArray [ 1, 2, 3, 4, 8 ];\n```\n\n`indexOf` and `includes` use binary search that increasingly outperforms the\nbuilt-in methods as the size of the array grows.\n\nSortedArray provides `isSorted` method to check if the collection is sorted, and\n`range` method to get elements of the collection whose values are between the\nspecified range:\n\n```js\n//=\u003e SortedArray [ 2, 3, 4, 5, 9 ]\nsortedArray.range(3, 5);\n// =\u003e SortedArray [ 3, 4, 5 ]\nsortedArray.range(undefined, 4);\n// =\u003e SortedArray [ 2, 3, 4 ]\nsortedArray.range(4);\n// =\u003e SortedArray [ 4, 5, 8 ]\n```\n\nSortedArray also provides a set of functions to perform common set operations\nand find statistics of any sorted array-like objects.\n\n## License\n\nMIT © [Maga D. Zandaqo](http://maga.name)\n","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzandaqo%2Fstructurae","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzandaqo%2Fstructurae","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzandaqo%2Fstructurae/lists"}