2009-07-07 7 views
5

Das Problem: Pflegen Sie eine bidirektionale Viele-zu-Eins-Beziehung zwischen Java-Objekten.Einfache Datenbank-ähnliche Erfassungsklasse in Java

So etwas wie die Google/Commons Sammlungen bidi Karten, aber ich möchte doppelte Werte auf der Vorderseite ermöglichen und Sätze der vorderen Tasten wie die Rückseite Werte aufweisen. Gebraucht etwas wie folgt aus:

// maintaining disjoint areas on a gameboard. Location is a space on the 
// gameboard; Regions refer to disjoint collections of Locations. 

MagicalManyToOneMap<Location, Region> forward = // the game universe 
Map<Region, <Set<Location>>> inverse = forward.getInverse(); // live, not a copy 
Location parkplace = Game.chooseSomeLocation(...); 
Region mine = forward.get(parkplace); // assume !null; should be O(log n) 
Region other = Game.getSomeOtherRegion(...); 
// moving a Location from one Region to another: 
forward.put(parkplace, other); 
// or equivalently: 
inverse.get(other).add(parkplace); // should also be O(log n) or so 
// expected consistency: 
assert ! inverse.get(mine).contains(parkplace); 
assert forward.get(parkplace) == other; 
// and this should be fast, not iterate every possible location just to filter for mine: 
for (Location l : mine) { /* do something clever */ } 

Die einfache Java-Ansätze sind: 1. Um nur eine Seite der Beziehung zu pflegen, entweder als Map<Location, Region> oder ein Map<Region, Set<Location>>, und die inverse Beziehung von Iteration zu sammeln, wenn erforderlich; Oder 2. Einen Wrapper erstellen, der die Maps beider Seiten verwaltet und alle mutierenden Aufrufe abfängt, um beide Seiten synchron zu halten.

1 ist O (n) anstelle von O (log n), was ein Problem wird. Ich begann mit 2 und war sofort im Unkraut. (Wissen Sie, wie viele verschiedene Möglichkeiten es gibt, einen Map-Eintrag zu ändern?)

Dies ist fast trivial in der SQL-Welt (Location-Tabelle erhält eine indizierte RegionID-Spalte). Gibt es etwas Offensichtliches, das ich vermisse, das es für normale Objekte trivial macht?

+1

Ich zögere, dies als eine Antwort zu setzen, weil ich nicht weiß, was Sie tatsächlich tun, aber für was es wert ist, denke ich, dass Sie nur die falsche Datenstruktur verwenden. Was Sie beschreiben, ist keine Map oder eine Menge, sondern ein Graph, bei dem alle Ecken einer beliebigen Anzahl anderer Ecken benachbart sind. Sie erhalten ein besseres Programmiermodell und eine bessere Laufzeit, indem Sie eine geeignete Diagrammdatenstruktur verwenden, anstatt Sätze und Karten willkürlich zusammen zu kleben. – Juliet

+0

Wahrscheinlich. Alles, was ich von Maps and Sets herhabe, ist die sehr grundlegende Semantik und die Log-n-Leistung. Wenn eine bessere Struktur eine vage kartografische "Ansicht" der Dinge bietet, perfekt. Ich erforsche http://jung.sourceforge.net/ dank einer jetzt gelöschten Antwort und es sieht vielversprechend aus. – rgeorge

+0

Sind Ihnen die Kosten der Einfügung wichtig? (Meinst du auch, dass andere in dieser letzten Zeile stehen?) – Stobor

Antwort

0

Ich denke, Sie könnten mit den folgenden zwei Klassen erreichen, was Sie brauchen. Obwohl es sich um zwei Karten handelt, sind sie nicht der Außenwelt ausgesetzt, daher sollte es für sie keine Möglichkeit geben, nicht synchron zu sein. Wenn ich die gleiche "Tatsache" zweimal speichere, glaube ich nicht, dass Sie das in irgendeiner effizienten Implementierung umgehen können, egal ob die Tatsache doppelt so gespeichert wird wie hier, oder implizit, wie wenn Ihre Datenbank einen Index erstellt um Joins effizienter an Ihren 2 Tischen zu machen. Sie können dem magicset neue Dinge hinzufügen und beide Mappings aktualisieren, oder Sie können dem magmapper Dinge hinzufügen, die dann die inverse Map automatisch aktualisieren. Die Freundin ruft mich jetzt ins Bett, also kann ich das nicht durch einen Compiler laufen lassen - es sollte genug sein, um loszulegen. Welches Rätsel versuchen Sie zu lösen?

public class MagicSet<L> { 
    private Map<L,R> forward; 
    private R r; 
    private Set<L> set; 

    public MagicSet<L>(Map forward, R r) { 
     this.forward = map; 
     this.r = r; 
     this.set = new HashSet<L>(); 
    } 

    public void add(L l) { 
     set.add(l); 
     forward.put(l,r); 
    } 

    public void remove(L l) { 
     set.remove(l); 
     forward.remove(l); 
    } 

    public int size() { 
     return set.size(); 
    } 

    public in contains(L l){ 
     return set.contains(l); 
    } 

    // caution, do not use the remove method from this iterator. if this class was going 
    // to be reused often you would want to return a wrapped iterator that handled the remove method properly. In fact, if you did that, i think you could then extend AbstractSet and MagicSet would then fully implement java.util.Set. 
    public Iterator iterator() { 
     return set.iterator(); 
    } 
} 



public class MagicMapper<L,R> { // note that it doesn't implement Map, though it could with some extra work. I don't get the impression you need that though. 
private Map<L,R> forward; 
private Map<R,MagicSet<L>> inverse; 

public MagicMapper<L,R>() { 
     forward = new HashMap<L,R>; 
     inverse = new HashMap<R,<MagicSet<L>>; 
} 


public R getForward(L key) { 
     return forward.get(key); 
} 


public Set<L> getBackward(R key) { 
     return inverse.get(key); // this assumes you want a null if 
           // you try to use a key that has no mapping. otherwise you'd return a blank MagicSet 
} 

public void put (L l, R r) { 

     R oldVal = forward.get(l); 

     // if the L had already belonged to an R, we need to undo that mapping 
     MagicSet<L> oldSet = inverse.get(oldVal); 

     if (oldSet != null) {oldSet.remove(l);} 

     // now get the set the R belongs to, and add it. 
     MagicSet<L> newSet = inverse.get(l); 

     if (newSet == null) { 
       newSet = new MagicSet<L>(forward, r); 
       inverse.put(r,newSet); 
     } 
     newSet.add(l); // magically updates the "forward" map 
} 


} 
+0

Wow. Vielen Dank. Das entspricht tatsächlich meinem fehlgeschlagenen Versuch # 2. Ich hätte wahrscheinlich versucht, kleinere Bissen zu nehmen. Das Rätsel, das untersucht wird, ist Nurikabe - Ich mache einen Editor für meinen eigenen Gebrauch, um hochwertige Nurikabe-Puzzles zu machen (die automatisch erzeugten auf puzzle-nurikabe.com sind meh) und dafür brauche ich einen schnellen Löser, um meine Arbeit zu überprüfen und schätze Schwierigkeit. – rgeorge

+0

Wenn Sie wirklich wirklich wollten, dass diese die java.util Set- und Map-Interfaces implementieren, sollten Sie das Javadoc auf AbstractMap und AbstractSet lesen - sie erledigen einen Teil der Arbeit für Sie. –

1

Ich könnte Ihr Modell missverstehen, aber wenn Ihr Standort und Ihre Region korrekt equals() und hashCode() implementiert haben, dann ist der Satz von Ort -> Region nur eine klassische einfache Map - Implementierung (mehrere unterschiedliche Schlüssel können auf den verweisen gleicher Objektwert). Die Region -> Set of Location ist eine Multimap (verfügbar in Google Coll.). Sie könnten Ihre eigene Klasse mit den richtigen add/remove-Methoden erstellen, um beide Submaps zu bearbeiten.

Vielleicht ein Overkill, aber Sie könnten auch In-Memory-SQL-Server (HSQLDB, etc) verwenden. Es ermöglicht Ihnen, einen Index für viele Spalten zu erstellen.

+0

Ja, aus zwei Submaps zu komponieren, versuche ich zu vermeiden. Es speichert die gleiche "Tatsache" zweimal ("A ist in B" und "B enthält A"), und es gibt eine Menge Möglichkeiten, einen Map-Eintrag zu ändern, den ich fangen müsste, um die beiden konsistent zu halten. Ich kann das SQL-Ding mit SQLite oder Ähnlichem machen, wenn ich nicht einen besseren Weg mit einer mir unbekannten Datenstruktur durchlaufe. – rgeorge

+2

Wenn ich es wäre, würde ich jedes interne Durcheinander von 'Set's und' Map's, die ich dachte, funktionieren, schreiben Sie einige Tests gegen die externe Schnittstelle, und ersetze die Interna später, wenn ich zufällig rüber kam eine bessere Datenstruktur. (Und ich würde die externe Schnittstelle nicht allgemeiner machen, als ich es brauche.) Können Sie die Submaps nicht kapseln, so dass sie von außerhalb Ihrer Schnittstelle nicht zugänglich sind, und die tatsächlichen Karteneinträge nie aussetzen? Dann verwenden Sie nur die Submaps für die Buchhaltung, und Sie müssen wirklich nichts "fangen". –