-
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
Conversation
Currently it is quite expensive to do operations like this: ``` set2 = map1.keys intersect set1 ``` This is because the `keys` property on `TreapMap` is an `ImmuableSet`, not a `TreapSet`, so the intersection operation must convert it to a `TreapSet` first, using the generic `Set`->`TreapSet` conversion, which is rather expensive. This is a shame, because a `TreapMap<K, V>` of course is already structured the same way as a `TreapSet<K>`, so the conversion to `TreapSet` should be very cheap. This PR redefines the `keys` property as a `TreapSet`, and defines efficient implementations of this. Depending on usage, we may be able to avoid the conversion to `TreapSet` altogether; if not, we can do the conversion as a straightforward projection of the existing `TreapMap` structure, avoiding most of the overhead of the generic conversion. We also add `single` and `singleOrNull` methods to `TreapMap`, which are useful in their own right, and can be used to avoid the conversion to `TreapSet` in some cases.
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.
some questions
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 comment
The reason will be displayed to describe this comment to others. Learn more.
any reason not to do (here and also in the hash version)
override fun single(): E = singleOrNull() ?: throw ...
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.
E
might be nullable, so singleOrNull
doesn't give the correct behavior.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
why do you create the keys
set for containsAny
and containsAll
?
is it because if elements
happens to be a TreapSet
it's more efficient?
but then if elements
is not then then can be pretty costly. I'm especially thinking of the case where keys
is very large and elements
is tiny. You'll run in time O(|keys|)
.
maybe you can check here if elements
is a TreapSet
or not, and according to that choose what to do?
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.
Yes, I'm opting for simplicity here. The problem you point out (large map, small elements
) is still a problem even if everything's a treap. Optimizing that is tricky, since getting the size of a Treap (as you point out elsewhere) is surprisingly expensive.
We could have different cases here:
- Empty elements
- Single Treap element
- Non-treap (maybe broken down further by size?)
...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 comment
The 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.
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(1) time. |
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 think it's O(n)
time, instead of the naive algorithm to create the set, which would take O(n log(n))
, no?
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.
Haha yes, this was really wishful thinking. :)
(At one point I thought I could do this in O(1) time, by having the maps actually implement TreapSet
, but that idea didn't pan out)
override fun toString() = keys.value.toString() | ||
|
||
override val size get() = map.size | ||
override fun isEmpty() = map.isEmpty() |
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.
@@ -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 comment
The 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 PresistentSet.Builder
and not SortedTreapSet.Builder
?
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.
There is no SortedTreapSet.Builder
.
private fun treapSetFromKeys(): SortedTreapSet<K> = | ||
SortedTreapSet(treapKey, left?.treapSetFromKeys(), right?.treapSetFromKeys()) | ||
|
||
@Suppress("Treapability") |
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.
why do you suppress here?
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.
The treapability analysis doesn't see that the superclass's hashCode implementation is stable. I'll fix this a different way.
|
||
@Suppress("Treapability") | ||
class KeySet<@Treapable K>( | ||
override val map: SortedTreapMap<K, *>, |
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.
is there no avoiding the * here?
can't KeySet
be an inner class, and then you get K and V for free?
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.
It didn't occur to me to make this an inner class! That's a good idea (though you'll see the *
pop up elsewhere after this change :) )
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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
Fine. :)
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 comment
The reason will be displayed to describe this comment to others. Learn more.
same objection as above. Just use a block body
@@ -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 comment
The 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 also
nonsense. I confess I don't have a great rule of thumb, except this looks more idiomatic, and the takeIf
is clearly indicating what's happening, whereas using also
to exceptionally abort is pretty surprising
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.
lgtm!
Currently it is quite expensive to do operations like this:
This is because the
keys
property onTreapMap
is anImmuableSet
, not aTreapSet
, so the intersection operation must convert it to aTreapSet
first, using the genericSet
->TreapSet
conversion, which is rather expensive.This is a shame, because a
TreapMap<K, V>
of course is already structured the same way as aTreapSet<K>
, so the conversion toTreapSet
should be very cheap.This PR redefines the
keys
property as aTreapSet
, and defines efficient implementations of this. Depending on usage, we can sometimes avoid the conversion toTreapSet
altogether; if not, we can do the conversion as a straightforward projection of the existingTreapMap
structure, avoiding most of the overhead of the generic conversion.We also add
single
andsingleOrNull
methods toTreapMap
, which are useful in their own right, and can be used to avoid the conversion toTreapSet
in some cases.