Skip to content

symmetricDiff Method

The symmetricDiff() method returns a new collection containing items that exist in either the original collection or the provided collection, but not in both. This is also known as the symmetric difference or disjunctive union.

Basic Syntax

typescript
collect(items).symmetricDiff<U = T>(other: U[] | Collection<U>): Collection<T | U>

Examples

Basic Usage

typescript
import { collect } from 'ts-collect'

// Simple symmetric difference
const set1 = collect([1, 2, 3, 4])
const set2 = [3, 4, 5, 6]
const diff = set1.symmetricDiff(set2)
console.log(diff.all())  // [1, 2, 5, 6]

// With strings
const tags1 = collect(['new', 'sale', 'featured'])
const tags2 = ['sale', 'clearance', 'featured']
const uniqueTags = tags1.symmetricDiff(tags2)
console.log(uniqueTags.all())  // ['new', 'clearance']

Working with Objects

typescript
interface Product {
  id: string
  sku: string
  name: string
}

const catalog1 = collect<Product>([
  { id: '1', sku: 'WIDGET-1', name: 'Widget' },
  { id: '2', sku: 'GADGET-1', name: 'Gadget' }
])

const catalog2 = [
  { id: '2', sku: 'GADGET-1', name: 'Gadget' },
  { id: '3', sku: 'TOOL-1', name: 'Tool' }
]

// Find unique products in each catalog
const uniqueProducts = catalog1.symmetricDiff(catalog2)

Real-world Examples

Inventory Comparison System

typescript
interface InventoryItem {
  sku: string
  warehouse: string
  quantity: number
  lastUpdated: Date
}

class InventoryComparer {
  compareWarehouses(
    warehouse1: Collection<InventoryItem>,
    warehouse2: Collection<InventoryItem>
  ): {
    uniqueItems: Collection<InventoryItem>
    summary: {
      onlyInWarehouse1: number
      onlyInWarehouse2: number
      different: InventoryItem[]
    }
  } {
    const uniqueItems = warehouse1.symmetricDiff(warehouse2)

    return {
      uniqueItems,
      summary: {
        onlyInWarehouse1: warehouse1
          .filter(item => !warehouse2.pluck('sku').contains(item.sku))
          .count(),
        onlyInWarehouse2: warehouse2
          .filter(item => !warehouse1.pluck('sku').contains(item.sku))
          .count(),
        different: this.findDifferentQuantities(warehouse1, warehouse2)
      }
    }
  }

  private findDifferentQuantities(
    warehouse1: Collection<InventoryItem>,
    warehouse2: Collection<InventoryItem>
  ): InventoryItem[] {
    const commonSkus = warehouse1
      .pluck('sku')
      .intersect(warehouse2.pluck('sku'))
      .toArray()

    return commonSkus
      .filter(sku => {
        const qty1 = warehouse1.firstWhere('sku', sku)?.quantity
        const qty2 = warehouse2.firstWhere('sku', sku)?.quantity
        return qty1 !== qty2
      })
      .map(sku => warehouse1.firstWhere('sku', sku)!)
  }
}

Catalog Change Detector

typescript
interface CatalogItem {
  id: string
  name: string
  category: string
  price: number
  status: 'active' | 'discontinued'
}

class CatalogChangeDetector {
  detectChanges(
    oldCatalog: Collection<CatalogItem>,
    newCatalog: Collection<CatalogItem>
  ): {
    changes: Collection<CatalogItem>
    summary: {
      added: CatalogItem[]
      removed: CatalogItem[]
      priceChanges: Array<{
        id: string
        oldPrice: number
        newPrice: number
      }>
      statusChanges: Array<{
        id: string
        oldStatus: 'active' | 'discontinued'
        newStatus: 'active' | 'discontinued'
      }>
    }
  } {
    const changes = oldCatalog.symmetricDiff(newCatalog)

    const added = newCatalog
      .filter(item => !oldCatalog.pluck('id').contains(item.id))
      .toArray()

    const removed = oldCatalog
      .filter(item => !newCatalog.pluck('id').contains(item.id))
      .toArray()

    const commonItems = oldCatalog
      .filter(item => newCatalog.pluck('id').contains(item.id))

    const priceChanges = commonItems
      .filter(oldItem => {
        const newItem = newCatalog.firstWhere('id', oldItem.id)
        return newItem && newItem.price !== oldItem.price
      })
      .map(oldItem => {
        const newItem = newCatalog.firstWhere('id', oldItem.id)!
        return {
          id: oldItem.id,
          oldPrice: oldItem.price,
          newPrice: newItem.price
        }
      })
      .toArray()

    const statusChanges = commonItems
      .filter(oldItem => {
        const newItem = newCatalog.firstWhere('id', oldItem.id)
        return newItem && newItem.status !== oldItem.status
      })
      .map(oldItem => {
        const newItem = newCatalog.firstWhere('id', oldItem.id)!
        return {
          id: oldItem.id,
          oldStatus: oldItem.status,
          newStatus: newItem.status
        }
      })
      .toArray()

    return {
      changes,
      summary: {
        added,
        removed,
        priceChanges,
        statusChanges
      }
    }
  }
}

Advanced Usage

typescript
interface MenuItem {
  id: string
  name: string
  price: number
  available: boolean
  modifiers: string[]
}

class MenuSyncManager {
  compareLocations(
    location1: Collection<MenuItem>,
    location2: Collection<MenuItem>
  ): {
    differences: Collection<MenuItem>
    report: {
      uniqueToLocation1: MenuItem[]
      uniqueToLocation2: MenuItem[]
      priceDifferences: Array<{
        id: string
        location1Price: number
        location2Price: number
        difference: number
      }>
      availabilityDifferences: Array<{
        id: string
        name: string
        location1Available: boolean
        location2Available: boolean
      }>
      modifierDifferences: Array<{
        id: string
        name: string
        location1Modifiers: string[]
        location2Modifiers: string[]
      }>
    }
  } {
    const differences = location1.symmetricDiff(location2)

    const commonItems = location1
      .filter(item => location2.pluck('id').contains(item.id))

    return {
      differences,
      report: {
        uniqueToLocation1: location1
          .filter(item => !location2.pluck('id').contains(item.id))
          .toArray(),
        uniqueToLocation2: location2
          .filter(item => !location1.pluck('id').contains(item.id))
          .toArray(),
        priceDifferences: this.findPriceDifferences(commonItems, location2),
        availabilityDifferences: this.findAvailabilityDifferences(commonItems, location2),
        modifierDifferences: this.findModifierDifferences(commonItems, location2)
      }
    }
  }

  private findPriceDifferences(
    items1: Collection<MenuItem>,
    items2: Collection<MenuItem>
  ) {
    return items1
      .filter(item1 => {
        const item2 = items2.firstWhere('id', item1.id)
        return item2 && item2.price !== item1.price
      })
      .map(item1 => {
        const item2 = items2.firstWhere('id', item1.id)!
        return {
          id: item1.id,
          location1Price: item1.price,
          location2Price: item2.price,
          difference: item2.price - item1.price
        }
      })
      .toArray()
  }

  private findAvailabilityDifferences(
    items1: Collection<MenuItem>,
    items2: Collection<MenuItem>
  ) {
    return items1
      .filter(item1 => {
        const item2 = items2.firstWhere('id', item1.id)
        return item2 && item2.available !== item1.available
      })
      .map(item1 => {
        const item2 = items2.firstWhere('id', item1.id)!
        return {
          id: item1.id,
          name: item1.name,
          location1Available: item1.available,
          location2Available: item2.available
        }
      })
      .toArray()
  }

  private findModifierDifferences(
    items1: Collection<MenuItem>,
    items2: Collection<MenuItem>
  ) {
    return items1
      .filter(item1 => {
        const item2 = items2.firstWhere('id', item1.id)
        return item2 && !this.arraysEqual(item1.modifiers, item2.modifiers)
      })
      .map(item1 => {
        const item2 = items2.firstWhere('id', item1.id)!
        return {
          id: item1.id,
          name: item1.name,
          location1Modifiers: item1.modifiers,
          location2Modifiers: item2.modifiers
        }
      })
      .toArray()
  }

  private arraysEqual(a: string[], b: string[]): boolean {
    return a.length === b.length &&
           a.every((val, index) => val === b[index])
  }
}

Type Safety

typescript
interface TypedItem {
  id: number
  value: string
}

const set1 = collect<TypedItem>([
  { id: 1, value: 'A' },
  { id: 2, value: 'B' }
])

const set2: TypedItem[] = [
  { id: 2, value: 'B' },
  { id: 3, value: 'C' }
]

// Type-safe symmetric difference
const diff: Collection<TypedItem> = set1.symmetricDiff(set2)

Return Value

  • Returns a new Collection with items unique to each set
  • Original collections remain unchanged
  • Maintains type safety with TypeScript
  • Can be chained with other collection methods
  • Uses strict equality (===) for comparison
  • Empty collection if sets are identical

Common Use Cases

1. Inventory Management

  • Stock differences
  • Location comparisons
  • Supplier catalogs
  • Warehouse sync
  • Stock transfers

2. Catalog Management

  • Product updates
  • Price changes
  • Category changes
  • Feature changes
  • Availability updates

3. Menu Management

  • Location differences
  • Price variations
  • Availability changes
  • Modifier updates
  • Special offerings

4. Order Processing

  • Status changes
  • Fulfillment differences
  • Shipping variations
  • Payment discrepancies
  • Item changes

5. Customer Data

  • Profile changes
  • Preference updates
  • Setting differences
  • Access changes
  • Status updates

6. Price Management

  • Rate differences
  • Discount variations
  • Promotion changes
  • Special pricing
  • Regional differences

7. Feature Management

  • Access differences
  • Permission changes
  • Role variations
  • Setting updates
  • Configuration changes

8. Content Management

  • Version differences
  • Update tracking
  • Change detection
  • Content sync
  • Publication status

9. Data Synchronization

  • System differences
  • Integration sync
  • Data migration
  • Change tracking
  • Update detection

10. Report Generation

  • Change reports
  • Difference analysis
  • Variation tracking
  • Update summaries
  • Sync status

Released under the MIT License.