Skip to Content
  • Website
Codsen
  • Home
  • Open Source
  • Articles
  • About

prevOpen Source→ranges-mergenext

ranges-merge9.2.1

Merge and sort string index ranges

Downloads per monthChangelogMIT Licenselibera manifesto
  • the top
  • Installation
  • Quick Take
  • Examples
  • API — RMERGE()
  • API — DEFAULTS
  • API — VERSION
  • API — TYPES
  • OPTS — PROGRESSFN
  • OPTS — MERGETYPE
  • EXAMPLE
  • Changelog

Installation

Quick Take

Examples

  • Keep the touching ranges separate
  • Merge two overlapping ranges
  • Prefer the latter value with merge type two
  • A null replacement wins over text
  • Remove the ranges that do nothing
  • Report the merging progress

API — rMerge()

The main function rMerge() is imported like this:

It’s a function which takes two input arguments:

Input argumentTypeObligatoryDescription
arrOfRanges
Type: Array
Obligatory: yes
arrOfRangesArrayyesArray of zero or more arrays meaning natural number ranges (2 elements each)
opts
Type: Plain object
Obligatory: no
optsPlain objectnoOptional Options Object. See its API below.

None of the input arguments will be mutated by this program, we have unit tests to prove that.

The Optional Options Object has the following shape:

KeyTypeDefaultDescription
mergeType
Type: number
Default: 1
mergeTypenumber1The type of merge. See below for explanation.
progressFn
Type: null or Boolean false or function
Default: null
progressFnnull or Boolean false or functionnullIf you supply a function here, it will be called, and an argument will be given to it, a natural number, which means percentage complete at that moment. Values will range from 1 to 99, and finally, the main function will return the result’s plain object.
joinRangesThatTouchEdges
Type: boolean
Default: true
joinRangesThatTouchEdgesbooleantrueBy default, if two ranges “touch”, [[1, 2], [2, 3]] they are joined. Set this option to false to stop that. Handy when reporting separate issues.

Here are all defaults in one place for copying:

Function will return merged ranges — a null (theoretically possible) or array of one or more range arrays:

API — defaults

You can import defaults:

It's a plain object:

The main function calculates the options to be used by merging the options you passed with these defaults.

API — version

You can import version:

API — types

This package is written in TypeScript and exports the following types:

TypeDescription
Opts
Type: Opts
OptsThe Optional Options Object of rMerge(), documented above.
ProgressFn
Type: ProgressFn
ProgressFnThe signature of opts.progressFn — called with the percentage done.
UnknownValueObj
Type: UnknownValueObj
UnknownValueObjA plain object with string keys and values of any type.
Range
Type: Range
RangeA single range, re-exported from ranges-sort.
Ranges
Type: Ranges
RangesZero or more Ranges, or null, re-exported from ranges-sort.
import type { Opts, ProgressFn, Range, Ranges, UnknownValueObj } from "ranges-merge";

opts.progressFn

Consider this example (notice an arrow function in the second input argument):

console.log(
  rMerge(
    [
      [1, 5],
      [11, 15],
      [6, 10],
      [16, 20],
      [10, 30],
    ],
    {
      progressFn: (perc) => {
        console.log(`done: ${perc}`);
      },
    },
  ),
);
//
// done: 0
// done: 1
// done: 2
// done: 3
// done: 4
// done: 4
// done: 5
// done: 21
// done: 40
// done: 60
// done: 79
// done: 99
// [[1, 5], [6, 30]]

Imagine, instead of console.log, this function could sit in a worker and report its progress, then, finally, ping the last value — result.

Whatever function you give in opts.progressFn, it will be called with percentage done so far. Grab that argument (perc in the example above) and do whatever you want with it in your function.

opts.mergeType

When merging, ranges are sorted first. Then, pairs starting from the end of the sorted array are merged. Last two becomes one, last two becomes one and so on.

The challenge is, what to do with values to add, third range array’s element.

For example,

const range1 = [1, 2, "a"];
const range2 = [1, 2, "b"];

The above ranges are “saying”: replace characters in a string from index 1 to 2 with "a", replace characters in string from index 1 to 2 with "b".

Do you end up with "ab" or "b" or something else?

opts.mergeType let’s you customise this behaviour:

  • In default mode, opts.mergeType === 1, clashing “to insert” values will always be concatenated ("ab" in example above)
  • In mode opts.mergeType === 2, if “to insert” values clash and starting indexes are the same — the latter value overrides the former ("b" in example above).

In all other aspects, opts.mergeType modes 1 and 2 are the same.

Example

Imagine a messed up piece of code: <div>&nbbsp;</div>. Let’s say our imaginary cleaning program detected two issues with it:

  • Unencoded ampersand at position 5
  • Malformed &nbsp; where b is duplicated

Range-wise, it could look like this:

[
  {
    name: "bad-character-unencoded-ampersand",
    position: [[5, 6, "&amp;"]],
  },
  {
    name: "malformed &nbsp;",
    position: [[5, 12, "&nbsp;"]],
  },
];

Notice there are two ranges’ “insert” values clashing, [5, 6] and [5, 12], but you want the latter to discard the former. That’s where opts.mergeType setting 2 come in.

Mode 2 is the same to 1 except clashing “insert” values are resolved by deleting value on the left and keeping one on the right, in the order of sorted ranges array.

For example,

import { rMerge } from "ranges-merge";
const res1 = rMerge(
  [
    [3, 4, "aaa"],
    [3, 12, "zzz"],
  ],
  { mergeType: 1 },
);
console.log(res1);
// => [[3, 12, "aaazzz"]]

const res2 = rMerge(
  [
    [3, 4, "aaa"],
    [3, 12, "zzz"],
  ],
  { mergeType: 2 },
);
console.log(res2);
// => [[3, 12, "zzz"]]

In the example above, notice how ranges got sorted. The sorting algorithm first sorted by first element (3 which was the same on both) then on a second (4 vs 12). The range [3, 12, "zzz"] came second, its third element, “what to insert”, "zzz" clashed with first one’s "aaa", wiping it.

Changelog

Open Changelog
↑ back to top
prev next

Copyright

All rights reserved © Roy Revelt 2026
All our open source packages are under MIT licenceopens in a new tab

Activities

🐛 See a bug? Raise an issueopens in a new tab
💘 Check out the Indiewebopens in a new tab and Libera manifestoopens in a new tab