2016-03-22 3 views
5

Я пытаюсь реализовать структуру данных, подобную сценарию, в Rust.Рекурсивные структуры данных в ржавчине

Я хотел бы эквивалент этой C++ код:

struct Node 
{ 
    Node* parent; // should be mutable, and nullable (no parent) 
    std::vector<Node*> child; 

    virtual ~Node() 
    { 
     for(auto it = child.begin(); it != child.end(); ++it) 
     { 
      delete *it; 
     } 
    } 

    void addNode(Node* newNode) 
    { 
     if (newNode->parent) 
     { 
      newNode->parent.child.erase(newNode->parent.child.find(newNode)); 
     } 
     newNode->parent = this; 
     child.push_back(newNode); 
    } 
} 

Выражаясь безопасным Rust:

  • с родителем несение своих детей
  • с узлами быть доступным извне через ссылку какого-либо типа

EDIT

Спасибо всем за полезные узоры, которые я не знал. Я думаю, что могу использовать их для решения этой проблемы, мне нужно некоторое время, чтобы переварить их.

я не смог указать другое ограничение:

  • Я хочу события, которые касаются один Node потенциально мутировать целое дерево.

Таким образом, я думаю, что пример из DK является самым близким к тому, что я имел в виду.

+2

В чем Ваш вопрос? – Shepmaster

+2

Владение не выражается через типы в вашем текущем примере, вы намеревались написать 'std :: vector > children;'?Кроме того, почему деструктор «виртуальный»? Является ли это интерфейсом? –

+2

Узел владеет своими дочерними элементами и принадлежит родительскому объекту. Я не хотел использовать больше шаблонов, чем необходимо. – nulleight

ответ

11

Проблема в том, что эта структура данных по своей сути является небезопасной; он не имеет прямой эквивалент в ржавчине, который не использует unsafe. Это по дизайну.

Если вы хотите перевести это в безопасный код ржавчины, вам нужно быть более конкретным о том, что именно вы хотите от него. Я знаю, что вы указали некоторые свойства выше, но часто люди, приезжающие в Rust, скажут: «Я хочу все, что у меня есть в этом коде на C/C++», на который прямой ответ «хорошо, вы не можете».

Вы также, неизбежно, придется изменить, как вы подходите к этому. Приведенный пример имеет указатели без семантики владения, изменчивого сглаживания и циклов; все из которых Rust не позволит вам просто игнорировать, как это делает C++.

Простейшим решением является просто избавиться от указателя parent и поддерживать его внешне (например, путь к файловой системе). Это также играет хорошо с займами, потому что нет циклов в любом месте:

pub struct Node1 { 
    children: Vec<Node1>, 
} 

Если вы потребность родительские указатели, вы могли бы пойти на полпути и использовать Идентификаторы вместо:

use std::collections::BTreeMap; 

type Id = usize; 

pub struct Tree { 
    descendants: BTreeMap<Id, Node2>, 
    root: Option<Id>, 
} 

pub struct Node2 { 
    parent: Id, 
    children: Vec<Id>, 
} 

BTreeMap эффективно ваше «адресное пространство», минуя проблемы заимствования и сглаживания на , а не напрямую с использованием адресов памяти.

Конечно, это создает проблему данного Id не привязанный к конкретному дереву, а это означает, что узел принадлежит может быть уничтожен, и теперь у вас есть то, что эффективно зависший указатель.Но это цена, которую вы платите за сглаживание и мутацию. Это также менее прямо.

Или вы могли бы пойти все-боры и использование подсчет ссылок и динамическую проверку Заимствования:

use std::cell::RefCell; 
use std::rc::{Rc, Weak}; 

// Note: do not derive Clone to make this move-only. 
pub struct Node3(Rc<RefCell<Node3_>>); 

pub type WeakNode3 = Weak<RefCell<Node3>>; 

pub struct Node3_ { 
    parent: Option<WeakNode3>, 
    children: Vec<Node3>, 
} 

impl Node3 { 
    pub fn add(&self, node: Node3) { 
     // No need to remove from old parent; move semantics mean that must have 
     // already been done. 
     (node.0).borrow_mut().parent = Some(Rc::downgrade(&self.0)); 
     self.children.push(node); 
    } 
} 

Здесь вы использовать Node3 для передачи права собственности узла между частями дерева, и WeakNode3 для внешних ссылок. Или, вы могли бы сделать Node3 cloneable и добавить обратно логику в add, чтобы удостовериться, что данный узел случайно не оставил ребенка от неправильного родителя.

Это не строго лучше, чем второй вариант, потому что это дизайн абсолютно не может выгоды от статического заема-проверки. Второй может не менее не позволяет вам мутировать график из двух мест одновременно во время компиляции; здесь, если это произойдет, вы просто потерпите крах.

Дело в том, что вы не можете просто все. Вы должны решить, какие операции вам на самом деле необходимо поддерживать. В этот момент обычно это случай выбора типов, которые дают вам необходимые свойства.

+0

, хотя я согласен с тем, что пример, предоставленный OP, является _unsafe_, но обратите внимание, что указатель «parent» не является небезопасным сам по себе, поскольку он не является владельцем родительской области памяти. Проблема заключается в 'vector ', который должен был быть 'unique_ptr' или' shared_ptr' atleast. –

13

Rust пытается обеспечить безопасность памяти, запрещая вам делать вещи, которые потенциально могут быть небезопасными. Поскольку этот анализ выполняется во время компиляции, компилятор может только рассуждать о подмножестве манипуляций, которые, как известно, безопасны.

В Русте, вы можете легко хранить либо ссылка на родитель (путем заимствования родителя, таким образом предотвращая мутацию) или списка дочерних узлов (по владеющим ими, что дает вам больше свободы), но не оба (без использования unsafe). Это особенно проблематично для вашей реализации addNode, которая требует измененного доступа к родительскому элементу данного узла. Однако, если вы храните указатель parent в качестве изменяемой ссылки, тогда, поскольку только одна изменяемая ссылка на конкретный объект может быть использована одновременно, единственный способ получить доступ к родительскому элементу будет через дочерний узел, и вы только иметь единственный дочерний узел, иначе у вас есть две изменяемые ссылки на один и тот же родительский узел.

Если вы хотите избежать небезопасного кода, есть много альтернатив, но все они потребуют некоторых жертв.


Самое простое решение - просто удалить поле parent. Мы можем определить отдельную структуру данных, чтобы помнить родителя узла, пока мы пересекаем дерево, а не сохраняем его в самом узле.

Во-первых, давайте определим Node:

#[derive(Debug)] 
struct Node<T> { 
    data: T, 
    children: Vec<Node<T>>, 
} 

impl<T> Node<T> { 
    fn new(data: T) -> Node<T> { 
     Node { data: data, children: vec![] } 
    } 

    fn add_child(&mut self, child: Node<T>) { 
     self.children.push(child); 
    } 
} 

(я добавил data поле, потому что дерево не очень полезно без данных в узлах!)

Давайте теперь определим еще-структуру для отслеживания родителей, как мы перемещаемся:

#[derive(Debug)] 
struct NavigableNode<'a, T: 'a> { 
    node: &'a Node<T>, 
    parent: Option<&'a NavigableNode<'a, T>>, 
} 

impl<'a, T> NavigableNode<'a, T> { 
    fn child(&self, index: usize) -> NavigableNode<T> { 
     NavigableNode { 
      node: &self.node.children[index], 
      parent: Some(self) 
     } 
    } 
} 

impl<T> Node<T> { 
    fn navigate<'a>(&'a self) -> NavigableNode<T> { 
     NavigableNode { node: self, parent: None } 
    } 
} 

Это решение работает хорошо, если вам не нужно мутировать дерево, как вы ориентируетесь, и вы можете сохранить родительский NavigableNode (что отлично работает для рекурсивного алгоритма, но не слишком хорошо работает, если вы хотите сохранить NavigableNode в какой-то другой структуре данных и сохранить его). Второе ограничение можно облегчить, используя что-то другое, кроме заимствованного указателя для хранения родителя; если вы хотите максимально типичность, вы можете использовать Borrow trait, чтобы прямые значения, заимствованные указатели, Box Э.С., Rc «S и т.д.


Теперь, давайте поговорим о zippers. В функциональном программировании молнии используются для «фокусировки» на конкретном элементе структуры данных (список, дерево, карта и т. Д.), Так что доступ к этому элементу занимает постоянное время, сохраняя при этом все данные этой структуры данных. Если вам нужно перемещаться по дереву и мутировать во время навигации, сохраняя при этом возможность перемещаться по дереву, вы можете превратить дерево в застежку-молнию и выполнить изменения через молнию.

Вот как мы могли бы реализовать молнию на Node определено выше:

#[derive(Debug)] 
struct NodeZipper<T> { 
    node: Node<T>, 
    parent: Option<Box<NodeZipper<T>>>, 
    index_in_parent: usize, 
} 

impl<T> NodeZipper<T> { 
    fn child(mut self, index: usize) -> NodeZipper<T> { 
     // Remove the specified child from the node's children. 
     // A NodeZipper shouldn't let its users inspect its parent, 
     // since we mutate the parents 
     // to move the focused nodes out of their list of children. 
     // We use swap_remove() for efficiency. 
     let child = self.node.children.swap_remove(index); 

     // Return a new NodeZipper focused on the specified child. 
     NodeZipper { 
      node: child, 
      parent: Some(Box::new(self)), 
      index_in_parent: index, 
     } 
    } 

    fn parent(self) -> NodeZipper<T> { 
     // Destructure this NodeZipper 
     let NodeZipper { node, parent, index_in_parent } = self; 

     // Destructure the parent NodeZipper 
     let NodeZipper { 
      node: mut parent_node, 
      parent: parent_parent, 
      index_in_parent: parent_index_in_parent, 
     } = *parent.unwrap(); 

     // Insert the node of this NodeZipper back in its parent. 
     // Since we used swap_remove() to remove the child, 
     // we need to do the opposite of that. 
     parent_node.children.push(node); 
     let len = parent_node.children.len(); 
     parent_node.children.swap(index_in_parent, len - 1); 

     // Return a new NodeZipper focused on the parent. 
     NodeZipper { 
      node: parent_node, 
      parent: parent_parent, 
      index_in_parent: parent_index_in_parent, 
     } 
    } 

    fn finish(mut self) -> Node<T> { 
     while let Some(_) = self.parent { 
      self = self.parent(); 
     } 

     self.node 
    } 
} 

impl<T> Node<T> { 
    fn zipper(self) -> NodeZipper<T> { 
     NodeZipper { node: self, parent: None, index_in_parent: 0 } 
    } 
} 

Чтобы использовать эту молнию, вы должны иметь право собственности на корневой узел дерева. Соблюдая узлы, молния может перемещать вещи вокруг, чтобы избежать копирования или клонирования узлов. Когда мы перемещаем застежку-молнию, мы фактически отбрасываем старую застежку-молнию и создаем новую (хотя мы могли бы также сделать это, изменив self, но я подумал, что это было более понятно, плюс это позволяет вам цеплять вызовы методов).


Если указанные выше параметры не являются удовлетворительными, и вы должны абсолютно сохранить родительский узел в узле, то следующий лучшим вариантом является использование Rc<RefCell<Node<T>>>, чтобы обратиться к родителю и к детям. Rc разрешает совместное использование, но добавляет служебные данные для выполнения подсчета ссылок во время выполнения. RefCell обеспечивает внутреннюю изменчивость, но добавляет накладные расходы, чтобы отслеживать активные роли во время выполнения. See DK.'s answer для реализации с использованием Rc и RefCell.

+0

+1! Я видел молнии раньше в контексте неизменных структур данных и даже не думал об их использовании в контексте * изменчивых *! –

1

В некоторых случаях вы также можете использовать арену . Арена гарантирует, что сохраненные в ней значения будут иметь тот же срок службы, что и сама арена. Это означает, что добавление большего количества значений не приведет к аннулированию любых существующих жизней, но перемещение арены будет. Таким образом, такое решение не является жизнеспособным, если вам нужно вернуть дерево.

Это решает проблему, удаляя владение с самих узлов.

Вот пример, который также использует внутреннюю изменчивость, чтобы позволить мутировать узел после его создания. В других случаях вы можете удалить эту изменчивость, если дерево будет построено один раз, а затем просто будет перемещаться.

extern crate typed_arena; 

use std::cell::{Cell, RefCell}; 
use std::fmt; 

use typed_arena::Arena; 

struct Tree<'a, T: 'a> { 
    nodes: Arena<Node<'a, T>>, 
} 

impl<'a, T> Tree<'a, T> { 
    fn new() -> Tree<'a, T> { 
     Self { nodes: Arena::new() } 
    } 

    fn new_node(&'a self, data: T) -> &'a mut Node<'a, T> { 
     self.nodes.alloc(Node { 
      data, 
      tree: self, 
      parent: Cell::new(None), 
      children: RefCell::new(Vec::new()), 
     }) 
    } 
} 

struct Node<'a, T: 'a> { 
    data: T, 
    tree: &'a Tree<'a, T>, 
    parent: Cell<Option<&'a Node<'a, T>>>, 
    children: RefCell<Vec<&'a Node<'a, T>>>, 
} 

impl<'a, T> Node<'a, T> { 
    fn add_node(&'a self, data: T) -> &'a Node<'a, T> { 
     let child = self.tree.new_node(data); 
     child.parent.set(Some(self)); 
     self.children.borrow_mut().push(child); 
     child 
    } 
} 

impl<'a, T> fmt::Debug for Node<'a, T> 
where 
    T: fmt::Debug, 
{ 
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 
     write!(f, "{:?}", self.data)?; 
     write!(f, " (")?; 
     for c in self.children.borrow().iter() { 
      write!(f, "{:?}, ", c)?; 
     } 
     write!(f, ")") 
    } 
} 

fn main() { 
    let tree = Tree::new(); 
    let head = tree.new_node(1); 
    let left = head.add_node(2); 
    let right = head.add_node(3); 

    println!("{:?}", head); // 1 (2(), 3(),) 
} 
Смежные вопросы