'Predicts the value of :math:`M_{i,j}`, using simple avg. (weighted) of
all the ratings by the most similar users (or items)
if *weighted*:
:math:`\hat{r}_{ui} = \frac{\sum_{j \in S^{k}(i;u)} sim(i, j) r_{uj}}{\sum_{j \in S^{k}(i;u)} sim(i, j)}`
else:
:math:`\hat{r}_{ui} = mean(\sum_{j \in S^{k}(i;u)} r_{uj})`
:param i: row in M, :math:`M_{i \cdot}`
:type i: user or item id
:param j: col in M, :math:`M_{\cdot j}`
:type j: item or user id
:param Sk: number of k elements to be used in :math:`S^k(i; u)`
:type Sk: int
:param weighted: compute avg. weighted of all the ratings?
:type weighted: Boolean
:param MIN_VALUE: min. value in M (e.g. in ratings[1..5] => 1)
:type MIN_VALUE: float
:param MAX_VALUE: max. value in M (e.g. in ratings[1..5] => 5)
:type MAX_VALUE: float'

def predict(self, i, j, Sk=10, weighted=True, MIN_VALUE=None, MAX_VALUE=None):

    if (not Sk):
        Sk = self._Sk
    similars = self.similar_neighbours(i, j, Sk)
    sim_ratings = []
    sum_similarity = 0.0
    for (similar, weight) in similars:
        sim_rating = self.get_matrix().value(similar, j)
        if (sim_rating is None):
            continue
        sum_similarity += weight
        if weighted:
            sim_ratings.append((weight * sim_rating))
        else:
            sim_ratings.append(sim_rating)
    if ((not sum_similarity) or (not sim_ratings)):
        return nan
    if weighted:
        predicted_value = (sum(sim_ratings) / sum_similarity)
    else:
        predicted_value = mean(sim_ratings)
    if MIN_VALUE:
        predicted_value = max(predicted_value, MIN_VALUE)
    if MAX_VALUE:
        predicted_value = min(predicted_value, MAX_VALUE)
    return float(predicted_value)
