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

Carry all incompatibilities during backtracking #60

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 24 additions & 12 deletions src/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,16 +99,16 @@ def merged_with(self, provider, requirement, parent):
raise RequirementsConflicted(criterion)
return criterion

def excluded_of(self, candidate):
"""Build a new instance from this, but excluding specified candidate.
def excluded_of(self, incompatibilities):
"""Build a new instance from this, but excluding specified candidates.

Returns the new instance, or None if we still have no valid candidates.
"""
cands = self.candidates.excluding(candidate)
cands = self.candidates.excluding(incompatibilities)
if not cands:
return None
incompats = list(self.incompatibilities)
incompats.append(candidate)
incompats.extend(incompatibilities)
return type(self)(cands, list(self.information), incompats)


Expand Down Expand Up @@ -238,10 +238,25 @@ def _attempt_to_pin_criterion(self, name, criterion):
# end, signal for backtracking.
return causes

def _mark_backtrack_on_state(self, incompatibilities):
state = self.state
for name, candidates in incompatibilities.items():
try:
criterion = state.criteria[name]
except KeyError:
continue
criterion = criterion.excluded_of(candidates)
if criterion is None:
return False
state.criteria[name] = criterion
return True

def _backtrack(self):
# Drop the current state, it's known not to work.
del self._states[-1]

incompatibilities = collections.defaultdict(list)

# We need at least 2 states here:
# (a) One to backtrack to.
# (b) One to restore state (a) to its state prior to candidate-pinning,
Expand All @@ -255,19 +270,16 @@ def _backtrack(self):
except KeyError:
continue
self._r.backtracking(candidate)
incompatibilities[name].append(candidate)

# Mark candidates identified during backtracking as incompatible.
if not self._mark_backtrack_on_state(incompatibilities):
continue

# Create a new state to work on, with the newly known not-working
# candidate excluded.
self._push_new_state()

# Mark the retracted candidate as incompatible.
criterion = self.state.criteria[name].excluded_of(candidate)
if criterion is None:
# This state still does not work. Try the still previous state.
del self._states[-1]
continue
self.state.criteria[name] = criterion

return True

return False
Expand Down
10 changes: 5 additions & 5 deletions src/resolvelib/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,11 +95,11 @@ def for_preference(self):
"""Provide an candidate iterable for `get_preference()`"""
return self._factory()

def excluding(self, candidate):
"""Create a new `Candidates` instance excluding `candidate`."""
def excluding(self, candidates):
"""Create a new `Candidates` instance excluding `candidates`."""

def factory():
return (c for c in self._factory() if c != candidate)
return (c for c in self._factory() if c not in candidates)

return type(self)(factory)

Expand Down Expand Up @@ -129,9 +129,9 @@ def for_preference(self):
"""Provide an candidate iterable for `get_preference()`"""
return self._sequence

def excluding(self, candidate):
def excluding(self, candidates):
"""Create a new instance excluding `candidate`."""
return type(self)([c for c in self._sequence if c != candidate])
return type(self)([c for c in self._sequence if c not in candidates])


def build_iter_view(matches):
Expand Down
7 changes: 4 additions & 3 deletions tests/test_structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def test_iter_view_for_preference_based_on_sequence(source):
def test_itera_view_excluding(source):
view = build_iter_view(source)

assert list(view.excluding(0)) == [1]
assert list(view.excluding(1)) == [0]
assert list(view.excluding(2)) == [0, 1]
assert list(view.excluding([0])) == [1]
assert list(view.excluding([2])) == [0, 1]
assert list(view.excluding([0, 1])) == []
assert list(view.excluding([1, 2])) == [0]