是否有用于复杂处理列表的Java API?

时间:2016-07-01 09:03:09

标签: java algorithm list java-stream

我有以下项目清单(订单事项):

<A> <B> [ ] [\n] <C> [  ] [   ] [\n\r] <D> <A> [ ] [ xyz ] [ abc ] <X>

我想合并用方括号表示的文本节点:

<A> <B> [ \n] <C> [     \n\r] <D> <A> [  xyz  abc ] <X>

我使用flags和 prevNode 指针编写一个for-each循环来执行任务,但它感觉不对:

  • 对于可以在10秒内口头表达的任务来说,这是太多的努力;
  • 没有评论,需要一些精力和时间来理解代码。

我相信 Java 8 Stream API 是关于:编写代码的工作量减少,阅读代码的工作量减少。

Java中是否有类似的机制来处理列表?

更新

鉴于没有现成的解决方案,我已经开发了一个非常简单的流畅的API 来解决任务。减少现在看起来像这样:

List<Node> output = ListStream.of(input)
                .continuousRegion(node -> node instanceof TextNode)
                .reduceRegion((a, b) -> new TextNode(a.value + b.value))
                .toArrayList();

它完美地解决了许多任务,您必须在列表中的连续区域上运行 。我有很多这样的,为进一步的数据处理准备XML。

算法:

public List<Node> reduce(List<Node> list) {
        List<Node> result = new ArrayList<>();

        TextNode prevTextNode = null;
        for (Node node : list) {
            if (node instanceof TextNode) {
                TextNode textNode = (TextNode) node;

                if (prevTextNode == null) {
                    prevTextNode = textNode;
                } else {
                    prevTextNode = new TextNode(prevTextNode.value + textNode.value);
                }
            } else {
                if (prevTextNode != null) {
                    result.add(prevTextNode);
                    prevTextNode = null;
                }

                result.add(node);
            }
        }

        if (prevTextNode != null) {
            result.add(prevTextNode);
        }

        return result;
    }

所有代码(可编辑):

import com.sun.deploy.util.StringUtils;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static org.junit.Assert.assertEquals;

public class ListReductionExample {
    class Node {
        final String value;

        Node(String value) {
            this.value = value;
        }

        @Override
        public String toString() {
            return "<" + value + ">";
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Node)) return false;

            Node node = (Node) o;

            return value != null ? value.equals(node.value) : node.value == null;
        }

        @Override
        public int hashCode() {
            return value != null ? value.hashCode() : 0;
        }
    }

    class TextNode extends Node {
        TextNode(String value) {
            super(value);
        }

        @Override
        public String toString() {
            return "[" + value + "]";
        }

    }

    public List<Node> reduce(List<Node> list) {
        List<Node> result = new ArrayList<>();

        TextNode prevTextNode = null;
        for (Node node : list) {
            if (node instanceof TextNode) {
                TextNode textNode = (TextNode) node;

                if (prevTextNode == null) {
                    prevTextNode = textNode;
                } else {
                    prevTextNode = new TextNode(prevTextNode.value + textNode.value);
                }
            } else {
                if (prevTextNode != null) {
                    result.add(prevTextNode);
                    prevTextNode = null;
                }

                result.add(node);
            }
        }

        if (prevTextNode != null) {
            result.add(prevTextNode);
        }

        return result;
    }

    public void printList(List<Node> list) {
        List<String> listOfStrings = list.stream().map(Node::toString).collect(Collectors.toList());

        System.out.println(StringUtils.join(listOfStrings, " "));
    }

    @Test
    public void test() {
        // <A> <B> [ ] [N] <C> [  ] [   ] [NR] <D> <A> [ ] [ xyz ] [ abc ] <X>
        List<Node> input = new ArrayList<>();
        input.add(new Node("A"));
        input.add(new Node("B"));
        input.add(new TextNode(" "));
        input.add(new TextNode("N"));
        input.add(new Node("C"));
        input.add(new TextNode("  "));
        input.add(new TextNode("   "));
        input.add(new TextNode("NR"));
        input.add(new Node("D"));
        input.add(new Node("A"));
        input.add(new TextNode(" "));
        input.add(new TextNode(" xyz "));
        input.add(new TextNode(" abc "));
        input.add(new Node("X"));

        printList(input);

        // <A> <B> [ N] <C> [     NR] <D> <A> [  xyz  abc ] <X>
        List<Node> expectedOutput = new ArrayList<>();
        expectedOutput.add(new Node("A"));
        expectedOutput.add(new Node("B"));
        expectedOutput.add(new TextNode(" N"));
        expectedOutput.add(new Node("C"));
        expectedOutput.add(new TextNode("     NR"));
        expectedOutput.add(new Node("D"));
        expectedOutput.add(new Node("A"));
        expectedOutput.add(new TextNode("  xyz  abc "));
        expectedOutput.add(new Node("X"));

        printList(expectedOutput);

        assertEquals(expectedOutput, reduce(input));
    }
}

2 个答案:

答案 0 :(得分:1)

没有这样的功能只能减少部分流。尝试在Stream API上构建此类功能的所有解决方案都比循环复杂得多。你可以做些什么来改善你的循环变体,是摆脱null值和相关的条件。一般逻辑如下:

public static <T> List<T> joinElements(
                          List<T> list, BiPredicate<T,T> p, BinaryOperator<T> join) {

    if(list.isEmpty()) return Collections.emptyList();
    T element=list.get(0);
    int num=list.size();
    if(num==1) return Collections.singletonList(element);
    List<T> result=new ArrayList<>(num);
    for(int ix=1; ix<num; ix++) {
        T next=list.get(ix);
        if(p.test(element, next)) {
            element=join.apply(element, next);
        }
        else {
            result.add(element);
            element=next;
        }
    }
    result.add(element);
    return result;
}

如果您怀疑调用者提供非随机访问列表(程序员应该避免),您可以使用

public static <T> List<T> joinElements(
                          List<T> list, BiPredicate<T,T> p, BinaryOperator<T> join) {

    Iterator<T> it=list.iterator();
    if(!it.hasNext()) return Collections.emptyList();
    T element=it.next();
    if(!it.hasNext()) return Collections.singletonList(element);
    List<T> result=new ArrayList<>();
    do {
        T next=it.next();
        if(p.test(element, next)) {
            element=join.apply(element, next);
        }
        else {
            result.add(element);
            element=next;
        }
    } while(it.hasNext());
    result.add(element);
    return result;
}

这可能像

一样使用
List<String> result = joinElements(list,
    (a,b) -> isTextNode(a) && isTextNode(b), (a,b) -> new TextNode(a.value+b.value));

为了进行比较,基于流的解决方案可以使用Collector。由于没有内置的收集器用于这样的任务,我们必须定义一个,它基本上与基于循环的解决方案相同,功能仅分布在多个函数上,由迭代的Stream评估:

List<String> result=list.stream().collect(ArrayList::new,
    (l,n) -> l.add(!l.isEmpty() && isTextNode(l.get(l.size()-1)) && isTextNode(n)?
                new TextNode(l.remove(l.size()-1).value+n.value): n),
    (l1,l2) -> {
        if(!l2.isEmpty() && !l1.isEmpty()
           && isTextNode(l1.get(l1.size()-1)) && isTextNode(l2.get(0))) {
                l2.set(0, new TextNode(l1.remove(l1.size()-1).value+l2.get(0).value));
        }
        l1.addAll(l2);
    });

决定自己这是否比循环变体更好......

答案 1 :(得分:0)

我强烈感觉你不是在讨论Collection API中的列表,而是讨论DOM节点。在那种情况下,你正在做不必要的工作。看看

Node.normalize()

  

将所有Text个节点放在此Node下的子树的完整深度(包括属性节点)中,形成“正常”形式,其中只有结构(例如,元素,注释,处理指令) ,CDATA部分和实体引用)分隔Text个节点,即既没有相邻的Text节点也没有空Text个节点。

结果没有相邻的Text个节点意味着加入先前相邻的Text个节点。所以你要做的就是在parant节点上调用normalize()