samuelcolvin / dirty-equals

Doing dirty (but extremely useful) things with equals.
https://dirty-equals.helpmanual.io
MIT License
798 stars 36 forks source link

Lacking a dirty way to count elements #77

Open flaeppe opened 1 year ago

flaeppe commented 1 year ago

I wonder if there's a dirty equals way to assert on element count?

I suppose it's possible to use IsListOrTuple with check_order=False. But then it's the precondition of having a list/tuple and explicitly multiply each element.

e.g.

from dirty_equals import IsListOrTuple
[3, 2, 1, 3, 2, 1] == IsListOrTuple(1, 1, 2, 2, 3, 3, check_order=False)

Another one is Contains, but as far as I can see it can't be upper bounded on "how many of something"?


Could this be some extension of Contains? e.g. under a parameter counts={<elem>: <count>}.

from dirty_equals import Contains
[3, 2, 1, 3, 2, 1] == Contains(counts={1: 2, 2: 2, 3: 2})

I suppose this might also imply some sort of partial={True|False} argument for Contains, for convenience?

Additionally, <element> of counts in a case like above could probably be useful as dirty equals types? e.g.

[("a", 1), ("b", 2), ("a", 1)] == Contains(counts={IsTuple(IsStr(), 1): 2, IsTuple(IsStr(), 2): 1})
alexmojaki commented 1 year ago

Why not use collections.Counter?

flaeppe commented 1 year ago

Yes, of course. That's not a dirty approach though and then what about the left hand side?

What if it wraps some sort of "EqualCounter"? So it counts items using equality instead of identity. Could that help align with other dirty types?

alexmojaki commented 1 year ago
assert Counter([3, 2, 1, 3, 2, 1]) == {1: 2, 2: 2, 3: 2}

Assuming the left side was something more complicated and you wanted to only do transformations on the right, maybe combined with other dirty operators:

assert [3, 2, 1, 3, 2, 1] == IsListOrTuple(*Counter({1: 2, 2: 2, 3: 2}).elements(), check_order=False)

That way you don't have to "explicitly multiply each element".

But then it's the precondition of having a list/tuple

IsIterable and IsSequence sound like sensible things to have.

flaeppe commented 1 year ago

What if I have something non-hashable on the left hand side that I want to count?

assert [{"a": 1}, {"b": 2}, {"a": 1}, {"c": 3}, {"a": 1}] == IsListOrTuple(???)

If I have my EqualCounter I could perhaps do

assert [{"a": 1}, {"b": 2}, {"a": 1}, {"c": 3}, {"a": 1}] == EqualCounter((IsDict(a=1), 2), (IsDict(b=2), 1), (IsDict(c=3), 1))

As the "EqualCounter" in this case would compare with each expected item with ==. Additionally one would probably have to figure out a decent way on how to handle items being equal to multiple other items, I suppose a naive approach is to only count each item once and towards the first encountered equal element.

samuelcolvin commented 1 year ago

It might be easiest to add your own custom type?

flaeppe commented 1 year ago

Yeah, sure, I'll try that out