/**
   * Evaluate an iterative recommender on the folds of a dataset split, display results on STDOUT.
   *
   * @param recommender a rating predictor
   * @param split a rating dataset split
   * @param max_iter the maximum number of iterations
   * @param find_iter the report interval
   * @throws Exception
   */
  public static void doIterativeCrossValidation(
      RatingPredictor recommender, ISplit<IRatings> split, int max_iter, Integer find_iter)
      throws Exception {
    if (find_iter == null) find_iter = 1;

    if (!(recommender instanceof IIterativeModel))
      throw new IllegalArgumentException("recommender must be of type IIterativeModel");

    RatingPredictor[] split_recommenders = new RatingPredictor[split.numberOfFolds()];
    IIterativeModel[] iterative_recommenders = new IIterativeModel[split.numberOfFolds()];

    // Initial training and evaluation
    for (int i = 0; i < split.numberOfFolds(); i++) {
      try {
        split_recommenders[i] = recommender.clone(); // to avoid changes : recommender
        split_recommenders[i].setRatings(split.train().get(i));
        split_recommenders[i].train();
        iterative_recommenders[i] = (IIterativeModel) split_recommenders[i];
        HashMap<String, Double> fold_results =
            Ratings.evaluate(split_recommenders[i], split.test().get(i));
        System.out.println(
            "fold "
                + i
                + " "
                + fold_results
                + " iteration "
                + iterative_recommenders[i].getNumIter());
      } catch (Exception e) {
        System.err.println("===> ERROR: " + e.getMessage());
        throw e;
      }
    }

    // Iterative training and evaluation
    for (int it = iterative_recommenders[0].getNumIter() + 1; it <= max_iter; it++) {
      for (int i = 0; i < split.numberOfFolds(); i++) {
        try {
          iterative_recommenders[i].iterate();

          if (it % find_iter == 0) {
            HashMap<String, Double> fold_results =
                Ratings.evaluate(split_recommenders[i], split.test().get(i));
            System.out.println("fold " + i + " " + fold_results + " iteration " + it);
          }
        } catch (Exception e) {
          System.err.println("===> ERROR: " + e.getMessage());
          throw e;
        }
      }
    }
  }
  /**
   * Evaluate on the folds of a dataset split.
   *
   * @param recommender a rating predictor
   * @param split a rating dataset split
   * @param compute_fit if set to true measure fit on the training data as well
   * @param show_results set to true to print results to STDERR
   * @return a dictionary containing the average results over the different folds of the split
   * @throws Exception
   */
  public static RatingPredictionEvaluationResults doCrossValidation(
      RatingPredictor recommender,
      ISplit<IRatings> split,
      Boolean compute_fit,
      Boolean show_results)
      throws Exception {

    if (compute_fit == null) compute_fit = false;
    if (show_results == null) show_results = false;

    RatingPredictionEvaluationResults avg_results = new RatingPredictionEvaluationResults();

    for (int i = 0; i < split.numberOfFolds(); i++) {
      try {
        RatingPredictor split_recommender = recommender.clone(); // to avoid changes : recommender
        split_recommender.setRatings(split.train().get(i));
        split_recommender.train();
        HashMap<String, Double> fold_results =
            Ratings.evaluate(split_recommender, split.test().get(i));
        if (compute_fit) fold_results.put("fit", new Double(Ratings.computeFit(split_recommender)));

        for (String key : fold_results.keySet())
          if (avg_results.containsKey(key))
            avg_results.put(key, avg_results.get(key) + fold_results.get(key));
          else avg_results.put(key, fold_results.get(key));

        if (show_results) System.out.println("fold " + i + " " + fold_results);
      } catch (Exception e) {
        System.err.println("===> ERROR: " + e.getMessage());
        throw e;
      }
    }

    for (String key : Ratings.getMeasures()) {
      avg_results.put(key, avg_results.get(key) / split.numberOfFolds());
    }
    return avg_results;
  }