2016-10-05 10 views
6

Mam strukturę drzewa węzłów tekstowych, które mogą mieć inny węzły tekstowe jak dzieci, i muszę zaktualizować jedną wartość w nim. Jaki jest najłatwiejszy sposób aktualizacji węzła tekstowego, który znajduje się gdzieś głęboko w tym drzewie (lub w ogóle nie jest w tym drzewie)?Aktualizacja wartości w typie rekurencyjnej - wiąz lang

w języku non-niezmiennej, chciałbym po prostu zmienić wartość tej pozycji, i to wszystko, ale jest to dość trudne w niezmiennej języku jak Elm.

type alias Item = 
    { id: String 
    , text: String 
    , children: ChildItems 
    } 

type ChildItems = ChildItems (List Item) 

type alias Model = 
    { rootItem: Item 
    } 

updateItem: Item -> Item -> Item 
updateItem: rootItem item = 
    -- TODO 

... 

update model = 
    case msg of 
    UpdateItem item updatedText -> 
     let 
     updatedItem = { item | text = updatedText } 
     in 
     ({ model | rootItem = (updateItem model.rootItem updatedItem) }, Cmd.none) 

to co wymyśliłem

updateItem: Item.Item -> Item.Item -> Item.Item 
updateItem rootItem updatedItem = 
    if rootItem.id == updatedItem.id then 
    updatedItem 
    else 
    case rootItem.children of 
     Item.ChildItem [] -> 
     rootItem 

     Item.ChildItem children -> 
     let 
      updatedChildren = 
      case children of 
       [] -> 
       [] 

       children -> 
       List.map (\item -> 
        updateItem rootItem item) children 
     in 
      { rootItem | children = Item.ChildItem updatedChildren } 

ale dostaję błąd Maximum call stack size exceeded

Odpowiedz

15

Powodem dostajesz przepełnienie stosu jest dlatego, że zamiast wracać rootItem[] w przypadku Item.ChildItems [].

mam zamiar zmodyfikować kod trochę ponieważ istnieją pewne wspólne wzorce możemy wyciągnąć na zewnątrz. Po pierwsze, weźmy pod spodem drzewo-owski strukturę i sprawiają, że bardziej ogólny, aby mogła ona pasuje do każdego typu rzeczy, nie tylko Item:

type Node a 
    = Node a (List (Node a)) 

To daje nam strukturę, która zawsze ma węzła głównego i może mieć dowolną liczbę dzieci, z których każda może mieć dowolną liczbę dzieci.

Jeśli myślimy o algorytmie Miałeś na, możemy ekstrapolować znajomy wzór. Masz strukturę z wieloma elementami i chcesz mieć algorytm, który odwiedza każdy element i opcjonalnie go zmienia. Brzmi bardzo podobnie do List.map. To taki wspólny idiom, że to dobry pomysł, aby zadzwonić nasza uogólniona funkcja map:

map : (a -> b) -> Node a -> Node b 
map f (Node item children) = 
    Node (f item) (List.map (map f) children) 

(uwaga Side: Właśnie natknął się functors!)

Ponieważ wziąłem pomysł dzieci i umieścił ją w rodzaju Node, musimy zmodyfikować alias Item tak:

type alias Item = 
    { id: String 
    , text: String 
    } 

teraz mamy Item, pomocne będzie posiadanie funkcji, która może ją zaktualizować, jeśli wartość id pasuje do określonej wartości. Ponieważ w przyszłości może mieć więcej funkcji aktualizacji, które chcesz wykonać, to najlepiej, aby utrzymać część odnośnika i pasujący ID oddzielone od funkcji, którą rzeczywiście chcesz wykonać:

updateByID : String -> (Item -> Item) -> Item -> Item 
updateByID id f item = 
    if item.id == id then 
    f item 
    else 
    item 

teraz, aby wykonać aktualizację na zasadzie element odpowiadający identyfikatorowi, w dowolnym miejscu w drzewie, można to zrobić po prostu:

map (updateByID "someID" (\x -> { x | text = "Woohoo!" })) rootNode 
+0

Doskonała odpowiedź! Zrobiłeś mój dzień :) –

+0

Jak zaktualizować zagnieżdżoną listę przedmiotów, używając tego wzoru? Problem mam trafienia jest, że wyrażenie zmiana rekordu ma być wyrazem powrotu dowolnej funkcji wydaje in.I na działanie mające na celu utworzyć nowy element, wyrażenie powrotny powinien być nowy element, a nie elementu lista rodziców dziecka, którą muszę zaktualizować. Czy mamy pismo święte o tym, jak radzić sobie z strukturami rekurencyjnymi (zwanymi "drzewami")? Widziałem instrukcje dotyczące uczynienia elementu aliasem i kolekcją typu; oferujesz inny protokół. –

+0

@RichardHaven, który jest obsługiwany przez funkcję mapowania - zauważ, że w jej ciele tworzy Węzeł złożony z elementu z Węzła, który został podany, ewentualnie zmodyfikowany przez funkcję, oraz potomków z Węzła, który został podany, każdy także ewentualnie zmodyfikowane przez funkcję. Docelowo podczas ładowania pliku updateById do mapy rekurencyjnie zbuduje drzewo węzłów przy użyciu tych samych elementów, z wyjątkiem jednego elementu, który został zmieniony. –