-
-
Notifications
You must be signed in to change notification settings - Fork 244
Option for custom compare function #241
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
Changes from all commits
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 |
---|---|---|
|
@@ -21,7 +21,7 @@ | |
number_to_string, datetime_normalize, KEY_TO_VAL_STR, booleans, | ||
np_ndarray, get_numpy_ndarray_rows, OrderedSetPlus, RepeatedTimer, | ||
TEXT_VIEW, TREE_VIEW, DELTA_VIEW, | ||
np, get_truncate_datetime, dict_) | ||
np, get_truncate_datetime, dict_, CannotCompare) | ||
from deepdiff.serialization import SerializationMixin | ||
from deepdiff.distance import DistanceMixin | ||
from deepdiff.model import ( | ||
|
@@ -139,6 +139,7 @@ def __init__(self, | |
truncate_datetime=None, | ||
verbose_level=1, | ||
view=TEXT_VIEW, | ||
iterable_compare_func=None, | ||
_original_type=None, | ||
_parameters=None, | ||
_shared_parameters=None, | ||
|
@@ -154,7 +155,8 @@ def __init__(self, | |
"view, hasher, hashes, max_passes, max_diffs, " | ||
"cutoff_distance_for_pairs, cutoff_intersection_for_pairs, log_frequency_in_sec, cache_size, " | ||
"cache_tuning_sample_size, get_deep_distance, group_by, cache_purge_level, " | ||
"math_epsilon, _original_type, _parameters and _shared_parameters.") % ', '.join(kwargs.keys())) | ||
"math_epsilon, iterable_compare_func, _original_type, " | ||
"_parameters and _shared_parameters.") % ', '.join(kwargs.keys())) | ||
|
||
if _parameters: | ||
self.__dict__.update(_parameters) | ||
|
@@ -182,6 +184,7 @@ def __init__(self, | |
self.ignore_string_case = ignore_string_case | ||
self.exclude_obj_callback = exclude_obj_callback | ||
self.number_to_string = number_to_string_func or number_to_string | ||
self.iterable_compare_func = iterable_compare_func | ||
self.ignore_private_variables = ignore_private_variables | ||
self.ignore_nan_inequality = ignore_nan_inequality | ||
self.hasher = hasher | ||
|
@@ -558,6 +561,72 @@ def _diff_iterable(self, level, parents_ids=frozenset(), _original_type=None): | |
else: | ||
self._diff_iterable_in_order(level, parents_ids, _original_type=_original_type) | ||
|
||
def _compare_in_order(self, level): | ||
""" | ||
Default compare if `iterable_compare_func` is not provided. | ||
This will compare in sequence order. | ||
""" | ||
|
||
return [((i, i), (x, y)) for i, (x, y) in enumerate( | ||
zip_longest( | ||
level.t1, level.t2, fillvalue=ListItemRemovedOrAdded))] | ||
|
||
def _get_matching_pairs(self, level): | ||
""" | ||
Given a level get matching pairs. This returns list of two tuples in the form: | ||
[ | ||
(t1 index, t2 index), (t1 item, t2 item) | ||
] | ||
|
||
This will compare using the passed in `iterable_compare_func` if available. | ||
Default it to compare in order | ||
""" | ||
|
||
if(self.iterable_compare_func is None): | ||
# Match in order if there is no compare function provided | ||
return self._compare_in_order(level) | ||
try: | ||
matches = [] | ||
y_matched = set() | ||
y_index_matched = set() | ||
for i, x in enumerate(level.t1): | ||
x_found = False | ||
for j, y in enumerate(level.t2): | ||
|
||
if(j in y_index_matched): | ||
# This ensures a one-to-one relationship of matches from t1 to t2. | ||
# If y this index in t2 has already been matched to another x | ||
# it cannot have another match, so just continue. | ||
continue | ||
|
||
if(self.iterable_compare_func(x, y, level)): | ||
deep_hash = DeepHash(y, | ||
hashes=self.hashes, | ||
apply_hash=True, | ||
**self.deephash_parameters, | ||
) | ||
y_index_matched.add(j) | ||
y_matched.add(deep_hash[y]) | ||
matches.append(((i, j), (x, y))) | ||
x_found = True | ||
break | ||
seperman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if(not x_found): | ||
matches.append(((i, -1), (x, ListItemRemovedOrAdded))) | ||
for j, y in enumerate(level.t2): | ||
|
||
deep_hash = DeepHash(y, | ||
hashes=self.hashes, | ||
apply_hash=True, | ||
**self.deephash_parameters, | ||
) | ||
if(deep_hash[y] not in y_matched): | ||
matches.append(((-1, j), (ListItemRemovedOrAdded, y))) | ||
return matches | ||
except CannotCompare: | ||
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. Should we log to the user that the 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. I think it will be common that |
||
return self._compare_in_order(level) | ||
|
||
|
||
def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type=None): | ||
# We're handling both subscriptable and non-subscriptable iterables. Which one is it? | ||
subscriptable = self._iterables_subscriptable(level.t1, level.t2) | ||
|
@@ -566,10 +635,7 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type | |
else: | ||
child_relationship_class = NonSubscriptableIterableRelationship | ||
|
||
for i, (x, y) in enumerate( | ||
zip_longest( | ||
dtorres-sf marked this conversation as resolved.
Show resolved
Hide resolved
|
||
level.t1, level.t2, fillvalue=ListItemRemovedOrAdded)): | ||
|
||
for (i, j), (x, y) in self._get_matching_pairs(level): | ||
if self._count_diff() is StopIteration: | ||
return # pragma: no cover. This is already covered for addition. | ||
|
||
|
@@ -586,10 +652,22 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type | |
notpresent, | ||
y, | ||
child_relationship_class=child_relationship_class, | ||
child_relationship_param=i) | ||
child_relationship_param=j) | ||
self._report_result('iterable_item_added', change_level) | ||
|
||
else: # check if item value has changed | ||
|
||
if (i != j): | ||
# Item moved | ||
change_level = level.branch_deeper( | ||
x, | ||
y, | ||
child_relationship_class=child_relationship_class, | ||
child_relationship_param=i, | ||
child_relationship_param2=j | ||
) | ||
self._report_result('iterable_item_moved', change_level) | ||
|
||
item_id = id(x) | ||
if parents_ids and item_id in parents_ids: | ||
continue | ||
|
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.
👍