二维地图Java

时间:2014-10-31 11:12:47

标签: java matrix hashmap

我需要一种能够以二维方式存储信息的数据结构。例如,想象一个包含用户项评级的表。我需要为所有用户存储所有评级。比方说,用户u1。我需要为用户u1和u2以及u3和所有其他用户存储评级。但问题是我还需要存储所有项目的所有评级。例如,我需要存储所有用户为每个项目提供的评级。所以我需要类似地图的东西,对于用户来说,密钥是用户ID,值是评级集。我可以轻松地做到这一点。但我的问题是如何存储物品的评级。例如,密钥是项目ID的映射,值是提供的评级集合,是该项目的用户。我想上传一个表,但由于我没有足够的声誉,我不能这样做。所以想象一个像二维矩阵的表,行是用户,列是项。是否有可以做到这一点的数据结构?或者我应该建立两个不同的地图?也许有一个比Map更好的选择但是因为我必须为我的问题选择一个标题我写了地图。

由于

5 个答案:

答案 0 :(得分:6)

您可以使用免费Table库中的Guava课程

Table<Integer, String, Double> table = HashBasedTable.create();

table.put(1, "a", 2.0);
double v = table.get(1, "a"); // getting 2.0

答案 1 :(得分:2)

这是我自己的相应Table对象的版本。请注意,使用现有图书馆提供的东西是好的。但尝试自己的实施将有助于您更好地了解所涉及的问题。因此,您可以尝试在我的实现中添加“删除”方法等来完成它。

我更喜欢将数据保存在表中,而不是在UserItem中实现映射,因为该表可以强制通过行和列添加每个新的评级。如果您将地图分成两个独立的对象,则无法强制执行此操作。

请注意,当我在getColgetRow中返回内部地图的保护副本时,我会返回对实际值的引用,而不是其副本,以便您可以更改用户的评级(假设您为此选择了一个可变对象而不更改表结构。另请注意,如果您的用户和项目对象是可变的,这会影响他们的equalshashCode,那么该表将会出现无法预测的行为。

public class Table<K1, K2, V> {

    // Two maps allowing us to retrieve the value through the row or the
    // column key.
    private Map<K1, Map<K2, V>> rowMap;
    private Map<K2, Map<K1, V>> colMap;

    public Table() {
        rowMap = new HashMap<>();
        colMap = new HashMap<>();
    }

    /**
     * Allows us to create a key for the row, and place it in the structure
     * while there are still no relations for it.
     *
     * @param key
     *            The key for which a new empty row will be created.
     */
    public void addEmptyRow(K1 key) {
        if (!rowMap.containsKey(key)) {
            rowMap.put(key, new HashMap<K2, V>());
        }
    }

    /**
     * Allows us to create a key for the column, and place it in the
     * structure while there are still no relations for it.
     * 
     * @param key
     *            The key for which a new empty column will be created.
     */
    public void addEmptyCol(K2 key) {
        if (!colMap.containsKey(key)) {
            colMap.put(key, new HashMap<K1, V>());
        }
    }

    /**
     * Insert a value into the table using the two keys.
     * 
     * @param rowKey
     *            Row key to access this value
     * @param colKey
     *            Column key to access this value
     * @param value
     *            The value to be associated with the above two keys.
     */
    public void put(K1 rowKey, K2 colKey, V value) {
        Map<K2, V> row;
        Map<K1, V> col;

        // Find the internal row. If there is no entry, create one.
        if (rowMap.containsKey(rowKey)) {
            row = rowMap.get(rowKey);
        } else {
            row = new HashMap<K2, V>();
            rowMap.put(rowKey, row);
        }

        // Find the internal column, If there is no entry, create one.
        if (colMap.containsKey(colKey)) {
            col = colMap.get(colKey);
        } else {
            col = new HashMap<K1, V>();
            colMap.put(colKey, col);
        }

        // Add the value to both row and column.
        row.put(colKey, value);
        col.put(rowKey, value);
    }

    /**
     * Get the value associated with the given row and column.
     * 
     * @param rowKey
     *            Row key to access the value
     * @param colKey
     *            Column key to access the value
     * @return Value in the given row and column. Null if mapping doesn't
     *         exist
     */

    public V get(K1 rowKey, K2 colKey) {
        Map<K2, V> row;
        row = rowMap.get(rowKey);
        if (row != null) {
            return row.get(colKey);
        }
        return null;
    }

    /**
     * Get a map representing the row for the given key. The map contains
     * only column keys that actually have values in this row.
     * 
     * @param rowKey
     *            The key to the row in the table
     * @return Map representing the row. Null if there is no row with the
     *         given key.
     */
    public Map<K2, V> getRow(K1 rowKey) {
        // Note that we are returning a protective copy of the row. The user
        // cannot change the internal structure of the table, but is allowed
        // to change the value's state if it is mutable.
        if (rowMap.containsKey(rowKey)) {
            return new HashMap<>(rowMap.get(rowKey));
        }
        return null;
    }

    /**
     * Get a map representing the column for the given key. The map contains
     * only row keys that actually have values in this column.
     * 
     * @param colKey
     *            The key to the column in the table.
     * @return Map representing the column. Null if there is no column with
     *         the given key.
     */
    public Map<K1, V> getCol(K2 colKey) {
        // Note that we are returning a protective copy of the column. The
        // user cannot change the internal structure.
        if (colMap.containsKey(colKey)) {
            return new HashMap<>(colMap.get(colKey));
        }
        return null;
    }

    /**
     * Get a set of all the existing row keys.
     * 
     * @return A Set containing all the row keys. The set may be empty.
     */
    public Set<K1> getRowKeys() {
        return new HashSet(rowMap.keySet());
    }

    /**
     * Get a set of all the existing column keys.
     * 
     * @return A set containing all the column keys. The set may be empty.
     */
    public Set<K2> getColKeys() {
        return new HashSet(colMap.keySet());
    }
}

我有方法addEmptyRowaddEmptyCol的原因是我认为为用户和项目保留单独的数据结构可能是多余的。一旦你将它们添加到这样的表中,你就可以通过getRowKeysgetColKeys来获取它们,所以不需要单独保存它们,除非你想用{{1}之外的任何东西构造它们。 }}

请注意,此表使用键的值 - 两个等效的Set键是相同的键,您应该相应地设计User和Item对象。

通过equalsUserItem的适当定义,您可以执行类似

的操作
Rating

然后在表中查询特定用户,如下所示:

    Table<User, Item, Rating> table = new Table<>();

    table.addEmptyCol(new Item("Television"));
    table.addEmptyCol(new Item("Sofa"));

    User user = new User("Anakin");
    Item item = new Item("Light Sabre");
    table.put(user, item, new Rating(5));

    Item item1 = new Item("Helmet");
    table.put(user, item1, new Rating(7));

    Rating rating = table.get(user, item);
    rating.setRating(rating.getRating() + 10);

    User user1 = new User("Obi-Wan");
    table.put(user1, item, new Rating(8));
    table.put(user1, new Item("Television"), new Rating(0));

或显示所有项目的评分列表,如下所示:

    Map<Item, Rating> anakinsRatings = table.getRow(user);

    for (Map.Entry<Item, Rating> entry : anakinsRatings.entrySet()) {
        System.out.println(user + " rated " + entry.getKey() + " as "
                + entry.getValue().getRating());
    }

正如我所说,实施尚未完成 - 表格中没有 for (Item currItem : table.getColKeys()) { Map<User, Rating> itemMap = table.getCol(currItem); if (itemMap.isEmpty()) { System.out.println("There are currently no ratings for \"" + currItem + "\""); } else { for (Map.Entry<User, Rating> entry : table.getCol(currItem).entrySet()) { System.out.println("\"" + currItem + "\" has been rated " + entry.getValue().getRating() + " by " + entry.getKey()); } } } ,例如,没有toStringremoveremoveRow,{{1等等。

答案 2 :(得分:1)

所以,你有两个一对多的关系。用户具有(给出)许多评级,并且项目具有许多评级;这给出了许多用户关系的关系,这是你的问题。为什么不简单地按照描述进行建模:

public class Rating {
  private User ratedBy;
  private Item itemRated;

  public Item getItem() { return itemRated; }
}

public class User {
  private Set<Rating> allRatings = new HashSet<>();

  public Rating getRatingFor(Item item) {
      for(Rating rating: allRatings) {
          if(item.equals(rating.getItem()) {
              return rating;
          }
      }
      return null;
  }
}

public class Item {
  private Set<Rating> allRatings = new HashSet<>();
}
根据需要

...和getters / setters等。然后,您可以通过以下方式获得评分:

User user1 = new User();
// ... do stuff to populate ratings
Rating itemRatingByUser = user1.getRatingFor(item);

答案 3 :(得分:0)

快速而肮脏的解决方案是使用二维密钥。 假设user和item的id属于同一类型,则可以创建一个类,该类只是id和key类型的持有者。 (类型可以是User或Item的类,如果可用,或者只是枚举值)。然后使地图具有此类型的键。当然,每个评级至少会被引用两次(每种类型一次)

答案 4 :(得分:0)

您需要的数据结构称为表。一个表有两个键和一个对象,或多或少看起来像一个excel表,其中两个键集作为列和行。由此得名。有各种各样的表实现。我认为现在使用Guava实现是正常的。 guava's table interface的解释就在这里。

guava表is here的API,以及您想要的实现,HashBasedTable,is here