Constructing a cumulative distribution function from a histogram During some running process, a histogram of values shall be build up. When done, the CDF shall be derived from it and used to get the quantiles for some values.

My current implementation looks as follows:

import java.util.*
import kotlin.math.max
import kotlin.math.min
import kotlin.math.roundToInt
import kotlin.random.Random


// 10 buckets for interval [0, 1000], outliers will be clamped
class Histogram(
    private val data: MutableMap&lt;Int, Int&gt; = mutableMapOf()
) {
    fun add(value: Int) {
        val bucket = max(0, min(1000, value)) / 100
        data[bucket] = data.getOrDefault(bucket, 0) + 1
    }

    fun deriveCDF(): CDF {
        val sum = data.values.sum()
        val pdfData = data.toSortedMap().mapValues { it.value.toDouble() / sum }
        val cdfData: MutableMap&lt;Int, Double&gt; = mutableMapOf()
        var acc = 0.0
        pdfData.forEach {
            acc += pdfData.getOrDefault(it.key, 0.0)
            cdfData[it.key] = acc
        }
        return CDF(cdfData.toSortedMap())
    }
}


// cumulative distribution function
class CDF(
    private val data: SortedMap&lt;Int, Double&gt;
) {
    fun getQuantile(value: Double): Double? {
        val bucket = (10 * value).roundToInt()
        return data[bucket]
    }
}


fun main() {
    val hist = Histogram()
    List(10000) {
        Random.nextInt(0, 1000)
    }.forEach {
        hist.add(it)
    }
    val cdf = hist.deriveCDF()
    println(cdf.getQuantile(0.3))
}


My problem with this is, that is it possible to construct instances of CDF with SortedMaps as data that don't make sense. I would like to prevent this, already at compile time.

The obvious alternative approach is to not have the construction logic (Histogram to CDF) in a Histogram::deriveCDF, but instead have a constructor of CDF take a Histogram instance and do the math there. This however forces Histogram to expose its internals data: MutableMap&lt;Int, Int&gt;, i.e., make them public, for the constructor of CDF to access them. But I would like to keep this implementation detail of Histogram hidden.

How can I escape this dilemma?
Your problem starts here:

val cdfData: MutableMap&lt;Int, Double&gt; = mutableMapOf()


This is the data that you want to your CDF class, but anyone can make a mutable map anywhere and use it for tons of things. So this is what you want to hide. So put this inside a CDFBuilder

class CDFBuilder {
     private val cdfData: MutableMap = mutableMapOf&lt;Int, Double&gt;()

     fun withData(a: Int, b: Double): CDFBuilder {
          // verification and stuff
          cdfData[a] = b
          return this
     }
}


Now, the problem is to build the CDF class without letting anyone else do it, right? There are a few options here:


Make a copy of the cdfData public from the CDFBuilder and read it in the CDF constructor, passing a CDFBuilder to the constructor.


Or 2, make a private constructor on the CDF-class, allowing it to only be constructed from the builder:

class CDF private constructor(val map: SortedMap&lt;Int, Double&gt;) {

    class CDFBuilder {
        private val cdfData: MutableMap = mutableMapOf&lt;Int, Double&gt;()

        fun withData(a: Int, b: Double): CDFBuilder {
            // verification and stuff
            cdfData[a] = b
            return this
        }

        fun build(): CDF {
            return CDF(map.toSortedMap())
        }
    }
}