FabienZE / sherlock

Collection of search functions for local data, customisable with priorities and strings normalization settings

Home Page:https://pub.dev/packages/sherlock

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

sherlock

Collection of search and smart search functions, for local data and given priorities

Usage

Sherlock needs the elements in which it (he?) will search. Priorities can be specified for results sorting, but it is not mandatory.

final foo = [
  {
    'col1': 'foo',
    'col2': ['foo1', 'foo2'],
    'col3': <non-string value>,
  },
  // Other elements...
];

// The bigger it is, the more important it is. 
final priorities = {
  'col2': 4,
  'col1': 3,
  // '*': 1,
};

final sherlock = Sherlock(elements: foo, priorities: priorities);
sherlock.query(where: '<column>', regex: r'<regex expression>');

List<Map> results = sherlock.results;
sherlock.forget(); // clear the results

// Other queries...

Note : this package is designed for searches in local data retrieved after an API call or something. It avoids requiring Internet during the search.

See the examples.

Overview

See also the search completion tool.

  • Create a Sherlock instance.

    Prototype

    Sherlock(
      List<Map<String, dynamic>> elements, 
      Map<String, int> priorities = {'*': 1},
      NormalizationSettings normalization = /* defaults */
    )

    Usage

    /// Users with their first and last name, and the city where they live.
    /// They also have an ID.
    List<Map<String, dynamic>> users = [
      {
        'firstName': 'Finn',
        'lastName': 'Thornton',
        'city': 'Edinburgh',
        'id': 1, // other types than string can be used.
      },
      {
        'firstName': 'Suz',
        'lastName': 'Judy',
        'city': 'Paris',
        'id': 2,
      },
      {
        'firstName': 'Suz',
        'lastName': 'Crystal',
        'city': 'Edinburgh',
        'hobbies': ['sport', 'programming'], // string lists can be used.
        'id': 3,
      },
    ];
    
    final sherlock = Sherlock(elements: users)

    Specifying priorities :

    // First and last name have the same priority.
    // The city is less important.
    // The default priority is `1`. 
    Map<String, int> priorities = [
      'firstName': 3,
      'lastName': 3,
      'city': 2,
    ];
    
    final sherlock = Sherlock(elements: users, priorities: priorities);

    Specifying normalization :

    final normalization = NormalizationSettings(
      normalizeCase: true,
      normalizeCaseType: false,
      removeDiacritics: true,
    );
    
    final sherlock = Sherlock(elements: users, normalization: normalization);
  • Priorities

    The priority map (also known as "priorities") is used to define the priority of each column. If there is no priority set for a column, the default priority will be used instead.

    The default priority value can be specified, otherwise it will be set to 1 :

    // The city is the least important.
    Map<String, int> priorities = [
      'firstName': 3,
      'lastName': 3,
      'city': 1,
      '*': 2,
    ];
  • Normalization settings

    The normalization settings are used to define the type of normalization that will be performed on the strings during searches.

    Prototype

    NormalizationSettings normalization;
    /// Out of the [Sherlock] class.
    
    NormalizationSettings(
      // If `true` : case insensitive.
      // If `false` : case sensitive.
      bool normalizeCase,
      // If `true` : no matter if it is snake or camel cased.
      // If `false` : it matters to be snake or camel cased.
      bool normalizeCaseType,
      // If `true` : keeps the diacritics.
      // If `false` : remove all the diacritics.
      bool removeDiacritics,
    )

    These settings are only used by query and queryMatch. The smart search uses its own normalization settings, which is :

    NormalizationSettings(
      normalizeCase: true,
      normalizeCaseType: false,
      removeDiacritics: true,
    );
  • Results

    Performed queries add the matching elements to the field unsortedResults, which can be used to get the results as Result objects.

    After that, the results can be retrieved sorted and unwrapped.

    Prototypes

    List<Map<String, dynamic>> get results; // sorted results
    List<Result> unsortedResults;
    
    void forget(); // resets the results
    /// Out of the [Sherlock] class.
    
    class Result {
      Map<String, dynamic> element;
      int priority;
    }
    
    List<Result> sortResults(List<Result> unsortedResults);
    
    extension UnwrapResults on List<Result> {
      List<Map<String, dynamic>> unwrap();
    }

    Usages

    Results sorted following the priorities map.

    final sherlock = Sherlock(/*...*/);
    // Queries...
    final results = sherlock.results;

    Getting results unsorted means the results will be in the order they were found. Each Result contain the actual result (an element matching with the query) and its priority.

    final sherlock = Sherlock(/*...*/);
    // Queries...
    final results = sherlock.unsortedResults;

    Also, the results can be sorted later :

    final unsortedResults = sherlock.unsortedResults;
    final results = sortResults(unsortedResults);

    But also unwrapped, in order to get elements instead of Result objects.

    final results = sortResults(unsortedResults).unwrap();

    Reset the values to perform new unrelated queries.

    final sherlock = Sherlock(/*...*/);
    // Queries...
    final results = sherlock.results; // save the results.
    sherlock.forget(); // `sherlock.results == []`.
    // Queries...
  • Queries

    Prototypes

    void query(
      String where = '*', 
      String regex, 
      NormalizationSettings specificNormalization = /* this.normalization */,
    ) 

    Usage

    /// All elements having a title, which contains the word 'game' or 'vr'.
    sherlock.query(where: 'title', regex: r'(game|vr)');
    
    /// All elements with in at least one of their fields which contain the word 
    /// 'cat'.
    sherlock.query(regex: r'cat');
    
    /// All elements having a title, which is equal to 'movie theatre'.
    sherlock.query(where: 'title', regex: r'^Movie Theatre$');
    
    /// All elements having a title, which is equal to 'Movie Theatre', the case 
    /// matters.
    sherlock.query(
      where: 'title', 
      regex: r'^Movie Theatre$', 
      specificNormalization: NormalizationSettings(
        normalizeCase: false,
        // other normalization settings are the one of [this.normalization].
      )
    );
    
    /// All elements with both words 'world' and 'pretty' in their descriptions.
    sherlock.query(where: 'description', regex: r'(?=.*pretty)(?=.*world).*');

    Prototype

    /// Searches for elements where [what] exists (is not null) in the column [where].
    void queryExist(String where, String what)

    Usage

    /// All activities where monday is specified in the opening hours.
    sherlock.queryExist(where: 'openingHours', what: 'monday');

    Prototypes

    void queryBool(String where = '*', bool Function(dynamic value) fn)
    
    void queryMatch(
      String where = '*', 
      dynamic match,
      NormalizationSettings specificNormalization = /* this.normalization */,
    )

    Usages

    /// All activities having a title which does not correspond to 'Parc'.
    sherlock.queryBool(where: 'title', fn: (value) => value != 'Parc');
    
    /// All activities starting at 7'o on tuesday.
    sherlock.queryBool(
      where: 'openingHours',
      fn: (value) => value['tuesday'][0] == 7,
    );
    /// All activities having a title corresponding to 'Parc', the case matters.
    sherlock.queryMatch(
      where: 'title', 
      match: 'Parc',     
      specificNormalization: NormalizationSettings(
        normalizeCase: false,
        // other normalization settings are the one of [this.normalization].
      ),
    );
    /// All activities having a title corresponding to 'parc', no matter the case.
    sherlock.queryMatch(
      where: 'title', 
      match: 'pArC',     
      specificNormalization: NormalizationSettings(
        normalizeCase: true,
        // other normalization settings are the one of [this.normalization].
      ),
    );
  • Smart search

    Prototype

    void search(
      dynamic where = '*', 
      String input,     
      int errorTolerance = 2,
    )

    Usages

    Perfect matches are searched first, it means they will be on top of the results if they exist.

    /// All elements having at least one of their field containing the word 'cats'
    sherlock.search(input: 'cAtS');
    /// Elements having their title or their categories containing the word 'cat'
    sherlock.search(where: ['title', 'categories'], input: 'cat');

Search completion tool

When doing searches from an user's input, it might be useful to help them completing their search. That's why SherlockCompletion exists.

The results could be used in a search widget for example.

Overview

  • Create a SherlockCompletion instance

    Prototype

    SherlockCompletion(
      String where, 
      List<Map<String, dynamic>> elements,
    )

    Usage

    final places = [
      {
        'name': 'Africa discovery',
      },
      {
        'name': 'Fruits and vegetables market',
        'description': 'A cool place to buy fruits and vegetables',
      },
      {
        'name': 'Fresh fish store',
      },
      {
        'name': 'Ball pool',
      },
      {
        'name': 'Finland discovery',
      },
    ];
    
    final completion = SherlockCompletion(where: 'name', elements: places);
  • Input

    Prototype

    List<String> input(
      String input,
      bool caseSensitive = false,
      bool? caseSensitiveFurtherSearches,
      int minResults = -1,
      int maxResults = -1,
    )

    Usage

    // Find all the names starting with 'fr'.
    final a = completion.input(input: 'fr');
    print(a);
    
    // Find all the names starting with 'Fr', and the case matters.
    final b = completion.input(input: 'Fr', caseSensitive: true);  
    print(b);
    [Fruits and vegetables market, Fresh fish store]
    [Fruits and vegetables market, Fresh fish store]
    
    // Try to find at least 4 names matching with 'fr'.
    final c = completion.input(input: 'fr', minResults: 4);
    print(c);
    
    // Try to find at least 3 names matching with 'Fr', and the case matters only 
    // for the searches that might be performed if there is less than 3 results.
    final d = completion.input(
      input: 'Fr', 
      minResults: 3, 
      caseSensitiveFurtherSearches: true,
    );
    print(d)
    [Fruits and vegetables market, Fresh fish store, Best place to find fruits, Museum of Africa]
    [Fruits and vegetables market, Fresh fish store]
    
    // Find maximum 1 name matching with 'fr'.
    final e = completion.input(input: 'fr', maxResults: 1);
    print(e);
    [Fruits and vegetables market]
    
  • Results

    Prototypes

    /// [Sherlock] results.
    List<Map<String, dynamic>> results;
    
    /// [input] results.
    List<String> input(...);

    Usage

    List<String> resultNames = completion.input(input: 'fr');
    print('names: $resultNames')
    
    List<Map<String, dynamic>> resultElements = completion.results;
    print('elements: $resultElements'); 
    names: [Fruits and vegetables market, Fresh fish store]
    elements: [
      {
        name: Fruits and vegetables market, 
        description: A cool place to buy fruits and vegetables
      }, 
      {
        name: Fresh fish store
      }
    ] 
    
  • Unchanged ranges of the results

    Prototype

    List<Range> unchangedRanges({
      String input,
      List<String> results,
    )
    class Range {
      int start;
      int end;
    }

    Usage

    This can be used to highlight the unchanged part while displaying the possible completions.

    What it could look like :

    const input = 'Fr';
    final results = completion.input(input: input, minResults: 4);
    
    // The case is ignored.
    List<Range> unchangedRanges = completion.unchangedRanges(
      input: input, 
      results: results,
    );
    
    print(results);
    print(unchangedRanges);
    [Fruits and vegetables market, Fresh fish store, Best place to find fruits, Museum of Africa]
    [[0, 2], [0, 2], [19, 21], [11, 13]]
    

About

Collection of search functions for local data, customisable with priorities and strings normalization settings

https://pub.dev/packages/sherlock

License:MIT License


Languages

Language:Dart 100.0%