Skip to content

cartesianProduct Method ​

The cartesianProduct() method returns a new collection containing all possible pairs of items from the original collection and the provided collection. This is equivalent to finding all possible combinations between two sets.

Basic Syntax ​

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

Examples ​

Basic Usage ​

typescript
import { collect } from 'ts-collect'

// Simple cartesian product
const sizes = collect(['S', 'M', 'L'])
const colors = ['Red', 'Blue']
const variations = sizes.cartesianProduct(colors)
console.log(variations.all())
// [
//   ['S', 'Red'],
//   ['S', 'Blue'],
//   ['M', 'Red'],
//   ['M', 'Blue'],
//   ['L', 'Red'],
//   ['L', 'Blue']
// ]

// Numbers and strings
const numbers = collect([1, 2])
const labels = ['A', 'B', 'C']
const combinations = numbers.cartesianProduct(labels)

Working with Objects ​

typescript
interface Size {
  id: string
  name: string
  dimensions: string
}

interface Color {
  id: string
  name: string
  hexCode: string
}

const sizes = collect<Size>([
  { id: 'S', name: 'Small', dimensions: '10x10' },
  { id: 'M', name: 'Medium', dimensions: '20x20' }
])

const colors: Color[] = [
  { id: 'RED', name: 'Red', hexCode: '#FF0000' },
  { id: 'BLUE', name: 'Blue', hexCode: '#0000FF' }
]

// Generate all size and color combinations
const productVariations = sizes.cartesianProduct(colors)

Real-world Examples ​

Product Variant Generator ​

typescript
interface BaseOption {
  id: string
  name: string
  price: number
}

interface ProductVariant {
  id: string
  options: [BaseOption, BaseOption]
  totalPrice: number
  sku: string
}

class VariantGenerator {
  generateVariants(
    options1: Collection<BaseOption>,
    options2: BaseOption[]
  ): Collection<ProductVariant> {
    return options1
      .cartesianProduct(options2)
      .map(([opt1, opt2]) => ({
        id: `${opt1.id}-${opt2.id}`,
        options: [opt1, opt2],
        totalPrice: opt1.price + opt2.price,
        sku: this.generateSku(opt1, opt2)
      }))
  }

  generateAllVariants(
    baseProduct: {
      id: string
      basePrice: number
    },
    sizes: BaseOption[],
    colors: BaseOption[],
    materials: BaseOption[]
  ): {
    variants: ProductVariant[]
    priceRange: { min: number; max: number }
  } {
    // Generate size-color combinations
    const sizeColorVariants = collect(sizes)
      .cartesianProduct(colors)
      .map(([size, color]) => ({
        id: `${size.id}-${color.id}`,
        options: [size, color] as [BaseOption, BaseOption],
        price: baseProduct.basePrice + size.price + color.price
      }))

    // Add materials to each size-color combination
    const allVariants = sizeColorVariants
      .cartesianProduct(materials)
      .map(([variant, material]) => ({
        id: `${variant.id}-${material.id}`,
        options: [...variant.options, material],
        totalPrice: variant.price + material.price,
        sku: this.generateFullSku(baseProduct.id, variant.options, material)
      }))
      .toArray()

    const prices = allVariants.map(v => v.totalPrice)
    return {
      variants: allVariants,
      priceRange: {
        min: Math.min(...prices),
        max: Math.max(...prices)
      }
    }
  }

  private generateSku(opt1: BaseOption, opt2: BaseOption): string {
    return `${opt1.id}-${opt2.id}`.toUpperCase()
  }

  private generateFullSku(
    productId: string,
    options: BaseOption[],
    material: BaseOption
  ): string {
    return `${productId}-${options.map(o => o.id).join('-')}-${material.id}`.toUpperCase()
  }
}

Bundle Creator ​

typescript
interface Product {
  id: string
  name: string
  price: number
  category: string
}

interface Bundle {
  id: string
  products: [Product, Product]
  totalPrice: number
  discountedPrice: number
  savings: number
}

class BundleCreator {
  constructor(private bundleDiscount: number = 0.1) {}

  generateCompatibleBundles(
    products1: Collection<Product>,
    products2: Product[],
    rules: {
      maxPrice?: number
      categories?: string[]
    } = {}
  ): Collection<Bundle> {
    return products1
      .cartesianProduct(products2)
      .filter(([p1, p2]) => this.areCompatible(p1, p2, rules))
      .map(([p1, p2]) => this.createBundle(p1, p2))
  }

  private areCompatible(
    product1: Product,
    product2: Product,
    rules: {
      maxPrice?: number
      categories?: string[]
    }
  ): boolean {
    const totalPrice = product1.price + product2.price

    if (rules.maxPrice && totalPrice > rules.maxPrice) {
      return false
    }

    if (rules.categories &&
        !rules.categories.includes(product1.category) &&
        !rules.categories.includes(product2.category)) {
      return false
    }

    return product1.id !== product2.id
  }

  private createBundle(p1: Product, p2: Product): Bundle {
    const totalPrice = p1.price + p2.price
    const discountedPrice = totalPrice * (1 - this.bundleDiscount)

    return {
      id: `${p1.id}-${p2.id}`,
      products: [p1, p2],
      totalPrice,
      discountedPrice,
      savings: totalPrice - discountedPrice
    }
  }
}

Advanced Usage ​

Custom Menu Builder ​

typescript
interface MenuItem {
  id: string
  name: string
  price: number
  type: 'main' | 'side' | 'drink'
  calories: number
}

interface MenuCombo {
  id: string
  items: [MenuItem, MenuItem]
  totalPrice: number
  totalCalories: number
  savings: number
}

class MenuComboBuilder {
  generateCombos(
    mains: Collection<MenuItem>,
    sides: MenuItem[]
  ): {
    combos: Collection<MenuCombo>
    categories: {
      budget: MenuCombo[]
      regular: MenuCombo[]
      premium: MenuCombo[]
    }
  } {
    const allCombos = mains
      .cartesianProduct(sides)
      .map(([main, side]) => this.createCombo(main, side))

    return {
      combos: allCombos,
      categories: {
        budget: allCombos
          .filter(combo => combo.totalPrice < 15)
          .toArray(),
        regular: allCombos
          .filter(combo => combo.totalPrice >= 15 && combo.totalPrice < 25)
          .toArray(),
        premium: allCombos
          .filter(combo => combo.totalPrice >= 25)
          .toArray()
      }
    }
  }

  private createCombo(main: MenuItem, side: MenuItem): MenuCombo {
    const totalPrice = main.price + side.price
    const comboDiscount = this.calculateComboDiscount(main, side)

    return {
      id: `${main.id}-${side.id}`,
      items: [main, side],
      totalPrice: totalPrice * (1 - comboDiscount),
      totalCalories: main.calories + side.calories,
      savings: totalPrice * comboDiscount
    }
  }

  private calculateComboDiscount(main: MenuItem, side: MenuItem): number {
    // Example discount logic
    if (main.price + side.price > 30) return 0.15
    if (main.price + side.price > 20) return 0.1
    return 0.05
  }
}

Type Safety ​

typescript
interface Option1 {
  id: string
  value: string
}

interface Option2 {
  id: string
  code: number
}

const options1 = collect<Option1>([
  { id: 'A', value: 'Value A' },
  { id: 'B', value: 'Value B' }
])

const options2: Option2[] = [
  { id: 'X', code: 1 },
  { id: 'Y', code: 2 }
]

// Type-safe cartesian product
const combinations: Collection<[Option1, Option2]> = options1.cartesianProduct(options2)

// Type safety maintained in results
combinations.forEach(([opt1, opt2]) => {
  const value: string = opt1.value
  const code: number = opt2.code
})

Return Value ​

  • Returns a new Collection of tuple pairs
  • Original collections remain unchanged
  • Maintains type safety with TypeScript
  • Can be chained with other collection methods
  • Preserves original order for pairs
  • Empty collection if either input is empty

Common Use Cases ​

1. Product Variations ​

  • Size/color combinations
  • Style variations
  • Material options
  • Customization choices
  • Feature combinations

2. Bundle Creation ​

  • Product bundles
  • Package deals
  • Combo offers
  • Set combinations
  • Kit assemblies

3. Menu Combinations ​

  • Meal combos
  • Dish pairings
  • Drink combinations
  • Side options
  • Special menus

4. Pricing Options ​

  • Price tiers
  • Service levels
  • Package options
  • Subscription combinations
  • Payment plans

5. Booking Options ​

  • Time slots
  • Service combinations
  • Resource allocation
  • Venue options
  • Package selections

6. Configuration Options ​

  • Feature combinations
  • Setting pairs
  • Option selections
  • Customization choices
  • Setup variations

7. Service Packages ​

  • Service combinations
  • Add-on options
  • Package builds
  • Upgrade options
  • Custom solutions

8. Event Planning ​

  • Venue/date combinations
  • Service pairings
  • Resource allocation
  • Schedule options
  • Package selections

9. Travel Packages ​

  • Flight/hotel combinations
  • Activity pairings
  • Transportation options
  • Meal plans
  • Tour combinations

10. Customization Options ​

  • Design combinations
  • Style pairings
  • Material selections
  • Color schemes
  • Feature sets

Released under the MIT License.