Skip to content
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

IndexMap / IndexSet comparison isn't order-aware. #153

Closed
emilio opened this issue Sep 8, 2020 · 11 comments
Closed

IndexMap / IndexSet comparison isn't order-aware. #153

emilio opened this issue Sep 8, 2020 · 11 comments

Comments

@emilio
Copy link

emilio commented Sep 8, 2020

This looks like a bug to me. I'd expect this test-case to pass:

    #[test]
    fn eq() {
        let m1: IndexMap<_, _> = vec![(1, 1), (2, 2)].into_iter().collect();
        let m2: IndexMap<_, _> = vec![(2, 2), (1, 1)].into_iter().collect();
        assert_ne!(m1, m2);
    }

The maps are most definitely not the same, and if you're using IndexMap is because you care about ordering.

This came up because I was doing some Firefox profiling, and transitioning youtube from fullscreen to not-fullscreen or vice versa spends a lot of time under find() comparing custom properties (doing the lookup in IndexMap::eq). Turns out we don't actually need to do any lookup, and not being ordering aware is a correctness issue.

emilio added a commit to emilio/indexmap that referenced this issue Sep 8, 2020
This is a breaking change. Not sure if you really want to take this but
I think it's the right thing to do.

Fixes indexmap-rs#153
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this issue Sep 8, 2020
When entering or leaving fullscreen in youtube, we spend most of the
restyle time diffing custom properties, under IndexMap::eq.

Turns out that IndexMap equality is not order-aware, and thus you
actually need to make a hashmap lookup for each entry in the map, which
is unnecessarily inefficient.

Instead, just compare the iterators.

See indexmap-rs/indexmap#153.

Differential Revision: https://phabricator.services.mozilla.com/D89434
@cuviper
Copy link
Member

cuviper commented Sep 8, 2020

It's been this way ever since PartialEq + Eq were implemented for the original OrderMap in #23, with the goal of HashMap consistency, and I followed suit in #46 when adding OrderSet. It also came up in #91 (comment) where I acknowledged that this may be surprising. Maybe we need to emphasize this point in documentation.

I've added this as a possible change for 2.0 in #135.

@cuviper cuviper mentioned this issue Sep 8, 2020
7 tasks
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified-and-comments-removed that referenced this issue Sep 16, 2020
When entering or leaving fullscreen in youtube, we spend most of the
restyle time diffing custom properties, under IndexMap::eq.

Turns out that IndexMap equality is not order-aware, and thus you
actually need to make a hashmap lookup for each entry in the map, which
is unnecessarily inefficient.

Instead, just compare the iterators.

See indexmap-rs/indexmap#153.

Differential Revision: https://phabricator.services.mozilla.com/D89434

UltraBlame original commit: d46a20917d66d3c7fc9ada2204eae0af92365e08
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified that referenced this issue Sep 16, 2020
When entering or leaving fullscreen in youtube, we spend most of the
restyle time diffing custom properties, under IndexMap::eq.

Turns out that IndexMap equality is not order-aware, and thus you
actually need to make a hashmap lookup for each entry in the map, which
is unnecessarily inefficient.

Instead, just compare the iterators.

See indexmap-rs/indexmap#153.

Differential Revision: https://phabricator.services.mozilla.com/D89434

UltraBlame original commit: d46a20917d66d3c7fc9ada2204eae0af92365e08
gecko-dev-updater pushed a commit to marco-c/gecko-dev-comments-removed that referenced this issue Sep 16, 2020
When entering or leaving fullscreen in youtube, we spend most of the
restyle time diffing custom properties, under IndexMap::eq.

Turns out that IndexMap equality is not order-aware, and thus you
actually need to make a hashmap lookup for each entry in the map, which
is unnecessarily inefficient.

Instead, just compare the iterators.

See indexmap-rs/indexmap#153.

Differential Revision: https://phabricator.services.mozilla.com/D89434

UltraBlame original commit: d46a20917d66d3c7fc9ada2204eae0af92365e08
ambroff pushed a commit to ambroff/gecko that referenced this issue Nov 4, 2020
When entering or leaving fullscreen in youtube, we spend most of the
restyle time diffing custom properties, under IndexMap::eq.

Turns out that IndexMap equality is not order-aware, and thus you
actually need to make a hashmap lookup for each entry in the map, which
is unnecessarily inefficient.

Instead, just compare the iterators.

See indexmap-rs/indexmap#153.

Differential Revision: https://phabricator.services.mozilla.com/D89434
@bluss
Copy link
Member

bluss commented Nov 30, 2020

IndexMap is definitely a bit inbetween, it partly wants to be a drop-in for HashMap (that's the starting design, drop-in with extra features). That's the reason for preserving the same equality semantics.

I think that a fully order preserving hashmap would be a different crate than this one. (One that preserves order on remove, efficiently).

emilio added a commit to servo/servo that referenced this issue Feb 26, 2021
When entering or leaving fullscreen in youtube, we spend most of the
restyle time diffing custom properties, under IndexMap::eq.

Turns out that IndexMap equality is not order-aware, and thus you
actually need to make a hashmap lookup for each entry in the map, which
is unnecessarily inefficient.

Instead, just compare the iterators.

See indexmap-rs/indexmap#153.

Differential Revision: https://phabricator.services.mozilla.com/D89434
@mwillsey
Copy link
Contributor

I would love to see an opt-in order-preserving comparison. Here's an idea that wouldn't require a 2.0:

Add an additional type parameter <..., E = Unordered> to IndexMap and IndexSet. E is one of the two unsized marker types Ordered and Unordered, which determines the equality semantics. This could also allow for Ord + Hash on IndexMap<..., Ordered. I think the implementations for Ordered could just defer to the underlying entries: Vec.

Happy to put together a PR if people are excited.

@cuviper
Copy link
Member

cuviper commented Sep 21, 2021

I fear a proliferation of type parameters -- I proposed an index type in #147, and I think it's likely we'll want an Allocator type once that stabilized in the standard library. But IndexMap<K, V, S, I, A> already scares me.

An ordering parameter seems less necessary to me, because that's a relatively superficial change which you could implement with a wrapper, just using Iterator::cmp. We could even add a method for that, but you'll want a wrapper to get that behavior in generic Ord contexts, kind of like std::cmp::Reverse.

FWIW, I also did add order-aware comparisons for the slice proposal in #177.

@mwillsey
Copy link
Contributor

Yeah, that makes sense. The proposed Slice type is sufficient for my use case anyway! It makes order-sensitive comparison on a newtyped IndexMap easy and cheap.

phorward added a commit to tokay-lang/tokay that referenced this issue Apr 26, 2022
@OvermindDL1
Copy link

Ran into this bug as well, and yes it is a bug, because when get_index'ing into the maps, even though they tested as equal, they returned different values, thus they are obviously not equal...

@cuviper
Copy link
Member

cuviper commented Sep 9, 2022

@OvermindDL1 that just means that it has some properties that are not part of the equality check. You could say the same about iteration order on a regular HashMap (different even when the maps compare equal), or capacity() on any collection. We've chosen to follow HashMap semantics here, where equality just means that they contain the same things, regardless of order.

@OvermindDL1
Copy link

OvermindDL1 commented Sep 27, 2022

std's HashMap's don't have an indexed access, and iterators by it are defined as undetermined order, thus their only public direct access API is by key, so matching on that is fine. IndexMap/Set on the other hand has both key and index direct access, and just like how on a HashMap you'd expect every key to return the same value in eq hashmaps, one would expect the value returned from the same get_index in IndexMap to return the same value as well for the same input.

In short, wouldn't expect capacities to be the same or so forth, but would expect for each key and index to have the same value output in both indexmaps that are equal as the index is a key.

@vasilakisfil
Copy link

It's a bit of gray zone whether it should take into account the key order in equality. I think it's fine as it is, since it's just a HashMap in the end. However, I think it would be nice if it provided a method to check for equality that takes into account the order of the keys.

@cuviper
Copy link
Member

cuviper commented Aug 14, 2023

The Slice API shipped in 2.0.0 with order-aware comparisons -- hope that helps!

use indexmap::IndexMap;

fn main() {
    let m1: IndexMap<_, _> = vec![(1, 1), (2, 2)].into_iter().collect();
    let m2: IndexMap<_, _> = vec![(2, 2), (1, 1)].into_iter().collect();
    assert_eq!(m1, m2);
    assert_ne!(m1.as_slice(), m2.as_slice());
}

playground

@cuviper
Copy link
Member

cuviper commented Jun 25, 2024

FYI: I've reintroduced ordermap as a newtype wrapper that is more order-aware:
https://crates.io/crates/ordermap/0.5.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants