{"id":5415,"date":"2016-04-27T17:19:18","date_gmt":"2016-04-27T15:19:18","guid":{"rendered":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/?p=5415"},"modified":"2016-04-30T07:21:06","modified_gmt":"2016-04-30T05:21:06","slug":"i1m2015-el-tipo-abstracto-de-datos-de-grafos-en-haskell","status":"publish","type":"post","link":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/i1m2015-el-tipo-abstracto-de-datos-de-grafos-en-haskell\/","title":{"rendered":"I1M2015: El tipo abstracto de datos de grafos en Haskell"},"content":{"rendered":"<p>En la clase de hoy del curso de <a href=\"http:\/\/www.cs.us.es\/~jalonso\/cursos\/i1m-15\">Inform\u00e1tica de 1\u00ba del Grado en Matem\u00e1ticas<\/a> hemos estudiado el tipo abstracto de datos de los grafos y dos de sus implementaciones en Haskell: mediante vectores y matrices de adyacencia.<\/p>\n<p>Adem\u00e1s, hemos estudiado los algoritmos de recorrido de los grafos en profundidad y en anchura.<\/p>\n<p>Las transparencias usadas en la clase son las p\u00e1ginas 1-39 del <a href=\"http:\/\/www.cs.us.es\/~jalonso\/cursos\/i1m-15\/temas\/tema-22.pdf\">tema 22<\/a>:<\/p>\n<p>El c\u00f3digo de la implementaci\u00f3n de grafos mediante vectores de adyacencia es<br \/>\n<!--more--><\/p>\n<pre lang=\"haskell\">\nmodule GrafoConVectorDeAdyacencia \n    (Orientacion (..),\n     Grafo,\n     creaGrafo,  -- (Ix v,Num p) => Orientacion -> (v,v) -> [(v,v,p)] -> \n                 --                 Grafo v p\n     dirigido,   -- (Ix v,Num p) => (Grafo v p) -> Bool\n     adyacentes, -- (Ix v,Num p) => (Grafo v p) -> v -> [v]\n     nodos,      -- (Ix v,Num p) => (Grafo v p) -> [v]\n     aristas,    -- (Ix v,Num p) => (Grafo v p) -> [(v,v,p)]\n     aristaEn,   -- (Ix v,Num p) => (Grafo v p) -> (v,v) -> Bool\n     peso        -- (Ix v,Num p) => v -> v -> (Grafo v p) -> p\n    ) where\n\n-- ---------------------------------------------------------------------\n-- Librer\u00edas auxiliares                                               --\n-- ---------------------------------------------------------------------\n\nimport Data.Array\n\n-- Orientacion es D (dirigida) \u00f3 ND (no dirigida).\ndata Orientacion = D | ND\n                   deriving (Eq, Show)\n\n-- (Grafo v p) es un grafo con v\u00e9rtices de tipo v y pesos de tipo p.\ndata Grafo v p = G Orientacion (Array v [(v,p)])\n                 deriving (Eq, Show)\n\n-- (creaGrafo o cs as) es un grafo (dirigido o no, seg\u00fan el valor de o),\n-- con el par de cotas cs y listas de aristas as (cada arista es un tr\u00edo\n-- formado por los dos v\u00e9rtices y su peso). Ver el ejemplo a continuaci\u00f3n.\ncreaGrafo :: (Ix v, Num p) => Orientacion -> (v,v) -> [(v,v,p)] -> Grafo v p\ncreaGrafo o cs vs =\n    G o (accumArray \n         (\\xs x -> xs++[x]) [] cs \n         ((if o == D then []\n           else [(x2,(x1,p))|(x1,x2,p) <- vs, x1 \/= x2]) ++\n          [(x1,(x2,p)) | (x1,x2,p) <- vs]))\n\n-- ejGrafoND es el grafo\n--             12\n--        1 -------- 2\n--        | \\78     \/|\n--        |  \\   32\/ |\n--        |   \\   \/  |\n--      34|     5    |55\n--        |   \/   \\  |\n--        |  \/44   \\ |\n--        | \/     93\\|\n--        3 -------- 4\n--             61\n-- representado mediante un vector de adyacencia; es decir,\n--    ghci> ejGrafoND\n--    G ND array (1,5) [(1,[(2,12),(3,34),(5,78)]),\n--                      (2,[(1,12),(4,55),(5,32)]),\n--                      (3,[(1,34),(4,61),(5,44)]),\n--                      (4,[(2,55),(3,61),(5,93)]),\n--                      (5,[(1,78),(2,32),(3,44),(4,93)])])\nejGrafoND = creaGrafo ND (1,5) [(1,2,12),(1,3,34),(1,5,78),\n                                (2,4,55),(2,5,32),\n                                (3,4,61),(3,5,44),\n                                (4,5,93)]\n\n-- ejGrafoD es el mismo grafo que ejGrafoND pero orientando las aristas;\n-- es decir,\n--    ghci> ejGrafoD\n--    G D array (1,5) [(1,[(2,12),(3,34),(5,78)]),\n--                     (2,[(4,55),(5,32)]),\n--                     (3,[(4,61),(5,44)]),\n--                     (4,[(5,93)]),\n--                     (5,[])])\nejGrafoD = creaGrafo D (1,5) [(1,2,12),(1,3,34),(1,5,78),\n                              (2,4,55),(2,5,32),\n                              (3,4,61),(3,5,44),\n                              (4,5,93)]\n\n-- (dirigido g) se verifica si g es dirigido. Por ejemplo,\n--    dirigido ejGrafoD   ==  True\n--    dirigido ejGrafoND  ==  False\ndirigido :: (Ix v,Num p) => (Grafo v p) -> Bool\ndirigido (G o _) = o == D\n\n-- (nodos g) es la lista de todos los nodos del grafo g. Por ejemplo,\n--    nodos ejGrafoND  ==  [1,2,3,4,5]\n--    nodos ejGrafoD   ==  [1,2,3,4,5]\nnodos :: (Ix v,Num p) => (Grafo v p) -> [v]\nnodos (G _ g) = indices g\n\n-- (adyacentes g v) es la lista de los v\u00e9rtices adyacentes al nodo v en\n-- el grafo g. Por ejemplo,\n--    adyacentes ejGrafoND 4  ==  [2,3,5]\n--    adyacentes ejGrafoD  4  ==  [5]\nadyacentes :: (Ix v,Num p) => (Grafo v p) -> v -> [v]\nadyacentes (G _ g) v = map fst (g!v)\n\n-- (aristaEn g a) se verifica si a es una arista del grafo g. Por\n-- ejemplo,\n--    aristaEn ejGrafoND (5,1)  ==  True\n--    aristaEn ejGrafoND (4,1)  ==  False\n--    aristaEn ejGrafoD (5,1)   ==  False\n--    aristaEn ejGrafoD (1,5)   ==  True\naristaEn :: (Ix v,Num p) => (Grafo v p) -> (v,v) -> Bool\naristaEn g (x,y) = y `elem` adyacentes g x\n\n-- (peso v1 v2 g) es el peso de la arista que une los v\u00e9rtices v1 y v2\n-- en el grafo g. Por ejemplo,\n--    peso 1 5 ejGrafoND  ==  78\n--    peso 1 5 ejGrafoD   ==  78\npeso :: (Ix v,Num p) => v -> v -> (Grafo v p) -> p\npeso x y (G _ g) = head [c | (a,c) <- g!x , a == y]\n\n-- (aristas g) es la lista de las aristas del grafo g. Por ejemplo, \n--    ghci> aristas ejGrafoD\n--    [(1,2,12),(1,3,34),(1,5,78),(2,4,55),(2,5,32),(3,4,61),\n--     (3,5,44),(4,5,93)] \n--    ghci> aristas ejGrafoND\n--    [(1,2,12),(1,3,34),(1,5,78),(2,1,12),(2,4,55),(2,5,32),\n--     (3,1,34),(3,4,61),(3,5,44),(4,2,55),(4,3,61),(4,5,93),\n--     (5,1,78),(5,2,32),(5,3,44),(5,4,93)]\naristas :: (Ix v,Num p) => (Grafo v p) -> [(v,v,p)]\naristas (G o g) = [(v1,v2,w) | v1 <- nodos (G o g) , (v2,w) <- g!v1] \n<\/pre>\n<p>El c\u00f3digo de la implementaci\u00f3n de grafos mediante matrices de adyacencia es<\/p>\n<pre lang=\"haskell\">\nmodule GrafoConMatrizDeAdyacencia\n    (Orientacion (..),\n     Grafo,\n     creaGrafo,  -- (Ix v,Num p) => Orientacion -> (v,v) -> [(v,v,p)] -> \n                 --                 Grafo v p\n     dirigido,   -- (Ix v,Num p) => (Grafo v p) -> Bool\n     adyacentes, -- (Ix v,Num p) => (Grafo v p) -> v -> [v]\n     nodos,      -- (Ix v,Num p) => (Grafo v p) -> [v]\n     aristas,    -- (Ix v,Num p) => (Grafo v p) -> [(v,v,p)]\n     aristaEn,   -- (Ix v,Num p) => (Grafo v p) -> (v,v) -> Bool\n     peso        -- (Ix v,Num p) => v -> v -> (Grafo v p) -> p\n    ) where\n\n-- ---------------------------------------------------------------------\n-- Librer\u00edas auxiliares                                               --\n-- ---------------------------------------------------------------------\n\nimport Data.Array\n\n-- Orientacion es D (dirigida) \u00f3 ND (no dirigida).\ndata Orientacion = D | ND\n                   deriving (Eq, Show)\n\n-- (Grafo v p) es un grafo con v\u00e9rtices de tipo v y pesos de tipo p.\ndata Grafo v p = G Orientacion (Array (v,v) (Maybe p))\n                 deriving (Eq, Show)\n\n-- (creaGrafo d cs as) es un grafo (dirigido o no, seg\u00fan el valor de o),\n-- con el par de cotas cs y listas de aristas as (cada arista es un tr\u00edo\n-- formado por los dos v\u00e9rtices y su peso). Ver el ejemplo a continuaci\u00f3n.\ncreaGrafo :: (Ix v, Num p) => Orientacion -> (v,v) -> [(v,v,p)] -> (Grafo v p)\ncreaGrafo o cs@(l,u) as \n    = G o (matrizVacia \/\/ \n            ([((x1,x2),Just w) | (x1,x2,w) <- as] ++\n             if o == D then []\n             else [((x2,x1),Just w) | (x1,x2,w) <- as, x1 \/= x2]))\n      where\n      matrizVacia = array ((l,l),(u,u)) \n                          [((x1,x2),Nothing) | x1 <- range cs, \n                                               x2 <- range cs]\n\n-- ejGrafoND es el grafo\n--             12\n--        1 -------- 2\n--        | \\78     \/|\n--        |  \\   32\/ |\n--        |   \\   \/  |\n--      34|     5    |55\n--        |   \/   \\  |\n--        |  \/44   \\ |\n--        | \/     93\\|\n--        3 -------- 4\n--             61\n-- representado mediante una matriz de adyacencia.\n--    ghci> ejGrafoND\n--    G ND array ((1,1),(5,5)) \n--               [((1,1),Nothing),((1,2),Just 12),((1,3),Just 34),\n--                ((1,4),Nothing),((1,5),Just 78),((2,1),Just 12),\n--                ((2,2),Nothing),((2,3),Nothing),((2,4),Just 55),\n--                ((2,5),Just 32),((3,1),Just 34),((3,2),Nothing),\n--                ((3,3),Nothing),((3,4),Just 61),((3,5),Just 44),\n--                ((4,1),Nothing),((4,2),Just 55),((4,3),Just 61),\n--                ((4,4),Nothing),((4,5),Just 93),((5,1),Just 78),\n--                ((5,2),Just 32),((5,3),Just 44),((5,4),Just 93),\n--                ((5,5),Nothing)]\nejGrafoND = creaGrafo ND (1,5) [(1,2,12),(1,3,34),(1,5,78),\n                                (2,4,55),(2,5,32),\n                                (3,4,61),(3,5,44),\n                                (4,5,93)]\n\n-- ejGrafoD es el mismo grafo que ejGrafoND pero orientando las aristas;\n-- es decir,\n--    ghci> ejGrafoD\n--    G D (array ((1,1),(5,5)) \n--               [((1,1),Nothing),((1,2),Just 12),((1,3),Just 34),\n--                ((1,4),Nothing),((1,5),Just 78),((2,1),Nothing),\n--                ((2,2),Nothing),((2,3),Nothing),((2,4),Just 55),\n--                ((2,5),Just 32),((3,1),Nothing),((3,2),Nothing),\n--                ((3,3),Nothing),((3,4),Just 61),((3,5),Just 44),\n--                ((4,1),Nothing),((4,2),Nothing),((4,3),Nothing),\n--                ((4,4),Nothing),((4,5),Just 93),((5,1),Nothing),\n--                ((5,2),Nothing),((5,3),Nothing),((5,4),Nothing),\n--                ((5,5),Nothing)])\nejGrafoD = creaGrafo D (1,5) [(1,2,12),(1,3,34),(1,5,78),\n                              (2,4,55),(2,5,32),\n                              (3,4,61),(3,5,44),\n                              (4,5,93)]\n\n-- (dirigido g) se verifica si g es dirigido. Por ejemplo,\n--    dirigido ejGrafoD   ==  True\n--    dirigido ejGrafoND  ==  False\ndirigido :: (Ix v,Num p) => (Grafo v p) -> Bool\ndirigido (G o _) = o == D\n\n-- (nodos g) es la lista de todos los nodos del grafo g. Por ejemplo,\n--    nodos ejGrafoND  ==  [1,2,3,4,5]\n--    nodos ejGrafoD   ==  [1,2,3,4,5]\nnodos :: (Ix v,Num p) => (Grafo v p) -> [v]\nnodos (G _ g) = range (l,u) \n    where ((l,_),(u,_)) = bounds g\n\n-- (adyacentes g v) es la lista de los v\u00e9rtices adyacentes al nodo v en\n-- el grafo g. Por ejemplo,\n--    adyacentes ejGrafoND 4  ==  [2,3,5]\n--    adyacentes ejGrafoD  4  ==  [5]\nadyacentes :: (Ix v,Num p) => (Grafo v p) -> v -> [v]\nadyacentes (G o g) v = \n    [v' | v' <- nodos (G o g), (g!(v,v')) \/= Nothing]\n\n-- (aristaEn g a) se verifica si a es una arista del grafo g. Por\n-- ejemplo,\n--    aristaEn ejGrafoND (5,1)  ==  True\n--    aristaEn ejGrafoND (4,1)  ==  False\naristaEn :: (Ix v,Num p) => (Grafo v p) -> (v,v) -> Bool\naristaEn (G _o g) (x,y)= (g!(x,y)) \/= Nothing\n\n-- (peso v1 v2 g) es el peso de la arista que une los v\u00e9rtices v1 y v2\n-- en el grafo g. Por ejemplo,\n--    peso 1 5 ejGrafoND  ==  78\n--    peso 1 5 ejGrafoD   ==  78\npeso :: (Ix v,Num p) => v -> v -> (Grafo v p) -> p\npeso x y (G _ g)  = w where (Just w) = g!(x,y)\n\n-- (aristas g) es la lista de las aristas del grafo g. Por ejemplo, \n--    ghci> aristas ejGrafoD\n--    [(1,2,12),(1,3,34),(1,5,78),(2,4,55),(2,5,32),(3,4,61),\n--     (3,5,44),(4,5,93)] \n--    ghci> aristas ejGrafoND\n--    [(1,2,12),(1,3,34),(1,5,78),(2,1,12),(2,4,55),(2,5,32),\n--     (3,1,34),(3,4,61),(3,5,44),(4,2,55),(4,3,61),(4,5,93),\n--     (5,1,78),(5,2,32),(5,3,44),(5,4,93)]\naristas :: (Ix v,Num p) => (Grafo v p) -> [(v,v,p)]\naristas g@(G o e) = [(v1,v2,extrae(e!(v1,v2))) \n                     | v1 <- nodos g, \n                       v2 <- nodos g,\n                       aristaEn g (v1,v2)]\n    where extrae (Just w) = w\n<\/pre>\n<p>El c\u00f3digo del recorridos de grafos en profundidad es<\/p>\n<pre lang=\"haskell\">\nmodule RecorridoEnProfundidad where\n\n-- ---------------------------------------------------------------------\n-- Librer\u00edas auxiliares                                               --\n-- ---------------------------------------------------------------------\n\n-- Nota: Elegir una implementaci\u00f3n de los grafos.\nimport GrafoConVectorDeAdyacencia\n-- import GrafoConMatrizDeAdyacencia\n\n-- ---------------------------------------------------------------------\n-- Ejemplo de grafo                                                   --\n-- ---------------------------------------------------------------------\n\n-- g es el grafo\n--    +---> 2 <---+\n--    |           |\n--    |           |\n--    1 --> 3 --> 6 --> 5\n--    |                 |\n--    |                 |\n--    +---> 4 <---------+\n\ng = creaGrafo D (1,6) \n              [(1,2,0),(1,3,0),(1,4,0),(3,6,0),(5,4,0),(6,2,0),(6,5,0)]\n\n-- ---------------------------------------------------------------------\n-- Recorrido en profundidad                                            --\n-- ---------------------------------------------------------------------\n\n-- (recorridoEnProfundidad i g) es el recorrido en profundidad del grafo g\n-- desde el v\u00e9rtice i. Por ejemplo,\n--    recorridoEnProfundidad 1 g  ==  [1,2,3,6,5,4]\nrecorridoEnProfundidad i g = rp [i] []\n    where \n      rp [] vis    = vis\n      rp (c:cs) vis \n          | c `elem` vis = rp cs vis\n          | otherwise    = rp ((adyacentes g c)++cs) (vis++[c])\n\n-- Traza del c\u00e1lculo de (recorridoEnProfundidad 1 g)\n--    recorridoEnProfundidad 1 g\n--    = rp [1]     []\n--    = rp [2,3,4] [1]\n--    = rp [3,4]   [1,2]\n--    = rp [6,4]   [1,2,3]\n--    = rp [2,5,4] [1,2,3,6]\n--    = rp [5,4]   [1,2,3,6]\n--    = rp [4,4]   [1,2,3,6,5]\n--    = rp [4]     [1,2,3,6,5,4]\n--    = rp []      [1,2,3,6,5,4]\n--    = [1,2,3,6,5,4]\n\n-- ---------------------------------------------------------------------\n-- Recorrido en profundidad con acumuladores                           --\n-- ---------------------------------------------------------------------\n\n-- (recorridoEnProfundidad' i g) es el recorrido en profundidad del\n-- grafo g desde el v\u00e9rtice i, usando la lista de los visitados como\n-- acumulador. Por ejemplo, \n--    recorridoEnProfundidad' 1 g  ==  [1,2,3,6,5,4]\nrecorridoEnProfundidad' i g = reverse (rp [i] [])\n    where\n      rp [] vis     = vis\n      rp (c:cs) vis \n          | c `elem` vis = rp cs vis\n          | otherwise    = rp ((adyacentes g c)++cs) (c:vis)\n\n-- Traza del c\u00e1lculo de (recorridoEnProfundidad' 1 g)\n--    RecorridoEnProfundidad' 1 g\n--    = reverse (rp [1]     [])\n--    = reverse (rp [2,3,4] [1])\n--    = reverse (rp [3,4]   [2,1])\n--    = reverse (rp [6,4]   [3,2,1])\n--    = reverse (rp [2,5,4] [6,3,2,1])\n--    = reverse (rp [5,4]   [6,3,2,1])\n--    = reverse (rp [4,4]   [5,6,3,2,1])\n--    = reverse (rp [4]     [4,5,6,3,2,1])\n--    = reverse (rp []      [4,5,6,3,2,1])\n--    = reverse [4,5,6,3,2,1]\n--    = [1,2,3,6,5,4]\n<\/pre>\n<p>El c\u00f3digo del recorridos de grafos en anchura es<\/p>\n<pre lang=\"haskell\">\nmodule RecorridoEnAnchura where\n\n-- ---------------------------------------------------------------------\n-- Librer\u00edas auxiliares                                               --\n-- ---------------------------------------------------------------------\n\n-- Nota: Elegir una implementaci\u00f3n de los grafos.\nimport GrafoConVectorDeAdyacencia\n-- import GrafoConMatrizDeAdyacencia\n\n-- ---------------------------------------------------------------------\n-- Ejemplo de grafo                                                   --\n-- ---------------------------------------------------------------------\n\n-- g es el grafo\n--    +---> 2 <---+\n--    |           |\n--    |           |\n--    1 --> 3 --> 6 --> 5\n--    |                 |\n--    |                 |\n--    +---> 4 <---------+\ng = creaGrafo D (1,6) \n              [(1,2,0),(1,3,0),(1,4,0),(3,6,0),(5,4,0),(6,2,0),(6,5,0)]\n\n-- ---------------------------------------------------------------------\n-- Recorrido en anchura con colas                                      --\n-- ---------------------------------------------------------------------\n\n-- (recorridoEnAnchura i g) es el recorrido en anchura del grafo g\n-- desde el v\u00e9rtice i, usando colas. Por ejemplo, \n--    recorridoEnAnchura 1 g  ==  [1,4,3,2,6,5]\nrecorridoEnAnchura i g = reverse (ra [i] [])\n    where \n      ra [] vis    = vis\n      ra (c:cs) vis \n          | c `elem` vis = ra cs vis\n          | otherwise    = ra (cs ++ adyacentes g c) (c:vis)\n\n-- Traza del c\u00e1lculo de (recorridoEnProfundidad 1 g)\n--    RecorridoEnAnchura 1 g\n--    = ra [1]     []\n--    = ra [2,3,4] [1]\n--    = ra [3,4]   [2,1]\n--    = ra [4,6]   [3,2,1]\n--    = ra [6]     [4,3,2,1]\n--    = ra [2,5]   [6,4,3,2,1]\n--    = ra [5]     [6,4,3,2,1]\n--    = ra [4]     [5,6,4,3,2,1]\n--    = ra []      [5,6,4,3,2,1]\n--    = [1,2,3,4,6,5]\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>En la clase de hoy del curso de Inform\u00e1tica de 1\u00ba del Grado en Matem\u00e1ticas hemos estudiado el tipo abstracto de datos de los grafos y dos de sus implementaciones en Haskell: mediante vectores y matrices de adyacencia. Adem\u00e1s, hemos estudiado los algoritmos de recorrido de los grafos en profundidad y en anchura. Las transparencias&#8230;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_kad_post_transparent":"","_kad_post_title":"","_kad_post_layout":"","_kad_post_sidebar_id":"","_kad_post_content_style":"","_kad_post_vertical_padding":"","_kad_post_feature":"","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"footnotes":"","_jetpack_memberships_contains_paid_content":false},"categories":[250,1],"tags":[270,310],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_likes_enabled":false,"_links":{"self":[{"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/posts\/5415"}],"collection":[{"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/comments?post=5415"}],"version-history":[{"count":1,"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/posts\/5415\/revisions"}],"predecessor-version":[{"id":5416,"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/posts\/5415\/revisions\/5416"}],"wp:attachment":[{"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/media?parent=5415"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/categories?post=5415"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.glc.us.es\/~jalonso\/vestigium\/wp-json\/wp\/v2\/tags?post=5415"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}