Contributing to the project is very easy.
searchIterator
strategies/algorithms are contained in thestrategies
object.- Data structures are contained in the
structs
object. - Unit testing is handled via Jasmine.
- Building is handled via NPM with Browserify, UglifyJS, and
build.sh
(Bash).
To contribute, simply fork the master
repository and submit a pull request containing your changes. Your changes must be clearly outlined in the description. Keep in mind that the stability of the source code in the master
repository may not always be favorable.
The only pre-requisites are Bash, NodeJS, and NPM.
The NPM production build, which uses Browserify and UglifyJS, can be created by entering npm run build
in Terminal.
Running npm run build
creates a prod-build
directory, copies src
into prod-build/src
, creates comment-less and minified versions in prod-build/dist
, copies the Jasmine unit test Specs into prod-build/spec
, and includes copies of package.json
& README.md
.
Do not use any functions or classes provided by the differentia
module to verify test results!
Browser tests can be run by opening SpecRunner.html
in a web browser, and uses the Browserify module located in prod-build/dist/differentia.js
; ensure you have first run npm run build
to create/update the module.
NodeJS tests can be run by running npm test
or jasmine
in Terminal, and uses the CommonJS module index located in src/index,js
.
Unit tests are performed with Jasmine, using describe
, it
, and expect
in nested order. Unit tests must be added either to an appropriate pre-existing Spec file, or a new Spec file. Typically, tests which operate similarly or on similar input data can be grouped, for example the Queue
and Stack
data structures are grouped with the LinkedList
tests because Queue
and Stack
utilize LinkedList
to implement their functionality.
Before tests execute, a module named globalLoader.js
injects the Differentia library into the Jasmine global
object, adding differentia
and its shorthand d
as properties. Additionally, several testing utilities are injected into the global
object by a module named testUtils.js
; the module also tests itself before doing so. The following utilities may be found in the global
object:
-
differentia
: The Differentia library, loaded usingsrc/index.js
. -
d
: Shorthand fordifferentia
. -
createTestObject
: Creates a complex object used for testingsearchIterator
and its strategues. -
testObjects
: Creates objects used for testingsearchIterator
and its strategies, sometimes utilizingcreateTestObject
.One of several methods may be used to create test objects:
- Multipath
- Linear Acylic
- Nested Acylic
- Multidimensional Acyclic
- Linear Cyclic
- Nested Cyclic
- Multidimensional Cyclic
-
createKeyCounter
: Creates a special tracking object forcreateTestObject
to storesearchIterator
node visit counts. -
testLength
: Returns a Number indicating how many enumerable properties are in an Object, or how many elements are in an Array. -
testDiff
: Compares two Object trees and returnstrue
if the Objects are differet, orfalse
if they are the same. -
supportedRegExpProps
: Lists 3 property Booleans to indicate whether specific "new"RegExp
Object properties are supported.sticky
: Set totrue
if thesticky
property is supported.unicode
: Set totrue
if theunicode
property is supported.flags
: Set totrue
if theflags
property is supported.
Here is a basic example of a jasmine
unit test:
describe("two plus two", function () {
it("should equal four", function () {
expect(2+2).toEqual(4);
});
});
Here is a real example taken from spec/Spec.js
:
describe("getContainerLength", function () {
var array = [1, 2, 3, 4, 5];
var object = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5 };
it("should count 5 items in each container", function () {
expect(d.getContainerLength(array)).toBe(5);
expect(d.getContainerLength(object)).toBe(5);
});
});
To add a data structure to the library, you must add a class constructor usable with the new
keyword. Your class must exist within it's own file in the src/structs
directory, which should be named after the class itself.
The class is named MyStruct
, and thus exists in a file such as this: src/structs/MyStruct.js
.
You should learn how searchIterator
works before continuing: Read on Documentation Site
To add an search algorithm to the library, you must use the Strategy Pattern together with runStrategy
, which is the primary gateway for your algorithms to interact with a search iterator. Your algorithm will be tightly coupled to searchIterator
, and you should make use of one of the many properties made available through it's shared state object. An algorithm may "steer" the search algorithm by directly mutating certain properties of state
.
All strategies added to the strategies
object will be automatically revealed to the end-user via their interface
properties.
A Strategy is an object with the following properties:
Property | Data Type | Description |
---|---|---|
interface |
Function | The interface function revealed by module.exports and the global differentia namespace, to be exposed to and directly run by the end-user. The function must contain a call to runStrategy , supplying it's parent object as the first parameter. |
entry |
Function | (Optional) A Call-With-Current-State callback to run once on the first iteration. This function cannot return anything. Initial set-up code should be run in this function. |
main |
Function | A Call-With-Current-State callback to run on every iteraton. If this function returns something other than undefined , it will be returned to the user's caller. |
done |
Function | (Optional) A Call-With-Current-State callback to run on the last iteration. It recieves the return value of main as its second argument. If this function returns something other than undefined , it will be returned to the user's caller; otherwise, the value returned by main will be returned to the user's caller. |
error |
Function | (Optional) A Call-With-Current-State callback to run when an error thrown. It recieves the Error object as its second argument. If this function returns something other than undefined , it will be returned to the user's caller. |
entry
, main
, done
, and error
all recieve a state
object as their first parameter, which is the iterator's state flyweight object; a single object which the iterator actively mutates per-iteration.
Your Strategy's interface
function must call runStrategy
if it needs to use the search iterators:
Function
runStrategy( strategy, searchAlg, parameters );
An IOC wrapper for the Search Iterators. runStrategy
advances the iterator returned by searchAlg
and executes Call-With-Current-State functions supplied in strategy
.
- The state flyweight object is passed to
strategy.entry
, which is only executed for the first element, andstrategy.main
which is executed for every element. - If
strategy.main
returns something other thanundefined
, it will be returned to the caller after passing throughstrategy.done
. - If the iterator has reached the last element then
strategy.done
will be executed, optionally with the return value ofstrategy.main
as it's second argument. - If the source code of
searchIterator
or the strategy throws an error, thestrategy.error
method will be invoked with the error object. Otherwise, the error is re-thrown byrunStrategy
. searchAlg
is the search algorithm iterator to use; it can bedfs
orbfs
, or any other Iterator.
-
strategy
ObjectThe strategy Object.
-
searchAlg
IteratorAn iterator to use as the search algorthm; it can be
dfs
orbfs
, or any Generator. -
parameters
ObjectAvaiable to callbacks via
state.parameters
. It consists of the following properties, but may contain any number of custom properties:
Property | Data Type | Description |
---|---|---|
subject |
Object/Array | The Object or Array to traverse/iterate. |
search |
Object/Array | (Optional) A search index specifying the paths to traverse, and property accessors to enumerate. All other properties are ignored. |
This example is an algorithm that returns an object if it contains the string "Good Morning".
// Our test Object we will search for "Good Morning".
var subject = {
greetings1: [
"Good Afternoon"
],
greetings2: [
"Good Morning"
]
};
// Define your Strategy, adding it to the "strategies" object. "main" and "interface" properties are required.
strategies.myStrategy = {
interface: function (object) {
// This function will be run directly by the library user.
// Here we call `runStrategy` and include our Strategy as parameter 1.
// `runStrategy` will then begin to execute below callbacks.
runStrategy(strategies.myStrategy, dfs {
subject: object
});
},
entry: function (state) {
// This function will be executed only on the first iteration.
// Here we run our initial set-up steps.
state.returnValue = null;
},
main: function (state) {
// This function will be executed for every iteration.
// Here, we compare the element being iterated over to our desired string.
if (state.tuple.subject[state.accessor] === "Good Morning") {
// We found the string, so we'll return the parent object.
return state.tuple.subject;
}
// Throw an error if we never found the string.
if (state.isLast) {
throw new Error("String missing");
}
},
done: function (state, returnValue) {
// This function will be executed for the last iteration, or after `main` returns something.
// We have the return value of `main` as our second argument.
// Here we will add more strings to the object before returning it.
returnValue.push("Good Night");
return returnValue;
},
error: function (error, returnValue) {
// This function will be executed if any of the above callbacks throws an error.
if (error.message === "String missing") {
// Handle the error
console.error(error.message);
// Logs: "String missing"
} else {
// Can't handle the error; so re-throw.
throw error;
}
}
};
// Runs the Strategy. This function is exposed to the end-user.
var greetings = strategies.myStrategy.interface(subject);
// We can now see the string we were searching for and the string we added:
console.log(greetings);
/* Logs:
"[
"Good Morning",
"Good Night"
]"
*/
To add documentation to the library, two areas require your attention: Markdown files in the /docs
directory, and the documentation website which serves to display it. Additions to documentation are not required for pull requests to be accepted. New or changed documentation must match the following format:
# `Entry Name`
*Entry Type*
```JavaScript
// Basic Usage Example
```
Describe your entry here, including the return value (if any).
## Parameters
- **`arg`** Type
Describe the above parameter here.
## Examples
<details><summary>Example 1: Describe your example here:</summary>
```JavaScript
// Include all code needed to run the example.
//
```
</details>
---
# `myFunction`
*Function*
```JavaScript
myFunction( arg [, optionalArg = null ] );
```
`myFunction` does Foo, and returns Bar.
## Parameters
- **`arg`** Number
This argument is for X purpose.
- **`optionalArg`** (*Optional*) Number
This argument is for Y purpose.
## Examples
Example 1: How to use myFunction:
```JavaScript
// Here are some numbers.
var arg = 123;
var optionalArg = 456;
// myFunction does X with the numbers.
differentia.myFunction(arg, optionalArg);
```
---