Java:如何将集合分成等价类?

时间:2016-09-04 19:15:49

标签: java grouping

我有一个项目清单(!):

  • A
  • C
  • d
  • 电子
  • ...

我希望将它们分组:

  • [A,C,D]
  • [B,E]
  • ...

组定义如下:

  • 根据自定义函数 f(a,b) - >>组中的所有项目均相等布尔
  • f(a,b)= f(b,a)

问题有现成的API吗?

<T> List<List<T>> group(Collection<T> collection, BiFunction<T, T, Boolean> eqF);

更新。这个问题完全不适用于你可以定义一些质量来分组的情况!在这种情况下,Java 8 Collectors.groupingBy 是最简单的答案。

我正在使用多维向量和等式函数看起来像:

  • 指标(a,b)&lt;阈值

对于这种情况,定义散列等于解决初始任务:)

5 个答案:

答案 0 :(得分:3)

您的方案听起来像是groupingBy收集器的一个很好的用例。通常,您不是提供相等函数,而是提供一个提取限定符的函数。然后将元素映射到列表中的这些限定符。

Map<Qualifier, List<T>> map = list.stream()
    .collect(Collectors.groupingBy(T::getQualifier));

Collection<List<T>> result = map.values();

如果T的身份是您的限定符,则可以使用Function.identity()作为参数。

但是当你的限定符超过T的1个字段时,这就成了一个问题。您可以使用元组类型为T创建备用标识,但这只是到目前为止,因为每个字段数都需要一个单独的元组类。

如果您想使用groupingBy,您确实需要为T创建一个温和的替代身份,这样您就不必更改T的{​​{1}}和equals方法。

要创建正确的标识,您需要实现hashCodeequals(或始终返回hashCode以获取哈希码,但性能下降)。我知道,没有API类,但我做了一个简单的实现:

0

您可以使用:

interface AlternateIdentity<T> {    
    public static <T> Function<T, AlternateIdentity<T>> mapper(
            BiPredicate<? super T, Object> equality, ToIntFunction<? super T> hasher) {
        return t -> new AlternateIdentity<T>() {
            @Override
            public boolean equals(Object other) {
                return equality.test(t, other);
            }

            @Override
            public int hashCode() {
                return hasher.applyAsInt(t);
            }
        };
    }
}

Collection<List<T>> result = list.stream() .collect(Collectors.groupingBy( AlternateIdentity.mapper(eqF, hashF) )) .values(); 是您的函数,而eqF是哈希码函数,其哈希值与hashF测试相同。 (同样,您也可以在eqF中返回0,但正确实施会加快速度。)

答案 1 :(得分:1)

您可以使用散列在线性时间内执行此操作。

为此,您需要首先在对象中实现hashCode()函数,因此它为相等的元素返回相等的哈希值(例如,通过对其实例属性的哈希码进行异或)。然后,您可以使用集合的哈希表来对元素进行分组。

Map<Integer, Set<T>> hashMap = new HashMap<>();
for (T element : collection) {
    if (!hashMap.containsKey(element.hashCode())
         hashMap.put(element.hashCode(), new HashSet<T>());
    hashMap.get(element.hashCode()).add(element);
}

由于相等的元素产生相同的哈希值,它们将被插入到相同的等价类中。

现在,您可以使用hashMap.values();

获取所有等价类(作为集合)的集合

答案 2 :(得分:1)

我很确定标准API中没有任何内容。您可以尝试第三方集合类,例如Trove的TCustomHashSet。 (有趣的是,根据this related thread中的评论,Guava小组(暂时)拒绝了类似的课程。请参阅讨论here。)

另一种方法是推出自己的解决方案。如果您没有太多项目,我建议采用暴力方法:保留项目列表列表,并为每个新项目查看列表列表,看看它是否等于第一个元素列表。如果是,请将新项目添加到匹配列表中,如果不是,则将新列表添加到列表列表中,并将该项目作为唯一成员。计算复杂性不是很好,这就是为什么我只推荐这个项目的数量很少或执行时间性能根本不是问题的原因。

第二种方法是修改项类以实现自定义相等功能。但要将其与基于散列的集合类一起使用,您还需要覆盖hashcode()。 (如果你不使用基于散列的集合,你可以选择暴力方法。)如果你不想(或不能)修改项目类(例如,你想使用各种等式测试),我建议创建一个包装类,可以使用相等(和哈希代码)策略进行参数化。 (这是在修改项目类和使用Trove类之间的一半。)

答案 3 :(得分:1)

这是一个分组字符串的简单示例。如果要分组的对象更复杂,则需要提供identity()以外的其他功能。

public class StreamGroupingBy
{

   public static void main( String[] args )
   {
      List<String> items = Arrays.asList(  
              "a", "b", "c", "d", 
              "a", "b", "c",
              "a", "b", 
              "a", "x" );

      Map<String,List<String>> result = items.stream().collect(
              Collectors.groupingBy( Function.identity() ) );
      System.out.println( result );
   }
}

输出:

{a=[a, a, a, a], b=[b, b, b], c=[c, c], d=[d], x=[x]}

答案 4 :(得分:0)

我还建议实现散列机制。您可以使用Guava FluentIterable执行类似的操作:

FluentIterable.from(collection)
    .index(new Function<T, K>() {
        K apply(T input) {
            //transform T to K hash
        }
    })//that would return ImmutableListMultimap<K, T>
    .asMap()//that would return Map<K, Collection<T>>
    .values();//Collection<Collection<T>>