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.