«это» в простом связанном списке С#

avatar
aldo
8 августа 2021 в 16:06
63
1
0

Я практикуюсь в C# и отказался от метода Add() для реализации простого связанного списка, показанного ниже. Я посмотрел на ответ и не могу понять одну вещь. Он присваивает переменной current_node значение «этот», переходит к последнему узлу и делает новый последний узел «следующим» из последнего узла. Но тогда как current_node связан с "this", так как я не вижу в коде никаких действий по эффекту "this.next = current_node"?

Кроме того, правильно ли мой комментарий относительно метода GetEnumerator(), что он автоматически вызывается, если сам класс (т.е. не методы внутри класса) передается в метод, который ожидает вывод IEnumerable<T>, например как XUnit Assert.Equal()?

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

public class SimpleLinkedList<T> : IEnumerable<T>
{
    private T value;
    private SimpleLinkedList<T> next;

    // START READING HERE, THESE TWO METHODS DEFINE THE LIST
    // "this" refers to the linked list itself.
    // Two constructors mean that the class can take
    // either a single input of type T or a collection of type T
    // that implements IEnumerable.
    public SimpleLinkedList(T value) => this.value = value;

    public SimpleLinkedList(IEnumerable<T> values)
    {
        this.value = values.First();
        foreach (var i in values.Skip(1))
        {
            this.Add(i);
        }
        
    }

    public T Value 
    { 
        get
        {
            return this.value;
        } 
    }

    public SimpleLinkedList<T> Next
    { 
        get
        {
            return this.next;
        } 
    }

    public SimpleLinkedList<T> Add(T value)
    {
        var current_node = this;
        while (current_node.next != null)
        {
            current_node = current_node.next;
        }
        current_node.next = new SimpleLinkedList<T>(value);
        return this;
    }

    // This method is automatically called if the class itself
    // (i.e. not the methods inside the class) is passed into a
    // method that expects an output of IEnumerable<T> 
    // such as XUnit's Assert.Equal().
    public IEnumerator<T> GetEnumerator()
    {
        yield return value;
        var current_node = this.next;
        while (current_node != null)
        {
            yield return current_node.value;
            current_node = current_node.next;   
        }
    }

    // Since we use Inenumerable<T> interface, need to also implement the
    // non-generic GetEnumerator() method for backwards compatibilty with previous
    // .net versions. 
    // Just make this method return the generic GetEnumerator() method.
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
}

Некоторые модульные тесты для кода:

using System.Linq;
using Xunit;

public class SimpleLinkedListTests
{
    [Fact]
    public void Single_item_list_value()
    {
        var list = new SimpleLinkedList<int>(1);
        Assert.Equal(1, list.Value);
    }

    [Fact(Skip = "Remove this Skip property to run this test")]
    public void Single_item_list_has_no_next_item()
    {
        var list = new SimpleLinkedList<int>(1);
        Assert.Null(list.Next);
    }

    [Fact]
    public void Two_item_list_first_value()
    {
        var list = new SimpleLinkedList<int>(2).Add(1);
        Assert.Equal(2, list.Value);
    }

    [Fact(Skip = "Remove this Skip property to run this test")]
    public void Two_item_list_second_value()
    {
        var list = new SimpleLinkedList<int>(2).Add(1);
        Assert.Equal(1, list.Next.Value);
    }

    [Fact(Skip = "Remove this Skip property to run this test")]
    public void Two_item_list_second_item_has_no_next()
    {
        var list = new SimpleLinkedList<int>(2).Add(1);
        Assert.Null(list.Next.Next);
    }

    [Fact]
    public void Implements_enumerable()
    {
        var values = new SimpleLinkedList<int>(2).Add(1);
        Assert.Equal(new[] { 2, 1 }, values);
    }

    [Fact]
    public void From_enumerable()
    {
        var list = new SimpleLinkedList<int>(new[] { 11, 7, 5, 3, 2 });
        Assert.Equal(11, list.Value);
        Assert.Equal(7, list.Next.Value);
        Assert.Equal(5, list.Next.Next.Value);
        Assert.Equal(3, list.Next.Next.Next.Value);
        Assert.Equal(2, list.Next.Next.Next.Next.Value);
    }

    [Theory(Skip = "Remove this Skip property to run this test")]
    [InlineData(1)]
    [InlineData(2)]
    [InlineData(10)]
    [InlineData(100)]
    public void Reverse(int length)
    {
        var values = Enumerable.Range(1, length).ToArray();
        var list = new SimpleLinkedList<int>(values);
        var reversed = list.Reverse();
        Assert.Equal(values.Reverse(), reversed);
    }

    [Theory(Skip = "Remove this Skip property to run this test")]
    [InlineData(1)]
    [InlineData(2)]
    [InlineData(10)]
    [InlineData(100)]
    public void Roundtrip(int length)
    {
        var values = Enumerable.Range(1, length);
        var listValues = new SimpleLinkedList<int>(values);
        Assert.Equal(values, listValues);
    }
}
Источник
OneCricketeer
8 августа 2021 в 16:10
3

Вы читаете код задом наперед. Он «присваивает this current_node». Вы не можете присвоить что-либо "на this"

user12031933
8 августа 2021 в 16:13
0

Ключевое слово this предназначено для управления текущим экземпляром с точки зрения кода экземпляра объекта класса. Например, наличие SimpleLinkedList<Something> instance; здесь this равно самому instance внутри класса. Это все. См. предлагаемые дубликаты для причин и использования.

Wiktor Zychla
8 августа 2021 в 16:15
0

Где в коде «this.next = current_node»? Или, может быть, вы считаете, что он должен быть там?

Ответы (1)

avatar
OneCricketeer
8 августа 2021 в 16:17
1

Для ясности

  1. this, кажется, всегда относится к началу вашего списка, поэтому this.next = ...; никогда не будет тем, что вам нужно, потому что это всегда приводит к тому, что у вас будет список длиной 2 (this.value + this.next.value).. То есть, если вы не создали узел с ненулевым .next, затем установите this.next на это, что почти то, что делает метод Add

    .
  2. это одно и то же, потому что this и x ссылаются на один и тот же объект (родственно - C# разница между == и Equals())72081020>701<782081029>701810298

    this.next = <some object>;
    

    var x = this;
    x.next = <some object>;
    

Теперь, что касается метода Add, он устанавливает начальную точку как первый узел (this), затем выполняет итерацию до конца списка и добавляет через последний узел, который имеет .next == null . Когда метод возвращается, вы возвращаете текущий экземпляр списка, который по-прежнему ссылается на головной узел, но в цепочке ссылок .next объектов имеет новый узел в конце.

Функции перечислителя не вызываются "сами по себе". Вы реализовали IEnumerable<T>, и некоторые действия над вашим классом связанного списка будут использовать перечислитель (для циклов, возможно, переворачивание списка и т. д.). Что касается комментария к Assert.Equal, поскольку вы звоните .Next.Next.Next и т. д., значит, вы неправильно используете перечислитель; тест, который имеет Assert.Equal(new[] { 2, 1 }, values); должно хватить для проверки