【问题标题】:Java: Equalator? (removing duplicates from a collection of objects)爪哇:等分线? (从对象集合中删除重复项)
【发布时间】:2010-01-01 04:45:47
【问题描述】:

我有一堆 Puzzle 类的对象。我已经覆盖了equals()hashCode()。当需要向用户展示解决方案时,我想过滤掉所有“相似”的谜题(按照我定义的标准),这样用户只能看到每个谜题中的一个。

相似性是传递性的。

例子:

Result of computations:
A    (similar to A)
B    (similar to C)
C
D

在这种情况下,只有 A 或 D 和 B 或 C 会呈现给用户 - 而不是两个类似的拼图。两个类似的谜题同样有效。重要的是不要同时向用户显示它们。

为了实现这一点,我想使用一个禁止重复的 ADT。但是,我不想更改 equals()hashCode() 方法来返回一个关于相似性的值。是否有一些Equalator,比如Comparator,我可以在这种情况下使用?还是我应该这样做的另一种方式?

我正在学习的课程是一个维护字母网格的拼图。 (如拼字游戏。)如果拼图包含相同的单词,但方向不同,则认为它是相似的。所以下面要解惑:

                                    (2, 2): A           
                                    (2, 1): C           
                                    (2, 0): T

类似于:

                    (1, 2): A           
                    (1, 1): C           
                    (1, 0): T      

【问题讨论】:

  • 相似度如何计算?例如,如果所有谜题都产生一个整数值,那么您可以创建一个 int -> Puzzle 的 Hashmap,将每个产生的值四舍五入到某个相似度阈值。

标签: java collections similarity comparator equivalence


【解决方案1】:

好的,您有一种方法可以测量对象之间的相似性。这意味着它们形成了Metric Space

问题是,你的空间也是像普通三维空间一样的Euclidean space,还是整数或类似的东西?如果是,那么您可以在您拥有的任何维度中使​​用binary space partition

(基本上,问题是:您的对象和 n 维实数向量之间是否存在同态?如果是,那么您可以使用技术来测量 n 维空间中点的接近度。)

现在,如果它不是欧几里得空间,那么你就有更大的问题了。程序员可能最熟悉的非欧几里得空间的一个例子是字符串之间的Levenshtein Distance

如果您的问题类似于查看字符串与现有字符串列表的相似程度,那么我不知道任何算法可以在没有 O(n2 的情况下做到这一点) 时间。也许有一些在那里。


但另一个重要的问题是:你有多少时间?有多少对象?如果您有时间或者如果您的数据集足够小以至于 O(n2) 算法是实用的,那么您只需遍历您的对象列表以查看它是否低于某个阈值。如果是这样,拒绝它。

只需重载AbstractCollection 并替换 Add 函数。使用 ArrayList 或其他。你的代码看起来像这样

class SimilarityRejector<T> extends AbstractCollection<T>{
     ArrayList<T> base;
     double threshold;

    public SimilarityRejector(double threshold){
        base = new ArrayList<T>();
        this.threshold = threshold;
    }

    public void add(T t){
       boolean failed = false;
       for(T compare : base){
          if(similarityComparison(t,compare) < threshold) faled = true;
       }
       if(!failed) base.add(t);
     }

    public Iterator<T> iterator() {
        return base.iterator();
    }

    public int size() {
        return base.size();
    }
}

等等。显然 T 需要是某个类的子类,您可以对其进行比较。如果您有欧几里得度量,那么您可以使用空间分区,而不是遍历所有其他项目。

【讨论】:

    【解决方案2】:

    我会使用一个包装类来相应地覆盖equalshashCode

    private static class Wrapper {
        public static final Puzzle puzzle;
        public Wrapper(Puzzle puzzle) { 
            this.puzzle = puzzle; 
        }
        @Override 
        public boolean equals(Object object) {
            // ...
        }
        @Override 
        public int hashCode() {
            // ...
        }
    }
    

    然后你把所有的谜题都包起来,放在地图上,然后再把它们拿出来……

    public Collection<Collection<Puzzle>> method(Collection<Puzzles> puzzles) {
        Map<Wrapper,<Collection<Puzzle>> map = new HashMap<Wrapper,<Collection<Puzzle>>();
        for (Puzzle each: puzzles) {
            Wrapper wrapper = new Wrapper(each);
            Collection<Puzzle> coll = map.get(wrapper);
            if (coll == null) map.put(wrapper, coll = new ArrayList<Puzzle>());
            coll.add(puzzle);
        }
        return map.values();
    }
    

    【讨论】:

    • 问题是相似性可能不是暂时的。您可能会遇到 Similar(A,B) && Similar(B,C) && !Similar(A,C);
    • OP 提出问题,这样我们就可以假设他的相似性是一个等式关系。
    • hashCode 呢?每组相似的项目可以减少到一个数字吗?
    • OP 声称已经解决了这个问题,OP 只是在寻找合适的位置来放置它不必更改他的 Puzzle 类。
    【解决方案3】:
    1. 使用比较器创建 TreeSet
    2. 将所有元素添加到集合中
    3. 删除所有重复项

    【讨论】:

      【解决方案4】:

      通常,“相似性”不是传递关系。所以第一步是从等价而不是相似的角度来考虑这一点。等价是自反的、对称的和传递的。

      这里的简单方法是定义一个拼图包装器,其 equals() 和 hashCode() 方法是根据所讨论的等价关系实现的。

      完成后,将包装的对象放入 java.util.Set 并过滤掉重复项。

      【讨论】:

        【解决方案5】:

        恕我直言,Gili(带有自定义比较器的 TreeSet)描述了最优雅的方式。

        但如果你想自己做,似乎这个最简单和最清晰的解决方案:

        /**
         * Distinct input list values (cuts duplications)
         * @param items items to process
         * @param comparator comparator to recognize equal items
         * @return new collection with unique values
         */
        public static <T> Collection<T> distinctItems(List<T> items, Comparator<T> comparator) {
            List<T> result = new ArrayList<>();
        
            for (int i = 0; i < items.size(); i++) {
                T item = items.get(i);
        
                boolean exists = false;
                for (int j = 0; j < result.size(); j++) {
                    if (comparator.compare(result.get(j), item) == 0) {
                        exists = true;
                        break;
                    }
                }
        
                if (!exists) {
                    result.add(item);
                }
            }
        
            return result;
        }
        

        【讨论】:

          猜你喜欢
          • 2020-07-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-02-17
          • 1970-01-01
          • 2020-09-03
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多