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

speedy-myers

v0.1.1

Published

A Speedy E.Meyers's O(ND) Based Diffing Algorithm

Downloads

101

Readme

A Speedy E.Meyers's O(ND) Based Diffing Algorithm

Build Status Coverage Status

This is a re-write of the original Arpad Borsos's diff repository.

As there's no single module in npm that doesn't couple Meyers with strings, and all do more than one thing, I've decided to re-publish it with performance improvements and in a modern ECMAScript fashion. Arpad Borsos agreed so ... here it is.

import {NOOP, REPLACE, INSERT, DELETE, diff} from 'speedy-myers';
// const {NOOP, REPLACE, INSERT, DELETE, diff} = require('speedy-myers');

const changes = diff(
  sourceList, // the list you'd like to diff
  targetList, // against its updated version
  // with an optional callback
  // to diff objects by keys too
  (a, b) => a.id === b.id
);

The resulting Array of changes will contain all the information to patch the sourceList through operations performed via the targetList.

Example

const {NOOP, REPLACE, INSERT, DELETE, diff} = Myers;
const source = [1, 2, 3, 4, 5];
const target = [2, 7, 4, 5, 9, 6];
const changes = diff(source, target);
let sourceIndex = 0;
let targetIndex = 0;
for (let i = 0, {length} = changes; i < length; i++) {
  switch (changes[i]) {

    // in both REPLACE and NOOP cases
    // move forward with both indexes
    case REPLACE:
      // in replace case, you can safely pass the value
      source[sourceIndex] = target[targetIndex];
      // se no break here: the fallthrough in meant to increment
    case NOOP:
      sourceIndex++;
      targetIndex++;
      break;

    case DELETE:
      source.splice(sourceIndex, 1);
      // Note: in this case don't increment the sourceIndex
      // as the length mutated via splice, however,
      // you should increment sourceIndex++ if you are dealing
      // with a parentNode, as example, and the source is a facade
      // never touch the targetIndex during DELETE
      break;

    case INSERT:
      // Note: in this case, as the length is mutated again
      // you need to move forward sourceIndex++ too
      // but if you appending nodes, or inserting before other nodes,
      // you should *not* move sourceIndex forward
      source.splice(sourceIndex++, 0, target[targetIndex]);

      // the targetIndex instead should *always* be incremented on INSERT
      targetIndex++;
      break;
  }
}

// verify everything went fine
console.assert(source.join(',') === target.join(','));

A DOM Based Example

As this module is general purpose, it is possible to use it to update a tree in the DOM too.

uhtml, lighterhtml, and hyperHTML use a very similar strategy through udomdiff and domdiff.

const {NOOP, REPLACE, INSERT, DELETE, diff} = Myers;
const parentNode = document.body;
const source = [].slice.call(document.body.childNodes);
const target = source.append(document.createElement('myers'));
const changes = diff(source, target);
let sourceIndex = 0;
let targetIndex = 0;
const comments = new Map;
for (let i = 0, {length} = changes; i < length; i++) {
  switch (changes[i]) {

    case REPLACE:
      // as a node cannot exists simultaneously in two parts of the tree
      // we cannot append directly the target[targetIndex] unless
      // its parentNode is different, or non-existent
      if (target[targetIndex].parentNode !== parentNode)
        parentNode.replaceChild(
          target[targetIndex],
          source[sourceIndex]
        );
      // otherwise we need to use a placeholder to keep the position
      // and replace it only once all operations are completed
      else {
        const comment = document.createComment('');
        comments.set(comment, target[targetIndex]);
        parentNode.replaceChild(comment, source[sourceIndex]);
      }
    // remember to move forward both indexes, with NOOP and REPLACE
    case NOOP:
      sourceIndex++;
      targetIndex++;
      break;

    case DELETE:
      // it is always safe to remove a live node from its deleted position
      parentNode.removeChild(source[sourceIndex]);
      // remember in this case to move the sourceIndex forward
      sourceIndex++;
      break;

    case INSERT:
      // similarly with the REPLACE case, we cannot insert the target node
      // right away, unless its parent is different
      if (target[targetIndex].parentNode !== parentNode)
        // INSERT can happen outside the source boundaries
        // however, `null` or `undefined`, with insertBefore
        // means `appendChild`, so since this is a root container
        // it is OK to just use insertBefore
        parentNode.insertBefore(
          target[targetIndex],
          source[sourceIndex]
        );
      // use the same comment strategy
      else {
        const comment = document.createComment('');
        comments.set(comment, target[targetIndex]);
        parentNode.insertBefore(comment, source[sourceIndex]);
      }
      // always move forward targetIndex++; on INSERT
      targetIndex++;
      break;
  }
}

// once the loop is completed, we can replace all placeholders
comments.forEach((node, comment) => {
  parentNode.replaceChild(node, comment);
});

Compatibility

This module is compatible with IE11 and every other Mobile or Desktop engine that supports Int8Array.