Разрешены ли двусвязные структуры?

Я пишу пакет с классом S4, который выглядит как простая древовидная структура: класс, скажем S4node, имеет слот nodes, который представляет собой список дочерних элементов S4node (через общего виртуального предка, скажем S4base). В классе также есть слот parent типа S4base, который ссылается обратно на родительский узел дочерних узлов. Это выглядит примерно так (для ясности с использованием простого кода):

# Create the root node, slot parent is set to NA, slot id is set to 567
> root <- new_node(567) 

# Create some child nodes, slot parent is set to root, slot nodes has new node added
> root <- add_node(root, 5671)
> root <- add_node(root, 5672)

Это работает нормально, и все слоты установлены правильно. Обход иерархии вниз работает так же, как и обход иерархии вверх. Но вот проблема: когда я хочу пройти вверх по иерархии, слот nodes устанавливается на NULL, что становится очевидным при путешествии вниз по другой ветке. Так:

> node5671 <- find_node(root, 5671)

# Now traverse 5671 -> 567 -> 5672
> node5672 <- node_travel(node5671, "../5672")
Error: path not found

На консоли это становится понятнее:

> length(root@nodes)
[1] 2
> node5671@parent@id
[1] 567
> nodes5671@parent@nodes
NULL

# Best one yet: take the root node, get a child, get the parent (= root), get the nodes = NULL!!!
> root@nodes[[1]]@parent@nodes
NULL

Мне кажется, что список с узлами очищается автоматически, но только при неявном доступе: я могу создать список в первую очередь, только при ссылке на список через поиск @parent список очищается. Это чтобы избежать бесконечной рекурсии? Это причуда gc()? Есть ли обходной путь для этого?

Что делает функция node_travel?

nicola 02.07.2024 10:45

Похоже также, что это может быть проблема копирования/ссылки. В этом случае, я думаю, вам нужна ссылочная семантика, а узел — это ссылка на структуру данных. Вы можете использовать среды RefClass или R6 вместо S4.

nicola 02.07.2024 11:29

@nicola Но все, что у меня здесь есть, это ссылки. Я не изменяю какую-либо часть древовидной структуры, поэтому не должно быть копирования, а только создание еще одной привязки к существующей древовидной структуре. Я рассмотрю ваши предложения по окружению. RC и R6 потребуют полного рефакторинга моего кода, поэтому я надеюсь, что смогу этого избежать.

Patrick 02.07.2024 13:03

Что значит, что у вас есть ссылки? Не видя реализации вашего класса, сложно что-либо сказать

nicola 02.07.2024 14:18

@nicola Когда я говорю «ссылки», я имею в виду, что привязываю имя к объекту. Я ничего не изменяю, поэтому копирования объекта быть не должно.

Patrick 02.07.2024 16:47

Непонятно, что вы имеете в виду. Есть ли причина, по которой вы не хотите делиться реализацией? Как уже говорилось, ответить на этот вопрос невозможно. Вы должны предоставить воспроизводимый пример.

nicola 02.07.2024 17:37

@nicola Отсутствие совместного использования не является проблемой, вся база кода находится на GitHub (github.com/pvanlaake/ncdfCF), за исключением этого фрагмента, потому что он не работает... MRE с классами S4 неработоспособен, как вы' мне придется загрузить, собрать и установить несколько файлов, чтобы получить работоспособный пример. Вот почему я предоставляю макет, чтобы продемонстрировать проблему.

Patrick 03.07.2024 10:19

Знаете ли вы о пакете ncdf4? Вот ссылка: cran.r-project.org/web/packages/ncdf4/index.html. Однако дело в реализации, поэтому вы можете показать, как вы определили этот класс S4node и какую функцию использовали. Иначе как можно помочь?

nicola 03.07.2024 11:27

Думаю, я бы попробовал использовать cran.r-project.org/web/packages/pointr для реализации идеи связанного списка; или отойдите от языка и сделайте что-нибудь типа rcpp.

Nir Graham 03.07.2024 12:35

@Patrick У вас здесь нет никаких ссылок, поскольку R использует семантику значений и копирование, а не семантику ссылок (за исключением сред).

Konrad Rudolph 03.07.2024 21:24

@NirGraham Господи, что это?! Это… извините за выбор слова, но, действительно: мерзость… совершенно не обязательно для реализации ссылочной семантики в R.

Konrad Rudolph 03.07.2024 21:26

@NirGraham Этот пакет меня настолько взволновал, что я написал свой собственный, чтобы показать, как это правильно реализовать: cran.r-project.org/package=aka — учтите, использование «aka» здесь все равно неуместно ( или даже для большинства целей, на которые неумело претендует «pointr»).

Konrad Rudolph 23.07.2024 14:46
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
12
79
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я могу только предположить, что add_node изменяет слот nodes копии своего аргумента и возвращает измененную копию. Копия получает новый узел, а оригинал сохраняется. Придумать пример с использованием tracemem было несложно...

> setClass("S4base")
> setClass("S4root",
+          contains = "S4base",
+          slots = c(id = "integer", parent = "NULL", nodes = "list"),
+          prototype = list(id = 0L, parent = NULL, nodes = list()))
> setClass("S4node",
+          contains = "S4base",
+          slots = c(id = "integer", parent = "S4base", nodes = "list"),
+          prototype = list(id = 1L, parent = new("S4root"), nodes = list()))
> 
> root <- new("S4root", id = 0L); tracemem(root)
[1] "<0x1370506d8>"
> node <- new("S4node", id = 1L, parent = root)
> root@nodes <- list(node)
tracemem[0x1370506d8 -> 0x1370f5aa8]: 
> identical(root, root@nodes[[1L]]@parent)
[1] FALSE
> 
> str(root)
Formal class 'S4root' [package ".GlobalEnv"] with 3 slots
  ..@ id    : int 0
  ..@ parent: NULL
  ..@ nodes :List of 1
  .. ..$ :Formal class 'S4node' [package ".GlobalEnv"] with 3 slots
  .. .. .. ..@ id    : int 1
  .. .. .. ..@ parent:Formal class 'S4root' [package ".GlobalEnv"] with 3 slots
  .. .. .. .. .. ..@ id    : int 0
  .. .. .. .. .. ..@ parent: NULL
  .. .. .. .. .. ..@ nodes : list()
  .. .. .. ..@ nodes : list()
>

Чтобы обойти копирование при изменении в R, вам, вероятно, следует использовать среды (возможно, расширяющие классы S4 environment):

> root <- new.env(parent = emptyenv()); try(tracemem(root))
Error in tracemem(root) : 
  'tracemem' is not useful for promise and environment objects
> node <- new.env(parent = emptyenv())
> 
> root$id <- 0L
> root$nodes <- list(node)
> 
> node$id <- 1L
> node$nodes <- list()
> node$parent <- root
> 
> identical(root, root$nodes[[1L]]$parent)
[1] TRUE
> 

Ого, это действительно удивительно! Изменение слота в экземпляре класса дает вам совершенно новый экземпляр! Я полагаю, что это имеет смысл, новое значение слота все-таки меняет экземпляр, но все же удивительно, поскольку оно делает S4 действительно неэффективным в таких типах иерархий классов. Ответ принят и +1 за отличное объяснение и демонстрацию.

Patrick 04.07.2024 12:44

Использование среды для решения этой проблемы означает, что я модифицирую свои классы с помощью R6. Может быть, все-таки пришло время перебазировать мой код...

Patrick 04.07.2024 12:45
root копируется только потому, что на него ссылается node, когда вы пытаетесь его изменить. Копия не возникла бы, если бы на root не было ссылки. Я подозреваю, что вы сможете реализовать это в S4, если убедитесь, что S4base расширяет базовый класс environment. Есть пример в help("setClass").
Mikael Jagan 04.07.2024 15:37

Другие вопросы по теме