Skip to content

Commit 362447a

Browse files
committed
Add new example: Disorder within order
Resolves satwikkansal#77
1 parent 4d5a2f0 commit 362447a

File tree

1 file changed

+109
-6
lines changed

1 file changed

+109
-6
lines changed

README.md

+109-6
Original file line numberDiff line numberDiff line change
@@ -261,26 +261,131 @@ some_dict[5] = "Python"
261261
"Ruby"
262262
>>> some_dict[5.0]
263263
"Python"
264-
>>> some_dict[5]
264+
>>> some_dict[5] # "Python" destroyed the existence of "JavaScript"?
265+
"Python"
266+
267+
>>> complex_five = 5 + 0j
268+
>>> type(complex_five)
269+
complex
270+
>>> some_dict[complex_five]
265271
"Python"
266272
```
267273
268-
"Python" destroyed the existence of "JavaScript"?
274+
So, why is Python all over the place?
275+
269276
270277
#### 💡 Explanation
271278
272279
* Python dictionaries check for equality and compare the hash value to determine if two keys are the same.
273280
* Immutable objects with same value always have the same hash in Python.
274281
```py
275-
>>> 5 == 5.0
282+
>>> 5 == 5.0 == 5 + 0j
276283
True
277-
>>> hash(5) == hash(5.0)
284+
>>> hash(5) == hash(5.0) == hash(5 + 0j)
278285
True
279286
```
280287
**Note:** Objects with different values may also have same hash (known as hash collision).
281288
* When the statement `some_dict[5] = "Python"` is executed, the existing value "JavaScript" is overwritten with "Python" because Python recognizes `5` and `5.0` as the same keys of the dictionary `some_dict`.
282289
* This StackOverflow [answer](https://stackoverflow.com/a/32211042/4354153) explains beautifully the rationale behind it.
283290
291+
---
292+
293+
### ▶ The disorder within order ^
294+
295+
```py
296+
from collections import OrderedDict
297+
298+
dictionary = dict()
299+
dictionary[1] = 'a'; dictionary[2] = 'b';
300+
301+
ordered_dict = OrderedDict()
302+
ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
303+
304+
another_ordered_dict = OrderedDict()
305+
another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
306+
307+
class DictWithHash(dict):
308+
"""
309+
A dict that also implements __hash__ magic.
310+
"""
311+
__hash__ = lambda self: 0
312+
313+
class OrderedDictWithHash(OrderedDict):
314+
"""
315+
A dict that also implements __hash__ magic.
316+
"""
317+
__hash__ = lambda self: 0
318+
```
319+
320+
**Output**
321+
```py
322+
>>> dictionary == ordered_dict # If a == b
323+
True
324+
>>> dictionary == another_ordered_dict # and b == c
325+
True
326+
>>> ordered_dict == another_ordered_dict # the why isn't c == a ??
327+
False
328+
329+
# We all know that a set consists of only unique elements,
330+
# let's try making a set of these dictionaries and see what happens...
331+
332+
>>> len({dictionary, ordered_dict, another_ordered_dict})
333+
Traceback (most recent call last):
334+
File "<stdin>", line 1, in <module>
335+
TypeError: unhashable type: 'dict'
336+
337+
# Makes sense since dict don't have __hash__ implemented, let's use
338+
# our wrapper classes.
339+
>>> dictionary = DictWithHash()
340+
>>> dictionary[1] = 'a'; dictionary[2] = 'b';
341+
>>> ordered_dict = OrderedDictWithHash()
342+
>>> ordered_dict[1] = 'a'; ordered_dict[2] = 'b';
343+
>>> another_ordered_dict = OrderedDictWithHash()
344+
>>> another_ordered_dict[2] = 'b'; another_ordered_dict[1] = 'a';
345+
>>> len({dictionary, ordered_dict, another_ordered_dict})
346+
1
347+
>>> len({ordered_dict, another_ordered_dict, dictionary}) # changing the order
348+
2
349+
```
350+
351+
What is going on here?
352+
353+
#### 💡 Explanation:
354+
355+
- The reason why intransitive equality didn't hold among `dictionary`, `ordered_dict` and `another_ordered_dict` is because of the way `__eq__` method is implemented in `OrderedDict` class. From the [docs](https://docs.python.org/3/library/collections.html#ordereddict-objects)
356+
> Equality tests between OrderedDict objects are order-sensitive and are implemented as `list(od1.items())==list(od2.items())`. Equality tests between `OrderedDict` objects and other Mapping objects are order-insensitive like regular dictionaries.
357+
- The reason for this equality is behavior is that it allows `OrderedDict` objects to be directly substituted anywhere a regular dictionary is used.
358+
- Okay, so why did changing the order affect the lenght of the generated `set` object? The answer is the lack of intransitive equality only. Since sets are "unordered" collections of unique elements, the order in which elements are inserted shouldn't matter. But in this case, it does matter. Let's break it down a bit,
359+
```py
360+
>>> some_set = set()
361+
>>> some_set.add(dictionary) # these are the mapping objects from the snippets above
362+
>>> ordered_dict in some_set
363+
True
364+
>>> some_set.add(ordered_dict)
365+
>>> len(some_set)
366+
1
367+
>>> another_ordered_dict in some_set
368+
True
369+
>>> some_set.add(another_ordered_dict)
370+
>>> len(some_set)
371+
1
372+
373+
>>> another_set = set()
374+
>>> another_set.add(ordered_dict)
375+
>>> another_ordered_dict in another_set
376+
False
377+
>>> another_set.add(another_ordered_dict)
378+
>>> len(another_set)
379+
2
380+
>>> dictionary in another_set
381+
True
382+
>>> another_set.add(another_ordered_dict)
383+
>>> len(another_set)
384+
2
385+
```
386+
So the inconsistency is due to `another_ordered_dict in another_set` being False because `ordered_dict` was already present in `another_set` and as observed before, `ordered_dict == another_ordered_dict` is `False`.
387+
388+
284389
---
285390
286391
### ▶ Keep trying? *
@@ -858,8 +963,6 @@ True
858963
'wt\\"f'
859964
860965
>>> print("\n")
861-
```
862-
863966
864967
>>> print(r"\\n")
865968
'\\\\n'

0 commit comments

Comments
 (0)