Skip to content

fuzzyMatch Method

The fuzzyMatch() method filters items based on fuzzy string matching against a specified key's value. This is particularly useful for implementing search functionality that tolerates typos and approximate matches.

Basic Syntax

typescript
collect(items).fuzzyMatch(
  key: keyof T,
  pattern: string,
  threshold: number = 0.7
): Collection<T>

Examples

Basic Usage

typescript
import { collect } from 'ts-collect'

// Simple fuzzy matching
const items = collect([
  { name: 'iPhone 12 Pro' },
  { name: 'iPhone 13 Pro' },
  { name: 'Samsung Galaxy' }
])

// Find matching items
const matches = items.fuzzyMatch('name', 'iphone pro', 0.7)
console.log(matches.all())
// [
//   { name: 'iPhone 12 Pro' },
//   { name: 'iPhone 13 Pro' }
// ]

Working with Objects

typescript
interface Product {
  id: string
  name: string
  description: string
  category: string
}

const products = collect<Product>([
  {
    id: '1',
    name: 'Wireless Headphones',
    description: 'Bluetooth enabled headphones',
    category: 'Electronics'
  },
  {
    id: '2',
    name: 'Wireless Earbuds',
    description: 'True wireless earbuds',
    category: 'Electronics'
  }
])

// Search products
const searchResults = products.fuzzyMatch('name', 'wireless head', 0.6)

Real-world Examples

Product Search System

typescript
interface SearchableProduct {
  id: string
  name: string
  brand: string
  category: string
  description: string
  tags: string[]
}

class ProductSearcher {
  constructor(
    private products: Collection<SearchableProduct>,
    private thresholds: {
      name: number
      brand: number
      description: number
    } = {
      name: 0.7,
      brand: 0.8,
      description: 0.6
    }
  ) {}

  search(query: string): Collection<SearchableProduct> {
    // Search across multiple fields with different thresholds
    const nameMatches = this.products.fuzzyMatch('name', query, this.thresholds.name)
    const brandMatches = this.products.fuzzyMatch('brand', query, this.thresholds.brand)
    const descMatches = this.products.fuzzyMatch('description', query, this.thresholds.description)

    // Combine results without duplicates
    return nameMatches
      .union(brandMatches.toArray())
      .union(descMatches.toArray())
      .sortBy('name')
  }

  findSimilarProducts(product: SearchableProduct): Collection<SearchableProduct> {
    // Find products with similar names or descriptions
    const similarNames = this.products
      .filter(p => p.id !== product.id)
      .fuzzyMatch('name', product.name, 0.6)

    const similarDesc = this.products
      .filter(p => p.id !== product.id)
      .fuzzyMatch('description', product.description, 0.5)

    return similarNames
      .union(similarDesc.toArray())
      .sortBy('name')
  }
}

Customer Support Lookup

typescript
interface CustomerQuery {
  id: string
  subject: string
  description: string
  status: 'open' | 'closed'
  category: string
}

class SupportQueryMatcher {
  constructor(private queries: Collection<CustomerQuery>) {}

  findSimilarQueries(
    newQuery: CustomerQuery,
    options: {
      subjectThreshold?: number
      descriptionThreshold?: number
      includeResolved?: boolean
    } = {}
  ): {
    matches: Collection<CustomerQuery>
    categories: string[]
  } {
    const {
      subjectThreshold = 0.7,
      descriptionThreshold = 0.6,
      includeResolved = false
    } = options

    let activeQueries = this.queries
    if (!includeResolved) {
      activeQueries = this.queries.where('status', 'open')
    }

    // Find queries with similar subjects
    const subjectMatches = activeQueries
      .fuzzyMatch('subject', newQuery.subject, subjectThreshold)

    // Find queries with similar descriptions
    const descriptionMatches = activeQueries
      .fuzzyMatch('description', newQuery.description, descriptionThreshold)

    // Combine matches
    const allMatches = subjectMatches
      .union(descriptionMatches.toArray())
      .sortByDesc('id')

    // Get suggested categories based on matches
    const suggestedCategories = allMatches
      .pluck('category')
      .unique()
      .toArray()

    return {
      matches: allMatches,
      categories: suggestedCategories
    }
  }

  findKnowledgeBaseArticles(query: string): Collection<CustomerQuery> {
    return this.queries
      .where('status', 'closed')
      .fuzzyMatch('subject', query, 0.6)
      .sortByDesc('id')
      .take(5)
  }
}

Advanced Usage

Address Matcher

typescript
interface Address {
  id: string
  street: string
  city: string
  state: string
  postalCode: string
}

class AddressMatcher {
  constructor(private addresses: Collection<Address>) {}

  findDuplicates(
    threshold: number = 0.8
  ): Array<{
    group: Address[]
    similarity: number
  }> {
    const duplicateGroups: Array<{
      group: Address[]
      similarity: number
    }> = []

    this.addresses.each(address => {
      // Find similar addresses
      const matches = this.addresses
        .filter(a => a.id !== address.id)
        .fuzzyMatch('street', address.street, threshold)
        .filter(a => this.isSameArea(address, a))

      if (matches.isNotEmpty()) {
        duplicateGroups.push({
          group: [address, ...matches.toArray()],
          similarity: threshold
        })
      }
    })

    return this.consolidateGroups(duplicateGroups)
  }

  findAddress(searchText: string): Collection<Address> {
    // Search across all address fields
    const streetMatches = this.addresses
      .fuzzyMatch('street', searchText, 0.7)

    const cityMatches = this.addresses
      .fuzzyMatch('city', searchText, 0.8)

    const stateMatches = this.addresses
      .fuzzyMatch('state', searchText, 0.9)

    return streetMatches
      .union(cityMatches.toArray())
      .union(stateMatches.toArray())
      .sortBy('street')
  }

  private isSameArea(addr1: Address, addr2: Address): boolean {
    return addr1.city === addr2.city &&
           addr1.state === addr2.state &&
           addr1.postalCode === addr2.postalCode
  }

  private consolidateGroups(
    groups: Array<{
      group: Address[]
      similarity: number
    }>
  ): Array<{
    group: Address[]
    similarity: number
  }> {
    // Consolidate overlapping groups
    return groups.reduce((acc, curr) => {
      const overlapping = acc.find(g =>
        g.group.some(addr =>
          curr.group.some(currAddr => currAddr.id === addr.id)
        )
      )

      if (overlapping) {
        overlapping.group = [...new Set([...overlapping.group, ...curr.group])]
        overlapping.similarity = Math.min(overlapping.similarity, curr.similarity)
      } else {
        acc.push(curr)
      }

      return acc
    }, [] as Array<{ group: Address[]; similarity: number }>)
  }
}

Type Safety

typescript
interface TypedItem {
  id: number
  name: string
  description: string
}

const items = collect<TypedItem>([
  { id: 1, name: 'Test Item', description: 'A test item' },
  { id: 2, name: 'Another Item', description: 'Another test' }
])

// Type-safe fuzzy matching
const matches = items.fuzzyMatch('name', 'test', 0.7)

// TypeScript enforces valid keys
// items.fuzzyMatch('invalid', 'test')  // ✗ TypeScript error

Return Value

  • Returns a new Collection of matching items
  • Original collection remains unchanged
  • Maintains type safety with TypeScript
  • Can be chained with other methods
  • Results ordered by match quality
  • Empty collection if no matches

Common Use Cases

  • Name matching
  • Description search
  • Category matching
  • Brand lookup
  • Tag matching

2. Customer Support

  • Query matching
  • Knowledge base search
  • Ticket routing
  • FAQ matching
  • Solution lookup

3. Address Validation

  • Duplicate detection
  • Address matching
  • Location search
  • Area lookup
  • Postal matching
  • Title matching
  • Description search
  • Tag matching
  • Category lookup
  • Keyword search

5. User Lookup

  • Name search
  • Email matching
  • Username lookup
  • Profile search
  • Contact matching

6. Error Handling

  • Error matching
  • Log searching
  • Issue lookup
  • Problem matching
  • Solution finding

7. Data Cleaning

  • Duplicate detection
  • Record matching
  • Entry validation
  • Data normalization
  • Format matching

8. Navigation

  • Menu search
  • Category matching
  • Route lookup
  • Link matching
  • Path finding

9. Autocomplete

  • Suggestion generation
  • Term completion
  • Query matching
  • Search assistance
  • Input helping

10. Reference Data

  • Code lookup
  • ID matching
  • Reference search
  • Key finding
  • Value matching

Released under the MIT License.