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

comparator-factory-factory

v0.2.1

Published

Create comparison functions to be used for sorting arrays.

Downloads

13

Readme

comparator-factory-factory

Create comparison functions to be used for sorting arrays.
This is a dedicated tiny library, not aiming at a framework.

Features

  • Available for both Node.js and browsers (including IE 11)
  • Function-based comparison value selection
    • Property-path-based comparison value selection can be implemented easily
  • Handling undefined, null, NaN as first or last
  • String collation using native Intl.Collator
  • Chaining comparison functions
  • Designed to share the comparison rule in the product;
    Creating not a comparison function directly but a comparison function factory that may be shared
  • Lightweight (~1.7kB minified, ~0.7kB gzipped)

Install

via npm

$ npm install comparator-factory-factory
import comparatorFactoryFactory from "comparator-factory-factory";
// const comparatorFactoryFactory = require("comparator-factory-factory");

const comparing = comparatorFactoryFactory();
[].sort(comparing());

via CDN

<script src="https://cdn.jsdelivr.net/npm/[email protected]"></script>
<script>
  const comparing = comparatorFactoryFactory();
  [].sort(comparing());
</script>

or for modern browsers:

<script type="module">
  import comparatorFactoryFactory from "https://cdn.jsdelivr.net/npm/[email protected]/index.min.mjs";

  const comparing = comparatorFactoryFactory();
  [].sort(comparing());
</script>

Usage & Examples

const comparing = comparatorFactoryFactory({
  specials: [[null, "last"]],
  collator: { caseFirst: "upper", numeric: true },
});

["A5", "A1", null, "A3", "A10", "a3", "A7"].sort(comparing());
// => ["A1", "A3", "a3", "A5", "A7", "A10", null]

["A5", "A1", null, "A3", "A10", "a3", "A7"].sort(comparing().reversed());
// => [null, "A10", "A7", "A5", "a3", "A3", "A1"]
const users = [
  { id: "01", name: "Alice", profile: { age: 17 } },
  { id: "02", name: "Bob"                         },
  { id: "03",                profile: { age: 16 } },
  { id: "04", name: "alice", profile: { age: 15 } },
  { id: "05", name: "bob",   profile: { age: 18 } },
  { id: "06", name: "Bob",   profile: { age: 15 } },
];

{
  const comparing = comparatorFactoryFactory();
  users.sort(comparing(x => [x.profile.age, x.id]));
  // => [
  //  { id: "02", name: "Bob"                         },
  //  { id: "04", name: "alice", profile: { age: 15 } },
  //  { id: "06", name: "Bob",   profile: { age: 15 } },
  //  { id: "03",                profile: { age: 16 } },
  //  { id: "01", name: "Alice", profile: { age: 17 } },
  //  { id: "05", name: "bob",   profile: { age: 18 } },
  // ]
}

{
  const comparing1 = comparatorFactoryFactory({
    specials: [[undefined, "last"]],
    collator: { sensitivity: "base" },
  });
  const comparing2 = comparatorFactoryFactory({
    specials: [[undefined, "last"]],
  });
  users.sort(
    comparing1(x => x.name)
      .reversed()
      .or(comparing2(x => x.profile.age))
      .or(comparing2(x => x.id))
  );
  // => [
  //  { id: "03",                profile: { age: 16 } },
  //  { id: "06", name: "Bob",   profile: { age: 15 } },
  //  { id: "05", name: "bob",   profile: { age: 18 } },
  //  { id: "02", name: "Bob"                         },
  //  { id: "04", name: "alice", profile: { age: 15 } },
  //  { id: "01", name: "Alice", profile: { age: 17 } },
  // ]
}

{
  const comparingPropertyPath = comparatorFactoryFactory({
    selector(fullpath) {
      const paths = fullpath.replace(/\[(\d+)]/g, ".$1").split(".").filter(Boolean);
      return obj => paths.every(path => (obj = obj[path]) != null) && obj;
    },
  });

  users.sort(comparingPropertyPath("profile.age", "id"));
  // => [
  //   { id: "02", name: "Bob"                         },
  //   { id: "04", name: "alice", profile: { age: 15 } },
  //   { id: "06", name: "Bob",   profile: { age: 15 } },
  //   { id: "03",                profile: { age: 16 } },
  //   { id: "01", name: "Alice", profile: { age: 17 } },
  //   { id: "05", name: "bob",   profile: { age: 18 } },
  // ]
}

API

Summary

// Create a comparison function factory based on the specified rule.
const comparatorFactory = comparatorFactoryFactory({
  selector: key => obj => comparisonResult,
  specials: [
    [undefined, "first"],     // array with 2 elements: 
    [null,      "first"],     // [0] value to treat specially
    [NaN,       "first"],     // [1] "first" / "last"
  ],
  collator: {
    locales:      undefined,  // a BCP 47 language tag, or an array of such strings
    sensitivity:  "variant",  // "base" / "accent" / "case" / "variant"
    numeric:      false,
    caseFirst:    "false",    // "upper" / "lower" / "false" (use the locale's default)
  },
});

// Create a comparison function.
const comparator = comparatorFactory(key1, key2, ...);

// Evaluate.
// 0 if obj1 and obj2 are equal,
// a negative number if obj1 is smaller,
// a positive number if obj1 is larger.
const comparisonResult = comparator(obj1, obj2);

// Create a comparison function with reverse order.
const reversedComparator = comparator.reversed();

// Comparator itself.
const comparatorItself = comparator.reversed(false);

// Create a combined comparison function.
// If comparator(obj1, obj2) is falsy, then evaluate specified comparison function.
const combinedComparator = comparator.or((obj1, obj2) => number);

comparatorFactoryFactory({ selector?, specials?, locales?, collator? }) => comparatorFactory

Create a comparison function factory based on the specified rule.

Parameters

  • selector: key => obj => comparisonResult

    A function selecting comparison value from key and obj.
    The receiving parameter key is each argument of comparatorFactory(key1, key2, ...).
    The receiving parameter obj is each argument of comparator(obj1, obj2).
    The default implementation is as follows.

    key => obj => {
      try {
        return key(obj);
      } catch {
        return undefined;
      }
    }

    Following code is a property-path-based comparison example using lodash/get. (BTW, there are so many similar modules.)

    const get = require("lodash/get");
    const comparingPropertyPath = comparatorFactoryFactory({
      selector: key => obj => get(obj, key),
    });
    // for TypeScript:
    // const comparingPropertyPath = comparatorFactoryFactory<string>({
    //   selector: key => obj => get(obj, key),
    // });
    
    const users = [
      { id: 1, profile: { age: 18 } },
      { id: 2, profile: { age: 15 } },
    ];
    
    users.sort(comparingPropertyPath("profile.age", "id"));
  • specials: [[value1, "first" (or "last")], [value2, "first" (or "last")], ...]

    Special values to place first or last.
    The default value is as follows.

    [
      [undefined, "first"],
      [null,      "first"],
      [NaN,       "first"],
    ]
  • collator: { locales?, sensitivity?, numeric?, caseFirst? } | { compare: (string1, string2) => number }

    String comparison method.
    Possible values are as follows.

    • An options object for Intl.Collator constructor with optional property locales
    • An object that has compare(string1, string2) => number method (a Intl.Collator instance does)

    See Intl.Collator at MDN for details.
    The default value is the default Intl.Collator().

comparatorFactory(key1, key2, ...) => comparator

Create a comparison function.

Parameters

  • key1, key2, ...

    Comparison key passed to the selector option of the rule.
    If the length of arguments is 0, obj itself becomes the comparison value.

comparator(obj1, obj2) => number

Evaluate.

0 if obj1 and obj2 are considered to be equal,
a negative number if obj1 is considered to be smaller than obj2,
a positive number if obj1 is considered to be larger than obj2.

comparator.reversed(really? = true) => comparator

Create a comparison function with reverse order.

comparator.or((obj1, obj2) => number) => comparator

Create a combined comparison function.

The combined comparison function evaluates the original comparator(obj1, obj2) first.
If that result equals to 0 (or falsy), it evaluates specified comparison function next.

Limitation

Array.prototype.sort() always put the undefined at the end of the array.
This behavior is specified in the ECMAScript specification.

const comparing = comparatorFactoryFactory({
  specials: [[undefined, "first"], [null, "first"], [NaN, "first"]],
});

[{ id: 3 }, { id: 1 }, { id: undefined }, { id: 7 }].sort(comparing(x => x.id));
// => [{ id: undefined }, { id: 1 }, { id: 3 }, { id: 7 }]
// As expected.

[3, 1, null, 7].sort(comparing());
// => [null, 1, 3, 7]
// As expected.

[3, 1, NaN, 7].sort(comparing());
// => [NaN, 1, 3, 7]
// As expected.

[3, 1, undefined, 7].sort(comparing());
// => [1, 3, 7, undefined]
// NOT as expected.
// The expected result is [undefined, 1, 3, 7]
// but `undefined` is always placed at the end...

License

WTFPL

Similar modules