Skip to content

Instantly share code, notes, and snippets.

@cbsmith
Created April 4, 2017 05:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cbsmith/e3528a6393eb072b1e89e002e5d1ad89 to your computer and use it in GitHub Desktop.
Save cbsmith/e3528a6393eb072b1e89e002e5d1ad89 to your computer and use it in GitHub Desktop.
Python to generate all possible permutations of config options
from itertools import chain, ifilter, product
# These first few functions are just to sanitize data structures to deal with huamns
# providing data in ways that are convenient for their minds.
def is_iterable(x):
return hasattr(x, '__iter__')
def as_config_values(values):
'Possible config values should always be an iterable (and not a string).'
return values if is_iterable(values) else (values,)
def as_optional_config_values(values):
'Optional config values just have a None prepended to them.'
return chain((None,), as_config_values(values))
def combine_configs(required={}, optional={}):
'''
Combine required an optional configs in to one big happy list of possible configs and their values.
Optional params are really just required params with an additional possible value of None. So
we can combine the two into one set of values.
'''
# belts and suspenders code, the required stuff should already be iterables, but
# just in case someone provides a required value that is always the same value, let's
# not risk breaking the things
for k, v in required.iteritems():
yield (k, as_config_values(v))
# optional params are just required params with an additional possible value of None
# so let's jam them all together in to one big, happy, dictionary
# this can be prettier with dictionary views, but not much prettier
for k, v in optional.iteritems():
assert k not in required, "{} found in both required and optional parameters".format(k)
yield (k, as_optional_config_values(v))
# This is the magic
def all_config_combos(**configs):
'''
Create an iterable of all possible permutations of configurations.
* configs - dictionary of key -> values pairs for config options and all possible values
Returns the product of all combinations of keywords and their possible values.
'''
# This is pretty dense here, so let's break it down:
# 1. The "inner" product((k,), values) is just a cheesy way of making an iterable of key-value pairs for all values of a given key.
# By giving it a single element tuple + another iterable, you get a pairs of that single element + each of the values in the iterable.
# 2. That's all rolled up in to a generator, one for each key.
# 3. We then make each key's iterables a seperate parameter [that's the *(...) bit]
# 4. Then we pass those iterables to product.
#
# So you effectively are calling product like:
# product(param_1_kv_pairs, param2_kv_pairs, param3_kv_pairs, ...)
#
# Thereby ensuring every possible value of param_1 is is matched up with every possible value of param_2 with every possible value of param_3...
return product(*(product((k,), values) for k, values in configs.iteritems()))
def all_configs(required={}, optional={}, filterNones=False):
'''
Return back all possible permutations of the provided options.
* required - dictionary of required parameter names and iterables of their possible values
* optional - diciontary of optional parameter names and iterables of their possible values
* filterNones - if True, remove any parameters set to "None" in the config combination
Returns an iterable of combinations of parameters
'''
required = dict(combine_configs(required, optional))
all_combos = all_config_combos(**required)
if filterNones:
return (filter(lambda x: x[1] is not None, config) for config in all_combos)
else:
return all_combos
def main():
bundled_metrics_opts = {
'required': {
'order_by': [
'viewers'
'avg_time_watched_per_viewers',
'video_starts'
]
},
'optional': {
'country': [
'single',
'list'
],
'device_platform':[
'single',
'list'
],
'stream_type': [
'single',
'list'
],
'order': 'ASC',
'limit': 5,
'offset': 5
}
}
configs = all_configs(filterNones=True, **bundled_metrics_opts)
for config in configs:
print(config)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment