text: #include <algorithm>
  #include <iostream>
  #include <fstream>
  #include <limits>
  #include <sstream>
  #include "common.hpp"
  
  namespace cpmf {
  namespace common {
  
  Matrix::Matrix(const cpmf::DataParams &data_params)
      : num_ratings(0), num_ratings_test(0), num_users(0), num_items(0),
        num_user_blocks(data_params.num_user_blocks),
        num_item_blocks(data_params.num_item_blocks),
        training_path_(data_params.training_path),
        test_path_(data_params.test_path) {
    initialize_blocks();
  
    read(training_path_, &num_ratings, &nodes);
    read(test_path_, &num_ratings_test, &nodes_test);
  
    std::vector<int> user_mapping(num_users, 0);
    std::vector<int> item_mapping(num_items, 0);
    generate_mapping_vector(&user_mapping, data_params.randomize);
    generate_mapping_vector(&item_mapping, data_params.randomize);
    assign_user_and_item_id(&nodes, user_mapping, item_mapping);
    assign_user_and_item_id(&nodes_test, user_mapping, item_mapping);
    assign_nodes_to_blocks();
  
    sort_nodes_by_user_id();
  }
  
  void Matrix::initialize_blocks() {
    const int total_num_blocks = num_user_blocks * num_item_blocks;
    for (int block_id = 0; block_id < total_num_blocks; block_id++) {
      int block_user_id = block_id / num_user_blocks;
      int block_item_id = block_id % num_user_blocks;
      blocks.push_back(Block(block_user_id, block_item_id));
    }
  }
  
  void Matrix::read(const std::string &input_path,
                    long *num_ratings,
                    std::vector<Node> *nodes) {
    std::ifstream input_ifs(input_path.c_str());
    if (input_ifs.fail()) {
      std::cerr << "FileReadError: Cannot open " << input_path << std::endl;
    }
  
    std::string line_buf;
    while (getline(input_ifs, line_buf)) {
      Node node;
      sscanf(line_buf.data(),
             "%d %d %f\n", &node.orig_user_id, &node.orig_item_id, &node.rating);
      if (node.orig_user_id > num_users) { num_users = node.orig_user_id; }
      if (node.orig_item_id > num_items) { num_items = node.orig_item_id; }
      ++*num_ratings;
      nodes->push_back(node);
    }
  }
  
  void Matrix::generate_mapping_vector(std::vector<int> * mapping_vec,
                                       bool randomize) {
    const int size = mapping_vec->size();
    for (int i = 0; i < size; i++) { mapping_vec->at(i) = i+1; }
    if (randomize) {
      std::random_shuffle(mapping_vec->begin(), mapping_vec->end());
    }
  }
  
  void Matrix::assign_user_and_item_id(std::vector<Node> *nodes,
                                       const std::vector<int> &user_mapping,
                                       const std::vector<int> &item_mapping) {
    for (auto node_itr = nodes->begin(), node_itr_end = nodes->end();
          node_itr != node_itr_end; ++node_itr) {
      node_itr->user_id = user_mapping[node_itr->orig_user_id - 1];
      node_itr->item_id = item_mapping[node_itr->orig_item_id - 1];
    }
  }
  
  void Matrix::assign_nodes_to_blocks() {
    const int blk_u_len = num_users / num_user_blocks + 1;
    const int blk_i_len = num_items / num_item_blocks + 1;
  
    for (auto node_itr = nodes.begin(), node_itr_end = nodes.end();
          node_itr != node_itr_end; ++node_itr) {
      int blk_u_id = (node_itr->user_id - 1) / blk_u_len;
      int blk_i_id = (node_itr->item_id - 1) / blk_i_len;
      int blk_id = blk_u_id * num_item_blocks + blk_i_id;
      blocks[blk_id].nodes.push_back(*node_itr);
    }
  }
  
  void Matrix::sort_nodes_by_user_id() {
    for (auto block = blocks.begin(); block != blocks.end(); block++) {
      std::sort(block->nodes.begin(), block->nodes.end(),
                  [] (const Node& left, const Node& right)
                    -> bool { return left.user_id < right.user_id; } );
    }
  }
  
  void Matrix::show_info(const std::string &message) {
    int num_min_ratings = std::numeric_limits<int>::max();
    int num_max_ratings = 0;
    for (int bid = 0, num_blks = blocks.size(); bid < num_blks; bid++) {
      int n = blocks[bid].nodes.size();
      if (n < num_min_ratings) { num_min_ratings = n; }
      if (n > num_max_ratings) { num_max_ratings = n; }
    }
  
    std::ostringstream oss;
    oss << message << std::endl;
    oss << "  #users:          " << num_users << std::endl;
    oss << "  #items:          " << num_items << std::endl;
    oss << "  #ratings:        " << num_ratings << std::endl;
    oss << "  #ratings (test): " << num_ratings_test << std::endl;
    oss << "  #blocks (user):  " << num_user_blocks << std::endl;
    oss << "  #blocks (item):  " << num_item_blocks << std::endl;
    oss << "  min #ratings in one block: " << num_min_ratings << std::endl;
    oss << "  max #ratings in one block: " << num_max_ratings << std::endl;
    std::cout << oss.str() << std::endl;
  }
  
  
  } // namespace common
  } // namespace cpmf
  