Как изменить связанный список, используя только указатели

У меня есть связанный список типа Option<Box<ListNode>>, узел этого списка содержит два свойства: значение (i32) и следующее (Option<Box<ListNode>>). Я создал вектор указателей (Vec<&Box<ListNode>>) с целью изменения исходного списка, как показано на следующем рисунке:

Примечание: полученный список должен быть: L: 1 -> 2 -> n-1 -> n

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

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
  pub val: i32,
  pub next: Option<Box<ListNode>>
}

impl ListNode {
  #[inline]
  pub fn new(val: i32) -> Self {
    ListNode {
      next: None,
      val
    }
  }
}

pub fn delete_duplicates(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    head.as_ref()?;

    type NodeRef<'a> = &'a Box<ListNode>;
    type OptNodeRef<'a> = Option<NodeRef<'a>>;

    const MAX_NODES: u16 = 300;
    // let mut head = head;
    let mut current: OptNodeRef = head.as_ref();
    let mut node_ref: OptNodeRef = None;
    let mut vec_ref: Vec<NodeRef> = Vec::with_capacity(MAX_NODES as usize);
    let mut counter: u16 = 0;

    while let Some(node) = current {
        if node_ref.is_none() || node_ref.unwrap().val != node.val {
            node_ref = Some(node);
            if counter > 0 { 
                vec_ref.push(node);
            }
            counter = 0;
        } else {
            counter += 1;
        }

        current = node.next.as_ref();
    }
    
    if counter > 0 {
        vec_ref.push(node_ref.unwrap());
    }
    // vec_ref.into_iter().map(|t| t.val).for_each(|e| println!("{:?}", e));

    // my problem starts from here...
    for i in 0..vec_ref.len() - 1 {
        vec_ref[i].next = Some(*vec_ref[i + 1]);
    }

    if let Some(last_node) = vec_ref.last() {
        last_node.next = None;
    }

    if let Some(first_node) = vec_ref.first() {
        Some(**first_node)
    } else {
        None
    }
}

как это получить? Я пробовал кое-что, но всегда получаю ошибку, связанную с ошибками заимствования, перемещения и несоответствия. Кажется, просто изменить свойство «следующее», но у меня нет идей.

Пожалуйста, покажите свой текущий код. Даже если он не работает, это дает нам отправную точку.

Chayim Friedman 18.08.2024 19:17

Почему заметка не согласуется с вашей фотографией? Измените любой, чтобы он соответствовал другому!

cafce25 18.08.2024 19:26
& — это общая ссылка, а не указатель, вы не можете использовать их для изменения чего-либо без внутренней изменчивости.
cafce25 18.08.2024 19:26

Также, если вы хотите реализовать связанные списки, настоятельно рекомендуется прочитать Rust со слишком большим количеством связанных списков.

cafce25 18.08.2024 19:29

Я добавил свой код для проверки

Ουιλιαμ Αρκευα 18.08.2024 19:33

«Я добавил свой код на проверку» — обратите внимание, что переполнение стека — это не то место, где можно просить о проверке кода. Вы можете найти codereview на stackexchange или форуме пользователей Rust лучшее место для этого.

Aleksander Krauze 18.08.2024 21:04

Добавлена ​​структура ListNode.

Ουιλιαμ Αρκευα 18.08.2024 21:13

Ах да, так и думал. С таким определением это невыполнимая задача.

cafce25 18.08.2024 21:18

Другие говорят следующее: Связанные списки в Rust сложны, потому что они представляют собой очень плохую структуру данных для средства проверки заимствований. Их реализация практически невозможна без использования unsafe и необработанных указателей. Все это объяснено в этом посте, на который @cafce25 уже ссылался. Прежде чем продолжать попытки, сначала прочтите это.

Finomnis 19.08.2024 00:27

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

Finomnis 19.08.2024 00:29

Мне неясно, хотите ли вы удалить дубликаты (как предполагает ваш код) или хотите ли вы сохранить только первые два и последние два элемента и удалить все, что между ними (как предполагает первая часть вопроса), что Вероятно, поэтому мой ответ отвергается. Не могли бы вы уточнить?

Andrea Corbellini 19.08.2024 01:09

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

Ουιλιαμ Αρκευα 19.08.2024 02:51

С добавленным кодом это теперь дубликат stackoverflow.com/questions/54510346/…

cafce25 19.08.2024 07:17
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
13
112
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Первое сообщение об ошибке, которое вы получаете, следующее (в Rust 1.80):

error[E0594]: cannot assign to data in an index of `Vec<&Box<ListNode>>`
  --> src/main.rs:51:9
   |
51 |         vec_ref[i].next = Some(*vec_ref[i + 1]);
   |         ^^^^^^^^^^^^^^^ cannot assign
   |
   = help: trait `IndexMut` is required to modify indexed content, but it is not implemented for `Vec<&Box<ListNode>>`

Сообщение об ошибке здесь немного неясно, но по сути оно возникает, потому что вы пытаетесь изменить элемент в Vec, однако элементы находятся за ссылкой &, поэтому они не изменяемы. Вам нужно изменить тип с Vec<&Box<ListNode>> на Vec<&mut Box<ListNode>>.

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

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

pub fn delete_duplicates(head: &mut Option<Box<ListNode>>) {
    // Make sure that `node` is `Option<&mut Box<ListNode>>`, to
    // avoid taking ownership of nodes after the head (which would
    // not be allowed by Rust)
    let mut node = head.as_mut();

    while let Some(this) = node {
        // Loop through every node that shares the same value as `this`
        loop {
            if let Some(ref mut next) = this.next {
                if this.val == next.val {
                    // `this` and `next` have the same value.
                    // Connect `this` to `next.next`, so that the
                    // list changes from:
                    //
                    //   ... -> this -> next -> next.next -> ...
                    //
                    // to:
                    //
                    //   ... -> this -> next.next -> ...
                    //
                    // The use of `take()` ensures that `next.next`
                    // is replaced with `None` (this avoids taking
                    // multiple ownerships).
                    this.next = next.next.take();
                    continue;
                }
            }
            // Either there's no next node, or the next node has a
            // different value than `this`
            break;
        }
        node = this.next.as_mut();
    }
}

Ссылка на игровую площадку

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

Ουιλιαμ Αρκευα 19.08.2024 02:58

Если вы хотите, чтобы функция принимала и возвращала Option<Box<ListNode>> (вместо &mut Option<Box<ListNode>>), это тривиальное изменение. Преимущество использования &mut заключается в том, что функция работает в большем количестве обстоятельств.

Andrea Corbellini 19.08.2024 03:43
Ответ принят как подходящий

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

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

подумайте о том, кому принадлежит часть данных.

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    pub fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }

    /// Functions I added for testing
    /// 
    /// Push an element to the end of the list
    pub fn push(&mut self, val: i32) {
        let mut current: &mut ListNode = self;
        while current.next.is_some() {
            current = current.next.as_mut().unwrap();
        }
        current.next = Some(Box::new(ListNode { val, next: None }));
    }
    /// Print Out the list
    pub fn print(&self) {
        print!("[{:>3?}]", self.val);
        let mut current: &ListNode = self;
        while current.next.is_some() {
            current = current.next.as_ref().unwrap();
            print!(" -> [{:>3?}]", current.val);
        }

        println!("");
    }
}

/// I refactored the whole function
pub fn delete_duplicates(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    // The head of the list, if there aren't a list, return None
    let mut head: Box<ListNode> = head?;
    let mut current: Option<Box<ListNode>> = head.next.take();
    let mut tail: &mut Box<ListNode> = &mut head;

    // we TAKE the second element as the current node
    //
    // you can think of it as
    //
    //               |- tail
    //               v
    // head : () -> [first val]
    //
    // current: () -> [second val] -> [thrid val] -> ...
    //

    // Then we keep dequeuing the current
    // when current is None, we know the list ended
    while let Some(mut c) = current {
        //
        //                                     | tail
        //                                     v
        //  head : () -> [1] -> [2] -> ... -> [4]
        //  c    : () -> [5] -> [6]-> ...
        //
        // TAKE out the next node as the next current
        current = c.next.take();
        //
        //                                        | tail
        //                                        v
        //  head    : () -> [1] -> [2] -> ... -> [4]
        //  c       : () -> [5]
        //  current : () -> [6] -> [7]-> ...
        //
        // if the c.val is not equal to the last list, we push it to the end of the list
        if c.val != tail.val {
            tail.next = Some(c);
            tail = tail.next.as_mut().unwrap();
            //
            //                                               | tail
            //                                               v
            //  head    : () -> [1] -> [2] -> ... -> [4] -> [5]
        }
    }

    Some(head)
}
fn main() {
    let mut list: ListNode = ListNode::new(1);
    list.push(2);
    list.push(2);
    list.push(3);
    list.push(4);
    list.print();
    let new_list: Box<ListNode> = delete_duplicates(Some(Box::new(list))).unwrap();
    new_list.print();
}

выход

[  1] -> [  2] -> [  2] -> [  3] -> [  4]
[  1] -> [  2] -> [  3] -> [  4]

таким образом, head владейте всем списком и tail управляйте его мутациями. которые соответствуют правилу владения, одному владельцу и только одной изменяемой ссылке.

Производительность

производительность намного выше, если функция берет ссылку, а не весь список, как в решении Андреа Корбеллини.

use std::io::Write;

#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ListNode {
    pub val: i32,
    pub next: Option<Box<ListNode>>,
}

impl ListNode {
    #[inline]
    pub fn new(val: i32) -> Self {
        ListNode { next: None, val }
    }

    /// Functions I added for testing
    ///
    /// Push an element to the end of the list
    pub fn push(&mut self, val: i32) {
        let mut current: &mut ListNode = self;
        while current.next.is_some() {
            current = current.next.as_mut().unwrap();
        }
        current.next = Some(Box::new(ListNode { val, next: None }));
    }
    /// Print Out the list
    pub fn print(&self) {
        print!("[{:>3?}]", self.val);
        let mut current: &ListNode = self;
        while current.next.is_some() {
            current = current.next.as_ref().unwrap();
            print!(" -> [{:>3?}]", current.val);
        }

        println!("");
    }
}

/// I refactored the whole function
pub fn delete_duplicates(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    // The head of the list, if there aren't a list, return None
    let mut head: Box<ListNode> = head?;
    let mut current: Option<Box<ListNode>> = head.next.take();
    let mut tail: &mut Box<ListNode> = &mut head;

    // we TAKE the second element as the current node
    //
    // you can think of it as
    //
    //               |- tail
    //               v
    // head : () -> [first val]
    //
    // current: () -> [second val] -> [thrid val] -> ...
    //

    // Then we keep dequeuing the current
    // when current is None, we know the list ended
    while let Some(mut c) = current {
        //
        //                                     | tail
        //                                     v
        //  head : () -> [1] -> [2] -> ... -> [4]
        //  c    : () -> [5] -> [6]-> ...
        //
        // TAKE out the next node as the next current
        current = c.next.take();
        //
        //                                        | tail
        //                                        v
        //  head    : () -> [1] -> [2] -> ... -> [4]
        //  c       : () -> [5]
        //  current : () -> [6] -> [7]-> ...
        //
        // if the c.val is not equal to the last list, we push it to the end of the list
        if c.val != tail.val {
            tail.next = Some(c);
            tail = tail.next.as_mut().unwrap();
            //
            //                                               | tail
            //                                               v
            //  head    : () -> [1] -> [2] -> ... -> [4] -> [5]
        }
    }

    Some(head)
}

pub fn delete_duplicates_2(head: Option<Box<ListNode>>) -> Option<Box<ListNode>> {
    // take ownership of the head, and we will manipulate this directly.
    let mut current = head;
    // Mutable reference to 'current' node. We use 'as_mut' and 'and_then' to traverse and modify the list in place
    let mut current_ref = current.as_mut();

    while let Some(curr_node) = current_ref {
        // check if the next node is a duplicate
        while curr_node
            .next
            .as_ref()
            .map_or(false, |next_node| next_node.val == curr_node.val)
        {
            //skip the duplicate
            curr_node.next = curr_node.next.as_mut().unwrap().next.take();
        }
        // Move to the next node
        current_ref = curr_node.next.as_mut();
    }
    current
}

pub fn delete_duplicates_3(head: &mut Option<Box<ListNode>>) {
    // Make sure that `node` is `Option<&mut Box<ListNode>>`, to
    // avoid taking ownership of nodes after the head (which would
    // not be allowed by Rust)
    let mut node = head.as_mut();

    while let Some(this) = node {
        // Loop through every node that shares the same value as `this`
        loop {
            if let Some(ref mut next) = this.next {
                if this.val == next.val {
                    // `this` and `next` have the same value.
                    // Connect `this` to `next.next`, so that the
                    // list changes from:
                    //
                    //   ... -> this -> next -> next.next -> ...
                    //
                    // to:
                    //
                    //   ... -> this -> next.next -> ...
                    //
                    // The use of `take()` ensures that `next.next`
                    // is replaced with `None` (this avoids taking
                    // multiple ownerships).
                    this.next = next.next.take();
                    continue;
                }
            }
            // Either there's no next node, or the next node has a
            // different value than `this`
            break;
        }
        node = this.next.as_mut();
    }
}

fn delete_duplicates_4(head: &mut Option<Box<ListNode>>) {
    if head.is_none() {
        return;
    }
    let mut head = head.as_mut().unwrap();

    while let Some(ref n) = head.next {
        if head.val == n.val {
            head.next = head.next.take().unwrap().next;
        } else {
            head = head.next.as_mut().unwrap();
        }
    }
}

fn make_list(len: usize) -> ListNode {
    let mut a = 1;
    let mut b = 1;
    let mut list = ListNode::new(a);

    for _ in 0..len {
        let t = b;
        b = (a + b) % 10;
        a = t;
        list.push(a);
    }

    list
}

fn bench(len: usize) {
    let list = make_list(len);

    let N = 2048;

    println!("Bench Testing n = {}", len);
    {
        print!("    running delete_duplicates   . . . ");
        std::io::stdout().flush().unwrap();
        let mut dt = 0;
        for _ in 0..N {
            let list = Some(Box::new(list.clone()));
            let t0 = std::time::Instant::now();
            let _ = delete_duplicates(list).unwrap();
            dt += t0.elapsed().as_micros();
        }
        println!("{:>8}us", (dt as f64) / (N as f64));
    }
    {
        print!("    running delete_duplicates_2 . . . ");
        std::io::stdout().flush().unwrap();
        let mut dt = 0;
        for _ in 0..N {
            let list = Some(Box::new(list.clone()));
            let t0 = std::time::Instant::now();
            let _ = delete_duplicates_2(list).unwrap();
            dt += t0.elapsed().as_micros();
        }
        println!("{:>8}us", (dt as f64) / (N as f64));
    }
    {
        print!("    running delete_duplicates_3 . . . ");
        std::io::stdout().flush().unwrap();
        let mut dt = 0;
        for _ in 0..N {
            let mut list = Some(Box::new(list.clone()));
            let t0 = std::time::Instant::now();
            let _ = delete_duplicates_3(&mut list);
            dt += t0.elapsed().as_micros();
        }
        println!("{:>8}us", (dt as f64) / (N as f64));
    }
    {
        print!("    running delete_duplicates_3 . . . ");
        std::io::stdout().flush().unwrap();
        let mut dt = 0;
        for _ in 0..N {
            let mut list = Some(Box::new(list.clone()));
            let t0 = std::time::Instant::now();
            let _ = delete_duplicates_4(&mut list);
            dt += t0.elapsed().as_micros();
        }
        println!("{:>8}us", (dt as f64) / (N as f64));
    }
}

fn main() {
    for i in 5..=11 {
        bench(1 << i);
    }
}

Выход

Bench Testing n = 32
    running delete_duplicates   . . . 2.052734375us
    running delete_duplicates_2 . . . 2.16943359375us
    running delete_duplicates_3 . . . 0.0556640625us 
    running delete_duplicates_4 . . . 0.1826171875us
Bench Testing n = 64
    running delete_duplicates   . . . 4.1171875us
    running delete_duplicates_2 . . . 5.03515625us
    running delete_duplicates_3 . . . 0.33154296875us
    running delete_duplicates_4 . . . 0.06396484375us
Bench Testing n = 128
    running delete_duplicates   . . . 8.5419921875us
    running delete_duplicates_2 . . . 8.87158203125us
    running delete_duplicates_3 . . . 1.1669921875us
    running delete_duplicates_4 . . . 1.193359375us
Bench Testing n = 256
    running delete_duplicates   . . . 18.2841796875us
    running delete_duplicates_2 . . . 20.169921875us
    running delete_duplicates_3 . . . 2.74169921875us
    running delete_duplicates_4 . . . 2.35791015625us
Bench Testing n = 512
    running delete_duplicates   . . . 37.2880859375us
    running delete_duplicates_2 . . . 36.0595703125us
    running delete_duplicates_3 . . . 5.30224609375us
    running delete_duplicates_4 . . . 5.31103515625us
Bench Testing n = 1024
    running delete_duplicates   . . . 66.796875us
    running delete_duplicates_2 . . . 69.91552734375us
    running delete_duplicates_3 . . . 9.84814453125us
    running delete_duplicates_4 . . . 10.646484375us
Bench Testing n = 2048
    running delete_duplicates   . . . 133.638671875us
    running delete_duplicates_2 . . . 143.2666015625us
    running delete_duplicates_3 . . . 20.03955078125us
    running delete_duplicates_4 . . . 20.87939453125us

Отличная работа @啊鹿Dizzyi. Ваш код легко понять, а тестирование ценно. По крайней мере, теперь я знаю, что мне нужно быть осторожным при обработке указателей со связанными списками.

Ουιλιαμ Αρκευα 19.08.2024 17:36

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