#!/bin/env python

import numpy as np

def nearest_neighbor(new_instance, training_data):
    # Zadefinujeme funkci, ktera pocita vzdalenost nove intance
    # od prikladu z trenovacich dat.
    def distance_from_instance(training_example):
        # Z trenovaciho prikladu nas zajima pouze vektor rysu.
        feature_vector, _ = training_example
        # Vratime vzdalenost vektoru rysu trenovaciho vektoru a nove instance.
        return distance(feature_vector, new_instance)
    
    # Definovanou funkci pouzije jako klic pro hledani minima
    # v trenovacich datech.
    nearest_vector, nearest_label = \
            min(training_data, key=distance_from_instance)
    # Vratime tridu nejbliziho souseda.
    return nearest_label

def distance(vec_a, vec_b):
    # pocitame odmocninu - np.sqrt(...)
    # ze souctu - sum(...)
    # operator "-" vektory po slozkach odecte,
    # operator "**" vektory po slozkach umocni
    return np.sqrt(sum((vec_a - vec_b) ** 2))

def k_nearst_neighbors(k, new_instance, training_data):
    # Seradime trenovaci data podle vzdalenosti od nove instance.
    # Funkce "distance_from_instance" z predchozi funkce je nahrazena
    # strucnejsim lambda zapisem.
    training_data_by_distance = \
        sorted(training_data, key=lambda x: distance(x[0], new_instance))[0:k]
    # Z tech vezmeme k nejblich.
    k_nearest_instances = training_data_by_distance[0:k]
    # Z nejblizsich trenovacich prikladu vezmeme pouze jejich tridy.
    k_nearest_labels = [y for x, y in k_nearest_instances]
    
    # Nyni spocitame, kolikrat se ktera trida vyskytuje v okoli nove instance.
    # V nasledujici promenne je tabulka, do ktere budeme zapisovat pocty trid.
    labels_counts = {}
    # Projdeme tridy nejblizsich prikladu jednu po druhe:
    for label in k_nearest_labels:
        # pokud jeste neni v tabulce, zapiseme jeji vyskyt 1-krat,
        if label not in labels_count:
            labels_counts[label] = 1
        # pokud uz je v tabulce, zvysime pocet vyskytu o 1.
        else:
            labels_counts[label] += 1
    # Vratime znacku, ktera se vyskytla nejvickrat.
    return max(labels_counts, key=lambda c: labels_counts[c])

