Skip to content

Commit

Permalink
Move combineCounts to util file (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbressler13 authored Mar 20, 2024
1 parent 0f796e6 commit 1838fdb
Show file tree
Hide file tree
Showing 10 changed files with 217 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import xyz.lbres.kotlinutils.set.multiset.MultiSet
* [MultiSet] implementation where values of elements are assumed to be constant.
* Behavior is not defined if values of elements are changed (i.e. elements are added to a mutable list).
*/
sealed class ConstMultiSet<E> constructor(private val initialElements: Collection<E>, private var initialCounts: Map<E, Int>? = null) : MultiSet<E> {
sealed class ConstMultiSet<E> : MultiSet<E> {

@Suppress(Suppressions.FUNCTION_NAME)
infix fun `+c`(other: ConstMultiSet<E>): ConstMultiSet<E> = plusC(other)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import xyz.lbres.kotlinutils.set.multiset.utils.countsToString
import xyz.lbres.kotlinutils.set.multiset.utils.createCountsMap

// final implementation of ConstMultiSet
internal class ConstMultiSetImpl<E>(private val elements: Collection<E>, initialCounts: Map<E, Int>? = null) : ConstMultiSet<E>(elements, initialCounts) {
internal class ConstMultiSetImpl<E>(private val elements: Collection<E>, initialCounts: Map<E, Int>? = null) : ConstMultiSet<E>() {
private val manager: ConstMultiSetManager<E>
override val size: Int = elements.size
override val distinctValues: Set<E>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package xyz.lbres.kotlinutils.set.multiset.const

import xyz.lbres.kotlinutils.general.simpleIf
import xyz.lbres.kotlinutils.set.multiset.MultiSet
import xyz.lbres.kotlinutils.set.multiset.impl.MultiSetImpl
import xyz.lbres.kotlinutils.set.multiset.utils.CountsMap
import xyz.lbres.kotlinutils.set.multiset.utils.combineCounts
import xyz.lbres.kotlinutils.set.multiset.utils.createCountsMap
import kotlin.math.min

Expand All @@ -25,47 +25,22 @@ internal class ConstMultiSetManager<E>(private val counts: Map<E, Int>) {
}

fun plus(other: MultiSet<E>): MultiSet<E> {
return combineCounts(other, Int::plus, true, const = false)
return combineCounts(CountsMap(counts), other, Int::plus, true, const = false)
}
fun minus(other: MultiSet<E>): MultiSet<E> {
return combineCounts(other, Int::minus, false, const = false)
return combineCounts(CountsMap(counts), other, Int::minus, false, const = false)
}
fun intersect(other: MultiSet<E>): MultiSet<E> {
return combineCounts(other, ::min, false, const = false)
return combineCounts(CountsMap(counts), other, ::min, false, const = false)
}
fun plusC(other: ConstMultiSet<E>): ConstMultiSet<E> {
return combineCounts(other, Int::plus, true, const = true) as ConstMultiSet<E>
return combineCounts(CountsMap(counts), other, Int::plus, true, const = true) as ConstMultiSet<E>
}
fun minusC(other: ConstMultiSet<E>): ConstMultiSet<E> {
return combineCounts(other, Int::minus, false, const = true) as ConstMultiSet<E>
return combineCounts(CountsMap(counts), other, Int::minus, false, const = true) as ConstMultiSet<E>
}
fun intersectC(other: ConstMultiSet<E>): ConstMultiSet<E> {
return combineCounts(other, ::min, false, const = true) as ConstMultiSet<E>
}

/**
* Combine counts with another MultiSet, using the given operation
*
* @param other [MultiSet]<E>: MultiSet to combine with
* @param operation (Int, Int) -> Int: combination function
* @param useAllValues [Boolean]: if all values from both sets should be used to generate the new set. If `false`, only the values from this set will be used.
* @param const [Boolean]: if the returned MultiSet should be a ConstMultiSet
* @return [MultiSet]<E>: new set where each element has the number of occurrences specified by the operation. If [const] is `true`, the set will be a const multi set
*/
private fun combineCounts(other: MultiSet<E>, operation: (count: Int, otherCount: Int) -> Int, useAllValues: Boolean, const: Boolean): MultiSet<E> {
val values: Set<E> = simpleIf(useAllValues, { counts.keys + other.distinctValues }, { counts.keys })
val newCounts: MutableMap<E, Int> = mutableMapOf()
val newElements: MutableList<E> = mutableListOf()

values.forEach { value ->
val count = operation(getCountOf(value), other.getCountOf(value))
if (count > 0) {
newCounts[value] = count
repeat(count) { newElements.add(value) }
}
}

return simpleIf(const, { ConstMultiSetImpl(newElements, newCounts) }, { MultiSetImpl(newElements) })
return combineCounts(CountsMap(counts), other, ::min, false, const = true) as ConstMultiSet<E>
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import xyz.lbres.kotlinutils.set.multiset.MutableMultiSet
* [MutableMultiSet] implementation where values of elements are assumed to be constant.
* Behavior is not defined if values of elements are changed (i.e. elements are added to a mutable list).
*/
sealed class ConstMutableMultiSet<E> constructor(initialElements: Collection<E>) : MutableMultiSet<E>, ConstMultiSet<E>(initialElements) {
sealed class ConstMutableMultiSet<E> : MutableMultiSet<E>, ConstMultiSet<E>() {
/**
* Get an iterator for the elements in this set.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import xyz.lbres.kotlinutils.set.multiset.utils.createCountsMap
import kotlin.math.min

// final implementation of ConstMutableMultiSet
internal class ConstMutableMultiSetImpl<E>(initialElements: Collection<E>) : ConstMutableMultiSet<E>(initialElements) {
internal class ConstMutableMultiSetImpl<E>(initialElements: Collection<E>) : ConstMutableMultiSet<E>() {
/**
* If all properties are up-to-date with the most recent changes to the counts map
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package xyz.lbres.kotlinutils.set.multiset.impl

import xyz.lbres.kotlinutils.general.simpleIf
import xyz.lbres.kotlinutils.general.tryOrDefault
import xyz.lbres.kotlinutils.internal.constants.Suppressions
import xyz.lbres.kotlinutils.iterable.ext.countElement
import xyz.lbres.kotlinutils.set.multiset.MultiSet
import xyz.lbres.kotlinutils.set.multiset.utils.CountsMap
import xyz.lbres.kotlinutils.set.multiset.utils.combineCounts
import xyz.lbres.kotlinutils.set.multiset.utils.createCountsMap
import kotlin.math.min

/**
* Partial [MultiSet] implementation which supports modifications to values of elements (i.e. adding elements to a mutable list).
*/
internal abstract class AbstractMultiSetImpl<E>(private val initialElements: Collection<E>) : MultiSet<E> {
internal abstract class AbstractMultiSetImpl<E>(initialElements: Collection<E>) : MultiSet<E> {
/**
* Number of elements in set.
*/
Expand Down Expand Up @@ -102,7 +103,7 @@ internal abstract class AbstractMultiSetImpl<E>(private val initialElements: Col
* @return [MultiSet]<E>: MultiSet containing only values that are in both MultiSets
*/
override infix fun intersect(other: MultiSet<E>): MultiSet<E> {
return genericBinaryOperation(other, { count, otherCount -> min(count, otherCount) }, useAllValues = false)
return combineCounts(CountsMap.from(elements), other, ::min, useAllValues = false, const = false)
}

/**
Expand All @@ -113,7 +114,7 @@ internal abstract class AbstractMultiSetImpl<E>(private val initialElements: Col
* @return [MultiSet]<E>: MultiSet containing the items in this MultiSet but not the other
*/
override operator fun minus(other: MultiSet<E>): MultiSet<E> {
return genericBinaryOperation(other, Int::minus, useAllValues = false)
return combineCounts(CountsMap.from(elements), other, Int::minus, useAllValues = false, const = false)
}

/**
Expand All @@ -124,43 +125,7 @@ internal abstract class AbstractMultiSetImpl<E>(private val initialElements: Col
* @return [MultiSet]<E>: MultiSet containing all values from both MultiSets
*/
override operator fun plus(other: MultiSet<E>): MultiSet<E> {
return genericBinaryOperation(other, Int::plus)
}

/**
* Execute a binary operation with another MultiSet, with special handling for the case of another AbstractMultiSetImpl.
*
* @param other [MultiSet]<E>: other set to use in operation
* @param operation (Int, Int) -> Int: function which uses the count of an element in this set and the count in another set, and returns the new count for the element
* @param useAllValues [Boolean]: if all values from both sets should be used to generate the new set. If `false`, only the values from this set will be used.
* Defaults to `true`
* @return [MultiSet]<E>: new set where each element has the number of occurrences specified by the operation
*/
private fun genericBinaryOperation(other: MultiSet<E>, operation: (count: Int, otherCount: Int) -> Int, useAllValues: Boolean = true): MultiSet<E> {
val counts = getCounts()
val distinct = counts.keys

var getOtherCount: (E) -> Int = { other.getCountOf(it) }
var getOtherDistinct: () -> Set<E> = { other.distinctValues }
// increase efficiency of operation with other AbstractMultiSetImpl
if (other is AbstractMultiSetImpl<E>) {
val otherCounts = other.getCounts()
getOtherCount = { otherCounts.getOrDefault(it, 0) }
getOtherDistinct = { otherCounts.keys }
}

val newElements: MutableList<E> = mutableListOf()

val values = simpleIf(useAllValues, { distinct + getOtherDistinct() }, { distinct })
values.forEach {
val count = counts.getOrDefault(it, 0)
val otherCount = getOtherCount(it)
val newCount = operation(count, otherCount)

repeat(newCount) { _ -> newElements.add(it) }
}

return MultiSetImpl(newElements)
return combineCounts(CountsMap.from(elements), other, Int::plus, useAllValues = true, const = false)
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package xyz.lbres.kotlinutils.set.multiset.utils

/**
* Mapping of occurrences to the number of times that they occur
*/
@JvmInline
internal value class CountsMap<E>(private val counts: Map<E, Int>) {
/**
* Distinct values in map
*/
val distinct: Set<E>
get() = counts.keys

/**
* Get number of occurrences of a single element
*
* @param element E: element to get count of
* @return [Int]: number of occurrences
*/
fun getCountOf(element: E): Int = counts.getOrDefault(element, 0)

companion object {
/**
* Create a CountsMap from a collection of elements
*
* @param elements [Collection]<E>: elements to include in the map
* @return [CountsMap]<E>: map containing exactly the elements in the given collection
*/
fun <E> from(elements: Collection<E>): CountsMap<E> {
val counts: MutableMap<E, Int> = mutableMapOf()
elements.forEach {
counts[it] = counts.getOrDefault(it, 0) + 1
}

return CountsMap(counts)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package xyz.lbres.kotlinutils.set.multiset.utils

import xyz.lbres.kotlinutils.general.simpleIf
import xyz.lbres.kotlinutils.set.multiset.MultiSet
import xyz.lbres.kotlinutils.set.multiset.const.ConstMultiSetImpl
import xyz.lbres.kotlinutils.set.multiset.impl.AbstractMultiSetImpl
import xyz.lbres.kotlinutils.set.multiset.impl.MultiSetImpl

/**
* Create a mapping of each element in a collection to the number of occurrences of the element.
*
Expand Down Expand Up @@ -51,3 +57,39 @@ internal fun <E> countsToList(counts: Map<E, Int>): List<E> {

return list
}

/**
* Combine counts map and MultiSet, using the given operation
*
* @param counts [CountsMap]<E>: counts map
* @param multiset [MultiSet]<E>: MultiSet to combine with
* @param operation (Int, Int) -> Int: combination function
* @param useAllValues [Boolean]: if all values from the map and the set should be used to generate the new set. If `false`, only the values from the counts map will be used.
* @param const [Boolean]: if the returned MultiSet should be a ConstMultiSet
* @return [MultiSet]<E>: new set where each element has the number of occurrences specified by the operation. If [const] is `true`, the set will be a const multi set.
*/
internal fun <E> combineCounts(counts: CountsMap<E>, multiset: MultiSet<E>, operation: (Int, Int) -> Int, useAllValues: Boolean, const: Boolean): MultiSet<E> {
var getOtherCount: (E) -> Int = { multiset.getCountOf(it) }
var otherDistinct: () -> Set<E> = { multiset.distinctValues }

// increase efficiency for AbstractMultiSetImpl
if (multiset is AbstractMultiSetImpl<E>) {
val otherCounts = CountsMap.from(multiset)
getOtherCount = { otherCounts.getCountOf(it) }
otherDistinct = { otherCounts.distinct }
}

val values: Set<E> = simpleIf(useAllValues, { counts.distinct + otherDistinct() }, { counts.distinct })
val newCounts: MutableMap<E, Int> = mutableMapOf()
val newElements: MutableList<E> = mutableListOf()

values.forEach { value ->
val count = operation(counts.getCountOf(value), getOtherCount(value))
if (count > 0) {
newCounts[value] = count
repeat(count) { newElements.add(value) }
}
}

return simpleIf(const, { ConstMultiSetImpl(newElements, newCounts) }, { MultiSetImpl(newElements) })
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,6 @@ class UtilsTest {
val stringOptions = setOf(listOf("hello", "world", "world"), listOf("world", "world", "hello"))
assertContains(stringOptions, countsToList(stringCounts))
}

@Test fun testCombineCounts() = runCombineCountsTests()
}
Loading

0 comments on commit 1838fdb

Please sign in to comment.