Oto mój problem: Mam sekwencję S (niepustych, ale prawdopodobnie nie odrębnych) zestawów s_i, a dla każdego s_i trzeba wiedzieć, ile zestawów s_j w S (i ≠ j) są podzbiorami s_i.Generowanie DAG z posetu przy użyciu ściśle funkcjonalnego programowania
Potrzebuję również przyrostowej wydajności: po uzyskaniu wszystkich danych mogę zastąpić jeden zestaw s_i przez pewien podzbiór s_i i aktualizować liczniki przyrostowo.
Wykonanie tego wszystkiego przy użyciu czysto funkcjonalnego kodu byłoby ogromnym plusem (koduję w Scali).
Jako że włączenie do zestawu jest częściowym porządkiem, pomyślałem, że najlepszym sposobem rozwiązania mojego problemu byłoby zbudowanie DAG, które reprezentowałoby diagram Hassego zestawów, z krawędziami reprezentującymi inkluzja i dołączanie wartości całkowitej do każdego węzła reprezentujący rozmiar subdag poniżej węzła plus 1. Jednak utknąłem przez kilka dni, próbując opracować algorytm, który buduje diagram Hassego z częściowego porządkowania (nie mówmy o inkrementalności!), mimo że myślałem to byłby jakiś standardowy materiał licencjacki.
Oto moja struktura danych:
case class HNode[A] (
val v: A,
val child: List[HNode[A]]) {
val rank = 1 + child.map(_.rank).sum
}
My DAG jest zdefiniowany przez liście i korzenie jakiegoś częściowego zamawiającego:
class Hasse[A](val po: PartialOrdering[A], val roots: List[HNode[A]]) {
def +(v: A): Hasse[A] = new Hasse[A](po, add(v, roots))
private def collect(v: A, roots: List[HNode[A]], collected: List[HNode[A]]): List[HNode[A]] =
if (roots == Nil) collected
else {
val (subsets, remaining) = roots.partition(r => po.lteq(r.v, v))
collect(v, remaining.map(_.child).flatten, subsets.filter(r => !collected.exists(c => po.lteq(r.v, c.v))) ::: collected)
}
}
jestem całkiem zatrzymany tutaj. Ostatnim wymyśliłem, aby dodać nową wartość V do DAG jest:
- znaleźć wszystkie „podzbiory root” rs_i V w DAG, czyli podzbiory v takie, że nie rozszerzeniem rs_i jest podzbiorem v. Można to zrobić całkiem łatwo, wykonując wyszukiwanie (BFS lub DFS) na wykresie (funkcja
collect
, prawdopodobnie nieoptymalna lub nawet wadliwa). - zbuduj nowy węzeł n_v, którego potomkami są poprzednio znalezione rs_i.
- Teraz znajdźmy, gdzie powinno być dołączone n_v: dla danej listy pierwiastków, dowiedz się, jakie są supersetne v. Jeśli nie ma żadnych, dodaj n_v do korzeni i usuń podzbiory n_v z korzeni. W przeciwnym razie wykonaj krok 3 rekursywnie na dzieciach nadzorów.
Jeszcze nie wdrożyłem w pełni tego algorytmu, ale wydaje mi się, że jest on niepotrzebnie przerywany i nieoptymalny dla mojego pozornie prostego problemu. Czy jest jakiś prosty algorytm (Google nie ma o tym pojęcia)?
Algorytm ten wydaje mi się wyjątkowo prosty, a nie niepotrzebnie zawiłowany. Czym dokładnie jest problem? Kod Scala będzie ledwie dłuższy niż twój opis. (Chociaż nie sądzę, że opisałeś to w pełni). –
Cóż, odkąd dostałem się do programowania funkcjonalnego (~ 6 miesięcy temu), byłem przyzwyczajony do jednolinijkowych, gdy zajmowałem się rekurencyjnymi strukturami danych. Czuł się niezręcznie opracować trójstopniowy algorytm, który nie jest związany z pojedynczym wywołaniem rekursywnym (krok 1. jest odłączony od kroku 3.) Te algorytmy sprawdzają również dwa podzestawy (krok 1 i 3), co jest odczuwalne. źle. – scand1sk
Jako odniesienie, ostatnio zaimplementowałem stertę dwumianową, która wydawała się znacznie łatwiejsza (choć prawdopodobnie wynika to z lepszej definicji algorytmów). – scand1sk