【问题标题】:SortedSet.subset returning a size == 0 subsetSortedSet.subset 返回大小 == 0 子集
【发布时间】:2020-12-13 20:26:41
【问题描述】:

放轻松,这是我第一次在这个级别上与比较器合作。

@SuppressWarnings({ "rawtypes", "unchecked" })
public T getObjectFromExample(T object)
    {
        if (!sorted)
            throw new IllegalStateException();
        SortedSet<T> subset = setReference.subSet(object,object);
        if (subset.size() == 0)
        {
            clsMainProgram.DebugMessage("Returned null for "+object.getClass().getName());
            return null;
        }
        return subset.first();
    }

...是我在这样声明的 TreeSet 上使用的方法调用。

setReference =  Collections.synchronizedSortedSet(new TreeSet<T>(comparator));

这是比较器

public class clsComparator implements Serializable, Comparator<infIDClass> {
    private static final long serialVersionUID = -4212404170394031421L;
    @Override
    public int compare(infIDClass o1, infIDClass o2) {
        // TODO Auto-generated method stub
                int z1 = (int)o1.GetID();
                int z2 = (int)o2.GetID();;
                if(z1 > z2)
                    return 1;
                return z2 <= z1 ? 0 : -1;
    }
}

infIDClass 就是这样:

public interface infIDClass {
    public int GetID();
    public int SetID(int newID);
}

而我传递来获取另一个相同 ID 的对象的构造如下:

//USED ONLY FOR COMPARISONS
private Building(int bID) {
    this.ID = bID;
}

正在传递的对象只是一个带有 int ID 的空白模板,比较器将不同项目比较到集合中的方式只是将 ID 整数从低到高排序。这背后的想法是快速有效地从 set Reference 中获取具有 ID 的对象,而无需遍历整个事物。

由于某种原因,每当我运行 GetObjectFromExample 时,它​​有一半的时间返回一个空值,而当使用 tailSet 和 headSet 时,它似乎做同样的事情。我不完全确定它为什么这样做,代码似乎对我来说是正确的。

我不知道它为什么会这样做,除了在将它发送到比较器时进行某种模板转换,但我不确定。

【问题讨论】:

  • 最好使用 Map 从整数键中快速查找值。
  • 你不是在对象已经在 TreeSet 中之后调用SetID 吗?这会破坏事情,因为树不会知道 id 已更改。
  • 这就是我最终做的@tgdavies,地图工作得更好,而且它比遍历它找到它相对更快。

标签: java sorting set


【解决方案1】:

我可以从您的代码中了解到,您有一个 SortedSet(这只是一个接口),它是由一个 TreeSet(SortedSet 接口的实现)实现的。我还假设您用于搜索“Building”集合中匹配元素的“dummySearchObject”对象/类也实现了infIDClass 接口 - 否则您可能已经遇到编译错误。

通过在 TreeSet 中指定比较器,可以确保您的元素在 TreeSet 中以正确的顺序排序。事实上,TreeSet 正在执行基于compare(To) 方法的所有操作,因此在Comparator 上执行所有操作,如JavaDocs 中所述。

这意味着违反hashcodeequals 方法的合同不是这里的问题,我认为首先出了问题(坚持这个合同非常重要,如果不这样做会导致意外和难以发现错误 - 所以你可能还想检查一下)。

进一步来自JavaDoc of subSet method

... 返回此集合的一部分的视图,其元素范围为 fromElement,包含,toElement,排除。 (如果 fromElement 和 toElement 相等,返回的集合为空。) ...

这意味着您的代码运行良好,并且符合文档。如果要检索非空子集,则必须指定 fromElement 和 fromElement + 1。这实际上很难做到,因为我们不知道 fromElement + 1 将是什么。那么如何从 Set 中找到/获取要查找的元素?

不幸的是,Set 实现没有指定任何“get”方法来直接访问基于它们的索引的任何元素。因此,要从 Set 中找到您的元素,您可以

  1. 切换到支持此类索引访问的实现(如 HashMap 或 LinkedHashMap - 但您的元素将不再被排序。在这种情况下还要记住满足 hashcodeequals 合同!)
  2. 您可以使用迭代器 (as explained here) 或流等通过遍历 Set 来查找元素

编辑:备注:SortedSet.subSet 的实现也会遍历整个列表以找到匹配的元素。

使用流可以这样实现:

yourSortedSet.stream().filter((elementFromSet) -> comparator.compare(dummySearchObject, elementFromSet) == 0).findFirst();

注意:流方法使用comparator.compare 方法(这与传递给 TreeSet 构造函数的 Comparator 实现相同)。正在使用比较器实现,因为您的示例中没有实现 hashcodeequals 方法(至少从上面的 sn-p 中不明显)。如果你正确地实现它们,equals方法可以用来做对象的“简单”比较。

.findFirst() 方法将返回一个 Optional 的实例,您可以继续使用。例如(根据你上面的例子):

if (!Optional.isPresent())
    {
        clsMainProgram.DebugMessage("Returned null for "+object.getClass().getName());
        return null;
    }
        
return Optional.get();

非常重要: 您的 Comparator 的实现没有错误或导致问题,但它根本不是最优的。它会阻止您的 TreeSet 正确分配和排序它的元素,使其变得非常低效。

编辑:实际上它使您的比较器实现正确,因为它使用 -1、0 和 1 的全部范围作为返回值。虽然它很难阅读/理解,所以仍然建议使用下面的实现。

因此使用 Comparator 实现,如:

Comparator.comparingInt(infIDClass::GetID);

正如 Thomas Kläger 在他的回答中所建议的那样,这是要走的路。此外,如果您的实际目的更复杂,您的模型类也非常简单和小,您可以考虑在 TreeSet 中添加其他属性以更有效地存储/排序(当然,前提是它也适合您的业务用例。可比性/唯一性您的对象应该主要由您的业务领域驱动)。

最后但并非最不重要的是,您可以从以下代码中找到完整的编码示例:

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.SortedSet;
import java.util.TreeSet;

public class Driver {

    static List<Wrapper> dummyData = initializeDummyData();

    // Used to create some dummy data
    private static List<Wrapper> initializeDummyData() {
        List<Wrapper> dummyData = new ArrayList<>(
                Arrays.asList(new MyClass(1), new MyClass(2), new MyClass(3), new MyClass(4), new MyClass(5)));
        return dummyData;
    }

    public static void main(String[] args) {

        MyComparator comparator = new MyComparator();

        // Creating the SortedSet with a Comparator and filling it with dummy data
        SortedSet<Wrapper> theSortedSet = new TreeSet<>(comparator);
        theSortedSet.addAll(dummyData);

        Wrapper dummySearchObject = new MyClass(2);
        Wrapper dummySearchObject2 = new MyClass(3);
        
        // Calling subSet and fromElement and toElement are same
        System.out.println(theSortedSet.subSet(dummySearchObject, dummySearchObject));
        // Will print empty result: []
        
        // Calling subSet and toElement is fromElement + 1
        System.out.println(theSortedSet.subSet(dummySearchObject, dummySearchObject2));
        // Will print: [My id is: 2]
        
        
        // Example with stream and Optional
        Optional<Wrapper> retrievedElement = theSortedSet.stream()
                .filter((elementFromSet) -> comparator.compare(dummySearchObject, elementFromSet) == 0).findFirst();

        if(retrievedElement.isPresent()) {
            // Element is there, do something with it
            System.out.println("Yes");
        } else {
            // Element is not there
            System.out.println("No");
        }
    }

}

interface Wrapper { // Equivalent to infIDClass interface

    public int getId();

}

class MyClass implements Wrapper { // Implementation of infIDClass => Equivalent to Building?
    private int id;

    MyClass(int id) {
        this.id = id;
    }

    @Override
    public int getId() {
        return id;
    }

    @Override
    public String toString() {
        return "My id is: " + id;
    }

}

class MyComparator implements Serializable, Comparator<Wrapper> { // The Comparator implementation
    private static final long serialVersionUID = -4212404170394031421L;

    @Override
    public int compare(Wrapper o1, Wrapper o2) {

        int z1 = (int) o1.getId();
        int z2 = (int) o2.getId();
        if (z1 > z2)
            return 1;
        return z2 <= z1 ? 0 : -1; // => This should be adjusted (e.g. "Comparator.comparingInt(infIDClass::GetID);") to have an efficient storing/ sorting in the TreeSet
    }
}

【讨论】:

    【解决方案2】:

    我认为问题出在SortedSet&lt;T&gt; subset = setReference.subSet(object,object);

    根据API subSet 为您提供第一个参数(包括)到第二个参数(不包括)之间的元素。所以这个调用setReference.subSet(object,object) 将总是返回空集

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-04-26
      • 1970-01-01
      • 2020-12-16
      • 2021-08-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多