npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

big-m

v7.0.0

Published

Utilities for building and transforming maps using iterables and streams.

Downloads

191

Readme

Big-M

Big-M is a library of utility functions for efficiently working with Maps. That's Maps with a big "M", commonly known as hashmaps; not array.map or "directions to the nearest Thai place" kind of map.

Specifically, Big-M enables composing Maps, decomposing them, recomposing them, and chaining operations without having to loop over the data structure more than once. It also includes some Map sub-classes for special use cases: EventualMap, BiMap, and CanonizedMap.

Complete docs are available at https://mattiasmartens.github.io/big-m/.

maps.ts

Pure function library. Most functions in this library can be used not only on Maps, but on anything that implements the Iterable interface. This includes arrays of key-value tuples and Iterables created by libraries like wu.js and lazy.js.

To allow efficient composition of operations, most functions return an Iterable of entries instead of an actual Map. To get to a Map from one of these Iterables, just call collectMap on it or use the Map constructor:

const myIterable = selectMap(myMap, a => isWorthKeeping(a));

// this works
const myNewMap = mapCollect(myIterable);

// this works, too
const myNewMap2 = new Map(myIterable);

See the docs for detailed descriptions of all the functions and what they do.

streams.ts

EventualMap

The main use case for EventualMap is when you are loading data from two remote sources and joining them together - say you're getting a user IDs from one source and a user metadata table from another - and you want to front-load as much of the work as possible, continuing processing on each user ID the moment it arrives in your system.

With EventualMap, you can perform async lookups on a Stream (note that this is from ts-stream, not the Node implementation) that return as soon as the value arrives. This simplifies the work of combining multiple incoming data streams while minimizing unnecessary awaiting.

StreamCollectInto

For the more straightforward case where you have a Stream of key-value pairs and you want it to be a Map, but don't care about it until it's complete, there's also streamCollectInto. This simply returns a Promise of the completed Map.

bidirectional.ts

BiMap

A subclass of Map which, for every key-value pair, maintains a corresponding value-key pair in another Map. Lookups can then be performed in either direction, from a key to a value or from a value to a key.

BiMap also exposes a reversed version of itself with .reverse, which behaves exactly like the original except that the key set and value set are flipped. The map can be reversed and re-reversed indefinitely without the data structure being rebuilt.

canon.ts

CanonMap

Normally Maps don't provide an effective way of doing things like this:

const myMap = new Map();
myMap.set(
  ["Farooq", "867-5309"],
  36.59
);

// ???
myMap.get(
  ["Farooq", "867-5309"]
) === undefined;

// Oh right, it's compare-by-reference and those two arrays were declared separately so they can't be considered as the same key.

But with CanonMap, the above actually works:

const myMap = new CanonMap();
myMap.set(
  ["Farooq", "867-5309"],
  36.59
);

myMap.get(
  ["Farooq", "867-5309"]
) === 36.59;

// Yay!

CanonMap behaves exactly as if it were mapping complex objects to values by the values in the objects, instead of their references. What it's actually doing is mapping these complex values to primitives such as strings, using a function called a canonizer. The default canonizer is pretty effective at recognizing when two values ought to be considered the same:

const myCanonMap = new CanonMap();

const indexable = (val1, val2) => {
  myCanonMap.set(val1, "TEST");
  const indexable = myCanonMap.get(val2) === "TEST";
  myCanonmMp.delete(val1);
  return indexable;
}

indexable(
  ["Farooq", "867-5309"],
  ["Farooq", "867-5309"]
);

indexable(
  {
    a: 1,
    b: [9, 10]
  },
  {
    a: 1,
    b: [9, 10]
  }
);

!indexable(
  ["1"],
  [1]
);

But it only looks two levels deep into deeply nested objects. However, users can have complete control over how the canonizer works using the canonizer argument, allowing them to adapt the class to their own particular use case.

Chaining

Chaining operations together can be done readably with https://github.com/gcanti/fp-ts's pipe function:

pipe(
  makeEntries(fibonacci, fib => [fib, romanNumerals(fib)]),
  x => selectMap(x, fib => fib % 2 === 1),
  x => mapKeys(fib => fib % 7),
  x => mapCollectBumping(fib => fib * 2)
);

It would be nicer to call functions directly on their first argument, i.e. map1.mapValues(), but this is not implemented yet - see the Contributing section below.

Contributing

Source code is hosted at https://github.com/MattiasMartens/big-m.

To set up locally (assuming you have Yarn installed and you've forked the repo):

yarn install

# To ensure tests pass and to see coverage report
yarn test

# To see performance benchmarks
yarn benchmark

To submit changes as a PR, before committing:

yarn test
yarn compile
yarn docs

Monkey-Patching

Currently the utility functions must be called as pure functions:

mapCollectInto(myListOfEntries, myNewMap);

It would be nicer to call them like this:

myListOfEntries.mapCollectInto(myNewMap)

But modifying the existing native classes is not recommended. Instead the plan is to patch them on using Symbols, which a user can import:

import { mapCollectInto } from "big-m/monkey";

myListOfEntries[mapCollectInto](myNewMap);

This is not implemented yet, however. TypeScript makes it pretty laborious to monkey-patch a large number of symbols onto various prototypes and communicate through declarations that you've done so. I'm thinking of writing a macro to generate the code that does this. PRs welcome.