Skip to content

Search for a given subsequence in a list of strings and transform the resulting list as required

License

Notifications You must be signed in to change notification settings

zeusdeux/subsequence-search

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

61 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

subsequence-search

Build Status

Search for a given subsequence in a list of strings and transform the resulting list as required.

Out of the box it can be made to behave a lot like the sublime text fuzzy search.

The resulting list can be transformed using chainable transforms.

Demo it here. Demo showing special character support is here.

Installation

npm install subsequence-search --save

or

bower install subsequence-search --save

Usage

Node

Go ahead and require('subsequence-search) in your node program after installation.

Browser

After installation, serve:

  • subsequence-search.js or
  • subsequence-search.min.js

out of node_modules/subsequence-search/build/

In your browser code, go ahead and use window.subsequenceSearch to use it globally

or

If you use a UMD compatible loader like require.js then go ahead and require('subsequence-search').

The search as well as the built-in transform functions, all auto-curry when given an incomplete set of arguments. Therefore, you can make reusable curried versions of those methods. For example, search that works on some fixed input dataList and fixed set of transforms but for varying searchString. Cleaner, composable code should be the result.

API

search(transforms, dataList, searchString)

  • transforms is an object containing transform functions (transforms are explained later)

    • transform functions are applied in order to the data list got after matching searchString and dataList.
  • dataList is an array of strings that you want to match against or an object (that has no circular references) with data and searchInProps properties.

    • data is an Array of objects
    • searchInProps is an Array of the properties that you want to search for the searchString in. They should be valid properties contained in the each object in the data array
    • example
      dataList = ['some string', 'some other string', 'oh look, another string'];
      /* OR */
      dataList = {
        data: [
                {id: 1, text: 'some string'},
                {id: 2, text: 'some other string'},
                {id: 3, text: 'oh look, another string'}
              ],
        searchInProps: ['text']
      };
      
      /* both of the above are valid dataList inputs */
    • searchInProps must only have properties that have string data
  • searchString is the string you want to match against the dataList

search usage example:

var subsearch = window.subsequenceSearch; //or require('subsequence-search') in node
var data1 = ['there is some fog', 'have an apple', 'omg! potato?', 'foxes are kinda cool!'];
var data2 = {
  data: [
    {a: 10, b: 'some text'},
    {a: 10, b: 'some more text'},
    {a: 200, b: 'you shall not pass?'}
  ],
  searchInProps: ['b']
};

console.log(subsearch.search({
  rank: subsearch.transforms.rank(0),
  highlight: subsearch.transforms.highlight('highlightClass')
}, data1, 'fo'));

/* output */
/* [
      '<span class="highlightClass">f</span><span class="highlightClass">o</span>xes are kinda cool!',
      'there is some <span class="highlightClass">f</span><span class="highlightClass">o</span>g'
    ]
  */

/*
 * Lets see another example where the dataList is an object.
 * Also, let's use the noHighlight transform instead of highlight transform.
 */
console.log(subsearch.search({
  rank: subsearch.transforms.rank('b'),
  noHighlight: subsearch.transforms.noHighlight
}, data2, 'text'));

/* output is shown in the image below */

output of using subsequence.search on an object with rank and noHighlight transforms

As you can see in the image, the input object structure is maintained and the properties that you search on are ranked (and modified if required, for e.g., by the highlight transform) in place.

You can write a custom transform to pick out only the data property from that object if required. Example:

console.log(subsearch.search({
  rank: subsearch.transforms.rank('b'),
  noHighlight: subsearch.transforms.noHighlight,
  pluckData: function(dataList){
    return dataList.data;
  }
}, data2, 'text'));

/* the output is in the image below */

output of using a custom transform to pluck data out

Transforms

transforms is an object can hold multiple transform functions.

It's modelled as an Object and not an Array for readability purposes (it helps to enforce naming of your function to explain what it does).

A transform is a function that accepts a dataList and returns transformed data.

A dataList, as mentioned before, is either:

  • An Array of Strings or
  • An Object containing a data property and a searchInProps property

When the input to search is an array of strings, the dataList received by a transform function is of the form of an array returned by String.prototype.match.

For example:

var subsearch = window.subsequenceSearch; //or require('subsequence-search') in node
//lets say you have the following data
var data = ['there is some fog', 'have an apple', 'omg! potato?', 'foxes are kinda cool!'];
//and you do
subsearch.search({
  myTransform: function(list){
    console.log(list);
    return list;
  }
}, data, 'fo');
//then you get an array containing to arrays printed in your console
//see the image below

data printed in console

As you can see in the image, each item is the same as what you get when you do

'some string'.match(/^(s)(.*?)(e)(.*)$/);

i.e., a String.prototype.match with some capturing groups.

When the input to search is an Object that has the same shape as a dataList object (i.e., it has data and searchInProps properties), the dataList received by a transform is of the same shape and form as the input dataList with its searchInProps data modified in place (if needed).

Example:

transform output when input is a dataList object

Transforms are applied in order. This is very important to remember while writing custom transforms so that you can match the output of one to the input of another.

You can chain as many transform functions as you want by passing them in the transforms object to the search call. The only requisite for chaining is that, the output of the nth transform should be in a form that is consumable by the (n+1)th transform (since they are applied in order.)

Keeping that in mind, you can do what you wish in those transform functions to get the data in a format that is useful for your application.

subsequence-search ships with three transform functions for your convenience. They are:

  • rank : returns a re-ordered dataList that has the most relevant results higher in the list. It takes the following parameter:

    • rankByKey - the key/property/index to rank by.
      • When dataList is an Array of Strings, this can be 0
      • When dataList is an Object with data and searchInProps, then this can be any property in the objects contained in data array
  • highlight: accepts a css class and transforms the result set to encapsulate the matching letters in a span with the given css class

    • its return value has the same shape as the input that was given to it
  • noHighlight: returns back an array of plaintext matches

  • noResults: returns a string that the user provides as input or default string ('No results found.') when input search string isn't found in input data

These are available on the transforms property on the object you get when you do require('subsequence-search') i.e.,

var subsearch = window.subsequenceSearch; //or require('subsequence-search') in node
//built in transforms:
//subsearch.transforms.rank
//subsearch.transforms.highlight(classname)
//subsearch.transforms.noHighlight
var data = ['there is some fog', 'have an apple', 'omg! potato?', 'foxes are kinda cool!'];

console.log(subsearch.search({
  rank: subsearch.transforms.rank(0),
  highlight: subsearch.transforms.highlight('highlightClass'),
  noResults: subsearch.transforms.noResults('No results found for your input.')
}, data, 'fo'));
//output
//["<span class="highlightClass">f</span><span class="highlightClass">o</span>xes are kinda cool!", "there is some <span class="highlightClass">f</span><span class="highlightClass">o</span>g"]

Compatibility

subsequence-search is compatible with browsers that are ES5 compliant and Node.js > 0.10.x.

It uses map, reduce, filter, etc heavily so if you need to use subsequence-search on older browsers, use a shim.

Changelog

  • 1.0.1

    • Fixed readme for npm website
  • 1.0.0

    • Since 0.3.4 has been in production usage for quite a while, promoting it to stable
  • 0.3.4

    • Fixed a bug where an empty string as the input string would mess with the ranking algorithm
    • Added clone as a dependency for deep cloning, cycle detection, etc
  • 0.3.3

    • Added a new transform called the noResults transform. This transform accepts a string that is displayed when no results are found
    var s = window.subsequenceSearch.search({ noResults: window.subsequenceSearch.transforms.noResults('No results found.')});
    • Fixed the rank transform. It now calculates grouping score correctly
    • Fixed searchString with special characters ('*', '+', '(', etc) breaking subsequence-search
  • 0.3.2

    • Built in a workaround for the instability of Array.prototype.sort implemented by browser vendors
  • 0.3.1

    • Bugfixes
      • The way subsequence-search handled an empty searchString is now fixed and should work as a user would expect it to
  • 0.3.0

    • Nuked my whole repo thanks to Google Drive and my idiocy and hence npm won't be able to download previous versions since technically they don't exist anymore. GOD DAMN IT! My apologies. :(

    • Massive rewrite to add support for dataList to be an object with shape

        var dataList = {
          data: [ ...objects... ],
          searchInProps: [ ...properties in objects given above... ]
        };
    • Changed the signature for search. transforms object is now the first parameter. This signature, combined with the fact that search auto-curries, you can produce a custom search function with a particular transform sequence just once and use it whenever you need by supplying the remaining two parameters. Example:

        var subsearch = window.subsequenceSearch; //or require('subsequence-search');
        var dataList = {
          data: [
            {a: 10, 'text': 'some text'},
            {a: 10, 'text': 'some more text'},
            {a: 200, 'text': 'you shall not pass?'}
          ],
          searchInProps: ['text']
        };
        var rankAndNohighlightSearch = subsearch.search({
          rank: subsearch.transforms.rank('text'),
          noHighlight: subsearch.transforms.noHighlight
        });
      
        console.log(rankAndNohighlightSearch(dataList, 'some'));
  • 0.2.0

    • Changed the search signature to search(dataList, transforms, searchString) to enable users to curry it more effectively
    • Added bower support
    • Refactored some code
    • Update auto-curry dependency
    • Jsdoc-ed them files
  • 0.1.4

    • Subsequence is now searched for, non-greedily from the beginning of input string
  • 0.1.3

    • Change the order in which inputs are validated in index.js
    • Added some more comments
  • 0.1.2

    • Fixed package.json (missing git repo)
  • 0.1.1

    • Fixed documentation (added demo)
  • 0.1.0

    • added chainable transforms
    • added test suite

About

Search for a given subsequence in a list of strings and transform the resulting list as required

Resources

License

Stars

Watchers

Forks

Packages

No packages published