【问题标题】:Update a POJO property with 'position' in a SortedMap(TreeMap)在 SortedMap(TreeMap) 中使用“位置”更新 POJO 属性
【发布时间】:2017-09-14 12:20:55
【问题描述】:

我正在尝试模拟一个游戏板,其中多个玩家可以提交他们的游戏分数。

POJO 即。 Entry.java 代表排行榜中的一个条目。 注意重写的 equals() 方法。

Position 是排行榜中的位置,1 是用户 最高分

public class EntryTreeMapOption {

private String uid;
private int score;
private int position;

public EntryTreeMapOption(String uid, int score) {

    this.uid = uid;
    this.score = score;

}

public EntryTreeMapOption() {

}

public String getUid() {
    return uid;
}

public void setUid(String uid) {
    this.uid = uid;
}

public int getScore() {
    return score;
}

public void setScore(int score) {
    this.score = score;
}

public int getPosition() {
    return position;
}

public void setPosition(int position) {
    this.position = position;
}

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((uid == null) ? 0 : uid.hashCode());
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    EntryTreeMapOption other = (EntryTreeMapOption) obj;
    if (uid == null) {
        if (other.uid != null)
            return false;
    } else if (!uid.equals(other.uid))
        return false;
    return true;
}

@Override
public String toString() {
    return "Entry [uid=" + uid + ", score=" + score + ", position=" + position + "]";
}}

我正在使用 TreeMap 来存储条目,根据分数,它们会自动排序

public class GameDefault2 {

    private TreeMap<EntryMapOption, String> leaderBoardEntryUserMap;

    {

        leaderBoardEntryUserMap = new TreeMap<>(Comparator.comparingInt(EntryTreeMapOption::getScore).reversed()
            .thenComparing(EntryTreeMapOption::getUid));
    }

    @Override
    public void submitScore(String uid, int score) {

        EntryMapOption newEntry = new EntryMapOption(uid, score);
        leaderBoardEntryUserMap.put(newEntry, uid);

    }

    @Override
    public List<EntryMapOption> getLeaderBoard(String uid) {

        List<EntryMapOption> userEntryList = .....
        .....
        .....

        return entriesOptionTwo;

    }

}

如何设置条目的“位置”字段?例如:以下是按分数排序的条目,我如何获得相应的“索引/位置”并将其设置在条目中?

Entry [uid=user1, score=14, position=0]
Entry [uid=user2, score=8, position=0]
Entry [uid=user3, score=7, position=0]
Entry [uid=user4, score=7, position=0]
Entry [uid=user5, score=4, position=0]
Entry [uid=user6, score=3, position=0]
Entry [uid=user7, score=3, position=0]
Entry [uid=user8, score=1, position=0]

现在,user1 条目的 position=1,user2 条目的 position=2,依此类推。

【问题讨论】:

  • 什么意思?您想用不同的位置值更新地图中的一个条目吗?我假设你想通过uid 找到它,对吧?
  • 编辑了问题 - 添加条目时,TreeMap 会自动对其进行排序。基于分数,但 Entry.position 应设置为条目在游戏板中的“排名/位置”
  • 这样的东西在 jdk 中默认是不存在的,例如stackoverflow.com/questions/7911621/…

标签: java collections java-8


【解决方案1】:

据我了解,您应该可以使用listArray = list.toArray() 将您的列表转换为数组,然后使用

for (int i = 0; i < listArray.length; i++) { listArray[i].position = i + 1; }

将它们的位置设置为它们的索引。 (加一,因为获胜者应该是第一名,而不是第零名)

【讨论】:

    【解决方案2】:

    您不能在TreeMap 中自动执行此操作,您必须自己编写,但这并不便宜。在每次更新 score 时(我实际上是指删除该条目,然后将其放回地图中),您将需要遍历整个 map 并相应地更新它。您可以使用 entrySet() 迭代器来做到这一点:The set's iterator returns the entries in ascending key order. 类似这样的东西:

    Iterator<...> it = map.entrySet().iterator();
    
        int num = 1;
    
        while(it.hasNext()) {
            Entry en = it.next;
            en.getKey().setPosition(num++);
        }
    

    【讨论】:

      【解决方案3】:

      也许,您最好使用已排序的List

      考虑

      public class RankList<T> extends AbstractCollection<T> {
      
          private final Comparator<T> order;
          private final List<T> contents;
      
          public RankList(Comparator<T> order) {
              this.order = Objects.requireNonNull(order);
              contents = new ArrayList<>();
          }
          public RankList(Comparator<T> order, List<? extends T> initialContents) {
              this.order = Objects.requireNonNull(order);
              contents = new ArrayList<>(initialContents);
              contents.sort(order);
          }
      
          @Override
          public boolean add(T e) {
              int index = Collections.binarySearch(contents, e, order);
              if(index>=0) return false;
              contents.add(~index, e);
              return true;
          }
      
          public int addAndGetIndex(T e) {
              int index = Collections.binarySearch(contents, e, order);
              if(index>=0) throw new IllegalStateException("duplicate element");
              index = ~index;
              contents.add(index, e);
              return index;
          }
      
          @Override
          public boolean remove(Object o) {
              T t = (T)o;
              int index = Collections.binarySearch(contents, t, order);
              if(index<0) return false;
              contents.remove(index);
              return true;
          }
      
          @Override
          public boolean contains(Object o) {
              T t = (T)o;
              return Collections.binarySearch(contents, t, order)>=0;
          }
      
          public int indexOf(T element) {
              int ix = Collections.binarySearch(contents, element, order);
              return ix<0? -1: ix;
          }
      
          public List<T> asList() {
              return Collections.unmodifiableList(contents);
          }
      
          @Override
          public Iterator<T> iterator() {
              return contents.iterator();
          }
      
          @Override
          public int size() {
              return contents.size();
          }
      }
      

      通过确保列表始终是排序的,您可以利用查找和插入操作中的排序特性,因此您将拥有与 TreeMap 相同的 O(log n) 时间复杂度,所需的空间可能会更少由于平面阵列存储的元素数量。将List 包装在另一个类中,例如RankList,有助于确保在修改List 时不会意外使排序的属性失效。

      在改变它的排序属性时仍然需要删除和重新插入一个元素,但它仍然是直截了当的,例如

      RankList<EntryOption> rankList=new RankList<>(
          Comparator.comparingInt(EntryOption::getScore).reversed()
                    .thenComparing(EntryOption::getUid));
      ThreadLocalRandom r = ThreadLocalRandom.current();
      for(int id=1; id<100; id++)
          rankList.add(new EntryOption(String.valueOf(id), r.nextInt(100)));
      
      EntryOption justAnother = new EntryOption("101", r.nextInt(100));
      int pos = rankList.addAndGetIndex(justAnother);
      int rangeStart = Math.max(0, pos-2), rangeEnd=Math.min(rankList.size(), rangeStart+5);
      
      System.out.println("entries around "+justAnother);
      rankList.asList().subList(rangeStart, rangeEnd)
              .forEach(System.out::println);
      
      System.out.println("update score of "+justAnother);
      
      rankList.remove(justAnother);
      justAnother.score+=20;
      rankList.add(justAnother);
      
      System.out.println("entries around "+justAnother);
      pos = rankList.indexOf(justAnother);
      rangeStart = Math.max(0, pos-2); rangeEnd=Math.min(rankList.size(), rangeStart+5);
      rankList.asList().subList(rangeStart, rangeEnd)
              .forEach(System.out::println);
      

      但是,当您要更新所有元素时,执行批量更新并随后实例化一个新的 RankList 可能会更有效……

      或者添加类似的东西

      public void updateAll(Consumer<? super T> updateElementAction) {
          contents.forEach(updateElementAction);
          contents.sort(order);
      }
      

      RankList,如果您可以将更新操作表示为Consumer,让列表暂时未排序,然后通过单个sort 操作进行验证。

      【讨论】:

      • 这里有点迷茫,虽然排名不能重复,但同一个用户可以提交多个分数例如:("user6", 3), ("user6", 2), ("user7 ", 3), ("user7", 3).排名列表正在添加重复项(无论您使用的 Comparator 或 Comparator.comparing(EntryListOption::getUid).thenComparingInt(EntryListOption::getScore).reversed()) 您能否对 EntryOption.equals() 的方式有所了解应该看看?
      • @Kaliyug Antagonist:这个类只根据比较器的逻辑检查重复项。这是通过分数来区分元素,如果分数匹配,则考虑它们的 Uid。所以一个元素只有在两个属性都匹配时才能相等。 TreeMap 也会遇到同样的问题,因为它也不关心 equals。但是RankList 只是展示逻辑的骨架。如果您愿意,您可以扩展它以强制执行额外的约束。但是,当您最终维护所有元素的 Set 时,始终排序的镜像几乎没有什么好处。
      猜你喜欢
      • 2011-01-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-07
      相关资源
      最近更新 更多