from __future__ import print_function

from operator import mul as mul_op
from functools import reduce
from itertools import product
from random import choice


def random_chooser():
    while True:
        values = yield
        yield choice(values)


def random_grid(params, n=60, grid_cache=None):
    if not isinstance(n, int):
        raise TypeError('n must be an integer, not {}'.format(type(n)))
    if n < 0:
        raise ValueError('n should be >= 0')

    grid = grid_cache or set()
    max_n = reduce(mul_op, [len(vals) for vals in params.values()])
    n = min(n if n > 0 else max_n, max_n)
    param_chooser = random_chooser()
    try:
        while len(grid) < n:
            level_choice = []
            for v in params.values():
                next(param_chooser)
                level_choice.append(param_chooser.send(v))
            grid.add(tuple(level_choice))
    except KeyboardInterrupt:
        print('Interrupted by user. Providing current results.')
    return grid


def set_config(model, attributes, values):
    for name, value in zip(attributes, values):
        setattr(model, name, value)

from __future__ import print_function

from operator import mul as mul_op
from functools import reduce
from random import choice
import pandas as pd


def random_chooser():
    while True:
        values = yield
        yield choice(values)


def random_grid(params, n=60, grid_cache=None):
    if not isinstance(n, int):
        raise TypeError('n must be an integer, not {}'.format(type(n)))
    if n < 0:
        raise ValueError('n should be >= 0')

    grid = grid_cache or set()
    max_n = reduce(mul_op, [len(vals) for vals in params.values()])
    n = min(n if n > 0 else max_n, max_n)
    param_chooser = random_chooser()
    try:
        while len(grid) < n:
            level_choice = []
            for v in params.values():
                next(param_chooser)
                level_choice.append(param_chooser.send(v))
            grid.add(tuple(level_choice))
    except KeyboardInterrupt:
        print('Interrupted by user. Providing current results.')
    return grid


def set_config(model, attributes, values):
    for name, value in zip(attributes, values):
        setattr(model, name, value)


def evaluate_models(models, target_metric='precision', metric_type='all', **kwargs):
    if not isinstance(models, (list, tuple)):
        models = [models]

    model_scores = {}
    for model in models:
        scores = model.evaluate(metric_type, **kwargs)
        scores = [scores] if not isinstance(scores, list) else scores
        scores_df = pd.concat([pd.DataFrame([s]) for s in scores], axis=1)
        if isinstance(target_metric, str):
            model_scores[model.method] = scores_df[target_metric].squeeze()
        elif callable(target_metric):
            model_scores[model.method] = scores_df.apply(target_metric, axis=1).squeeze()
        else:
            raise NotImplementedError
    return model_scores

add target metric-based evaluation