diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..90e28e5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - uses: purescript-contrib/setup-purescript@main + with: + purescript: "unstable" + + - uses: actions/setup-node@v2 + with: + node-version: "22.x" + + - name: Install dependencies + run: | + npm install -g spago@next + + - name: Run tests + run: spago test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..07086f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.psc-ide-port +.direnv +.envrc +output +.spago +node_modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..73c6a93 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# purescript-priority-queue + +[![Latest release](http://img.shields.io/github/release/f-f/purescript-priority-queue.svg)](https://github.com/f-f/purescript-priority-queue/releases) +[![Build status](https://github.com/f-f/purescript-priority-queue/workflows/CI/badge.svg?branch=main)](https://github.com/f-f/purescript-priority-queue/actions?query=workflow%3ACI+branch%3Amain) +[![Pursuit](https://pursuit.purescript.org/packages/purescript-priority-queue/badge)](https://pursuit.purescript.org/packages/purescript-priority-queue) + +Fast min and max `PriorityQueue`, based on binary heaps. + +## Installation + +``` +spago install priority-queue +``` + +## Documentation + +Module documentation is [published on Pursuit](http://pursuit.purescript.org/packages/purescript-priority-queue). diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a20613b --- /dev/null +++ b/flake.lock @@ -0,0 +1,87 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1721622093, + "narHash": "sha256-iQ+quy3A1EKeFyLyAtjhgSvZHH7r+xybXZkxMhasN4I=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "453402b94f39f968a7c27df28e060f69e4a50c3b", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "purescript-overlay": { + "inputs": { + "flake-compat": "flake-compat", + "nixpkgs": [ + "nixpkgs" + ], + "slimlock": "slimlock" + }, + "locked": { + "lastModified": 1720786645, + "narHash": "sha256-eiuWqQ9xTA2E76XDg13TQBecnLV1g+ahdauC0FYSD7U=", + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "rev": "c46925ec09fdf54b5bb4ba38b40e19918d3be7be", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "purescript-overlay", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "purescript-overlay": "purescript-overlay" + } + }, + "slimlock": { + "inputs": { + "nixpkgs": [ + "purescript-overlay", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1688756706, + "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=", + "owner": "thomashoneyman", + "repo": "slimlock", + "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c", + "type": "github" + }, + "original": { + "owner": "thomashoneyman", + "repo": "slimlock", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..1759e09 --- /dev/null +++ b/flake.nix @@ -0,0 +1,40 @@ +{ + inputs = { + nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; + purescript-overlay.url = "github:thomashoneyman/purescript-overlay"; + purescript-overlay.inputs.nixpkgs.follows = "nixpkgs"; + }; + + outputs = { self, nixpkgs, ... }@inputs: + let + supportedSystems = [ "aarch64-linux" "x86_64-linux" "aarch64-darwin" "x86_64-darwin" ]; + + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + + nixpkgsFor = forAllSystems (system: import nixpkgs { + inherit system; + overlays = builtins.attrValues self.overlays; + }); + in + { + overlays = { + purescript = inputs.purescript-overlay.overlays.default; + }; + + devShells = forAllSystems (system: + let pkgs = nixpkgsFor.${system}; + in { + default = pkgs.mkShell { + name = "dev"; + buildInputs = with pkgs; [ + purs-bin.purs-0_15_15 + purs-tidy + purs-backend-es + spago-unstable + purescript-language-server + nodejs + ]; + }; + }); + }; +} diff --git a/spago.lock b/spago.lock new file mode 100644 index 0000000..a4b0511 --- /dev/null +++ b/spago.lock @@ -0,0 +1,870 @@ +workspace: + packages: + priority-queue: + path: ./ + dependencies: + - arrays + - effect + - foldable-traversable + - integers + - lists + - maybe + - prelude + - st + test_dependencies: + - assert + - console + build_plan: + - arrays + - assert + - bifunctors + - console + - const + - contravariant + - control + - distributive + - effect + - either + - exists + - foldable-traversable + - functions + - functors + - identity + - integers + - invariant + - lazy + - lists + - maybe + - newtype + - nonempty + - numbers + - orders + - partial + - prelude + - profunctor + - refs + - safe-coerce + - st + - tailrec + - tuples + - type-equality + - unfoldable + - unsafe-coerce + package_set: + address: + registry: 53.1.0 + compiler: ">=0.15.15 <0.16.0" + content: + abc-parser: 2.0.1 + ace: 9.1.0 + address-rfc2821: 0.1.1 + aff: 7.1.0 + aff-bus: 6.0.0 + aff-coroutines: 9.0.0 + aff-promise: 4.0.0 + aff-retry: 2.0.0 + affjax: 13.0.0 + affjax-node: 1.0.0 + affjax-web: 1.0.0 + ansi: 7.0.0 + apexcharts: 0.5.0 + applicative-phases: 1.0.0 + argonaut: 9.0.0 + argonaut-aeson-generic: 0.4.1 + argonaut-codecs: 9.1.0 + argonaut-core: 7.0.0 + argonaut-generic: 8.0.0 + argonaut-traversals: 10.0.0 + argparse-basic: 2.0.0 + array-builder: 0.1.2 + array-search: 0.6.0 + arraybuffer: 13.2.0 + arraybuffer-builder: 3.1.0 + arraybuffer-types: 3.0.2 + arrays: 7.3.0 + arrays-extra: 0.6.1 + arrays-zipper: 2.0.1 + ask: 1.0.0 + assert: 6.0.0 + assert-multiple: 0.4.0 + avar: 5.0.0 + b64: 0.0.8 + barbies: 1.0.1 + barlow-lens: 0.9.0 + bifunctors: 6.0.0 + bigints: 7.0.1 + bolson: 0.3.9 + bookhound: 0.1.7 + bower-json: 3.0.0 + call-by-name: 4.0.1 + canvas: 6.0.0 + canvas-action: 9.0.0 + cartesian: 1.0.6 + catenable-lists: 7.0.0 + cbor-stream: 1.3.0 + chameleon: 1.0.0 + chameleon-halogen: 1.0.3 + chameleon-react-basic: 1.1.0 + chameleon-styled: 2.5.0 + chameleon-transformers: 1.0.0 + channel: 1.0.0 + checked-exceptions: 3.1.1 + choku: 1.0.1 + classless: 0.1.1 + classless-arbitrary: 0.1.1 + classless-decode-json: 0.1.1 + classless-encode-json: 0.1.3 + classnames: 2.0.0 + codec: 6.1.0 + codec-argonaut: 10.0.0 + codec-json: 1.2.0 + colors: 7.0.1 + concur-core: 0.5.0 + concur-react: 0.5.0 + concurrent-queues: 3.0.0 + console: 6.1.0 + const: 6.0.0 + contravariant: 6.0.0 + control: 6.0.0 + convertable-options: 1.0.0 + coroutines: 7.0.0 + css: 6.0.0 + css-frameworks: 1.0.1 + csv-stream: 2.3.0 + data-mvc: 0.0.2 + datetime: 6.1.0 + datetime-parsing: 0.2.0 + debounce: 0.1.0 + debug: 6.0.2 + decimals: 7.1.0 + default-values: 1.0.1 + deku: 0.9.23 + deno: 0.0.5 + dissect: 1.0.0 + distributive: 6.0.0 + dom-filereader: 7.0.0 + dom-indexed: 12.0.0 + dom-simple: 0.4.0 + dotenv: 4.0.3 + droplet: 0.6.0 + dts: 1.0.0 + dual-numbers: 1.0.2 + dynamic-buffer: 3.0.1 + echarts-simple: 0.0.1 + effect: 4.0.0 + either: 6.1.0 + elmish: 0.11.3 + elmish-enzyme: 0.1.1 + elmish-hooks: 0.10.0 + elmish-html: 0.8.3 + elmish-testing-library: 0.3.2 + email-validate: 7.0.0 + encoding: 0.0.9 + enums: 6.0.1 + env-names: 0.4.0 + error: 2.0.0 + eta-conversion: 0.3.2 + exceptions: 6.0.0 + exists: 6.0.0 + exitcodes: 4.0.0 + expect-inferred: 3.0.0 + ezfetch: 1.0.0 + fahrtwind: 2.0.0 + fallback: 0.1.0 + fast-vect: 1.2.0 + fetch: 4.1.0 + fetch-argonaut: 1.0.1 + fetch-core: 5.1.0 + fetch-yoga-json: 1.1.0 + ffi-simple: 0.5.1 + fft-js: 0.1.0 + filterable: 5.0.0 + fix-functor: 0.1.0 + fixed-points: 7.0.0 + fixed-precision: 5.0.0 + flame: 1.3.0 + float32: 2.0.0 + fmt: 0.2.1 + foldable-traversable: 6.0.0 + foldable-traversable-extra: 0.0.6 + foreign: 7.0.0 + foreign-object: 4.1.0 + foreign-readwrite: 3.4.0 + forgetmenot: 0.1.0 + fork: 6.0.0 + form-urlencoded: 7.0.0 + formatters: 7.0.0 + framer-motion: 1.0.1 + free: 7.1.0 + freeap: 7.0.0 + freer-free: 0.0.1 + freet: 7.0.0 + functions: 6.0.0 + functor1: 3.0.0 + functors: 5.0.0 + fuzzy: 0.4.0 + gen: 4.0.0 + generate-values: 1.0.1 + generic-router: 0.0.1 + geojson: 0.0.5 + geometry-plane: 1.0.3 + gojs: 0.1.1 + grain: 3.0.0 + grain-router: 3.0.0 + grain-virtualized: 3.0.0 + graphs: 8.1.0 + group: 4.1.1 + halogen: 7.0.0 + halogen-bootstrap5: 5.3.2 + halogen-canvas: 1.0.0 + halogen-css: 10.0.0 + halogen-echarts-simple: 0.0.4 + halogen-formless: 4.0.3 + halogen-helix: 1.0.0 + halogen-hooks: 0.6.3 + halogen-hooks-extra: 0.9.0 + halogen-infinite-scroll: 1.1.0 + halogen-store: 0.5.4 + halogen-storybook: 2.0.0 + halogen-subscriptions: 2.0.0 + halogen-svg-elems: 8.0.0 + halogen-typewriter: 1.0.4 + halogen-vdom: 8.0.0 + halogen-vdom-string-renderer: 0.5.0 + halogen-xterm: 2.0.0 + heckin: 2.0.1 + heterogeneous: 0.6.0 + homogeneous: 0.4.0 + http-methods: 6.0.0 + httpurple: 4.0.0 + huffman: 0.4.0 + humdrum: 0.0.1 + hyrule: 2.3.8 + identity: 6.0.0 + identy: 4.0.1 + indexed-db: 1.0.0 + indexed-monad: 3.0.0 + int64: 3.0.0 + integers: 6.0.0 + interpolate: 5.0.2 + intersection-observer: 1.0.1 + invariant: 6.0.0 + jarilo: 1.0.1 + jelly: 0.10.0 + jelly-router: 0.3.0 + jelly-signal: 0.4.0 + jest: 1.0.0 + js-abort-controller: 1.0.0 + js-bigints: 2.2.1 + js-date: 8.0.0 + js-fetch: 0.2.1 + js-fileio: 3.0.0 + js-intl: 1.0.4 + js-iterators: 0.1.1 + js-maps: 0.1.2 + js-promise: 1.0.0 + js-promise-aff: 1.0.0 + js-timers: 6.1.0 + js-uri: 3.1.0 + json: 1.1.0 + json-codecs: 5.0.0 + justifill: 0.5.0 + jwt: 0.0.9 + labeled-data: 0.2.0 + language-cst-parser: 0.14.0 + lazy: 6.0.0 + lazy-joe: 1.0.0 + lcg: 4.0.0 + leibniz: 5.0.0 + leveldb: 1.0.1 + liminal: 1.0.1 + linalg: 6.0.0 + lists: 7.0.0 + literals: 1.0.2 + logging: 3.0.0 + logging-journald: 0.4.0 + lumi-components: 18.0.0 + machines: 7.0.0 + maps-eager: 0.5.0 + marionette: 1.0.0 + marionette-react-basic-hooks: 0.1.1 + marked: 0.1.0 + matrices: 5.0.1 + matryoshka: 1.0.0 + maybe: 6.0.0 + media-types: 6.0.0 + meowclient: 1.0.0 + midi: 4.0.0 + milkis: 9.0.0 + minibench: 4.0.1 + mmorph: 7.0.0 + monad-control: 5.0.0 + monad-logger: 1.3.1 + monad-loops: 0.5.0 + monad-unlift: 1.0.1 + monoid-extras: 0.0.1 + monoidal: 0.16.0 + morello: 0.4.0 + mote: 3.0.0 + motsunabe: 2.0.0 + mvc: 0.0.1 + mysql: 6.0.1 + n3: 0.1.0 + nano-id: 1.1.0 + nanoid: 0.1.0 + naturals: 3.0.0 + nested-functor: 0.2.1 + newtype: 5.0.0 + nextjs: 0.1.1 + nextui: 0.2.0 + node-buffer: 9.0.0 + node-child-process: 11.1.0 + node-event-emitter: 3.0.0 + node-execa: 5.0.0 + node-fs: 9.2.0 + node-glob-basic: 1.3.0 + node-http: 9.1.0 + node-http2: 1.1.1 + node-human-signals: 1.0.0 + node-net: 5.1.0 + node-os: 5.1.0 + node-path: 5.0.0 + node-process: 11.2.0 + node-readline: 8.1.0 + node-sqlite3: 8.0.0 + node-stream-pipes: 2.1.4 + node-streams: 9.0.0 + node-tls: 0.3.1 + node-url: 7.0.1 + node-zlib: 0.4.0 + nonempty: 7.0.0 + now: 6.0.0 + npm-package-json: 2.0.0 + nullable: 6.0.0 + numberfield: 0.1.0 + numbers: 9.0.1 + oak: 3.1.1 + oak-debug: 1.2.2 + object-maps: 0.3.0 + ocarina: 1.5.4 + open-folds: 6.3.0 + open-memoize: 6.1.0 + open-pairing: 6.1.0 + options: 7.0.0 + optparse: 5.0.1 + ordered-collections: 3.2.0 + ordered-set: 0.4.0 + orders: 6.0.0 + owoify: 1.2.0 + pairs: 9.0.1 + parallel: 7.0.0 + parsing: 10.2.0 + parsing-dataview: 3.2.4 + partial: 4.0.0 + pathy: 9.0.0 + pha: 0.13.0 + phaser: 0.7.0 + phylio: 1.1.2 + pipes: 8.0.0 + pirates-charm: 0.0.1 + pmock: 0.9.0 + point-free: 1.0.0 + pointed-list: 0.5.1 + polymorphic-vectors: 4.0.0 + posix-types: 6.0.0 + postgresql: 2.0.15 + precise: 6.0.0 + precise-datetime: 7.0.0 + prelude: 6.0.1 + prettier-printer: 3.0.0 + profunctor: 6.0.1 + profunctor-lenses: 8.0.0 + protobuf: 4.3.0 + psa-utils: 8.0.0 + psci-support: 6.0.0 + punycode: 1.0.0 + qualified-do: 2.2.0 + quantities: 12.2.0 + quickcheck: 8.0.1 + quickcheck-combinators: 0.1.3 + quickcheck-laws: 7.0.0 + quickcheck-utf8: 0.0.0 + random: 6.0.0 + rationals: 6.0.0 + rdf: 0.1.0 + react: 11.0.0 + react-aria: 0.2.0 + react-basic: 17.0.0 + react-basic-classic: 3.0.0 + react-basic-dnd: 10.1.0 + react-basic-dom: 6.1.0 + react-basic-emotion: 7.1.0 + react-basic-hooks: 8.2.0 + react-basic-storybook: 2.0.0 + react-dom: 8.0.0 + react-halo: 3.0.0 + react-icons: 1.1.5 + react-markdown: 0.1.0 + react-testing-library: 4.0.1 + react-virtuoso: 1.0.0 + reactix: 0.6.1 + read: 1.0.1 + recharts: 1.1.0 + record: 4.0.0 + record-extra: 5.0.1 + record-ptional-fields: 0.1.2 + record-studio: 1.0.4 + refs: 6.0.0 + remotedata: 5.0.1 + repr: 0.5.0 + resize-observer: 1.0.0 + resource: 2.0.1 + resourcet: 1.0.0 + result: 1.0.3 + return: 0.2.0 + ring-modules: 5.0.1 + rito: 0.3.4 + roman: 0.4.0 + rough-notation: 1.0.2 + routing: 11.0.0 + routing-duplex: 0.7.0 + run: 5.0.0 + safe-coerce: 2.0.0 + safely: 4.0.1 + school-of-music: 1.3.0 + selection-foldable: 0.2.0 + selective-functors: 1.0.1 + semirings: 7.0.0 + signal: 13.0.0 + simple-emitter: 3.0.1 + simple-i18n: 2.0.1 + simple-json: 9.0.0 + simple-json-generics: 0.2.1 + simple-ulid: 3.0.0 + sized-matrices: 1.0.0 + sized-vectors: 5.0.2 + slug: 3.0.8 + small-ffi: 4.0.1 + soundfonts: 4.1.0 + sparse-matrices: 1.3.0 + sparse-polynomials: 2.0.5 + spec: 7.6.0 + spec-mocha: 5.1.0 + spec-quickcheck: 5.0.0 + splitmix: 2.1.0 + ssrs: 1.0.0 + st: 6.2.0 + statistics: 0.3.2 + strictlypositiveint: 1.0.1 + string-parsers: 8.0.0 + strings: 6.0.1 + strings-extra: 4.0.0 + stringutils: 0.0.12 + substitute: 0.2.3 + supply: 0.2.0 + svg-parser: 3.0.0 + systemd-journald: 0.3.0 + tagged: 4.0.2 + tailrec: 6.1.0 + tecton: 0.2.1 + tecton-halogen: 0.2.0 + test-unit: 17.0.0 + thermite: 6.3.1 + thermite-dom: 0.3.1 + these: 6.0.0 + toestand: 0.9.0 + transformation-matrix: 1.0.1 + transformers: 6.1.0 + tree-rose: 4.0.2 + ts-bridge: 4.0.0 + tuples: 7.0.0 + two-or-more: 1.0.0 + type-equality: 4.0.1 + typedenv: 2.0.1 + typelevel: 6.0.0 + typelevel-lists: 2.1.0 + typelevel-peano: 1.0.1 + typelevel-prelude: 7.0.0 + typelevel-regex: 0.0.3 + typelevel-rows: 0.1.0 + typisch: 0.4.0 + uint: 7.0.0 + ulid: 3.0.1 + uncurried-transformers: 1.1.0 + undefined: 2.0.0 + undefined-is-not-a-problem: 1.1.0 + unfoldable: 6.0.0 + unicode: 6.0.0 + unique: 0.6.1 + unlift: 1.0.1 + unordered-collections: 3.1.0 + unsafe-coerce: 6.0.0 + unsafe-reference: 5.0.0 + untagged-to-tagged: 0.1.4 + untagged-union: 1.0.0 + uri: 9.0.0 + url-immutable: 1.0.0 + uuid: 9.0.0 + uuidv4: 1.0.0 + validation: 6.0.0 + variant: 8.0.0 + variant-encodings: 2.0.0 + vectorfield: 1.0.1 + vectors: 2.1.0 + versions: 7.0.0 + visx: 0.0.2 + web-clipboard: 5.0.0 + web-cssom: 2.0.0 + web-cssom-view: 0.1.0 + web-dom: 6.0.0 + web-dom-parser: 8.0.0 + web-dom-xpath: 3.0.0 + web-encoding: 3.0.0 + web-events: 4.0.0 + web-fetch: 4.0.1 + web-file: 4.0.0 + web-geometry: 0.1.0 + web-html: 4.1.0 + web-pointerevents: 2.0.0 + web-proletarian: 1.0.0 + web-promise: 3.2.0 + web-resize-observer: 2.1.0 + web-router: 1.0.0 + web-socket: 4.0.0 + web-storage: 5.0.0 + web-streams: 4.0.0 + web-touchevents: 4.0.0 + web-uievents: 5.0.0 + web-url: 2.0.0 + web-workers: 1.1.0 + web-xhr: 5.0.1 + webextension-polyfill: 0.1.0 + webgpu: 0.0.1 + which: 2.0.0 + xterm: 1.0.0 + yoga-fetch: 1.0.1 + yoga-json: 5.1.0 + yoga-om: 0.1.0 + yoga-postgres: 6.0.0 + yoga-tree: 1.0.0 + z3: 0.0.2 + zipperarray: 2.0.0 + extra_packages: {} +packages: + arrays: + type: registry + version: 7.3.0 + integrity: sha256-tmcklBlc/muUtUfr9RapdCPwnlQeB3aSrC4dK85gQlc= + dependencies: + - bifunctors + - control + - foldable-traversable + - functions + - maybe + - nonempty + - partial + - prelude + - safe-coerce + - st + - tailrec + - tuples + - unfoldable + - unsafe-coerce + assert: + type: registry + version: 6.0.0 + integrity: sha256-hCSYcCw9kj3qujoDcriWhCdmrpPZoguSPDZhEMnTl3A= + dependencies: + - console + - effect + - prelude + bifunctors: + type: registry + version: 6.0.0 + integrity: sha256-/gZwC9YhNxZNQpnHa5BIYerCGM2jeX9ukZiEvYxm5Nw= + dependencies: + - const + - either + - newtype + - prelude + - tuples + console: + type: registry + version: 6.1.0 + integrity: sha256-CxmAzjgyuGDmt9FZW51VhV6rBPwR6o0YeKUzA9rSzcM= + dependencies: + - effect + - prelude + const: + type: registry + version: 6.0.0 + integrity: sha256-tNrxDW8D8H4jdHE2HiPzpLy08zkzJMmGHdRqt5BQuTc= + dependencies: + - invariant + - newtype + - prelude + contravariant: + type: registry + version: 6.0.0 + integrity: sha256-TP+ooAp3vvmdjfQsQJSichF5B4BPDHp3wAJoWchip6c= + dependencies: + - const + - either + - newtype + - prelude + - tuples + control: + type: registry + version: 6.0.0 + integrity: sha256-sH7Pg9E96JCPF9PIA6oQ8+BjTyO/BH1ZuE/bOcyj4Jk= + dependencies: + - newtype + - prelude + distributive: + type: registry + version: 6.0.0 + integrity: sha256-HTDdmEnzigMl+02SJB88j+gAXDx9VKsbvR4MJGDPbOQ= + dependencies: + - identity + - newtype + - prelude + - tuples + - type-equality + effect: + type: registry + version: 4.0.0 + integrity: sha256-eBtZu+HZcMa5HilvI6kaDyVX3ji8p0W9MGKy2K4T6+M= + dependencies: + - prelude + either: + type: registry + version: 6.1.0 + integrity: sha256-6hgTPisnMWVwQivOu2PKYcH8uqjEOOqDyaDQVUchTpY= + dependencies: + - control + - invariant + - maybe + - prelude + exists: + type: registry + version: 6.0.0 + integrity: sha256-A0JQHpTfo1dNOj9U5/Fd3xndlRSE0g2IQWOGor2yXn8= + dependencies: + - unsafe-coerce + foldable-traversable: + type: registry + version: 6.0.0 + integrity: sha256-fLeqRYM4jUrZD5H4WqcwUgzU7XfYkzO4zhgtNc3jcWM= + dependencies: + - bifunctors + - const + - control + - either + - functors + - identity + - maybe + - newtype + - orders + - prelude + - tuples + functions: + type: registry + version: 6.0.0 + integrity: sha256-adMyJNEnhGde2unHHAP79gPtlNjNqzgLB8arEOn9hLI= + dependencies: + - prelude + functors: + type: registry + version: 5.0.0 + integrity: sha256-zfPWWYisbD84MqwpJSZFlvM6v86McM68ob8p9s27ywU= + dependencies: + - bifunctors + - const + - contravariant + - control + - distributive + - either + - invariant + - maybe + - newtype + - prelude + - profunctor + - tuples + - unsafe-coerce + identity: + type: registry + version: 6.0.0 + integrity: sha256-4wY0XZbAksjY6UAg99WkuKyJlQlWAfTi2ssadH0wVMY= + dependencies: + - control + - invariant + - newtype + - prelude + integers: + type: registry + version: 6.0.0 + integrity: sha256-sf+sK26R1hzwl3NhXR7WAu9zCDjQnfoXwcyGoseX158= + dependencies: + - maybe + - numbers + - prelude + invariant: + type: registry + version: 6.0.0 + integrity: sha256-RGWWyYrz0Hs1KjPDA+87Kia67ZFBhfJ5lMGOMCEFoLo= + dependencies: + - control + - prelude + lazy: + type: registry + version: 6.0.0 + integrity: sha256-lMsfFOnlqfe4KzRRiW8ot5ge6HtcU3Eyh2XkXcP5IgU= + dependencies: + - control + - foldable-traversable + - invariant + - prelude + lists: + type: registry + version: 7.0.0 + integrity: sha256-EKF15qYqucuXP2lT/xPxhqy58f0FFT6KHdIB/yBOayI= + dependencies: + - bifunctors + - control + - foldable-traversable + - lazy + - maybe + - newtype + - nonempty + - partial + - prelude + - tailrec + - tuples + - unfoldable + maybe: + type: registry + version: 6.0.0 + integrity: sha256-5cCIb0wPwbat2PRkQhUeZO0jcAmf8jCt2qE0wbC3v2Q= + dependencies: + - control + - invariant + - newtype + - prelude + newtype: + type: registry + version: 5.0.0 + integrity: sha256-gdrQu8oGe9eZE6L3wOI8ql/igOg+zEGB5ITh2g+uttw= + dependencies: + - prelude + - safe-coerce + nonempty: + type: registry + version: 7.0.0 + integrity: sha256-54ablJZUHGvvlTJzi3oXyPCuvY6zsrWJuH/dMJ/MFLs= + dependencies: + - control + - foldable-traversable + - maybe + - prelude + - tuples + - unfoldable + numbers: + type: registry + version: 9.0.1 + integrity: sha256-/9M6aeMDBdB4cwYDeJvLFprAHZ49EbtKQLIJsneXLIk= + dependencies: + - functions + - maybe + orders: + type: registry + version: 6.0.0 + integrity: sha256-nBA0g3/ai0euH8q9pSbGqk53W2q6agm/dECZTHcoink= + dependencies: + - newtype + - prelude + partial: + type: registry + version: 4.0.0 + integrity: sha256-fwXerld6Xw1VkReh8yeQsdtLVrjfGiVuC5bA1Wyo/J4= + dependencies: [] + prelude: + type: registry + version: 6.0.1 + integrity: sha256-o8p6SLYmVPqzXZhQFd2hGAWEwBoXl1swxLG/scpJ0V0= + dependencies: [] + profunctor: + type: registry + version: 6.0.1 + integrity: sha256-E58hSYdJvF2Qjf9dnWLPlJKh2Z2fLfFLkQoYi16vsFk= + dependencies: + - control + - distributive + - either + - exists + - invariant + - newtype + - prelude + - tuples + refs: + type: registry + version: 6.0.0 + integrity: sha256-Vgwne7jIbD3ZMoLNNETLT8Litw6lIYo3MfYNdtYWj9s= + dependencies: + - effect + - prelude + safe-coerce: + type: registry + version: 2.0.0 + integrity: sha256-a1ibQkiUcbODbLE/WAq7Ttbbh9ex+x33VCQ7GngKudU= + dependencies: + - unsafe-coerce + st: + type: registry + version: 6.2.0 + integrity: sha256-z9X0WsOUlPwNx9GlCC+YccCyz8MejC8Wb0C4+9fiBRY= + dependencies: + - partial + - prelude + - tailrec + - unsafe-coerce + tailrec: + type: registry + version: 6.1.0 + integrity: sha256-Xx19ECVDRrDWpz9D2GxQHHV89vd61dnXxQm0IcYQHGk= + dependencies: + - bifunctors + - effect + - either + - identity + - maybe + - partial + - prelude + - refs + tuples: + type: registry + version: 7.0.0 + integrity: sha256-1rXgTomes9105BjgXqIw0FL6Fz1lqqUTLWOumhWec1M= + dependencies: + - control + - invariant + - prelude + type-equality: + type: registry + version: 4.0.1 + integrity: sha256-Hs9D6Y71zFi/b+qu5NSbuadUQXe5iv5iWx0226vOHUw= + dependencies: [] + unfoldable: + type: registry + version: 6.0.0 + integrity: sha256-JtikvJdktRap7vr/K4ITlxUX1QexpnqBq0G/InLr6eg= + dependencies: + - foldable-traversable + - maybe + - partial + - prelude + - tuples + unsafe-coerce: + type: registry + version: 6.0.0 + integrity: sha256-IqIYW4Vkevn8sI+6aUwRGvd87tVL36BBeOr0cGAE7t0= + dependencies: [] diff --git a/spago.yaml b/spago.yaml new file mode 100644 index 0000000..ef32894 --- /dev/null +++ b/spago.yaml @@ -0,0 +1,19 @@ +package: + name: priority-queue + dependencies: + - arrays + - effect + - foldable-traversable + - integers + - lists + - maybe + - prelude + - st + test: + main: Test.PriorityQueue + dependencies: + - assert + - console +workspace: + packageSet: + registry: 53.1.0 diff --git a/src/PriorityQueue.purs b/src/PriorityQueue.purs new file mode 100644 index 0000000..a118e27 --- /dev/null +++ b/src/PriorityQueue.purs @@ -0,0 +1,192 @@ +module Data.PriorityQueue + ( Queue + , newMaxQueue + , newMinQueue + -- , peekBack + , peekFront + , pop + , popN + , push + , pushMany + , size + , toArray + , toList + ) where + +import Prelude + +import Control.Monad.ST (ST) +import Control.Monad.ST.Global (Global, toEffect) +import Data.Array.ST (STArray) +import Data.Array.ST as STA +import Data.Int as Int +import Data.List (List) +import Data.List as List +import Data.Maybe (Maybe(..)) +import Data.Ord (greaterThan, lessThan) +import Data.Traversable (for, for_) +import Effect (Effect) + +-------------------------------------------------------------------------------- +-- API + +-- | A priority queue that allows for efficient insertion and removal of elements. +-- The queue can be created with a custom ordering function that determines the +-- priority of elements. +-- Note: it's not possible to have a meaninful Eq instance, as two queues with +-- the same elements might have them in different order due to the heap structure. +-- It's recommended to convert the queue to an array to compare it. +newtype Queue a = Queue + { contents :: STArray Global a + , ordering :: a -> a -> Boolean + } + +-- | Create a new priority queue where the element with the smallest value +-- according to the provided function will be at the front of the queue. +newMinQueue :: forall a. (a -> Number) -> Effect (Queue a) +newMinQueue fn = toEffect do + contents <- STA.new + pure $ Queue + { contents + , ordering: \a b -> greaterThan (fn a) (fn b) + } + +-- | Create a new priority queue where the element with the largest value +-- according to the provided function will be at the front of the queue. +newMaxQueue :: forall a. (a -> Number) -> Effect (Queue a) +newMaxQueue fn = toEffect do + contents <- STA.new + pure $ Queue + { contents + , ordering: \a b -> lessThan (fn a) (fn b) + } + +-- | Add an element to the queue. +push :: forall a. Queue a -> a -> Effect Unit +push q els = pushMany q [ els ] + +-- | Add multiple elements to the queue. +pushMany :: forall a. Queue a -> Array a -> Effect Unit +pushMany (Queue { contents, ordering }) newVals = toEffect do + for_ newVals (insert ordering contents) + +-- | Remove and return the element at the front of the queue. +pop :: forall a. Queue a -> Effect (Maybe a) +pop (Queue { contents, ordering }) = toEffect do + removeMax contents ordering + +-- | Remove and return the first n elements from the queue. +-- Note: we guarantee that the elements are sorted in the order of the queue, +-- first element is the one with the highest priority. +popN :: forall a. Int -> Queue a -> Effect (Array a) +popN n (Queue { contents, ordering }) = toEffect do + result <- STA.new + go 0 result contents ordering + where + go :: forall h. Int -> STArray h a -> STArray h a -> (a -> a -> Boolean) -> ST h (Array a) + go i result arr lt = do + case i == n of + true -> STA.freeze result + false -> do + maybeEl <- removeMax arr lt + case maybeEl of + -- If the queue is empty, there are no more items to pop + Nothing -> STA.freeze result + Just el -> do + void $ STA.push el result + go (i + 1) result arr lt + +-- | Return the element at the front of the queue without removing it. +peekFront :: forall a. Queue a -> Effect (Maybe a) +peekFront (Queue { contents }) = toEffect do + STA.peek 0 contents + +-- TODO: we are not guaranteeing that the last element is actually sorted, +-- need to look into this. +-- peekBack :: forall a. Queue a -> Effect (Maybe a) +-- peekBack (Queue { contents }) = toEffect do +-- len <- STA.length contents +-- STA.peek (len - 1) contents + +-- | Return the number of elements in the queue. +size :: forall a. Queue a -> Effect Int +size (Queue { contents }) = toEffect (STA.length contents) + +-- | Convert the queue to an array. +toArray :: forall a. Queue a -> Effect (Array a) +toArray (Queue { contents }) = toEffect (STA.freeze contents) + +-- | Convert the queue to a list. +toList :: forall a. Queue a -> Effect (List a) +toList = toArray >>> map List.fromFoldable + +-------------------------------------------------------------------------------- +-- Implementation + +-- Implementation of the binary heap lifted from: +-- https://github.com/shawnw/racket-priority-queue (MIT License) + +insert :: forall h a. (a -> a -> Boolean) -> STArray h a -> a -> ST h Unit +insert ordering arr el = do + len <- STA.push el arr + bubbleUp arr (len - 1) ordering + +bubbleUp :: forall h a. STArray h a -> Int -> (a -> a -> Boolean) -> ST h Unit +bubbleUp arr i lt = do + when (i > 0) do + let j = Int.quot (i - 1) 2 + maybeIv <- STA.peek i arr + maybeJv <- STA.peek j arr + case maybeIv, maybeJv of + Just iv, Just jv | not (lt iv jv) -> do + swap arr i j + bubbleUp arr j lt + _, _ -> pure unit + +removeMax :: forall h a. STArray h a -> (a -> a -> Boolean) -> ST h (Maybe a) +removeMax contents ordering = do + maybeMax <- STA.peek 0 contents + for maybeMax \maxEl -> do + len <- STA.length contents + case len == 1 of + true -> void $ STA.pop contents + false -> heapRemoveMax contents ordering + pure maxEl + +heapRemoveMax :: forall h a. STArray h a -> (a -> a -> Boolean) -> ST h Unit +heapRemoveMax contents lt = do + len <- STA.length contents + swap contents 0 (len - 1) + _ <- STA.pop contents + bubbleDown contents 0 lt + +bubbleDown :: forall h a. STArray h a -> Int -> (a -> a -> Boolean) -> ST h Unit +bubbleDown arr idx lt = do + let leftIdx = 2 * idx + 1 + let rightIdx = 2 * idx + 2 + maybeIv <- STA.peek idx arr + maybeLeftV <- STA.peek leftIdx arr + len <- STA.length arr + let + largestIdx' = case maybeIv, maybeLeftV of + Just iv, Just lv | leftIdx < len, lt iv lv -> leftIdx + _, _ -> idx + maybeLargestV' <- STA.peek largestIdx' arr + maybeRightV <- STA.peek rightIdx arr + let + largestIdx = case maybeLargestV', maybeRightV of + Just lv, Just rv | rightIdx < len, lt lv rv -> rightIdx + _, _ -> largestIdx' + unless (largestIdx == idx) do + swap arr idx largestIdx + bubbleDown arr largestIdx lt + +swap :: forall h a. STArray h a -> Int -> Int -> ST h Unit +swap arr i j = do + maybeIv <- STA.peek i arr + maybeJv <- STA.peek j arr + case maybeIv, maybeJv of + Just iv, Just jv -> do + void $ STA.modify i (const jv) arr + void $ STA.modify j (const iv) arr + _, _ -> pure unit diff --git a/test/PriorityQueue.purs b/test/PriorityQueue.purs new file mode 100644 index 0000000..8b09997 --- /dev/null +++ b/test/PriorityQueue.purs @@ -0,0 +1,103 @@ +module Test.PriorityQueue where + +import Prelude + +import Data.Array as Array +import Data.Int as Int +import Data.List as List +import Data.Maybe (Maybe(..)) +import Data.PriorityQueue as PriorityQueue +import Effect (Effect) +import Effect.Class.Console (log) +import Test.Assert as Assert + +main :: Effect Unit +main = do + log "PriorityQueue tests" + log " pushAndPop" + pushAndPop + log " customSelector" + customSelector + log " conversions" + conversions + log " pushPopManyMax" + pushPopManyMax + log " pushPopManyMin" + pushPopManyMin + log " peek" + peek + +pushAndPop :: Effect Unit +pushAndPop = do + queue <- PriorityQueue.newMinQueue Int.toNumber + PriorityQueue.push queue 1 + PriorityQueue.push queue 2 + size' <- PriorityQueue.size queue + Assert.assertEqual { actual: size', expected: 2 } + el1 <- PriorityQueue.pop queue + el2 <- PriorityQueue.pop queue + size'' <- PriorityQueue.size queue + Assert.assertEqual { actual: size'', expected: 0 } + Assert.assertEqual { actual: { el1, el2 }, expected: { el1: Just 1, el2: Just 2 } } + +customSelector :: Effect Unit +customSelector = do + queue <- PriorityQueue.newMinQueue (_.foo >>> Int.toNumber) + PriorityQueue.push queue { foo: 1 } + PriorityQueue.push queue { foo: 2 } + size' <- PriorityQueue.size queue + Assert.assertEqual { actual: size', expected: 2 } + el1 <- PriorityQueue.pop queue + el2 <- PriorityQueue.pop queue + size'' <- PriorityQueue.size queue + Assert.assertEqual { actual: size'', expected: 0 } + Assert.assertEqual { actual: el1, expected: Just { foo: 1 } } + Assert.assertEqual { actual: el2, expected: Just { foo: 2 } } + +conversions :: Effect Unit +conversions = do + queue <- PriorityQueue.newMaxQueue Int.toNumber + PriorityQueue.push queue 1 + PriorityQueue.push queue 2 + PriorityQueue.push queue 3 + list <- PriorityQueue.toList queue + array <- PriorityQueue.toArray queue + -- Note: we sort here because we can't guarantee the order of the elements, + -- that's an implementation detail. + Assert.assertEqual { actual: List.sort list, expected: List.fromFoldable [ 1, 2, 3 ] } + Assert.assertEqual { actual: Array.sort array, expected: [ 1, 2, 3 ] } + +pushPopManyMax :: Effect Unit +pushPopManyMax = do + queue <- PriorityQueue.newMaxQueue Int.toNumber + PriorityQueue.pushMany queue [ 1, 2, 3 ] + els <- PriorityQueue.popN 4 queue + Assert.assertEqual + { actual: els + , expected: [ 3, 2, 1 ] + } + size <- PriorityQueue.size queue + Assert.assertEqual { actual: size, expected: 0 } + +pushPopManyMin :: Effect Unit +pushPopManyMin = do + queue <- PriorityQueue.newMinQueue Int.toNumber + PriorityQueue.pushMany queue [ 1, 2, 3 ] + els <- PriorityQueue.popN 2 queue + Assert.assertEqual + { actual: els + , expected: [ 1, 2 ] + } + size <- PriorityQueue.size queue + Assert.assertEqual { actual: size, expected: 1 } + +peek :: Effect Unit +peek = do + queue <- PriorityQueue.newMaxQueue Int.toNumber + PriorityQueue.pushMany queue [ 1, 2, 3 ] + front <- PriorityQueue.peekFront queue + -- back <- PriorityQueue.peekBack queue + Assert.assertEqual { actual: { front }, expected: { front: Just 3 } } + -- Assert.assertEqual { actual: { back }, expected: { back: Just 1 } } + size <- PriorityQueue.size queue + Assert.assertEqual { actual: size, expected: 3 }