2012-03-28 36 views
10

Đây không phải là vấn đề về bài tập về nhà. Câu hỏi này đã được hỏi một người bạn của tôi trong một bài kiểm tra phỏng vấn.ánh xạ danh sách chuỗi thành cấu trúc phân cấp của các đối tượng

Tôi có một dòng list được đọc từ tệp dưới dạng đầu vào. Mỗi dòng có một định danh như (A, B, NN, C, DD) ở đầu dòng. Tùy thuộc vào định danh, tôi cần ánh xạ danh sách các bản ghi vào một đối tượng đơn lẻ A có chứa cấu trúc phân cấp của các đối tượng.

enter image description here

Mô tả Hierarchy: Mỗi A có thể có không hoặc nhiều B loại. Mỗi số nhận dạng B có thể có số không bằng hoặc lớn hơn NNC làm con. Tương tự, mỗi phân khúc C có thể có số không bằng hoặc lớn hơn NNDD con. Mỗi số điện thoại DD có thể có số không hoặc bằng NN khi còn nhỏ.

lớp Mapping và hệ thống cấp bậc của họ:

Tất cả các lớp học sẽ có value để giữ giá trị String từ dòng hiện tại.

**A - will have list of B** 

    class A { 
     List<B> bList; 
     String value; 

     public A(String value) { 
      this.value = value; 
     } 

     public void addB(B b) { 
      if (bList == null) { 
       bList = new ArrayList<B>(); 
      } 
      bList.add(b); 
     } 
    } 


**B - will have list of NN and list of C** 

    class B { 
      List<C> cList; 
      List<NN> nnList; 
      String value; 
       public B(String value) { 
       this.value = value; 
      } 
       public void addNN(NN nn) { 
       if (nnList == null) { 
        nnList = new ArrayList<NN>(); 
       } 
       nnList.add(nn); 
      } 
       public void addC(C c) { 
       if (cList == null) { 
        cList = new ArrayList<C>(); 
       } 
       cList.add(c); 
      } 
     } 

**C - will have list of DDs and NNs** 

    class C { 
      List<DD> ddList; 
      List<NN> nnList; 
      String value; 
      public C(String value) { 
       this.value = value; 
      } 
      public void addDD(DD dd) { 
       if (ddList == null) { 
        ddList = new ArrayList<DD>(); 
       } 
       ddList.add(dd); 
      } 
      public void addNN(NN nn) { 
       if (nnList == null) { 
        nnList = new ArrayList<NN>(); 
       } 
       nnList.add(nn); 
      } 
     } 

**DD - will have list of NNs** 

    class DD { 
      String value; 
      List<NN> nnList; 
      public DD(String value) { 
       this.value = value; 
      } 
      public void addNN(NN nn) { 
       if (nnList == null) { 
        nnList = new ArrayList<NN>(); 
       } 
       nnList.add(nn); 
      } 
     } 

**NN- will hold the line only** 

    class NN { 
     String value; 

     public NN(String value) { 
      this.value = value; 
     } 
    } 

Những gì tôi đã làm như vậy Viễn:

Phương pháp public A parse(List<String> lines) đọc danh sách đầu vào và trả về đối tượng A. Vì, có thể có nhiều B, tôi đã tạo phương thức riêng biệt 'parseB để phân tích từng lần xuất hiện.

Tại phương thức parseB, lặp lại thông qua i = startIndex + 1 to i < lines.size() và kiểm tra đầu dòng. Sự xuất hiện của "NN" được thêm vào đối tượng hiện tại của B. Nếu "C" được phát hiện lúc khởi động, nó gọi phương thức khác parseC. Vòng lặp sẽ ngắt khi chúng tôi phát hiện "B" hoặc "A" khi bắt đầu.

Logic tương tự được sử dụng trong parseC_DD.

public class GTTest {  
    public A parse(List<String> lines) { 
     A a; 
     for (int i = 0; i < lines.size(); i++) { 
      String curLine = lines.get(i); 
      if (curLine.startsWith("A")) { 
       a = new A(curLine); 
       continue; 
      } 
      if (curLine.startsWith("B")) { 
       i = parseB(lines, i); // returns index i to skip all the lines that are read inside parseB(...) 
       continue; 
      } 
     } 
     return a; // return mapped object 
    } 

    private int parseB(List<String> lines, int startIndex) { 
     int i; 
     B b = new B(lines.get(startIndex)); 
     for (i = startIndex + 1; i < lines.size(); i++) { 
      String curLine = lines.get(i); 
      if (curLine.startsWith("NN")) { 
       b.addNN(new NN(curLine)); 
       continue; 
      } 
      if (curLine.startsWith("C")) { 
       i = parseC(b, lines, i); 
       continue; 
      } 
      a.addB(b); 
      if (curLine.startsWith("B") || curLine.startsWith("A")) { //ending condition 
       System.out.println("B A "+curLine); 
       --i; 
       break; 
      } 
     } 
     return i; // return nextIndex to read 
    } 

    private int parseC(B b, List<String> lines, int startIndex) { 

     int i; 
     C c = new C(lines.get(startIndex)); 

     for (i = startIndex + 1; i < lines.size(); i++) { 
      String curLine = lines.get(i); 
      if (curLine.startsWith("NN")) { 
       c.addNN(new NN(curLine)); 
       continue; 
      }   

      if (curLine.startsWith("DD")) { 
       i = parseC_DD(c, lines, i); 
       continue; 
      } 

      b.addC(c); 
      if (curLine.startsWith("C") || curLine.startsWith("A") || curLine.startsWith("B")) { 
       System.out.println("C A B "+curLine); 
       --i; 
       break; 
      } 
     } 
     return i;//return next index 

    } 

    private int parseC_DD(C c, List<String> lines, int startIndex) { 
     int i; 
     DD d = new DD(lines.get(startIndex)); 
     c.addDD(d); 
     for (i = startIndex; i < lines.size(); i++) { 
      String curLine = lines.get(i); 
      if (curLine.startsWith("NN")) { 
       d.addNN(new NN(curLine)); 
       continue; 
      } 
      if (curLine.startsWith("DD")) { 
       d=new DD(curLine); 
       continue; 
      }  
      c.addDD(d); 
      if (curLine.startsWith("NN") || curLine.startsWith("C") || curLine.startsWith("A") || curLine.startsWith("B")) { 
       System.out.println("NN C A B "+curLine); 
       --i; 
       break; 
      } 

     } 
     return i;//return next index 

    } 
public static void main(String[] args) { 
     GTTest gt = new GTTest(); 
     List<String> list = new ArrayList<String>(); 
     list.add("A1"); 
     list.add("B1"); 
     list.add("NN1"); 
     list.add("NN2"); 
     list.add("C1"); 
     list.add("NNXX"); 
     list.add("DD1"); 
     list.add("DD2"); 
     list.add("NN3"); 
     list.add("NN4"); 
     list.add("DD3"); 
     list.add("NN5"); 
     list.add("B2"); 
     list.add("NN6"); 
     list.add("C2"); 
     list.add("DD4"); 
     list.add("DD5"); 
     list.add("NN7"); 
     list.add("NN8"); 
     list.add("DD6"); 
     list.add("NN7"); 
     list.add("C3"); 
     list.add("DD7"); 
     list.add("DD8"); 
     A a = gt.parse(list); 
      //show values of a 

    } 
} 

Logic của tôi không hoạt động bình thường. Có cách tiếp cận nào khác mà bạn có thể tìm ra không? Bạn có bất cứ gợi ý/cải tiến nào cho tôi không?

+6

"Logic của tôi không hoạt động". Câu này truyền tải thông tin bằng không. Xin giải thích kết quả nào bạn mong đợi, bạn nhận được gì, và tại sao bạn nghĩ rằng bạn nên nhận được cái cũ và không phải là cái sau. –

Trả lời

7

Sử dụng hệ thống phân cấp các đối tượng:


    public interface Node { 
     Node getParent(); 
     Node getLastChild(); 
     boolean addChild(Node n); 
     void setValue(String value); 
     Deque getChildren(); 
    } 

    private static abstract class NodeBase implements Node { 
     ...  
     abstract boolean canInsert(Node n);  
     public String toString() { 
      return value; 
     } 
     ...  
    } 

    public static class A extends NodeBase { 
     boolean canInsert(Node n) { 
      return n instanceof B; 
     } 
    } 
    public static class B extends NodeBase { 
     boolean canInsert(Node n) { 
      return n instanceof NN || n instanceof C; 
     } 
    } 

    ... 

    public static class NN extends NodeBase { 
     boolean canInsert(Node n) { 
      return false; 
     } 
    } 

Tạo một lớp cây:

public class MyTree { 

    Node root; 
    Node lastInserted = null; 

    public void insert(String label) { 
     Node n = NodeFactory.create(label); 

     if (lastInserted == null) { 
      root = n; 
      lastInserted = n; 
      return; 
     } 
     Node current = lastInserted; 
     while (!current.addChild(n)) { 
      current = current.getParent(); 
      if (current == null) { 
       throw new RuntimeException("Impossible to insert " + n); 
      } 
     } 
     lastInserted = n; 
    } 
    ... 
} 

Và sau đó in cây:


public class MyTree { 
    ... 
    public static void main(String[] args) { 
     List input; 
     ... 
     MyTree tree = new MyTree(); 
     for (String line : input) { 
      tree.insert(line); 
     } 
     tree.print(); 
    } 

    public void print() { 
     printSubTree(root, ""); 
    } 
    private static void printSubTree(Node root, String offset) { 
     Deque children = root.getChildren(); 
     Iterator i = children.descendingIterator(); 
     System.out.println(offset + root); 
     while (i.hasNext()) { 
      printSubTree(i.next(), offset + " "); 
     } 
    } 
} 
+0

cảm ơn bạn vì câu trả lời tuyệt vời. Giải pháp cây đẹp. Nhưng nó cần rất nhiều thay đổi đối với các lớp như A, B, C, DD, NN. – gtiwari333

+1

Bạn có thể tách các lớp A, B, C ra khỏi nút; chỉ để lại phương thức canInsert(). Hoặc bạn thậm chí có thể làm cho chúng hoàn toàn trống rỗng nếu bạn chèn chiến lược chèn vào một lớp Tree. –

+0

cảm ơn một lần nữa. 50 cho bạn ..: D – gtiwari333

3

Một giải pháp automaton bở với 5 trạng thái: chờ A, thấy Một, thấy B, thấy C, và thấy DD.

Phân tích cú pháp được thực hiện hoàn toàn theo một phương pháp. Có một current Nút là nút cuối cùng được xem ngoại trừ các nút NN. Nút có nút Node gốc ngoại trừ gốc. Ở trạng thái đã xem (0), nút current Nút đại diện cho (0) (ví dụ: ở trạng thái nhìn thấy C, current có thể là C1 trong ví dụ ở trên). Không quan trọng nhất là ở trạng thái đã thấy DD, có nhiều cạnh ngoài nhất (B, C, DDNN).

public final class Parser { 
    private final static class Token { /* represents A1 etc. */ } 
    public final static class Node implements Iterable<Node> { 
     /* One Token + Node children, knows its parent */ 
    } 

    private enum State { ExpectA, SeenA, SeenB, SeenC, SeenDD, } 

    public Node parse(String text) { 
     return parse(Token.parseStream(text)); 
    } 

    private Node parse(Iterable<Token> tokens) { 
     State currentState = State.ExpectA; 
     Node current = null, root = null; 
     while(there are tokens) { 
      Token t = iterator.next(); 
      switch(currentState) { 
       /* do stuff for all states */ 

       /* example snippet for SeenC */ 
       case SeenC: 
       if(t.Prefix.equals("B")) { 
        current.PN.PN.AddChildNode(new Node(t, current.PN.PN)); 
        currentState = State.SeenB; 
       } else if(t.Prefix.equals("C")) { 

      } 
     } 
     return root; 
    } 
} 

Tôi không hài lòng với những đắm tàu ​​đó đi lên hệ thống phân cấp để chèn nút ở một nơi khác (current.PN.PN). Cuối cùng, các lớp trạng thái rõ ràng sẽ làm cho phương thức riêng tư parse dễ đọc hơn. Sau đó, giải pháp sẽ giống với giải pháp được cung cấp bởi @AlekseyOtrubennikov. Có lẽ cách tiếp cận LL thẳng tạo ra mã đẹp hơn. Có lẽ tốt nhất để chỉ cần rephrase ngữ pháp để một BNF một và ủy quyền tạo phân tích cú pháp.


Một LL phân tích cú pháp đơn giản, quy tắc một sản xuất:

// "B" ("NN" || C)* 
private Node rule_2(TokenStream ts, Node parent) { 
    // Literal "B" 
    Node B = literal(ts, "B", parent); 
    if(B == null) { 
     // error 
     return null; 
    } 

    while(true) { 
     // check for "NN" 
     Node nnLit = literal(ts, "NN", B); 
     if(nnLit != null) 
      B.AddChildNode(nnLit); 

     // check for C 
     Node c = rule_3(ts, parent); 
     if(c != null) 
      B.AddChildNode(c); 

     // finished when both rules did not match anything 
     if(nnLit == null && c == null) 
      break; 
    } 

    return B; 
} 

TokenStream tăng cường Iterable<Token> bằng cách cho phép để lookahead vào trong dòng - LL(1) vì phân tích cú pháp phải lựa chọn giữa đen NN hay lặn sâu trong hai trường hợp (rule_2 là một trong số họ). Có vẻ đẹp, tuy nhiên, thiếu một số tính năng C# ở đây ...

+0

Hi Stefan! Trình phân tích cú pháp LL thường hoạt động giống như một mã trong câu trả lời của tôi. Mã của tôi sử dụng cấu trúc cây, máy của bạn sử dụng cây đệ quy. –

+0

Vâng, các phương thức 'canInsert' tương tự như lookahead của một token/node. Bằng cách nào đó, trong giải pháp của bạn, các quy tắc sản xuất và nút tương ứng được xếp thành một lớp. Và kể từ khi các nút biết con cái của họ, mã cũng có thể đối phó với các ngữ pháp phức tạp hơn. Hmm, tôi nghĩ rằng tôi _have to_ reimplement giải pháp của bạn :) –

3

@Stefan và @Aleksey là chính xác: đây là vấn đề phân tích cú pháp đơn giản. Bạn có thể xác định ràng buộc hệ thống cấp bậc của bạn trong Extended Backus-Naur Form:

A ::= { B } 
B ::= { NN | C } 
C ::= { NN | DD } 
DD ::= { NN } 

Sự mô tả này có thể được chuyển đổi thành máy nhà nước và thực hiện. Nhưng có rất nhiều công cụ có thể thực hiện điều này một cách hiệu quả cho bạn: Parser generators.

Tôi chỉ đăng câu trả lời của mình để cho thấy rằng khá dễ dàng để giải quyết các vấn đề như vậy với Haskell (hoặc một số ngôn ngữ chức năng khác).
Đây là hoàn thành chương trình đọc chuỗi ký tự stdin và in cây được phân tích cú pháp thành giá trị chuẩn.

-- We are using some standard libraries. 
import Control.Applicative ((<$>), (<*>)) 
import Text.Parsec 
import Data.Tree 

-- This is EBNF-like description of what to do. 
-- You can almost read it like a prose. 
yourData = nodeA +>> eof 

nodeA = node "A" nodeB 
nodeB = node "B" (nodeC <|> nodeNN) 
nodeC = node "C" (nodeNN <|> nodeDD) 
nodeDD = node "DD" nodeNN 
nodeNN = (`Node` []) <$> nodeLabel "NN" 

node lbl children 
    = Node <$> nodeLabel lbl <*> many children 

nodeLabel xx = (xx++) 
    <$> (string xx >> many digit) 
    +>> newline 

-- And this is some auxiliary code. 
f +>> g = f >>= \x -> g >> return x 

main = do 
    txt <- getContents 
    case parse yourData "" txt of 
    Left err -> print err 
    Right res -> putStrLn $ drawTree res 

Thi nó với dữ liệu của bạn trong zz.txt sẽ in cây đẹp này:

$ ./xxx < zz.txt 
A1 
+- B1 
| +- NN1 
| +- NN2 
| `- C1 
|  +- NN2 
|  +- DD1 
|  +- DD2 
|  | +- NN3 
|  | `- NN4 
|  `- DD3 
|  `- NN5 
`- B2 
    +- NN6 
    +- C2 
    | +- DD4 
    | +- DD5 
    | | +- NN7 
    | | `- NN8 
    | `- DD6 
    |  `- NN9 
    `- C3 
     +- DD7 
     `- DD8 

Và đây là cách nó xử lý đầu vào bị thay đổi:

$ ./xxx 
A1 
B2 
DD3 
(line 3, column 1): 
unexpected 'D' 
expecting "B" or end of input 
+2

Tôi đoán đó là một vấn đề OOP –

+0

@Aleksey, có vẻ như bạn đã đúng. Sự hiện diện của thẻ 'phân tích cú pháp' và thiếu' oop' dẫn tôi đến sự nhầm lẫn. –

Các vấn đề liên quan