2009-07-07 17 views
5

Problem: Utrzymywanie dwukierunkowej relacji wielu do jednego między obiektami Java.Prosta klasa kolekcji podobna do bazy danych w języku Java

coś jak Google/Commons Collections BiDi map, ale chcę pozwalają zduplikowane wartości na przedniej stronie i mieć zestawy z przodu kluczy jako odwróconych wartości ubocznych. Używane coś takiego:

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

prostych metodach java: 1. Aby zachować tylko jedną stronę relacji, zarówno jako Map<Location, Region> lub Map<Region, Set<Location>> i zebrać odwrotną zależność od iteracji, gdy są potrzebne; Lub 2. Aby utworzyć opakowanie zachowujące mapy obu stron i przechwytywać wszystkie wywołania mutacji, aby obie strony były zsynchronizowane.

1 to O (n) zamiast O (log n), co staje się problemem. Zacząłem od 2 i od razu znalazłem się w chwastach. (Wiesz, ile różnych sposobów modyfikacji wpisu mapy?)

Jest to prawie banalne w świecie sql (tabela Lokalizacja pobiera indeksowaną kolumnę RegionID). Czy jest coś oczywistego, czego mi brakuje, co czyni go trywialnym dla normalnych obiektów?

+1

Nie chcę tego ująć jako odpowiedzi, ponieważ nie wiem, co właściwie robisz, ale na ile jest to warte, myślę, że używasz niewłaściwej struktury danych. To, co opisujesz, nie jest mapą ani zbiorem, jest to wykres, w którym wszystkie wierzchołki sąsiadują z dowolną liczbą innych wierzchołków. Otrzymasz lepszy model programowania + środowisko wykonawcze, używając odpowiedniej struktury danych wykresu, zamiast sklejać ze sobą zestawy i mapy w sposób chaotyczny. – Juliet

+0

Prawdopodobnie. Wszystko, czego oczekuję od Map and Sets, to bardzo podstawowa semantyka i wydajność log-n. Jeśli lepsza struktura zapewnia niejasny "widok" rzeczy, idealny. Eksploruję http://jung.sourceforge.net/ dzięki usuniętej odpowiedzi i wygląda obiecująco. – rgeorge

+0

Czy koszt wstawek jest dla ciebie ważny? (Co więcej, masz na myśli inny zestaw w ostatniej linii?) – Stobor

Odpowiedz

0

Myślę, że możesz osiągnąć to, czego potrzebujesz w dwóch poniższych klasach. Chociaż obejmuje dwie mapy, nie są one eksponowane na świat zewnętrzny, więc nie powinno być sposobu na ich zsynchronizowanie. Jeśli chodzi o dwukrotne przechowywanie tego samego "faktu", nie sądzę, że obejdzie się to w jakiejkolwiek skutecznej implementacji, niezależnie od tego, czy fakt jest przechowywany dwa razy jawnie, tak jak tutaj, lub w sposób dorozumiany, jak wtedy, gdy twoja baza danych tworzy indeks aby usprawnić połączenia na twoich 2 stołach. możesz dodać nowe rzeczy do magicznego zestawu, i zaktualizuje oba odwzorowania, lub możesz dodać rzeczy do magicznej mapy, która zaktualizuje odwrotną mapę auotmatycznie. Dziewczyna dzwoni do mnie już teraz, więc nie mogę uruchomić tego przez kompilator - powinno wystarczyć, żebyś zaczął. jakie puzzle próbujesz rozwiązać?

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. Dzięki. To rzeczywiście jest na wzór mojej nieudanej próby nr 2. Prawdopodobnie powinienem próbować brać mniejsze ugryzienia. Puzzlem, którego dotyczy dochodzenie, jest Nurikabe - przygotowuję własny edytor do tworzenia wysokiej jakości puzzli Nurikabe (te z autogeneracją na stronie puzzle-nurikabe.com to meh) i do tego potrzebuję szybkiego solver, aby sprawdzić moją pracę i oszacuj trudność. – rgeorge

+0

Jeśli z jakiegoś powodu naprawdę chciałeś, aby implementowały interfejsy ustawiania i mapowania java.util, powinieneś przeczytać javadoc na temat AbstractMap i AbstractSet - wykonują dla ciebie część pracy. –

1

Mogę źle zrozumieć twój model, ale jeśli Twoja lokalizacja i region mają poprawne equals() i hashCode() zaimplementowane, to zestaw Lokalizacja -> Region to tylko klasyczna prosta implementacja mapy (wiele różnych kluczy może wskazywać na ta sama wartość obiektu). Region -> Zestaw lokalizacji to Multimap (dostępny w Google Coll.). Możesz skomponować własną klasę z odpowiednimi metodami dodawania/usuwania, aby manipulować obydwoma submaps.

Może przesada, ale można również użyć serwera sql w pamięci (HSQLDB itp.). Umożliwia tworzenie indeksu na wielu kolumnach.

+0

Tak, komponowanie z dwóch podmenu jest tym, czego staram się uniknąć. Przechowuje ten sam "fakt" dwa razy ("A jest w B" i "B zawiera A"), i istnieje * dużo * sposobów modyfikowania wpisu mapy, który musiałbym złapać, aby zachować dwa spójne. Mogę dobrze skończyć robiąc sql z sqlite lub podobnym, jeśli nie natknę się na lepszy sposób, wykorzystując nową strukturę danych. – rgeorge

+2

Gdybym to był ja, wpadłbym w komplecie z jakimkolwiek wewnętrznym bałaganem "Setu" i "Mapy", który według mnie działał, napisz kilka testów na zewnętrznym interfejsie i wymień na późniejszych wersjach, jeśli zdarzy mi się, że natknę się na lepsza struktura danych. (Nie chciałbym, aby interfejs zewnętrzny był bardziej ogólny, niż jest to potrzebne). Nie możesz zamknąć enkapsów, aby nie były dostępne spoza twojego interfejsu i nigdy nie ujawniały rzeczywistych wpisów na mapie? Następnie używasz podklejek do księgowości i nie musisz niczego "łapać". –

Powiązane problemy