# Path: simpleai/machine_learning/models.py
# class Classifier(object):
#     """
#     Base of all classifiers.
#     This specifies the classifier API.
# 
#     Each classifier holds at least a dataset and a ClassificationProblem.
#     """
# 
#     def __init__(self, dataset, problem):
#         self.dataset = dataset
#         self.problem = problem
#         self.learn()
# 
#     def learn(self):
#         """
#         Does the training. Returns nothing.
#         """
#         raise NotImplementedError()
# 
#     @property
#     def attributes(self):
#         """
#         The attributes of the problem.
#         A list of callable objects.
#         """
#         return self.problem.attributes
# 
#     @property
#     def target(self):
#         """
#         The problem's target.
#         A callable that takes an observation and returns the correct
#         classification for it.
#         """
#         return self.problem.target
# 
#     def classify(self, example):
#         """
#         Returns the classification for example.
#         """
#         raise NotImplementedError()
# 
#     def save(self, filepath):
#         """
#         Pickles the tree and saves it into `filepath`
#         """
# 
#         if not filepath or not isinstance(filepath, str):
#             raise ValueError("Invalid filepath")
# 
#         # Removes dataset so is not saved in the pickle
#         self.dataset = None
#         with open(filepath, "wb") as filehandler:
#             pickle.dump(self, filehandler)
# 
#     def distance(self, a, b):
#         """
#         Custom distance between `a` and `b`.
#         """
#         raise NotImplementedError()
# 
#     @classmethod
#     def load(cls, filepath):
#         """
#         Loads a pickled version of the classifier saved in `filepath`
#         """
#         with open(filepath, "rb") as filehandler:
#             classifier = pickle.load(filehandler)
# 
#         if not isinstance(classifier, Classifier):
#             raise ValueError("Pickled object is not a Classifier")
# 
#         return classifier
# 
# Path: simpleai/machine_learning/metrics.py
# class Counter(defaultdict):
#     """
#     Counter of examples. Counts the total of examples added
#     and also the times that the target of that example appears.
# 
#     To add an example use the `add` method and to check the
#     values use it like a dictionary.
#     """
# 
#     def __init__(self, target):
#         super(Counter, self).__init__(int)
#         self.target = target
#         self.total = 0
# 
#     def add(self, example):
#         value = self.target(example)
#         self[value] += 1
#         self.total += 1
# 
# class OnlineInformationGain(object):
#     def __init__(self, attribute, target):
#         self.attribute = attribute
#         self.H = OnlineEntropy(target)
#         self.G = defaultdict(lambda: OnlineEntropy(target))
# 
#     def add(self, example):
#         self.H.add(example)
#         value = self.attribute(example)
#         self.G[value].add(example)
# 
#     def get_target_class_counts(self):
#         return self.H
# 
#     def get_branches(self):
#         return list(self.G.items())
# 
#     def get_gain(self):
#         H1 = self.H.get_entropy()
#         H2 = 0.0
#         for G in list(self.G.values()):
#             w = G.total / float(self.H.total)
#             H2 += w * G.get_entropy()
#         return H1 - H2
# 
# class OnlineLogProbability(object):
#     def __init__(self):
#         self.d = defaultdict(int)
#         self._logtotal = None
# 
#     def add(self, x):
#         if self._logtotal is not None:
#             raise ValueError("OnlineLogProbability is frozen since first read")
#         self.d[x] += 1
# 
#     def __getitem__(self, x):
#         if x not in self:
#             raise KeyError(x)
#         if self._logtotal is None:
#             self._logtotal = numpy.log(sum(self.d.values()))
#         return numpy.log(self.d[x]) - self._logtotal
# 
#     def __contains__(self, x):
#         return x in self.d
# 
#     def __iter__(self):
#         return iter(self.d)
# 
#     def __len__(self):
#         return len(self.d)
# 
#     def iteritems(self):
#         for x in self.d:
#             yield x, self[x]


import numpy
    import cPickle as pickle
    import pickle
from collections import defaultdict
from simpleai.machine_learning.models import Classifier
from simpleai.machine_learning.metrics import Counter, OnlineInformationGain, \
                                              OnlineLogProbability
            node.attribute = A.attribute
            for value, counts in A.get_branches():
                branch = node.add_branch(value)
                branch.set_results_from_counts(counts)
                bdataset = [e for e in examples if node.attribute(e) == value]
                q.append((branch, bdataset))

    def _max_gain_split(self, examples):
        """
        Returns an OnlineInformationGain of the attribute with
        max gain based on `examples`.
        """
        gains = self._new_set_of_gain_counters()
        for example in examples:
            for gain in gains:
                gain.add(example)
        winner = max(gains, key=lambda gain: gain.get_gain())
        if not winner.get_target_class_counts():
            raise ValueError("Dataset is empty")
        return winner

    def _new_set_of_gain_counters(self):
        """
        Creates a new set of OnlineInformationGain objects
        for each attribute.
        """
        return [OnlineInformationGain(attribute, self.target)
                for attribute in self.attributes]

    def _single_node_tree(self):

# Path: simpleai/machine_learning/models.py
# class Classifier(object):
#     """
#     Base of all classifiers.
#     This specifies the classifier API.
# 
#     Each classifier holds at least a dataset and a ClassificationProblem.
#     """
# 
#     def __init__(self, dataset, problem):
#         self.dataset = dataset
#         self.problem = problem
#         self.learn()
# 
#     def learn(self):
#         """
#         Does the training. Returns nothing.
#         """
#         raise NotImplementedError()
# 
#     @property
#     def attributes(self):
#         """
#         The attributes of the problem.
#         A list of callable objects.
#         """
#         return self.problem.attributes
# 
#     @property
#     def target(self):
#         """
#         The problem's target.
#         A callable that takes an observation and returns the correct
#         classification for it.
#         """
#         return self.problem.target
# 
#     def classify(self, example):
#         """
#         Returns the classification for example.
#         """
#         raise NotImplementedError()
# 
#     def save(self, filepath):
#         """
#         Pickles the tree and saves it into `filepath`
#         """
# 
#         if not filepath or not isinstance(filepath, str):
#             raise ValueError("Invalid filepath")
# 
#         # Removes dataset so is not saved in the pickle
#         self.dataset = None
#         with open(filepath, "wb") as filehandler:
#             pickle.dump(self, filehandler)
# 
#     def distance(self, a, b):
#         """
#         Custom distance between `a` and `b`.
#         """
#         raise NotImplementedError()
# 
#     @classmethod
#     def load(cls, filepath):
#         """
#         Loads a pickled version of the classifier saved in `filepath`
#         """
#         with open(filepath, "rb") as filehandler:
#             classifier = pickle.load(filehandler)
# 
#         if not isinstance(classifier, Classifier):
#             raise ValueError("Pickled object is not a Classifier")
# 
#         return classifier
# 
# Path: simpleai/machine_learning/metrics.py
# class Counter(defaultdict):
#     """
#     Counter of examples. Counts the total of examples added
#     and also the times that the target of that example appears.
# 
#     To add an example use the `add` method and to check the
#     values use it like a dictionary.
#     """
# 
#     def __init__(self, target):
#         super(Counter, self).__init__(int)
#         self.target = target
#         self.total = 0
# 
#     def add(self, example):
#         value = self.target(example)
#         self[value] += 1
#         self.total += 1
# 
# class OnlineInformationGain(object):
#     def __init__(self, attribute, target):
#         self.attribute = attribute
#         self.H = OnlineEntropy(target)
#         self.G = defaultdict(lambda: OnlineEntropy(target))
# 
#     def add(self, example):
#         self.H.add(example)
#         value = self.attribute(example)
#         self.G[value].add(example)
# 
#     def get_target_class_counts(self):
#         return self.H
# 
#     def get_branches(self):
#         return list(self.G.items())
# 
#     def get_gain(self):
#         H1 = self.H.get_entropy()
#         H2 = 0.0
#         for G in list(self.G.values()):
#             w = G.total / float(self.H.total)
#             H2 += w * G.get_entropy()
#         return H1 - H2
# 
# class OnlineLogProbability(object):
#     def __init__(self):
#         self.d = defaultdict(int)
#         self._logtotal = None
# 
#     def add(self, x):
#         if self._logtotal is not None:
#             raise ValueError("OnlineLogProbability is frozen since first read")
#         self.d[x] += 1
# 
#     def __getitem__(self, x):
#         if x not in self:
#             raise KeyError(x)
#         if self._logtotal is None:
#             self._logtotal = numpy.log(sum(self.d.values()))
#         return numpy.log(self.d[x]) - self._logtotal
# 
#     def __contains__(self, x):
#         return x in self.d
# 
#     def __iter__(self):
#         return iter(self.d)
# 
#     def __len__(self):
#         return len(self.d)
# 
#     def iteritems(self):
#         for x in self.d:
#             yield x, self[x]


# Path: simpleai/machine_learning/classifiers.py
import numpy
    import cPickle as pickle
    import pickle
from collections import defaultdict
from simpleai.machine_learning.models import Classifier
from simpleai.machine_learning.metrics import Counter, OnlineInformationGain, \
                                              OnlineLogProbability

            node.attribute = A.attribute
            for value, counts in A.get_branches():
                branch = node.add_branch(value)
                branch.set_results_from_counts(counts)
                bdataset = [e for e in examples if node.attribute(e) == value]
                q.append((branch, bdataset))

    def _max_gain_split(self, examples):
        """
        Returns an OnlineInformationGain of the attribute with
        max gain based on `examples`.
        """
        gains = self._new_set_of_gain_counters()
        for example in examples:
            for gain in gains:
                gain.add(example)
        winner = max(gains, key=lambda gain: gain.get_gain())
        if not winner.get_target_class_counts():
            raise ValueError("Dataset is empty")
        return winner

    def _new_set_of_gain_counters(self):
        """
        Creates a new set of OnlineInformationGain objects
        for each attribute.
        """
        return [OnlineInformationGain(attribute, self.target)
                for attribute in self.attributes]

    def _single_node_tree(self):

        c = Counter(self.target)