Skip to content

Commit

Permalink
🚧 progress: First draft.
Browse files Browse the repository at this point in the history
  • Loading branch information
make-github-pseudonymous-again committed May 16, 2021
1 parent e6a20a5 commit 8057d4d
Show file tree
Hide file tree
Showing 8 changed files with 10,197 additions and 27 deletions.
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
String searching failure function for JavaScript.
See [docs](https://string-data-structure.github.io/failure-function/index.html).

> :building_construction: Caveat emptor! This is work in progress. Code may be
> working. Documentation may be present. Coherence may be. Maybe.
> :warning: Depending on your environment, the code may require
> `regeneratorRuntime` to be defined, for instance by importing
> [regenerator-runtime/runtime](https://www.npmjs.com/package/regenerator-runtime).
```js
import {build} from '@string-data-structure/failure-function';
const s = 'abracadabra';
const t = new Int32Array(s.length + 1);
build(s, 0, s.length, t, 0);
```

[![License](https://img.shields.io/github/license/string-data-structure/failure-function.svg)](https://raw.githubusercontent.com/string-data-structure/failure-function/main/LICENSE)
[![Version](https://img.shields.io/npm/v/@string-data-structure/failure-function.svg)](https://www.npmjs.org/package/@string-data-structure/failure-function)
Expand Down
17 changes: 3 additions & 14 deletions doc/manual/usage.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,8 @@
# Usage

> :warning: Depending on your environment, the code may require
> `regeneratorRuntime` to be defined, for instance by importing
> [regenerator-runtime/runtime](https://www.npmjs.com/package/regenerator-runtime).
First, require the polyfill at the entry point of your application
```js
require( 'regenerator-runtime/runtime' ) ;
// or
import 'regenerator-runtime/runtime.js' ;
```

Then, import the library where needed
Import the library where needed
```js
const failureFunction = require( '@string-data-structure/failure-function' ) ;
const {build} = require( '@string-data-structure/failure-function' ) ;
// or
import * as failureFunction from '@string-data-structure/failure-function' ;
import {build} from '@string-data-structure/failure-function' ;
```
50 changes: 50 additions & 0 deletions src/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Computes the failure function for input string.
*
* This is the "next[j]" table found in
* "Fast pattern matching in strings" by Knuth, Morris, and Pratt,
* although here indices are 0-based hence all indices and inputs are one less
* than in that paper.
*
* NOTE The main loop is somewhat unrolled for faster execution. This was not
* benchmarked.
*
* @param {ArrayLike} p
* @param {number} pi
* @param {number} pj
* @param {number[]} t
* @param {number} ti
*/
const build = (p, pi, pj, t, ti) => {
t[ti] = -1;
if (pi === pj) return;
const p0 = pi;
if (++pi === pj) {
t[++ti] = 0;
return;
}

const t0 = ti;
t[++ti] = p[pi] === p[p0] ? -1 : 0;
let m = 0;
--pj;
while (pi < pj) {
while (p[pi] !== p[p0 + m]) {
m = t[t0 + m];
if (m === -1) break;
}

++m;
t[++ti] = p[++pi] === p[p0 + m] ? t[t0 + m] : m;
}

while (p[pj] !== p[p0 + m]) {
m = t[t0 + m];
if (m === -1) break;
}

++m;
t[++ti] = m;
};

export default build;
3 changes: 1 addition & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
const answer = 42;
export default answer;
export {default as build} from './build.js';
10 changes: 10 additions & 0 deletions test/src/_fixtures.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import {build} from '../../src/index.js';

const _table = (p, pi, pj) => {
// eslint-disable-next-line unicorn/no-new-array
const next = new Array(pj - pi + 1);
build(p, pi, pj, next, 0);
return next;
};

export const table = (input) => _table(input, 0, input.length);
5 changes: 0 additions & 5 deletions test/src/api.js

This file was deleted.

67 changes: 67 additions & 0 deletions test/src/next.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import test from 'ava';

import {table} from './_fixtures.js';

const isNext = (t, table, input) => {
if (input === '') {
t.deepEqual(table, [-1]);
return;
}

// TODO this test is not complete

t.is(table[0], -1);
t.not(table[input.length], -1);
for (let j = 1; j <= input.length; ++j) {
const i = table[j];
// TODO test when i === -1
if (i !== -1) {
// Prefix and suffix match
t.true(input.slice(0, i) === input.slice(j - i, j));
// Next character does not match
t.true(j === input.length || input[i] !== input[j]);
// A longer proper prefix/suffix pair does not match
// TODO More possibilities need to be checked
t.true(
i + 1 === j ||
input.slice(0, i + 1) !== input.slice(j - (i + 1), j) ||
i + 2 === j ||
j === input.length ||
input[i + 1] === input[j],
);
}
}
};

const macro = (t, input, expected) => {
const next = table(input);
t.deepEqual(next, expected);
isNext(t, next, input);
};

macro.title = (title, input, expected) =>
title ?? `next(${input}) is ${JSON.stringify(expected)}`;

const auto = (t, input) => {
const next = table(input);
isNext(t, next, input);
};

auto.title = (title, input) => title ?? `isNext(next(${input}))`;

test(macro, '', [-1]);
test(macro, 'z', [-1, 0]);
test(macro, 'abcd', [-1, 0, 0, 0, 0]);
test(macro, 'aaaa', [-1, -1, -1, -1, 3]);
test(macro, 'axax', [-1, 0, -1, 0, 2]);
test(macro, 'axxa', [-1, 0, 0, -1, 1]);
test(macro, 'aaaab', [-1, -1, -1, -1, 3, 0]);
test(macro, 'abracadabra', [-1, 0, 0, -1, 1, -1, 1, -1, 0, 0, -1, 4]);
test(
macro,
'abaababaabaababaababa',
[-1, 0, -1, 1, 0, -1, 3, -1, 1, 0, -1, 6, 0, -1, 3, -1, 1, 0, -1, 11, -1, 8],
);

test(auto, 'eifoiwhfeldkasjflkdshfldshflkkdadkkkkkkkkkkkkasjfdljfdleifo');
test(auto, 'aaaaaaaaaaaabbbbbbbbbbaaaaaaaaabbbbbbbb');
Loading

0 comments on commit 8057d4d

Please sign in to comment.