Java 8 将两个字符串简化为一个字符串

4

我能用流来实现这个吗?

StringBuilder text = new StringBuilder();
StringBuilder dupText = new StringBuilder();
String lastLetter = "";

for (Container cont : containersList) {
    String letter = cont.getLetter();
    text.append(letter);
    if (letter.equals(lastLetter) == false) {
        dupText.append(letter);
    }
    lastLetter = letter;
}

System.out.println(text);
System.out.println(dupText);

我要遍历容器列表,每个容器都有一个字符。 我需要组装两个字符串——一个是所有字符的组合,另一个是所有字符但不包含重复的(ABABAAAB -> ABABAB)

这可以使用流完成吗?

我尝试像这样做:

Optional<String> text = containersList.stream()
            .map(Container::getLetter)
            .reduce((letter,accumalator) -> accumalator += letter);

Optional<String> dupText = session.containersList().stream()
            .map(Container::getLetter)
            .reduce((letter, accumalator) ->{
                if ((accumalator.endsWith(letter) == false)) {
                    accumalator += letter;
                }
                return accumalator;
            });

4
不要编写像if(condition == false)这样的语句。使用逻辑非符号,即“!”运算符来表示布尔型的规范否定形式:if(!condition),就像您写if(condition)而不是if(condition==true)一样,是吗?* 没错,是吗?* - Holger
这是一个标准问题。在我们公司,我们写成==true。 - Ido Barash
2
为什么?难道你不应该写(condition==true)==true以保持一致性吗?毕竟,condition==true 也是 一种条件。哦等等……(condition==true)==true也是一种条件……你可以写a!=b吗?还是必须写(a==b)==false - Holger
我们总是写成 if (x == true)。即使只有一行,我们也总是使用花括号。这只是标准,不要紧。 - Ido Barash
2
始终使用花括号是一种有合理依据的编码风格。它可以防止某些类型的错误。相比之下,在布尔表达式上使用“==true”否认了Java中已经防止值和条件混合的“boolean”类型的事实。添加“==true”无法解决任何问题。 - Holger
2
尽管这与问题无关,但我碰巧同意Holger的观点。将布尔值与“true”或“false”进行比较是不好的风格。很遗憾你公司的编码标准要求这样做。撰写该标准的人应该受到严厉的惩罚。 - Stuart Marks
4个回答

5

使用StreamEx

您可以使用StreamEx库在单个流管道中完成此操作。

List<Container> containersList = Arrays.asList(new Container("A"), new Container("B"), new Container("A"), new Container("A"), new Container("B"));
    
String[] result =
        StreamEx.of(containersList)
                .map(Container::getLetter)
                .groupRuns(Object::equals)
                .collect(MoreCollectors.pairing(
                    MoreCollectors.flatMapping(List::stream, Collectors.joining()),
                    MoreCollectors.mapping(l -> l.get(0), Collectors.joining()),
                    (s1, s2) -> new String[] { s1, s2 }
                ));
    
System.out.println(result[0]);
System.out.println(result[1]);

这段代码创建了一个容器的流,并将每个容器映射到它们的字母上。
然后,方法“groupRuns”将匹配给定谓词的连续元素折叠成一个List。在这种情况下,谓词是字符串的相等性:因此,如果您从流[ A,A,B ]开始,则此方法将其折叠成流[ List(A, A),List(B)](第一个元素是输入中2个A连续元素的列表)。
最后,使用“pairing”收集器将结果收集到两种不同的收集器中。第一个收集器将每个列表的平面图结果连接在一起,而第二个收集器仅连接列表的第一个元素(因此删除连续的元素)。
结果存储在一个数组中,该数组只作为两个值的持有者。
输出:
ABAAB
ABAB

直接使用Stream API

如果您想继续使用当前的API而不使用库,最好的方法是编写自定义Collector

public static void main(String[] args) {
    List<Container> containersList = Arrays.asList(new Container("A"), new Container("B"), new Container("A"), new Container("A"), new Container("B"));
    
    String[] result = containersList.stream().parallel().map(Container::getLetter).collect(ContainerCollector.collector());
    
    System.out.println(result[0]);
    System.out.println(result[1]);
}

private static final class ContainerCollector {
    
    private StringBuilder text = new StringBuilder();
    private StringBuilder dupText = new StringBuilder();
    
    private void accept(String letter) {
        text.append(letter);
        if (dupText.indexOf(letter, dupText.length() - letter.length()) < 0) {
            dupText.append(letter);
        }
    }
    
    private ContainerCollector combine(ContainerCollector other) {
        text.append(other.text);
        other.dupText.codePoints().forEach(i -> {
            String letter = new String(Character.toChars(i));
            if (dupText.indexOf(letter, dupText.length() - letter.length()) < 0) {
                dupText.append(letter);
            }
        });
        return this;
    }
    
    private String[] finish() {
        return new String[] { text.toString(), dupText.toString() };
    }
    
    private static Collector<String, ?, String[]> collector() {
        return Collector.of(ContainerCollector::new, ContainerCollector::accept, ContainerCollector::combine, ContainerCollector::finish);
    }
    
}

这个自定义收集器在每个字母被接受时构建textdupText。对于text字符串,字母总是被追加。对于dupText,只有当最后一个字符不同时才会追加该字符。
组合器代码(在并行执行的情况下运行)对于dupText有些棘手:如果第二个字符串不以第一个字符串的结尾开始,则追加第二个字符串。否则,删除第一个字母并追加其余部分。
输出结果相同。

这些都非常有帮助,但最终代码量太大了。简单的for循环似乎是快速且容易的选择。谢谢。 - Ido Barash

3
使用流适合解开容器。然而,使用循环更容易删除重复的字符。
我建议最好将两者结合使用。
import java.util.ArrayList;
import java.util.Collection;
import java.util.stream.Collectors;

class Container {

    private char letter;

    public String getLetter() {
        return Character.toString(letter);
    }

    public static Container of(char letter) {
        Container container = new Container();
        container.letter = letter;
        return container;
    }

}
public class T {

    public static void main(String[] args) {

        Collection<Container> containersList = new ArrayList<>();
        containersList.add(Container.of('A'));
        containersList.add(Container.of('B'));
        containersList.add(Container.of('A'));
        containersList.add(Container.of('B'));
        containersList.add(Container.of('A'));
        containersList.add(Container.of('A'));
        containersList.add(Container.of('A'));
        containersList.add(Container.of('B'));

        // at first join characters, don't bother about duplicates
        String text = containersList.stream()
        .map(Container::getLetter)
        .collect(Collectors.joining());

        // afterwards remove duplicates
        StringBuilder dupText = new StringBuilder();
        Character lastLetter = null;
        for (Character c : text.toCharArray()) {
            if (c.equals(lastLetter))
                continue;
            dupText.append(c);
            lastLetter = c;
        }

        System.out.println(text);
        System.out.println(dupText);
    }

}

一个没有循环的解决方案可能是这样的:
// at first join characters, don't bother about duplicates
String text = containersList.stream()
        .map(Container::getLetter)
        .collect(Collectors.joining());

// afterwards remove duplicates
String dupText = text.chars()
        .mapToObj(i -> Character.toString((char)i))
        .reduce((left,right) -> {
            if (left.endsWith(right))
                return left;
            return left+right;
        })
        .get();

如果您不得不避免迭代两次,请使用以下方法:
MyBuilder myBuilder = new MyBuilder();

containersList.stream()
.map(Container::getLetter)
.forEachOrdered(myBuilder::accept);

System.out.println(myBuilder.text);
System.out.println(myBuilder.dupText);

使用类似这样的构建器:

class MyBuilder {

    StringBuilder text = new StringBuilder();
    StringBuilder dupText = new StringBuilder();
    String lastLetter;

    void accept(String letter) {
        text.append(letter);

        if (letter.equals(lastLetter) == false) {
            dupText.append(letter);
        }

        lastLetter = letter;
    }
}

3

我会将其分为两个独立的操作。首先,获取带有重复文本的内容:

String dupText = containersList.stream()
        .map(Container::getLetter)
        .collect(Collectors.joining());

第二步是使用正则表达式去重:

String text = dupText.replaceAll("(.)\\1+", "$1");

虽然它在技术上是一个双通解决方案,但它并没有遍历输入容器两次,我相信它应该非常快,至少不会比其他提出的解决方案慢。而且它很简单,不需要第三方库。


不错的解决方案。我想使用流来跳过重复可能有些过度设计了。正则表达式看起来很自然。 - Sergii Lagutin

1

使用我的StreamEx库的另一种解决方案:

Collector<Entry<String, Long>, ?, String[]> collector = MoreCollectors.pairing(
    Collectors.mapping(e -> StreamEx.constant(e.getKey(), e.getValue()).joining(), 
                            Collectors.joining()),
    Collectors.mapping(e -> e.getKey(), Collectors.joining()),
    (s1, s2) -> new String[] { s1, s2 }
);
String[] result = StreamEx.of(containersList).map(Container::getLetter)
        .runLengths().collect(collector);

System.out.println(result[0]);
System.out.println(result[1]);

当连续出现长串相等的字母时,它应该比@Tunaki提出的解决方案更加高效:该解决方案通过groupRuns()将它们收集到列表中,而此解决方案仅仅通过runLengths()计算它们的数量。请注意,保留html标签。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接