CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-org-scala-lang-modules--scala-collection-compat

A library that makes some Scala 2.13 APIs available on Scala 2.11 and 2.12, facilitating cross-building Scala 2.13 and 3.0 code on older versions

Pending
Overview
Eval results
Files

iterator-size-ops.mddocs/

Iterator and Size Operations

Enhanced iterator methods and efficient size comparison utilities for collections.

Iterator Extensions

IteratorExtensionMethods

implicit class IteratorExtensionMethods[A](private val self: Iterator[A]) extends AnyVal {
  def nextOption(): Option[A]
  def sameElements[B >: A](that: TraversableOnce[B]): Boolean
  def concat[B >: A](that: TraversableOnce[B]): TraversableOnce[B]
  def tapEach[U](f: A => U): Iterator[A]
}

nextOption

def nextOption(): Option[A]

Safe version of next() that returns None when the iterator is exhausted instead of throwing an exception.

Usage:

val iterator = List(1, 2, 3).iterator

iterator.nextOption()  // Some(1)
iterator.nextOption()  // Some(2)
iterator.nextOption()  // Some(3)
iterator.nextOption()  // None (instead of NoSuchElementException)

sameElements

def sameElements[B >: A](that: TraversableOnce[B]): Boolean

Compare two iterators/traversables element by element for equality.

Usage:

val iter1 = List(1, 2, 3).iterator
val iter2 = Vector(1, 2, 3).iterator
val iter3 = List(1, 2, 4).iterator

iter1.sameElements(iter2)  // true
iter1.sameElements(iter3)  // false

// Works with any TraversableOnce
val list = List(1, 2, 3)
list.iterator.sameElements(list)  // true

concat

def concat[B >: A](that: TraversableOnce[B]): TraversableOnce[B]

Concatenate the iterator with another TraversableOnce.

Usage:

val iter1 = List(1, 2, 3).iterator
val iter2 = List(4, 5, 6)

val combined = iter1.concat(iter2)
combined.toList  // List(1, 2, 3, 4, 5, 6)

tapEach

def tapEach[U](f: A => U): Iterator[A]

Apply a function to each element for side effects while preserving the iterator.

Usage:

val iterator = List(1, 2, 3, 4, 5).iterator

val processed = iterator
  .tapEach(x => println(s"Processing: $x"))
  .filter(_ % 2 == 0)
  .tapEach(x => println(s"Even number: $x"))

processed.toList
// Output:
// Processing: 1
// Processing: 2
// Even number: 2
// Processing: 3
// Processing: 4
// Even number: 4
// Processing: 5
// Result: List(2, 4)

Size Comparison Operations

SizeCompareOps

class SizeCompareOps(private val it: Traversable[_]) extends AnyVal {
  def <(size: Int): Boolean
  def <=(size: Int): Boolean
  def ==(size: Int): Boolean
  def !=(size: Int): Boolean
  def >=(size: Int): Boolean
  def >(size: Int): Boolean
}

Efficient size comparison operations that can short-circuit for better performance, especially with large or infinite collections.

Size Comparison Methods

def <(size: Int): Boolean    // Tests if collection size is less than given size
def <=(size: Int): Boolean   // Tests if collection size is less than or equal to given size
def ==(size: Int): Boolean   // Tests if collection size equals given size
def !=(size: Int): Boolean   // Tests if collection size is not equal to given size
def >=(size: Int): Boolean   // Tests if collection size is greater than or equal to given size
def >(size: Int): Boolean    // Tests if collection size is greater than given size

Usage:

import scala.collection.compat._

val list = List(1, 2, 3, 4, 5)

// Size comparisons - more efficient than list.size comparisons
list.sizeIs < 10     // true
list.sizeIs <= 5     // true
list.sizeIs == 5     // true
list.sizeIs != 3     // true
list.sizeIs >= 5     // true
list.sizeIs > 2      // true

// Works with any Traversable
val set = Set("a", "b", "c")
set.sizeIs == 3      // true
set.sizeIs > 5       // false

TraversableExtensionMethods

implicit class TraversableExtensionMethods[A](private val self: Traversable[A])
    extends AnyVal {
  def iterableFactory: GenericCompanion[Traversable]
  def sizeCompare(otherSize: Int): Int
  def sizeIs: SizeCompareOps
  def sizeCompare(that: Traversable[_]): Int
}

sizeCompare

def sizeCompare(otherSize: Int): Int
def sizeCompare(that: Traversable[_]): Int

Compare collection size with an integer or another collection, returning negative/zero/positive like Ordering.

Returns:

  • Negative value if the collection is smaller
  • Zero if sizes are equal
  • Positive value if the collection is larger

Usage:

val list1 = List(1, 2, 3)
val list2 = List(4, 5)

list1.sizeCompare(5)     // negative (3 < 5)
list1.sizeCompare(3)     // zero (3 == 3)
list1.sizeCompare(2)     // positive (3 > 2)

list1.sizeCompare(list2) // positive (3 > 2)
list2.sizeCompare(list1) // negative (2 < 3)

sizeIs

def sizeIs: SizeCompareOps

Get a SizeCompareOps instance for the collection to perform size comparisons.

SeqExtensionMethods

implicit class SeqExtensionMethods[A](private val self: Seq[A]) extends AnyVal {
  def lengthIs: SizeCompareOps
}

lengthIs

def lengthIs: SizeCompareOps

Size comparison operations specifically for sequences (using length terminology).

Usage:

val seq = Seq(1, 2, 3, 4)

seq.lengthIs < 10    // true
seq.lengthIs == 4    // true
seq.lengthIs > 5     // false

Performance Benefits

Short-Circuit Evaluation

Size comparison operations can short-circuit, providing better performance than computing the full size:

val largeList = (1 to 1000000).toList

// Inefficient - computes full size
largeList.size > 100  // Processes all 1M elements

// Efficient - stops after 101 elements
largeList.sizeIs > 100  // Only processes 101 elements

Lazy Collections

Size operations work efficiently with lazy collections:

import scala.collection.compat.immutable._

val infiniteStream = LazyList.from(1)

// These operations terminate quickly
infiniteStream.sizeIs > 5     // true (checks first 6 elements)
infiniteStream.sizeIs == 10   // false (checks first 11 elements)

// This would never terminate
// infiniteStream.size  // Don't do this!

Complete Usage Examples

Processing Large Datasets

import scala.collection.compat._

def processDataBatch[T](data: Iterable[T], maxBatchSize: Int): List[List[T]] = {
  if (data.sizeIs <= maxBatchSize) {
    List(data.toList)
  } else {
    data.grouped(maxBatchSize).toList
  }
}

// Efficient - stops counting after maxBatchSize + 1 elements
val batches = processDataBatch(largeDataset, 1000)

Validation and Constraints

def validateCollectionSizes[T](
    required: Traversable[T],
    optional: Traversable[T]
): Either[String, Unit] = {
  
  if (required.sizeIs == 0) {
    Left("Required items cannot be empty")
  } else if (required.sizeIs > 100) {
    Left("Too many required items (max 100)")
  } else if (optional.sizeIs > 50) {
    Left("Too many optional items (max 50)")
  } else {
    Right(())
  }
}

Iterator Processing with Debugging

def processUserData(users: Iterator[User]): List[ProcessedUser] = {
  users
    .tapEach(user => println(s"Processing user: ${user.id}"))
    .filter(_.isActive)
    .tapEach(user => println(s"Active user: ${user.id}"))
    .take(100)  // Limit processing
    .tapEach(user => println(s"Within limit: ${user.id}"))
    .map(processUser)
    .toList
}

case class User(id: String, isActive: Boolean, name: String)
case class ProcessedUser(id: String, name: String, processedAt: Long)

def processUser(user: User): ProcessedUser = 
  ProcessedUser(user.id, user.name, System.currentTimeMillis())

Safe Iterator Consumption

def safeIteratorProcessing[T](iterator: Iterator[T]): List[T] = {
  val buffer = scala.collection.mutable.ListBuffer[T]()
  
  while (iterator.hasNext) {
    iterator.nextOption() match {
      case Some(element) => buffer += element
      case None => // Iterator was exhausted between hasNext and next
    }
  }
  
  buffer.toList
}

// Alternative using tap for logging
def loggedIteratorProcessing[T](iterator: Iterator[T]): List[T] = {
  iterator
    .tapEach(element => println(s"Processing: $element"))
    .toList
}

Comparing Collection Sizes Efficiently

def selectSmallestCollection[T](collections: List[Traversable[T]]): Option[Traversable[T]] = {
  collections match {
    case Nil => None
    case head :: tail =>
      Some(tail.foldLeft(head) { (smallest, current) =>
        if (current.sizeCompare(smallest) < 0) current else smallest
      })
  }
}

val lists = List(
  List(1, 2, 3),
  List(4, 5, 6, 7, 8),
  List(9, 10)
)

val smallest = selectSmallestCollection(lists)  // Some(List(9, 10))

Batch Processing with Size Constraints

def createBalancedBatches[T](
    items: Traversable[T], 
    minBatchSize: Int, 
    maxBatchSize: Int
): List[List[T]] = {
  
  if (items.sizeIs < minBatchSize) {
    List(items.toList)  // Single batch if too few items
  } else {
    val iterator = items.toIterator
    val batches = scala.collection.mutable.ListBuffer[List[T]]()
    
    while (iterator.hasNext) {
      val batch = iterator.take(maxBatchSize).toList
      if (batch.sizeIs >= minBatchSize || !iterator.hasNext) {
        batches += batch
      } else {
        // Combine small final batch with previous batch
        if (batches.nonEmpty) {
          val lastBatch = batches.remove(batches.length - 1)
          batches += (lastBatch ++ batch)
        } else {
          batches += batch
        }
      }
    }
    
    batches.toList
  }
}

Implementation Notes

  • Size comparison operations use optimized implementations that can short-circuit
  • For Seq types, lengthCompare is used when available for O(1) comparisons
  • Iterator extensions preserve lazy evaluation where possible
  • tapEach creates a new iterator that applies the side effect function during iteration
  • All operations are implemented as value classes for minimal runtime overhead

Install with Tessl CLI

npx tessl i tessl/maven-org-scala-lang-modules--scala-collection-compat

docs

annotation-backports.md

backported-collections.md

collection-extensions.md

collection-factories.md

index.md

iterator-size-ops.md

java-interop.md

map-extensions.md

method-chaining.md

option-converters.md

resource-management.md

string-parsing.md

tile.json