Skip to content

Commit f776698

Browse files
authored
Merge pull request #699 from Axelrod-Python/issue-696
Filtering Strategies
2 parents 39d33ef + 7c2c223 commit f776698

File tree

5 files changed

+575
-14
lines changed

5 files changed

+575
-14
lines changed

axelrod/strategies/__init__.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from ..player import is_basic, obey_axelrod
22
from ._strategies import *
3+
from ._filters import passes_filterset
34

45
# `from ._strategies import *` import the collection `strategies`
56
# Now import the Meta strategies. This cannot be done in _strategies
@@ -29,3 +30,44 @@
2930
cheating_strategies = [s for s in all_strategies if not obey_axelrod(s())]
3031

3132
ordinary_strategies = strategies # This is a legacy and will be removed
33+
34+
35+
def filtered_strategies(filterset, strategies=all_strategies):
36+
"""
37+
Applies the filters defined in the given filterset dict and returns those
38+
strategy classes which pass all of those filters from the given list of
39+
strategies.
40+
41+
e.g.
42+
43+
For the filterset dict:
44+
{
45+
'stochastic': True,
46+
'min_memory_depth': 2
47+
}
48+
49+
the function will return a list of all deterministic strategies with a
50+
memory_depth of 2 or more.
51+
52+
Parameters
53+
----------
54+
filterset : dict
55+
mapping filter name to criterion.
56+
e.g.
57+
{
58+
'stochastic': True,
59+
'min_memory_depth': 2
60+
}
61+
strategies: list
62+
of subclasses of axelrod.Player
63+
64+
Returns
65+
-------
66+
list
67+
68+
of subclasses of axelrod.Player
69+
70+
"""
71+
return [
72+
s for s in strategies
73+
if passes_filterset(s, filterset)]

axelrod/strategies/_filters.py

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
from collections import namedtuple
2+
import operator
3+
4+
5+
def passes_operator_filter(strategy, classifier_key, value, operator):
6+
"""
7+
Tests whether a given strategy passes a filter for a
8+
given key in its classifier dict using a given (in)equality operator.
9+
10+
e.g.
11+
12+
For the following strategy:
13+
14+
class ExampleStrategy(Player):
15+
classifier = {
16+
'stochastic': True,
17+
'inspects_source': False,
18+
'memory_depth': 10,
19+
'makes_use_of': ['game', 'length']
20+
}
21+
22+
passes_operator_filter(ExampleStrategy, 'memory_depth', 10, operator.eq)
23+
24+
would test whether the 'memory_depth' entry equals 10 and return True
25+
26+
Parameters
27+
----------
28+
strategy : a descendant class of axelrod.Player
29+
classifier_key: string
30+
Defining which entry from the strategy's classifier dict is to be
31+
tested (e.g. 'memory_depth').
32+
value: int
33+
The value against which the strategy's classifier dict entry is to
34+
be tested.
35+
operator: operator.le, operator.ge or operator.eq
36+
Indicating whether a 'less than or equal to' or 'greater than or
37+
equal to' test should be applied.
38+
39+
Returns
40+
-------
41+
boolean
42+
43+
True if the value from the strategy's classifier dictionary matches
44+
the value and operator passed to the function.
45+
"""
46+
classifier_value = strategy.classifier[classifier_key]
47+
if (isinstance(classifier_value, str) and
48+
classifier_value.lower() == 'infinity'):
49+
classifier_value = float('inf')
50+
51+
return operator(classifier_value, value)
52+
53+
54+
def passes_in_list_filter(strategy, classifier_key, value):
55+
"""
56+
Tests whether a given list of values exist in the list returned from the
57+
given strategy's classifier dict for the given classifier_key.
58+
59+
e.g.
60+
61+
For the following strategy:
62+
63+
class ExampleStrategy(Player):
64+
classifier = {
65+
'stochastic': True,
66+
'inspects_source': False,
67+
'memory_depth': 10,
68+
'makes_use_of': ['game', 'length']
69+
}
70+
71+
passes_in_list_filter(ExampleStrategy, 'makes_use_of', 'game', operator.eq)
72+
73+
would test whether 'game' exists in the strategy's' 'makes_use_of' entry
74+
and return True.
75+
76+
Parameters
77+
----------
78+
strategy : a descendant class of axelrod.Player
79+
classifier_key: string
80+
Defining which entry from the strategy's classifier dict is to be
81+
tested (e.g. 'makes_use_of').
82+
value: list
83+
The values against which the strategy's classifier dict entry is to
84+
be tested.
85+
86+
Returns
87+
-------
88+
boolean
89+
"""
90+
result = True
91+
for entry in value:
92+
if entry not in strategy.classifier[classifier_key]:
93+
result = False
94+
return result
95+
96+
97+
def passes_filterset(strategy, filterset):
98+
"""
99+
Determines whether a given strategy meets the criteria defined in a
100+
dictionary of filters.
101+
102+
e.g.
103+
104+
For the following strategy:
105+
106+
class ExampleStrategy(Player):
107+
classifier = {
108+
'stochastic': True,
109+
'inspects_source': False,
110+
'memory_depth': 10,
111+
'makes_use_of': ['game', 'length']
112+
}
113+
114+
and this filterset dict:
115+
116+
example_filterset = {
117+
'stochastic': True,
118+
'memory_depth': 10
119+
}
120+
121+
passes_filterset(ExampleStrategy, example_filterset)
122+
123+
would test whether both the strategy's 'stochastic' entry is True AND
124+
that its 'memory_depth' equals 10 and return True.
125+
126+
Parameters
127+
----------
128+
strategy : a descendant class of axelrod.Player
129+
filterset : dict
130+
mapping filter name to criterion.
131+
e.g.
132+
{
133+
'stochastic': True,
134+
'min_memory_depth': 2
135+
}
136+
137+
Returns
138+
-------
139+
boolean
140+
141+
True if the given strategy meets all the supplied criteria in the
142+
filterset, otherwise false.
143+
144+
"""
145+
FilterFunction = namedtuple('FilterFunction', 'function kwargs')
146+
147+
# A dictionary mapping filter name (from the supplied filterset) to
148+
# the relevant function and arguments for that filter.
149+
filter_functions = {
150+
'stochastic': FilterFunction(
151+
function=passes_operator_filter,
152+
kwargs={
153+
'classifier_key': 'stochastic',
154+
'operator': operator.eq
155+
}),
156+
'long_run_time': FilterFunction(
157+
function=passes_operator_filter,
158+
kwargs={
159+
'classifier_key': 'long_run_time',
160+
'operator': operator.eq
161+
}),
162+
'manipulates_state': FilterFunction(
163+
function=passes_operator_filter,
164+
kwargs={
165+
'classifier_key': 'manipulates_state',
166+
'operator': operator.eq
167+
}),
168+
'manipulates_source': FilterFunction(
169+
function=passes_operator_filter,
170+
kwargs={
171+
'classifier_key': 'manipulates_source',
172+
'operator': operator.eq
173+
}),
174+
'inspects_source': FilterFunction(
175+
function=passes_operator_filter,
176+
kwargs={
177+
'classifier_key': 'inspects_source',
178+
'operator': operator.eq
179+
}),
180+
'memory_depth': FilterFunction(
181+
function=passes_operator_filter,
182+
kwargs={
183+
'classifier_key': 'memory_depth',
184+
'operator': operator.eq
185+
}),
186+
'min_memory_depth': FilterFunction(
187+
function=passes_operator_filter,
188+
kwargs={
189+
'classifier_key': 'memory_depth',
190+
'operator': operator.ge
191+
}),
192+
'max_memory_depth': FilterFunction(
193+
function=passes_operator_filter,
194+
kwargs={
195+
'classifier_key': 'memory_depth',
196+
'operator': operator.le
197+
}),
198+
'makes_use_of': FilterFunction(
199+
function=passes_in_list_filter,
200+
kwargs={'classifier_key': 'makes_use_of'})
201+
}
202+
203+
# A list of boolean values to record whether the strategy passed or failed
204+
# each of the filters in the supplied filterset.
205+
passes_filters = []
206+
207+
# Loop through each of the entries in the filter_functions dict and, if
208+
# that filter is defined in the supplied filterset, call the relevant
209+
# function and record its result in the passes_filters list.
210+
for _filter, filter_function in filter_functions.items():
211+
212+
if filterset.get(_filter, None) is not None:
213+
kwargs = filter_function.kwargs
214+
kwargs['strategy'] = strategy
215+
kwargs['value'] = filterset[_filter]
216+
passes_filters.append(filter_function.function(**kwargs))
217+
218+
# Return True if the strategy passed all the supplied filters
219+
return all(passes_filters)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import unittest
2+
from hypothesis import given, example
3+
from hypothesis.strategies import integers
4+
from axelrod import all_strategies, filtered_strategies
5+
6+
7+
class TestFiltersAgainstComprehensions(unittest.TestCase):
8+
"""
9+
Test that the results of filtering strategies via a filterset dict
10+
match the results from using a list comprehension.
11+
"""
12+
13+
def test_boolean_filtering(self):
14+
15+
classifiers = [
16+
'stochastic',
17+
'long_run_time',
18+
'manipulates_state',
19+
'manipulates_source',
20+
'inspects_source']
21+
22+
for classifier in classifiers:
23+
comprehension = set([
24+
s for s in all_strategies if
25+
s.classifier[classifier]])
26+
filterset = {
27+
classifier: True
28+
}
29+
filtered = set(filtered_strategies(filterset))
30+
self.assertEqual(comprehension, filtered)
31+
32+
@given(
33+
min_memory_depth=integers(min_value=1, max_value=10),
34+
max_memory_depth=integers(min_value=1, max_value=10),
35+
memory_depth=integers(min_value=1, max_value=10))
36+
@example(
37+
min_memory_depth=float('inf'),
38+
max_memory_depth=float('inf'),
39+
memory_depth=float('inf'))
40+
def test_memory_depth_filtering(self, min_memory_depth, max_memory_depth,
41+
memory_depth):
42+
43+
min_comprehension = set([
44+
s for s in all_strategies if
45+
s.classifier['memory_depth'] >= min_memory_depth])
46+
min_filterset = {
47+
'min_memory_depth': min_memory_depth
48+
}
49+
min_filtered = set(filtered_strategies(min_filterset))
50+
self.assertEqual(min_comprehension, min_filtered)
51+
52+
max_comprehension = set([
53+
s for s in all_strategies if
54+
s.classifier['memory_depth'] <= max_memory_depth])
55+
max_filterset = {
56+
'max_memory_depth': max_memory_depth
57+
}
58+
max_filtered = set(filtered_strategies(max_filterset))
59+
self.assertEqual(max_comprehension, max_filtered)
60+
61+
comprehension = set([
62+
s for s in all_strategies if
63+
s.classifier['memory_depth'] == memory_depth])
64+
filterset = {
65+
'memory_depth': memory_depth
66+
}
67+
filtered = set(filtered_strategies(filterset))
68+
self.assertEqual(comprehension, filtered)
69+
70+
def test_makes_use_of_filtering(self):
71+
classifiers = [
72+
['game'],
73+
['length'],
74+
['game', 'length']
75+
]
76+
77+
for classifier in classifiers:
78+
comprehension = set([
79+
s for s in all_strategies if
80+
set(classifier).issubset(set(s.classifier['makes_use_of']))
81+
])
82+
filterset = {
83+
'makes_use_of': classifier
84+
}
85+
filtered = set(filtered_strategies(filterset))
86+
self.assertEqual(comprehension, filtered)

0 commit comments

Comments
 (0)