Skip to content

Zip Method

The zip() method merges the elements of the current collection with the corresponding elements from another array. It creates pairs of elements from both sources, returning an array of tuples.

Basic Syntax

typescript
collect(items).zip<U>(array: U[]): Collection<[T, U | undefined]>

Examples

Basic Usage

typescript
import { collect } from 'ts-collect'

const numbers = collect([1, 2, 3])
const letters = ['a', 'b', 'c']

const zipped = numbers.zip(letters)
console.log(zipped.all())
// [[1, 'a'], [2, 'b'], [3, 'c']]

// With arrays of different lengths
const moreNumbers = collect([1, 2, 3, 4])
const fewerLetters = ['a', 'b']
console.log(moreNumbers.zip(fewerLetters).all())
// [[1, 'a'], [2, 'b'], [3, undefined], [4, undefined]]

Working with Objects

typescript
interface User {
  id: number
  name: string
}

interface Role {
  id: number
  title: string
}

const users = collect<User>([
  { id: 1, name: 'John' },
  { id: 2, name: 'Jane' }
])

const roles: Role[] = [
  { id: 1, title: 'Admin' },
  { id: 2, title: 'User' }
]

const usersWithRoles = users.zip(roles)
console.log(usersWithRoles.all())
// [
//   [{ id: 1, name: 'John' }, { id: 1, title: 'Admin' }],
//   [{ id: 2, name: 'Jane' }, { id: 2, title: 'User' }]
// ]

Real-world Examples

Data Pairing System

typescript
interface StudentData {
  id: number
  name: string
  grade: number
}

interface TestScore {
  studentId: number
  score: number
  date: Date
}

class GradeAnalyzer {
  private students: Collection<StudentData>

  constructor(students: StudentData[]) {
    this.students = collect(students)
  }

  pairWithTestScores(scores: TestScore[]): Array<[StudentData, TestScore | undefined]> {
    return this.students.zip(scores).all()
  }

  generateReport(): string[] {
    const scores: TestScore[] = [
      { studentId: 1, score: 85, date: new Date() },
      { studentId: 2, score: 92, date: new Date() }
    ]

    return this.pairWithTestScores(scores)
      .map(([student, score]) =>
        score
          ? `${student.name}: ${score.score}%`
          : `${student.name}: No score available`
      )
  }
}

Coordinate System

typescript
interface Point {
  x: number
  y: number
}

class CoordinateMapper {
  createPoints(xCoords: number[], yCoords: number[]): Collection<Point> {
    return collect(xCoords)
      .zip(yCoords)
      .map(([x, y]) => ({
        x,
        y: y ?? 0 // Default to 0 if y coordinate is undefined
      }))
  }

  calculateDistances(points: Point[]): number[] {
    const coordinates = this.splitCoordinates(points)
    return coordinates.map(([p1, p2]) =>
      p2 ? this.distance(p1, p2) : 0
    )
  }

  private splitCoordinates(points: Point[]): Array<[Point, Point | undefined]> {
    const pairs = collect(points.slice(0, -1))
      .zip(points.slice(1))
    return pairs.all()
  }

  private distance(p1: Point, p2: Point): number {
    return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2)
  }
}

Advanced Usage

Translation System

typescript
interface Translation {
  key: string
  translations: Map<string, string>
}

class TranslationManager {
  private defaultLanguage: string
  private translations: Collection<[string, string]>

  constructor(keys: string[], defaultTranslations: string[]) {
    this.defaultLanguage = 'en'
    this.translations = collect(keys).zip(defaultTranslations)
  }

  addLanguage(translations: string[]): void {
    const languageTranslations = this.translations
      .map(([key, _]) => key)
      .zip(translations)
      .all()

    // Store new language translations
    console.log(languageTranslations)
  }

  translate(key: string, language: string = this.defaultLanguage): string {
    const translation = this.translations
      .first(([k, _]) => k === key)

    return translation?.[1] ?? key
  }
}

Data Synchronizer

typescript
interface LocalData {
  id: string
  value: any
  timestamp: number
}

interface RemoteData {
  id: string
  value: any
  lastSync: number
}

class DataSynchronizer {
  synchronize(
    localData: LocalData[],
    remoteData: RemoteData[]
  ): Array<[LocalData, RemoteData | undefined]> {
    return collect(localData)
      .sortBy('id')
      .zip(collect(remoteData).sortBy('id').all())
      .all()
  }

  findConflicts(
    local: LocalData[],
    remote: RemoteData[]
  ): Array<[LocalData, RemoteData]> {
    return this.synchronize(local, remote)
      .filter(([l, r]): r is RemoteData => {
        return r !== undefined && l.timestamp !== r.lastSync
      })
      .map(([l, r]) => [l, r])
  }
}

Type Safety

typescript
interface FirstType {
  id: number
  value: string
}

interface SecondType {
  id: number
  data: number
}

// Type-safe zipping
const first = collect<FirstType>([
  { id: 1, value: 'one' }
])

const second: SecondType[] = [
  { id: 1, data: 100 }
]

const zipped = first.zip(second)
// Type is Collection<[FirstType, SecondType | undefined]>

// TypeScript ensures type safety
zipped.each(([firstItem, secondItem]) => {
  console.log(firstItem.value) // ✓ Valid
  console.log(secondItem?.data) // ✓ Valid (with optional chaining)
})

Return Value

  • Returns a new Collection containing tuples of paired items
  • Each tuple contains:
    • Item from the original collection
    • Corresponding item from the provided array (or undefined)
  • Length matches the length of the longer collection
  • Maintains type safety with TypeScript
  • Can be chained with other collection methods

Released under the MIT License.