Skip to content

Commit

Permalink
add persistent queues package (#59)
Browse files Browse the repository at this point in the history
* add persistent queues

* add changeset

* add README for rescript-queue

* add website content

* fix doc link

* update README

* explain deque

* update lock

* prepare publish

* update README

* sync docs

* chore

---------

Co-authored-by: Hyeseong Kim <[email protected]>
  • Loading branch information
namenu and cometkim authored Nov 10, 2023
1 parent 1b60288 commit 755acdc
Show file tree
Hide file tree
Showing 29 changed files with 454 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/smart-feet-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"rescript-queue": major
---

Initial release of rescript-queue
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ Fast and efficiant immutable collection for ReScript/TypeScript/JavaScript writt

## Packages

| Package | Description | |
| :--------------------------------- | :------------------------------- | --- |
| [rescript-vector](packages/vector) | Persistent Vector | [![Package Version](https://img.shields.io/npm/v/rescript-vector)](https://www.npmjs.com/package/rescript-vector) |
| Package | Description | |
| :--------------------------------- | :------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| [rescript-vector](packages/vector) | Persistent Vector | [![rescript-vector package version](https://img.shields.io/npm/v/rescript-vector)](https://www.npmjs.com/package/rescript-vector) |
| [rescript-queue](packages/queue) | Persistent Queue & Deque | [![rescript-queue package version](https://img.shields.io/npm/v/rescript-queue)](https://www.npmjs.com/package/rescript-queue) |

## License

Expand Down
3 changes: 3 additions & 0 deletions packages/queue/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
src/*.mjs linguist-generated
src/*.tsx linguist-generated
tests/*.mjs linguist-generated
5 changes: 5 additions & 0 deletions packages/queue/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.coverage

/coverage/
/lib/
/dist/
50 changes: 50 additions & 0 deletions packages/queue/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# ReScript Queue

[![Package Version](https://img.shields.io/npm/v/rescript-qeueue)](https://www.npmjs.com/package/rescript-queue)
[![License - MIT](https://img.shields.io/npm/l/rescript-queue)](#license)

`rescript-queue` is a **persistent queue and deque** data structure that can be used in ReScript and JavaScript.

**_Persistent_**

Any function that changes the queue returns a new instance of it while not modifying the original queue. Just like any strings or numbers, queues are treated as immutable values.

**_Queue_**

A queue is a data structure that follows the First-In-First-Out (FIFO) principle. This means that the first element added to the queue will be the first one to be removed.

**_Deque_**

A deque, short for "double-ended queue", is a data structure that allows elements to be inserted and removed from both ends. Like a regular queue, it follows the FIFO principle, but it also allows for elements to be added and removed from the back, making it a "double-ended" data structure.

## Rationale

### Finger Tree

The [**Finger Tree**](https://en.wikipedia.org/wiki/Finger_tree) data structure is a persistent data structure that allows for efficient insertion and deletion at the front and back of the queue or deque. The finger tree is divided into "fingers" which are small, constant-size sub-trees and "digits" which are individual elements. The fingers are used to represent the larger elements in the tree, while the digits are used to represent the smaller elements.

It allows for better efficiency and better space complexity than the list when implementing queue and deque.

1. **Efficiency**: Lists have a linear time complexity for insertion and deletion operations at the front and back of the queue, whereas finger trees have a logarithmic time complexity for these operations. This means that finger trees can be more efficient when dealing with large queues or deques.

2. **Space complexity**: Lists have a linear space complexity, which means that they will use more memory as the number of elements in the queue or deque increases. Finger trees, on the other hand, have a balanced space complexity, which means that they will use less memory for large queues or deques.

`rescript-queue` provides a finger tree-based implementation of a deque in ReScript.

### Batch Queue

`rescript-queue` provides a **Batch Queue** implementation, which is introduced from [Purely Functional Data Structures](https://doc.lagout.org/programmation/Functional%20Programming/Chris_Okasaki-Purely_Functional_Data_Structures-Cambridge_University_Press%281998%29.pdf) by Chris Okasaki.

The Batch Queue is based on the idea of "batches" of elements, which are grouped together to form a single unit.

The Batch Queue has two main components: a front queue and a back queue. The front queue holds the elements that have been dequeued, while the back queue holds the elements that have been enqueued. The front and back queues are both using finger tree data structure.

When an element is enqueued, it is added to the back queue. If the back queue becomes too large, it is split into two smaller queues, and the front queue is concatenated with one of the smaller queues to form a new front queue.

When an element is dequeued, it is removed from the front queue. If the front queue becomes too small, it is concatenated with the back queue to form a new front queue.

By using the concept of "batches" the Batch Queue is able to maintain a balance between time and space complexity, and also allows for efficient insertion and deletion at the front and back of the queue, as well as efficient concatenation and splitting of the queue.

## LICENSE

MIT
29 changes: 29 additions & 0 deletions packages/queue/bsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "rescript-queue",
"sources": [
{
"dir": "src",
"subdirs": true
},
{
"dir": "tests",
"type": "dev"
}
],
"package-specs": [
{
"module": "es6",
"in-source": true
}
],
"suffix": ".mjs",
"external-stdlib": "@rescript/std",
"bs-dev-dependencies": [
"@dusty-phillips/rescript-zora"
],
"gentypeconfig": {
"language": "typescript",
"shims": {},
"generatedFileExtension": ""
}
}
53 changes: 53 additions & 0 deletions packages/queue/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "rescript-queue",
"version": "0.1.0",
"description": "Persistent Queue & Deque implementation in ReScript",
"license": "MIT",
"author": {
"name": "Hyunwoo Nam"
},
"keywords": [
"rescript",
"data-structure",
"immutable",
"queue",
"deque"
],
"sideEffects": false,
"type": "module",
"main": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
},
"scripts": {
"res:build": "rescript build -with-deps",
"res:watch": "rescript build -w",
"res:clean": "rescript clean",
"bundle": "nanobundle build --clean",
"prepack": "yarn res:build && yarn bundle",
"test": "node tests/deque_test.mjs | faucet"
},
"files": [
"dist",
"src/*.res",
"src/*.resi",
"bsconfig.json"
],
"dependencies": {
"@rescript/std": "^10.1.0"
},
"devDependencies": {
"@dusty-phillips/rescript-zora": "git+https://github.com/reason-seoul/rescript-zora",
"c8": "^7.12.0",
"faucet": "^0.0.3",
"nanobundle": "^1.3.6",
"rescript": "^10.0.1",
"typescript": "^4.9.4"
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
type t<'a> = FingerTree.tree<'a>

let empty = FingerTree.Empty
let make = () => empty

let pushFront = (tree, x) => FingerTree.pushl(tree, x)
let pushBack = (tree, x) => FingerTree.pushr(tree, x)
Expand Down
19 changes: 19 additions & 0 deletions packages/vector/src/Deque.resi → packages/queue/src/Deque.resi
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
/**
Persistent Deque

See FingerTree.res for implementation.
For pushing, popping, peeking, time complexity is O(1) amortized.
*/
type t<'a>

let empty: t<'a>

@genType
let make: unit => t<'a>

@genType
let pushFront: (t<'a>, 'a) => t<'a>

@genType
let pushBack: (t<'a>, 'a) => t<'a>

@genType
let popFront: t<'a> => t<'a>

@genType
let popBack: t<'a> => t<'a>

@genType
let peekFront: t<'a> => option<'a>

@genType
let peekBack: t<'a> => option<'a>

@genType
let toArray: t<'a> => array<'a>
37 changes: 37 additions & 0 deletions packages/queue/src/Deque.tsx

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 72 additions & 0 deletions packages/queue/src/Queue.mjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 28 additions & 0 deletions packages/queue/src/Queue.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Batch Queue (Chris Okasaki's)
*/
type t<'a> = (list<'a>, list<'a>)

let empty = (list{}, list{})
let make = () => empty

let isEmpty = ((f, _)) => Belt.List.size(f) == 0

let checkf = fr =>
switch fr {
| (list{}, r) => (Belt.List.reverse(r), list{})
| q => q
}

let snoc = ((f, r), x) => checkf((f, list{x, ...r}))
let head = q =>
switch q {
| (list{}, _) => raise(Not_found)
| (list{x, ..._}, _) => x
}

let tail = q =>
switch q {
| (list{}, _) => raise(Not_found)
| (list{_, ...f}, r) => checkf((f, r))
}
18 changes: 18 additions & 0 deletions packages/queue/src/Queue.resi
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type t<'a>

let empty: t<'a>

@genType
let make: unit => t<'a>

@genType
let isEmpty: t<'a> => bool

@genType
let snoc: (t<'a>, 'a) => t<'a>

@genType
let head: t<'a> => 'a // unsafe

@genType
let tail: t<'a> => t<'a> // unsafe
Loading

0 comments on commit 755acdc

Please sign in to comment.