The fresh array initially holds null references, whereas a zero row must be an empty list. Initialize every slot explicitly:
for (i <- 0 until N) rows(i) = List.empty[Entry]
Then
The following methods belong inside SparseMatrix, using the supplied Entry and Row type aliases.
The representation invariant is , rows.length == N, and every rows(i) is a nonnull finite list of entries satisfying
Strict increase excludes repeated columns. The abstraction function gives when the unique entry occurs in rows(i), and otherwise. Thus omitted positions represent zero; omission does not mean an unknown matrix entry.
Select entries having the requested column. The representation invariant ensures that the selected list has length at most one.
def get(i: Int, j: Int): Double = {
  require(0 <= i && i < N && 0 <= j && j < N)
  val entries = rows(i).filter(e => e._1 == j)
  if (entries.isEmpty) 0.0 else entries.head._2
}
Consequently
This version follows the hint and has time complexity linear in the number of stored entries in row i. An ordered early-exit scan is also possible.
Keep the entries before column j, discard any old entry at j, and retain those after it. Insert the replacement only when it is nonzero.
def set(i: Int, j: Int, x: Double): Unit = {
  require(0 <= i && i < N && 0 <= j && j < N)
  val before = rows(i).takeWhile(e => e._1 < j)
  val after = rows(i).dropWhile(e => e._1 <= j)
  val middle: Row = if (x == 0.0) Nil else List((j, x))
  rows(i) = before ::: middle ::: after
}
All indices in before are below j and all in after are above it, so the representation invariant is preserved. Other rows and all other column values remain unchanged:
Assigning zero removes storage for that entry. The time complexity is linear in the stored row length.
Construct a fresh sparse matrix, transform each stored value, and omit products that become zero.
def scale(s: Double): SparseMatrix = {
  val out = new SparseMatrix(N)
  for (i <- 0 until N) {
    out.rows(i) = rows(i).map { case (j, x) => (j, s * x) }
      .filter(e => e._2 != 0.0)
  }
  out
}
Filtering preserves column order. It handles s == 0.0 and any floating point underflow producing zero. Sharing immutable list structure elsewhere would be safe; this code constructs new transformed rows. Within the class, Scala's ordinary private permits access to out.rows. The result is
The time complexity is for stored entries. As usual for the supplied Double model, arithmetic means machine arithmetic; for a finite-real interpretation assume finite operands and products, excluding infinities and NaNs.
Entry in source row i becomes entry in destination row j. Visit source rows in descending order and prepend, producing increasing destination column order without a sorting pass.
def transpose: SparseMatrix = {
  val out = new SparseMatrix(N)
  var i = N - 1
  while (i >= 0) {
    for ((j, x) <- rows(i)) {
      out.rows(j) = (i, x) :: out.rows(j)
    }
    i -= 1
  }
  out
}
Before processing source row i, each destination row contains exactly the contributions from source rows with index greater than i, in increasing index order. Prepending index i preserves that order, and each source position is unique. Hence the representation invariant holds and
The source sparse matrix is unchanged.
For each source row, use a temporary dense array to accumulate contributions from nonzero pairs. For every stored , traverse stored entries in row of the other sparse matrix.
def mult(that: SparseMatrix): SparseMatrix = {
  require(N == that.N)
  val out = new SparseMatrix(N)
  for (i <- 0 until N) {
    val acc = Array.fill[Double](N)(0.0)
    for ((k, x) <- rows(i)) {
      for ((j, y) <- that.rows(k)) {
        acc(j) += x * y
      }
    }
    var j = N - 1
    var result: Row = Nil
    while (j >= 0) {
      if (acc(j) != 0.0) result = (j, acc(j)) :: result
      j -= 1
    }
    out.rows(i) = result
  }
  out
}
During accumulation, acc(j) is the sum of the already processed terms contributing to position . Missing entries multiply to zero, so ignoring them loses no contribution under the finite arithmetic convention of part (e). At completion,
Descending j and prepending produce strictly increasing stored indices. Omitting zero also handles cancellation. The temporary array uses auxiliary space beyond the output. The time complexity is
at worst , but potentially much smaller when the matrices are sparse. The term covers clearing and scanning the row accumulators. Finite-precision rounding can affect the numerical result, as in any Double matrix multiplication.

Articles by others on the same topic (0)

There are currently no matching articles.