A lightweight fuzzy-search library, with zero dependencies

Overview

Fuse

CI Status Version License Platform Donate Donate

What is Fuse?

Fuse is a super lightweight library which provides a simple way to do fuzzy searching.

Demo

Usage

Example 1

let fuse = Fuse()
let result = fuse.search("od mn war", in: "Old Man's War")

print(result?.score)  // 0.44444444444444442
print(result?.ranges) // [CountableClosedRange(0...0), CountableClosedRange(2...6), CountableClosedRange(9...12)]

Example 2

Search for a text pattern in an array of srings.

let books = ["The Silmarillion", "The Lock Artist", "The Lost Symbol"]
let fuse = Fuse()

// Improve performance by creating the pattern once
let pattern = fuse.createPattern(from: "Te silm")

// Search for the pattern in every book
books.forEach {
    let result = fuse.search(pattern, in: $0)
    print(result?.score)
    print(result?.ranges)
}

Example 3

class Book: Fuseable {
    dynamic var name: String
    dynamic var author: String

    var properties: [FuseProperty] {
        return [
            FuseProperty(name: "title", weight: 0.3),
            FuseProperty(name: "author", weight: 0.7),
        ]
    }
}

let books: [Book] = [
    Book(author: "John X", title: "Old Man's War fiction"),
    Book(author: "P.D. Mans", title: "Right Ho Jeeves")
]

let fuse = Fuse()
let results = fuse.search("man", in: books)

results.forEach { item in
    print("index: " + item.index)
    print("score: " + item.score)
    print("results: " + item.results)
    print("---------------")
}

// Output:
//
// index: 1
// score: 0.015
// results: [(key: "author", score: 0.015000000000000003, ranges: [CountableClosedRange(5...7)])]
// ---------------
// index: 0
// score: 0.028
// results: [(key: "title", score: 0.027999999999999997, ranges: [CountableClosedRange(4...6)])]

Example 4

let fuse = Fuse()
fuse.search("Man", in: books, completion: { results in
    print(results)
})

Options

Fuse takes the following options:

  • location: Approximately where in the text is the pattern expected to be found. Defaults to 0
  • distance: Determines how close the match must be to the fuzzy location (specified above). An exact letter match which is distance characters away from the fuzzy location would score as a complete mismatch. A distance of 0 requires the match be at the exact location specified, a distance of 1000 would require a perfect match to be within 800 characters of the fuzzy location to be found using a 0.8 threshold. Defaults to 100
  • threshold: At what point does the match algorithm give up. A threshold of 0.0 requires a perfect match (of both letters and location), a threshold of 1.0 would match anything. Defaults to 0.6
  • maxPatternLength: The maximum valid pattern length. The longer the pattern, the more intensive the search operation will be. If the pattern exceeds the maxPatternLength, the search operation will return nil. Why is this important? Read this. Defaults to 32
  • isCaseSensitive: Indicates whether comparisons should be case sensitive. Defaults to false

Example Project

To run the example project, clone the repo, and run pod install from the Example directory first.

Requirements

Installation

Fuse is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod "Fuse"

License

Fuse is available under the MIT license. See the LICENSE file for more info.

Comments
  • Performance improvements

    Performance improvements

    Several improvements to the internals of _search(_:in:) to avoid the less efficient parts of Swift's String implementation, namely the fact that count is O(n) for a string of length n, and String.index(_, offsetBy: n) is also O(n).

    Overall speedup of up to 2.5x for search operations.

    FuseUtilities.swift:

    • add a version of calculateScore which takes a patternLength parameter, to avoid repeated calls to String.count, which is O(n) for a string of length n.
    • calculatePatternAlphabet: eliminate unnecessary loop zeroing mask by using nil coalescing instead.
    • calculatePatternAlphabet: eliminate unnecessary calls to String.index(_, offsetBy: n), which is O(n), by instead looping through pattern.enumerated().
    • findRanges: eliminate unnecessary variable end and unnecessary subtraction of one, using ..< instead of ... operator.

    Fuse.swift:

    • change all calls to calculateScore to use pattern.len (see FuseUtilities.swift)
    • calculate text.count only once
    • eliminate unnecessary Double/Int conversions in calculation of binMid
    • avoid calls to text.char(at:), which is O(n+j) for offset of j in string length n, instead calculate index of currentLocation once, then apply String.index(before:) once per loop iteration, which is O(1).

    String+Fuse.swift:

    • avoid deprecated encodedOffset and instead compare self.count to position
    opened by peteraisher 3
  • String Comparison Crash

    String Comparison Crash

    Hi,

    Is there any reason why the below line of string comparison would lead to a crash?

        let result = fuse.search("Rancheras 2016 Borracho de Amor  Por Tu Maldito Amor  Hermoso Carino  Se Va Muriendo Mi Alma  Por un Amor", in: "Borracho De Amor")
    
    fuse - comparison fuse - crash

    Any help would be appreciated. Thanks!

    question 
    opened by saru2020 2
  • Example 3 not working?

    Example 3 not working?

    Hi,

    it seems that Example 3 with search in Fusable is not working? The results is always empty? I tried installing from github both the latest version and from "c7adb16" as mentioned in issue 40, but still there is no printout happening.

    opened by andreyyudin 1
  • Make the async APIs completely parallel

    Make the async APIs completely parallel

    This removes the point of coordination introduced in a9c2f5b9893bd52e01c1d7bc5636f8bd2e77a2e0 when making the APIs thread-safe, by changing the updates to the result array from sync to async.

    opened by wearhere 1
  • Fix crash for optional properties (Also upgrades to Swift 5.0)

    Fix crash for optional properties (Also upgrades to Swift 5.0)

    This enables Fuse to safely access optional properties that might be nil.

    Also upgrades to Swift 5.0 & bumps version in Podspec but I can exclude that change if necessary for some reason.

    opened by florianbuerger 1
  • Fix weights when score is perfect

    Fix weights when score is perfect

    Hey, thanks for this lovely library!

    I noticed an issue where the score would equal 0 no matter the weights when having a perfect match on a property. Looking into Fusejs I found that they fallback to 0.001 when this is the case to account that issue. This PR adds the exact same fallback.

    I haven't looked into what different approaches your library uses to calculate the score compared to FuseJS so I'm not really sure whether the fallback number should be any different or not?

    opened by nicoeg 0
  • Improvement/crash fixes

    Improvement/crash fixes

    Added fixes for issues that caused quite frequent crashes in our app.

    Removed forced position unwrapping. Added check whether startIndex offset by position is still between bounds (less than endIndex). Added check for ... range iteration so that start is less than finish.

    opened by crtgregoric 0
  • Simplify highlights

    Simplify highlights

    This should be considered after Pull #3.

    Goal

    1. Simplify attributed string generation for displaying filtered results by using system provided functions.
    2. Use NSAttributedString instead of NSMutableAttributedString in contexts where attributed strings are not mutated.
    opened by gravicle 0
  • CountableRange is coming one char short

    CountableRange is coming one char short

    When using the returned CountableRange<Int> values to create attributed strings, highlights come a char short. For example:

    let options = ["active", "inactive", "canceled"]
    let query = "act"
    let results = fuse.search(query, in: options)
    

    Results: (0, [0..<2]) & (1, [2..<4]) Expected results: (0, [0...2]) & (1, [2...4])

    In my understanding search should be returning CountableClosedRanges, instead of half-closed CountbleRanges.

    The example project would have the same issue, if it were not for the <= comparison in offset <= range.endIndex. This essentially treats the half-closed range as a closed range.

    opened by gravicle 0
  • Is this repo still maintained?

    Is this repo still maintained?

    The maintainer hasn't responded to neither PRs nor Issues for more than six months.

    Yet the project is still asking for donations https://patreon.com/fusejs https://www.paypal.com/paypalme2/kirorisk

    We need to know if this repo is going to be maintained so that we know whether it's worth sponsoring this project or if we should fork it and continue ourselves.

    opened by arielelkin 4
  • Best practices around reusing `Fuse` objects?

    Best practices around reusing `Fuse` objects?

    If I repeatedly search over similar datasets with the same parameters, I wonder if I should use the same Fuse object for all searches:

    let fuse = Fuse()
    
    func search() {
      return fuse.search...
    }
    

    vs. using a new Fuse object for every search

    func search() {
      return Fuse().search...
    }
    

    Of particular concern to me is that I am using the async APIs which rely on per-instance dispatch queues. I kind of like the idea of using a new Fuse object for every search so that the searches don't "compete" on the same dispatch queue, but I don't know if there's actually any limit to concurrency per-custom-queue (as opposed to at the level of the global queues that back these custom queues, or eventually at the level where the work items are scheduled on threads, in which case different Fuse objects would be competing anyway).

    I also don't know if there's overhead to having many custom dispatch queues, in which case it would be better to reuse one Fuse object. This Stack Overflow makes it seem like this wouldn't be a problem.

    question 
    opened by wearhere 0
  • Did not work while searching in [Fuseable]

    Did not work while searching in [Fuseable]

    Here is my implementation. it always succeeds while checking if !item.responds(to: Selector(property.name))

    extension Member: Fuseable { var properties: [FuseProperty] { return [ FuseProperty(name: name ?? ""), FuseProperty(name: phone ?? "") ] } }

    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { guard searchText.count > 0 else { self.displayedMembers = self.allMembers self.membersTableView.reloadData() return } self.displayedMembers = searchEngine.search(searchText, in: self.allMembers).map { self.allMembers[$0.index] } self.membersTableView.reloadData() }

    opened by mahmoud-omara-goodsmart 1
  • Fuse can't find exact match in longer sting

    Fuse can't find exact match in longer sting

    In this operation, Fuse is given a pattern

    pattern = neighbours

    And a string

    content = Mr and Mrs Dursley, of number four, Privet Drive, were proud to say that they were perfectly normal, thank you very much. They were the last people you’d expect to be involved in anything strange or mysterious, because they just didn’t hold with such nonsense. Mr Dursley was the director of a firm called Grunnings, which made drills. He was a big, beefy man with hardly any neck, although he did have a very large moustache. Mrs Dursley was thin and blonde and had nearly twice the usual amount of neck, which came in very useful as she spent so much of her time craning over garden fences, spying on the neighbours

    However despite there being an exact match, the operation fails.

    Fuse().search(pattern, in: content) //Produces nil

    I'm using the blocking version of search, and would like to continue to do so if possible.

    Appreciate any help, thanks!

    question 
    opened by WillBishop 1
Owner
Kiro Risk
Kiro Risk
Lightweight library to set an Image as text background. Written in swift.

![](https://img.shields.io/badge/Swift 2-compatible-4BC51D.svg?style=flat-square) Simple and light weight UIView that animate text with an image. Demo

Lucas Ortis 552 Sep 9, 2022
VEditorKit - Lightweight and Powerful Editor Kit built on Texture(AsyncDisplayKit)

VEditorKit provides the most core functionality needed for the editor. Unfortunately, When combined words are entered then UITextView selectedRange will changed and typingAttribute will cleared. So, In combined words case, Users can't continue typing the style they want.

David Ha 471 Dec 27, 2022
A comprehensive, lightweight string extension for Swift

SwiftString SwiftString is a lightweight string extension for Swift. This library was motivated by having to search StackOverflow for common string op

Andrew Mayne 1.6k Dec 30, 2022
🎗 Super lightweight ISO8601 Date Formatter in Swift

ISO8601 ❤️ Support my apps ❤️ Push Hero - pure Swift native macOS application to test push notifications PastePal - Pasteboard, note and shortcut mana

Khoa 19 May 12, 2020
CodeMirror-Swift is a lightweight wrapper of CodeMirror for macOS and iOS

CodeMirror-Swift is a lightweight wrapper of CodeMirror for macOS and iOS. Features ?? Lightweight CodeMirror wrapper (build 5.52.2) ✅ 100% Native Swi

Proxyman 86 Dec 30, 2022
BonMot is a Swift attributed string library

BonMot (pronounced Bon Mo, French for good word) is a Swift attributed string library. It abstracts away the complexities of the iOS, macOS, tvOS, and

Rightpoint 3.4k Dec 30, 2022
Croc is a swift emoji string parsing library

Croc is a library for parsing emojis on iOS. It provides a simple and lightweight interface for detecting, generating, categorizing and managing emoji characters, making emoji-powered features an easy task for developers.

Joe Kalash 127 Nov 20, 2022
A library for formatting strings on iOS and macOS

Sprinter Introduction What? Why? How? Usage Installation Integration Localization Thread Safety Advanced Usage Introduction What? Sprinter is a librar

Nick Lockwood 168 Feb 6, 2022
SwiftVerbalExpressions is a Swift library that helps to construct difficult regular expressions

SwiftVerbalExpressions Swift Regular Expressions made easy SwiftVerbalExpressions is a Swift library that helps to construct difficult regular express

null 582 Jun 29, 2022
🌭 Mustard is a Swift library for tokenizing strings when splitting by whitespace doesn't cut it.

Mustard ?? Mustard is a Swift library for tokenizing strings when splitting by whitespace doesn't cut it. Quick start using character sets Foundation

Mathew Sanders 695 Nov 11, 2022
Swift markdown library

Markdown ![Swift version](https://img.shields.io/badge/Swift-2.1 | 2.2-blue.svg) ![GitHub license](https://img.shields.io/badge/license-LGPL v3-green.

Crossroad Labs 79 Oct 9, 2022
A simple library for building attributed strings, for a more civilized age.

Veneer A simple library for building attributed strings, for a more civilized age. Veneer was created to make creating attributed strings easier to re

Wess Cope 26 Dec 27, 2022
A simple library that provides standard Unicode emoji support across all platforms

Twitter Emoji (Twemoji) A simple library that provides standard Unicode emoji support across all platforms. Twemoji v13.1 adheres to the Unicode 13.0

Twitter 15k Jan 8, 2023
iOS port from libphonenumber (Google's phone number handling library)

libPhoneNumber for iOS NBPhoneNumberUtil NBAsYouTypeFormatter ARC only Update Log https://github.com/iziz/libPhoneNumber-iOS/wiki/Update-Log Issue You

iziz 2.3k Jan 3, 2023
User input masking library repo.

Migration Guide: v.6 This update brings breaking changes. Namely, the autocomplete flag is now a part of the CaretGravity enum, thus the Mask::apply c

red_mad_robot 548 Dec 20, 2022
A Cross-Platform String and Regular Expression Library written in Swift.

Guitar ?? A Cross-Platform String and Regular Expression Library written in Swift. About This library seeks to add common string manipulation function

Arthur Ariel Sabintsev 659 Dec 27, 2022
Swift emoji string parsing library

Croc is a library for parsing emojis on iOS. It provides a simple and lightweight interface for detecting, generating, categorizing and managing emoji

Joe Kalash 125 Sep 27, 2021
A "time ago", "time since", "relative date", or "fuzzy date" category for NSDate and iOS, Objective-C, Cocoa Touch, iPhone, iPad

Migration 2014.04.12 NSDate+TimeAgo has merged with DateTools. DateTools is the parent project and Matthew York is the project head. This project is n

Kevin Lawler 1.8k Dec 2, 2022
Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement.

Linenoise-Swift A pure Swift implementation of the Linenoise library. A minimal, zero-config readline replacement. Supports Mac OS and Linux Line edit

Andy Best 114 Dec 14, 2022
A library to inject your dependencies via property wrappers

?? DependencyInjection A library to inject your dependencies via property wrappers ?? Features DependencyInjection allows you to define the dependenci

Alberto Garcia 4 Dec 10, 2022