Java:轻松地在列表中向后或向前查看

时间:2014-12-27 16:03:53

标签: java list iterator lexical-analysis

我正在编写一个java中的词法分析器,需要在自定义数据类型列表(我的标记)中轻松地向后或向前查看。我已经尝试将下一个和上一个项目保存为副本,但后来我发现我需要任意向前或向后看。然后我尝试使用一个索引,但是调试之外是不愉快的,因为我不得不考虑减少,增加并获得当前位置(我甚至让对象存储它们所处位置的int)所有虽然保持在列表的范围内,所以这也是一个难看的,难以阅读的意大利面条代码。

然后我调查了linked lists,但他们的工作方式并不像我想要的那样。我想要一个节点,我希望能够向前看两三个位置,或者回来,我在那个地方找不到任何好的工具。

现在,我正在尝试iterators,但我遇到与索引相同的问题:我必须减少并再次增加到我所处的位置,因为next()移动光标而不仅仅是“偷看“。

我正在考虑编写自己的链接列表,如果我想前进两步,只需点击node.next().next(),或者如果我想要比这更长时间,我会反复点击它。 Java中有没有内置的方法可以拯救我?

2 个答案:

答案 0 :(得分:1)

对于向前和向后遍历,您可以使用ListIterator而不是Iterator。你可以从LinkedList获取它:

http://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html#listIterator(int)

答案 1 :(得分:1)

你得到了意大利面条代码,因为你没有关注SoC。帮助自己的一种方法是创建一个专门的集合类来实现函数,对于你的问题域,隐藏数组导航的丑陋细节,例如跟踪当前位置,来回迭代N步,来回“偷看”,等

有一百种方法可以做到这一点,但在我下面的代码示例中,我选择编写而不是扩展ArrayList<>类。我之所以选择ArrayList<>,是因为它具有随机访问功能,并选择不延伸以避免直接从客户端代码操纵ArrayList<>并重新陷入意大利面。我没有考虑性能,但实际上ArrayList<>的随机访问函数主要是O(1)而不是O(n),如果你使用了迭代器或链表,你会得到它们。使用这些集合类型,您也将被迫遍历集合,只是为了窥视一个会进一步损害性能的对象,并使实现更加困难。

以下是我建议的解决方案的a link to an Ideone implementation。由于在线Java编译器的复杂性,它与下面显示的代码略有不同,但代码易于访问且完全可执行。

代码示例注释:这是一个完整的工作示例,其中包含演示概念所需的三个类。有一个类可以保存main函数,该函数演示了集合类的用法,也可以作为穷人的单元测试。有一个POJO - 样式类来表示节点或标记,最后是实用程序类,它公开一组专门的函数或API。代码非常基本和天真。没有任何错误或边界检查,但它很好地证明了我的建议,恕我直言。

代码!这是一个main函数,它使用类似Java的任意代码行初始化NodeList,然后继续查看并移动到令牌列表中。请注意,客户端代码中不需要变量来跟踪正在发生的事情。导航全部在NodeList类中处理。客户代码的担忧现在不包括丑陋。

import java.util.*;
import java.io.*;

public class TestNodeList {
  public static void main(String[] args) {
    // usage: basic initialization
    NodeList nl = new NodeList();
    nl.add(new Node("someUtilObj")); 
    nl.add(new Node("."));
    nl.add(new Node("print"));
    nl.add(new Node("("));
    nl.add(new Node("myIntValue"));
    nl.add(new Node(")"));
    nl.add(new Node(";"));
    nl.print();

    // usage: using the API, moving and peeking
    nl.peekAhead(1).print();
    nl.peekAhead(2).print();
    nl.peekAhead(3).print();
    nl.moveAhead(2).print();
    nl.getCurrentNode().print();
    nl.peekBack(2).print();
  }
}

这是专门的集合的实现,我认为这些字段和函数对你的词法分析很有用。同样,它非常简单,但涵盖了更重要的概念。

public class NodeList {
  private ArrayList<Node> nodeList = new ArrayList<Node>();
  private int currentNodeIndex = 0;

  public void add(Node node) {
    nodeList.add(node);
  }
  // Node is private/read-only - currentNode should only be set by internal operations
  public Node getCurrentNode() { 
    return nodeList.get(currentNodeIndex);
  }

  // moving back and forth
  public Node moveAhead(int count) { 
    currentNodeIndex += count;
    return nodeList.get(currentNodeIndex);
  }
  public Node moveBack(int count) { 
    currentNodeIndex -= count;
    return nodeList.get(currentNodeIndex);
  } 

  // peeking back and forth
  public Node peekAhead(int count) {
    return nodeList.get(currentNodeIndex + count);
  }
  public Node peekBack(int count) {
      return nodeList.get(currentNodeIndex - count);
  }

  public void print() {
    for (int i=0; i<nodeList.size(); i++) {
      System.out.print(nodeList.get(i).getToken());
    }
    System.out.println("");
  }
}

考虑实施更好,更清洁的API的其他功能:

  • peekNext() - 与peekAhead(1)相同,但没有幻数。我认为这也是您专业收藏中最常被调用的功能,因此有一个比peekAhead(1)
  • 更短,更清晰的操作版本是有意义的。
  • peekPrev() - 与peekBack(1)相同,但没有幻数
  • moveNext() - 与moveAhead(1)相同,但没有幻数。这也是API中经常调用的函数,以及moveAhead(1)
  • 的更简洁版本
  • movePrev() - 与moveBack(1)相同,但没有幻数
  • peekAt(int) - 查看集合中特定索引的元素
  • jumpTo(int) - 将当前位置移至集合中特定索引处的元素
  • moveFirst() - 将当前位置重置为集合中的第0个元素

以下是一些,但我不确定它们会非常有用:

  • moveLast() - 将当前位置设置为集合中的最后一个元素
  • peekFirst() - 查看集合中的第0个元素
  • peekLast() - 查看集合中的最后一个元素

要正确实现上面列出的功能,您应保持一致,并将它们视为过载。因此,例如,内部peekNext()实际上只会调用peekAhead(1)。如果核心函数peekAhead的实现需要更改,这将使您的API行为保持一致且易于维护。

最后,这是POJO。它只包含一个字段,标记值和一个帮助将值写入控制台的函数。请注意,该类没有自己的索引,因为它没有必要。

// Your node/token class
public class Node {
  private String token;
  public Node(String token) {
    this.token = token;
  }
  public String getToken() {
    return token;
  }
  public void print() {
    System.out.println(token);
  }
}