-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
TreapMap.keys
as TreapSet
#20
Changes from 4 commits
7d71fc3
fb82689
19cd783
fe1c6d9
861593a
d77f633
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package com.certora.collect | ||
|
||
/** | ||
Presents the keys of a [TreapMap] as a [TreapSet]. | ||
|
||
The idea here is that a `TreapMap<K, *>` is stored with the same Treap structure as a `TreapSet<K>`, so we can very | ||
quickly create the corresponding `TreapSet<K>` when needed, in O(n) time (as opposed to the naive O(n*log(n)) | ||
method). | ||
|
||
We lazily initialize the set, so that we don't create it until we need it. For many operations, we can avoid | ||
creating the set entirely, and just use the map directly. However, many operations, e.g. [addAll]/[union] and | ||
[retainAll/intersect], are much more efficient when we have a [TreapSet], so we create it when needed. | ||
*/ | ||
internal abstract class AbstractKeySet<@Treapable K, S : TreapSet<K>> : TreapSet<K> { | ||
/** | ||
The map whose keys we are presenting as a set. We prefer to use the map directly when possible, so we don't | ||
need to create the set. | ||
*/ | ||
abstract val map: AbstractTreapMap<K, *, *> | ||
/** | ||
The set of keys. This is a lazy property so that we don't create the set until we need it. | ||
*/ | ||
abstract val keys: Lazy<S> | ||
|
||
@Suppress("Treapability") | ||
override fun hashCode() = keys.value.hashCode() | ||
override fun equals(other: Any?) = keys.value.equals(other) | ||
override fun toString() = keys.value.toString() | ||
|
||
override val size get() = map.size | ||
override fun isEmpty() = map.isEmpty() | ||
override fun clear() = treapSetOf<K>() | ||
|
||
override operator fun contains(element: K) = map.containsKey(element) | ||
override operator fun iterator() = map.entrySequence().map { it.key }.iterator() | ||
|
||
override fun add(element: K) = keys.value.add(element) | ||
override fun addAll(elements: Collection<K>) = keys.value.addAll(elements) | ||
override fun remove(element: K) = keys.value.remove(element) | ||
override fun removeAll(elements: Collection<K>) = keys.value.removeAll(elements) | ||
override fun removeAll(predicate: (K) -> Boolean) = keys.value.removeAll(predicate) | ||
override fun retainAll(elements: Collection<K>) = keys.value.retainAll(elements) | ||
|
||
override fun single() = map.single().key | ||
override fun singleOrNull() = map.singleOrNull()?.key | ||
override fun arbitraryOrNull() = map.arbitraryOrNull()?.key | ||
|
||
override fun containsAny(elements: Iterable<K>) = keys.value.containsAny(elements) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do you create the but then if maybe you can check here if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I'm opting for simplicity here. The problem you point out (large map, small We could have different cases here:
...but I'd much rather just keep this simple for now. It's possible we will never even use this method. :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. alright, I agree it's likely that calling these will be pretty rare. |
||
override fun containsAny(predicate: (K) -> Boolean) = (this as Iterable<K>).any(predicate) | ||
override fun containsAll(elements: Collection<K>) = keys.value.containsAll(elements) | ||
override fun findEqual(element: K) = keys.value.findEqual(element) | ||
|
||
override fun forEachElement(action: (K) -> Unit) = map.forEachEntry { action(it.key) } | ||
|
||
override fun <R : Any> mapReduce(map: (K) -> R, reduce: (R, R) -> R) = | ||
this.map.mapReduce({ k, _ -> map(k) }, reduce) | ||
override fun <R : Any> parallelMapReduce(map: (K) -> R, reduce: (R, R) -> R, parallelThresholdLog2: Int) = | ||
this.map.parallelMapReduce({ k, _ -> map(k) }, reduce, parallelThresholdLog2) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -101,6 +101,7 @@ internal sealed class AbstractTreapMap<@Treapable K, V, @Treapable S : AbstractT | |
return when { | ||
otherMap == null -> false | ||
otherMap === this -> true | ||
otherMap.isEmpty() -> false // NB AbstractTreapMap always contains at least one entry | ||
else -> otherMap.useAsTreap( | ||
{ otherTreap -> this.self.deepEquals(otherTreap) }, | ||
{ other.size == this.size && other.entries.all { this.containsEntry(it) }} | ||
|
@@ -111,6 +112,9 @@ internal sealed class AbstractTreapMap<@Treapable K, V, @Treapable S : AbstractT | |
override val size: Int get() = computeSize() | ||
override fun isEmpty(): Boolean = false | ||
|
||
// NB AbstractTreapMap always contains at least one entry | ||
override fun single() = singleOrNull() ?: throw IllegalArgumentException("Map contains more than one entry") | ||
|
||
override fun containsKey(key: K) = | ||
key.toTreapKey()?.let { self.find(it) }?.shallowContainsKey(key) ?: false | ||
|
||
|
@@ -139,14 +143,6 @@ internal sealed class AbstractTreapMap<@Treapable K, V, @Treapable S : AbstractT | |
override fun iterator() = entrySequence().iterator() | ||
} | ||
|
||
override val keys: ImmutableSet<K> | ||
get() = object: AbstractSet<K>(), ImmutableSet<K> { | ||
override val size get() = [email protected] | ||
override fun isEmpty() = [email protected]() | ||
override operator fun contains(element: K) = containsKey(element) | ||
override operator fun iterator() = entrySequence().map { it.key }.iterator() | ||
} | ||
|
||
override val values: ImmutableCollection<V> | ||
get() = object: AbstractCollection<V>(), ImmutableCollection<V> { | ||
override val size get() = [email protected] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ internal class HashTreapMap<@Treapable K, V>( | |
this as? HashTreapMap<K, V> | ||
?: (this as? PersistentMap.Builder<K, V>)?.build() as? HashTreapMap<K, V> | ||
|
||
override fun singleOrNull() = MapEntry(key, value).takeIf { next == null && left == null && right == null } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you might wonder why I don't object to this, where I am objecting to the |
||
override fun arbitraryOrNull(): Map.Entry<K, V>? = MapEntry(key, value) | ||
|
||
override fun getShallowMerger(merger: (K, V?, V?) -> V?): (HashTreapMap<K, V>?, HashTreapMap<K, V>?) -> HashTreapMap<K, V>? = { t1, t2 -> | ||
|
@@ -350,6 +351,17 @@ internal class HashTreapMap<@Treapable K, V>( | |
forEachPair { (k, v) -> action(MapEntry(k, v)) } | ||
right?.forEachEntry(action) | ||
} | ||
|
||
private fun treapSetFromKeys(): HashTreapSet<K> = | ||
HashTreapSet(treapKey, next?.toKeyList(), left?.treapSetFromKeys(), right?.treapSetFromKeys()) | ||
|
||
inner class KeySet : AbstractKeySet<K, HashTreapSet<K>>() { | ||
override val map get() = this@HashTreapMap | ||
override val keys = lazy { treapSetFromKeys() } | ||
override fun hashCode() = super.hashCode() // avoids treapability warning | ||
} | ||
|
||
override val keys get() = KeySet() | ||
} | ||
|
||
internal interface KeyValuePairList<K, V> { | ||
|
@@ -359,6 +371,8 @@ internal interface KeyValuePairList<K, V> { | |
operator fun component1() = key | ||
operator fun component2() = value | ||
|
||
fun toKeyList(): ElementList.More<K> = ElementList.More(key, next?.toKeyList()) | ||
|
||
class More<K, V>( | ||
override val key: K, | ||
override val value: V, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ internal class HashTreapSet<@Treapable E>( | |
override fun Iterable<E>.toTreapSetOrNull(): HashTreapSet<E>? = | ||
(this as? HashTreapSet<E>) | ||
?: (this as? TreapSet.Builder<E>)?.build() as? HashTreapSet<E> | ||
?: (this as? HashTreapMap<E, *>.KeySet)?.keys?.value | ||
|
||
private inline fun ElementList<E>?.forEachNodeElement(action: (E) -> Unit) { | ||
var current = this | ||
|
@@ -228,8 +229,10 @@ internal class HashTreapSet<@Treapable E>( | |
} | ||
}.iterator() | ||
|
||
override fun shallowGetSingleElement(): E? = element.takeIf { next == null } | ||
|
||
override fun singleOrNull(): E? = element.takeIf { next == null && left == null && right == null } | ||
override fun single(): E = element.also { | ||
if (next != null || left != null || right != null) { throw IllegalArgumentException("Set contains more than one element") } | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just because you can write something as a one liner, doesn't mean you should. This "post fixing" conditional stuff sucks in ruby, let's not bring it into kotlin :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fine. :) |
||
override fun arbitraryOrNull(): E? = element | ||
|
||
override fun <R : Any> shallowMapReduce(map: (E) -> R, reduce: (R, R) -> R): R { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ internal class SortedTreapSet<@Treapable E>( | |
override fun Iterable<E>.toTreapSetOrNull(): SortedTreapSet<E>? = | ||
(this as? SortedTreapSet<E>) | ||
?: (this as? PersistentSet.Builder<E>)?.build() as? SortedTreapSet<E> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. a question about line 29, how come you check if it is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is no |
||
?: (this as? SortedTreapMap<E, *>.KeySet)?.keys?.value | ||
|
||
override val self get() = this | ||
override fun iterator(): Iterator<E> = this.asTreapSequence().map { it.treapKey }.iterator() | ||
|
@@ -49,7 +50,10 @@ internal class SortedTreapSet<@Treapable E>( | |
override fun shallowRemove(element: E): SortedTreapSet<E>? = null | ||
override fun shallowRemoveAll(predicate: (E) -> Boolean): SortedTreapSet<E>? = this.takeIf { !predicate(treapKey) } | ||
override fun shallowComputeHashCode(): Int = treapKey.hashCode() | ||
override fun shallowGetSingleElement(): E = treapKey | ||
override fun singleOrNull(): E? = treapKey.takeIf { left == null && right == null } | ||
override fun single(): E = treapKey.also { | ||
if (left != null || right != null) { throw IllegalArgumentException("Set contains more than one element") } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any reason not to do (here and also in the hash version)
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same objection as above. Just use a block body |
||
} | ||
override fun arbitraryOrNull(): E? = treapKey | ||
override fun shallowForEach(action: (element: E) -> Unit): Unit { action(treapKey) } | ||
override fun <R : Any> shallowMapReduce(map: (E) -> R, reduce: (R, R) -> R): R = map(treapKey) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i always find it very unintuitive that
size
for treaps is an expensive operation. No avoiding it really I guess...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, we could store the size in the treap nodes, but of course that increases memory usage. So far this still seems like the right tradeoff.