我正在编写一个java中的词法分析器,需要在自定义数据类型列表(我的标记)中轻松地向后或向前查看。我已经尝试将下一个和上一个项目保存为副本,但后来我发现我需要任意向前或向后看。然后我尝试使用一个索引,但是调试之外是不愉快的,因为我不得不考虑减少,增加并获得当前位置(我甚至让对象存储它们所处位置的int)所有虽然保持在列表的范围内,所以这也是一个难看的,难以阅读的意大利面条代码。
然后我调查了linked lists,但他们的工作方式并不像我想要的那样。我想要一个节点,我希望能够向前看两三个位置,或者回来,我在那个地方找不到任何好的工具。
现在,我正在尝试iterators,但我遇到与索引相同的问题:我必须减少并再次增加到我所处的位置,因为next()移动光标而不仅仅是“偷看“。
我正在考虑编写自己的链接列表,如果我想前进两步,只需点击node.next().next()
,或者如果我想要比这更长时间,我会反复点击它。 Java中有没有内置的方法可以拯救我?
答案 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);
}
}