在Java中对Eithers列表进行分区

时间:2017-01-27 14:00:21

标签: java

在很大程度上,作为一项学术活动,我正在试验我自己在Java中的Either实现,受Haskell的eitherhttps://hackage.haskell.org/package/base-4.9.1.0/docs/Data-Either.html)启发

我有一个令我满意的工作实施:

    Either<String, Integer> either = Either.left(A_STRING);
    assertThat(either.left(), is(A_STRING));
    assertThat(either.isLeft(), is(true));
    assertThat(either.isRight(), is(false));
    assertThat(either.map(x -> AN_INTEGER, y -> A_LONG), is(Either.left(AN_INTEGER)));
    assertThat(either.consolidate(x -> A_LONG, y -> ANOTHER_LONG), is(A_LONG));

相当于Haskell的leftsrights是过滤流:

 Stream<String> s = streamOfEithers
     .filter(Either::isLeft)
     .map( x -> x.left())

我遇到的问题是创建一个等效的Haskell partitionEithers

我的第一直觉是:

  streamOfEithers.collect(Collectors.groupingBy(Either::isLeft))

然而,这似乎丢失了我的类型 - 它返回Map<Boolean, Either<? extends Object, ? extends Object>>而不是(例如)Map<Boolean, Either<String,Integer>

当然,理想情况下,分区的结果不是两个Eithers列表,而是两个列表,每种类型一个。 Java没有Pair<T,U>类,但如果有,可能是:

 List<Either<String,Integer>> listOfEithers = ...
 Pair<List<String>,List<Integer>> partitioned = Either.partitionList(listOfEithers);

否则,我所管理的最好的是:

 Map<Boolean, Either<String,Integer>> partitioned = Either.partitionList(listOfEithers);

...对于客户来说这不是一个非常方便的结构。

除了编写Pair或使用某些馆藏库中的Pair之外(Guava,顺便提一下,拒绝提供Pair,因为它“弊大于利”,我可以最好地提供与Haskell的partitionEithers

相当的

3 个答案:

答案 0 :(得分:2)

您可以为此创建自己的聚合类型,代替Pair,专门为Either量身定制,其中包含一个收集器。这是一个简化的实现:

class Aggregate<T, U> {
    List<T> tList = new ArrayList<>();
    List<U> uList = new ArrayList<>();

    public void add(Either<T, U> e) {
        if(e.isLeft()) {
            tList.add(e.left());
        } else {
            uList.add(e.right());
        }
    }

    public Aggregate<T, U> combine(Aggregate<T, U> other) {
        tList.addAll(other.tList);
        uList.addAll(other.uList);
        return this; // could create a new Aggregate here instead...
    }

    public static <T, U> Collector<Either<T,U>, ? , Aggregate<T, U>> partitionEithers() {
        return Collector.of(Aggregate::new, Aggregate::add, Aggregate::combine);
    }
}

然后结果变为:

Aggregate<String, Integer> result = streamOfEithers.collect(Aggregate.partitionEithers());

答案 1 :(得分:1)

试试这个:

Map<Boolean, List<Either<Integer, String>>> collect = list.stream().collect(Collectors.partitioningBy(Either::isLeft));

假设你的Either实现的泛型类型签名是Either<L,R>(这是我用示例实现测试的),类型擦除应该没有问题,它将按预期编译。

之后,您可以像这样初始化Pair

Pair<List<Integer>, List<String>> result =
      pair(
          collect.getOrDefault(true, Collections.emptyList()).stream().map(Either::left).collect(toList()),
          collect.getOrDefault(false, Collections.emptyList()).stream().map(Either::right).collect(toList()));

答案 2 :(得分:0)

我不熟悉Haskell所以我无法提供完整的答案。

但我对Java泛型有所了解。

的问题

streamOfEithers.collect(Collectors.groupingBy(Either::isLeft))

返回Map<Boolean, Either<? extends Object, ? extends Object>>

是因为类型擦除。简而言之,java泛型比许多其他语言中的相同类型的逻辑弱得多(例如,C ++中的模板)。 Here更详细地解释了泛型可以做什么和不可以做什么。如果你想挖得很深,我会建议看here