2012-03-28 5 views
10

Dies ist kein Hausaufgaben-Problem. Diese Frage wurde einem meiner Freunde in einem Interviewtest gestellt.Mapping-Liste der Zeichenfolge in hierarchische Struktur von Objekten

Ich habe eine list Zeilen gelesen aus einer Datei als Eingabe. Jede Zeile hat eine Kennung wie (A, B, NN, C, DD) am Anfang der Zeile. Abhängig von der Kennung muss ich die Liste der Datensätze in ein einzelnes Objekt A abbilden, das eine hierarchische Struktur von Objekten enthält.

enter image description here

Beschreibung der Hierarchie: Jeder A haben kann null oder mehr B Typen. Jeder B Bezeichner kann null oder mehr NN und C als Kind haben. Ebenso kann jedes C Segment null oder mehr NN und DD Kind haben. Abd jede DD kann 0 oder NN als Kind haben.

Mapping-Klassen und ihre Hierarchie:

die All-Klasse wird value haben den String Wert von aktuellen Zeile zu halten.

**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; 
     } 
    } 

, was ich bisher getan habe:

Die Methode public A parse(List<String> lines) liest die Eingabeliste und gibt das Objekt A. Da es möglicherweise mehrere B gibt, habe ich separate Methode 'parseB erstellt, um jedes Auftreten zu analysieren.

Bei parseB Methode, durchläuft die i = startIndex + 1 to i < lines.size() und überprüft den Anfang der Zeilen. Das Auftreten von "NN" wird zu dem aktuellen Objekt von B hinzugefügt. Wird beim Start "C" erkannt, ruft es eine andere Methode parseC auf. Die Schleife wird unterbrochen, wenn wir beim Start "B" oder "A" erkennen.

Eine ähnliche Logik wird in parseC_DD verwendet.

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 

    } 
} 

Meine Logik funktioniert nicht richtig. Gibt es einen anderen Ansatz, den Sie herausfinden können? Hast du irgendwelche Vorschläge/Verbesserungen auf meinem Weg?

+6

"Meine Logik funktioniert nicht". Dieser Satz enthält keine Informationen. Bitte erklären Sie, welche Ergebnisse Sie erwarten, was Sie bekommen und warum sollten Sie das erstere und nicht das letztere bekommen. –

Antwort

7

Verwenden Hierarchie von Objekten:


    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; 
     } 
    } 

erstellen Baumklasse:

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; 
    } 
    ... 
} 

Und dann den Baum drucken:


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

danke für die tolle Antwort. Schöne Baumlösung. Aber es braucht viele Änderungen an den Klassen wie A, B, C, DD, NN. – gtiwari333

+1

Sie können A-, B- und C-Klassen von einem Knoten entkoppeln. Lassen Sie nur die Methode canInsert(). Oder Sie können sie sogar vollständig leer machen, wenn Sie eine Einfügestrategie in eine Tree-Klasse einfügen. –

+0

danke nochmal. +50 für dich ..: D – gtiwari333

3

A mehlig Automat Lösung mit 5 Zuständen: warte auf A, A gesehen, gesehen B, C gesehen und gesehen DD.

Die Analyse erfolgt vollständig in einer Methode. Es gibt einen current Knoten, der der letzte Knoten mit Ausnahme der NN Knoten ist. Ein Knoten hat einen übergeordneten Knoten außer dem Stamm. Im Zustand gesehen (0) die current Knoten repräsentieren ein (0) (beispielsweise im Zustand C kann C1 in dem obigen Beispiel current sehen). Das am meisten fummelnde ist im Zustand gesehen DD, das die meisten ausgehenden Kanten hat (B, C, DD und NN).

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; 
    } 
} 

Ich bin nicht mit diesem trainwrecks zufrieden die Hierarchie gehen woanders einen Knoten einzufügen (current.PN.PN). Schließlich würden explizite Zustandsklassen die private parse-Methode lesbarer machen. Dann wird die Lösung der von @AlekseyOtrubennikov bereitgestellten Lösung ähnlicher. Vielleicht ergibt ein direkter LL Ansatz Code, der schöner ist. Vielleicht am besten, die Grammatik einfach in eine BNF umformulieren und Parser-Erstellung delegieren.


Ein einfacher LL-Parser, eine Produktionsregel:

// "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 verbessert Iterable<Token>, indem in den Strom Vorgriff - LL(1) weil Parser zwischen wörtlichen NN oder Tieftauchen in zwei Fällen wählen müssen (rule_2 einen von ihnen). Sieht gut aus, aber hier fehlen einige C# -Features ...

+0

Hallo Stefan! Der LL-Parser funktioniert in der Regel wie ein Code in meiner Antwort. Mein Code verwendet eine Baumstruktur, Ihre verwendet einen Rekursionsbaum. –

+0

Ja, die 'canInsert' Methoden ähneln dem Token/Node Lookahead. Irgendwie sind in Ihrer Lösung Produktionsregeln und der entsprechende Knoten in einer Klasse zusammengefaßt. Und da Knoten ihre Kinder kennen, kann der Code auch komplexere Grammatiken verarbeiten. Hmm, ich glaube, ich muss deine Lösung neu implementieren :) –

3

@Stefan und @Aleksey sind richtig: das ist ein einfaches Parsing-Problem. Sie können Ihre Hierarchie Einschränkungen in Extended Backus-Naur Form definieren:

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

Diese Beschreibung kann in Zustandsmaschine und umgesetzt umgewandelt werden. Aber es gibt eine Menge Werkzeuge, die das effektiv für Sie tun können: Parser generators.

Ich poste meine Antwort nur, um zu zeigen, dass es ziemlich einfach ist, solche Probleme mit Haskell (oder einer anderen funktionalen Sprache) zu lösen.
Hier ist vollständige Programm, das Zeichenfolgen Form Stdin liest und geparste Baum auf die Stdout druckt.

-- 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 

Ausführen es mit Ihren Daten in zz.txt wird diesen schönen Baum drucken:

$ ./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 

Und hier ist, wie es fehlerhafte Eingabe Griffe:

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

Ich vermute, das war ein OOP-Problem –

+0

@Aleksey, scheint, dass Sie Recht haben. Das Vorhandensein von "Parsing" -Tag und das Fehlen von "oop" führen zu Verwirrung. –