Search code examples
c#classdata-structuresdoubly-linked-list

Managing Hospital Data with C# and Doubly Linked Circular Lists: Issue with Patient Information Output Display


This code aims to create a hospital data management application using doubly linked circular lists. It can be divided into two main parts:

Classes: The Patient, Medicine, Doctor, and Disease classes represent fundamental data structures for patients, medicines, doctors, and diseases, respectively. Each class includes properties such as ID, name, specialty, etc., and node references (Previous and Next) to represent the doubly linked structure.

Doubly Linked Circular List Class: The Node<T> class represents the nodes of the doubly linked circular list. The DoublyLinkedCircularList<T> class manages the doubly linked circular list based on the data type. The Add method adds a new node to the beginning of the list. The QueryById method searches within the list based on a given ID and returns the corresponding data if found.

Main Program: In the Main method, doubly linked circular lists specific to the Patient, Medicine, Doctor, and Disease types are created. Sample data is added to these lists. The user is prompted to enter an ID, and the program queries patient information using this ID. If found, it displays the information; otherwise, it shows the "Patient not found." message.

Everything is OK, The code is working.But why doesn't the code provide the output of patient information for me? How can I print the patient's doctor, medication and disease information in the code below?

using System;
using System.Collections.Generic;

// Patient class
public class Patient
{
    public long? ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public Patient? Previous { get; set; }
    public Patient? Next { get; set; }

    public override string ToString()
    {
        return $"{ID}. {FirstName} {LastName}";
    }
}

// Medicine class
public class Medicine
{
    public long? ID { get; set; }
    public string Name { get; set; }
    public Medicine? Previous { get; set; }
    public Medicine? Next { get; set; }

    public override string ToString()
    {
        return $"{ID}. {Name}";
    }
}

// Doctor class
public class Doctor
{
    public long ID { get; set; }
    public string Name { get; set; }
    public string Specialty { get; set; }
    public Doctor? Previous { get; set; }
    public Doctor? Next { get; set; }

    public override string ToString()
    {
        return $"{ID}. {Name} ({Specialty})";
    }
}

// Disease class
public class Disease
{
    public long ID { get; set; }
    public string Name { get; set; }
    public Disease? Previous { get; set; }
    public Disease? Next { get; set; }

    public override string ToString()
    {
        return $"{ID}. {Name}";
    }
}

// Doubly linked circular list class
public class Node\<T\>
{
    public T? ID { get; set; }
    public Node\<T\>? Previous { get; set; }
    public Node\<T\>? Next { get; set; }
}

public class DoublyLinkedCircularList\<T\>
{
    private Node\<T\>? Head;

    public void Add(T element)
    {
        Node<T> newNode = new Node<T> { ID = element };

        if (Head == null)
        {
            Head = newNode;
            Head.Previous = Head;
            Head.Next = Head;
        }
        else
        {
            newNode.Previous = Head.Previous;
            newNode.Next = Head;
            Head.Previous!.Next = newNode;
            Head.Previous = newNode;
        }
    }

    public T? QueryById(Func<T, bool> query)
    {
        if (Head == null)
            return default;

        Node<T> current = Head;
        do
        {
            if (query(current.ID!))
                return current.ID;

            current = current.Next!;
        } while (current != Head);

        return default;
    }
}

class Program
{
    static void Main(string\[\] args)
    {
        // Doubly linked circular lists
        DoublyLinkedCircularList\<Patient\> patientList = new DoublyLinkedCircularList\<Patient\>();
        DoublyLinkedCircularList\<Medicine\> medicineList = new DoublyLinkedCircularList\<Medicine\>();
        DoublyLinkedCircularList\<Doctor\> doctorList = new DoublyLinkedCircularList\<Doctor\>();
        DoublyLinkedCircularList\<Disease\> diseaseList = new DoublyLinkedCircularList\<Disease\>();

        // Add sample data
        patientList.Add(new Patient { ID = 46573806740, FirstName = "Hiranur", LastName = "Sazak" });
        patientList.Add(new Patient { ID = 23746982347, FirstName = "Nisa Nur", LastName = "Özdal" });
        patientList.Add(new Patient { ID = 89374938243, FirstName = "Nisa Gül", LastName = "Ünal" });
        patientList.Add(new Patient { ID = 98723424674, FirstName = "Berfin", LastName = "Geleş" });

        medicineList.Add(new Medicine { ID = 46573806740, Name = "Theraflu Forte" });
        medicineList.Add(new Medicine { ID = 46573806740, Name = "Ketober %1.6 Gargara" });
        // ... (add more medicines)

        doctorList.Add(new Doctor { ID = 46573806740, Name = "Dr. Ömer Kaplan", Specialty = "Ear, Nose, Throat" });
        doctorList.Add(new Doctor { ID = 23746982347, Name = "Dr. Ali Nazmican Güröz ", Specialty = "Orthopedics" });
        // ... (add more doctors)

        diseaseList.Add(new Disease { ID = 46573806740, Name = "Cold" });
        diseaseList.Add(new Disease { ID = 23746982347, Name = "Meniscus Tear" });
        // ... (add more diseases)

        Console.WriteLine("\n...ID Query...");

        Console.Write("Please enter the ID number: ");
        if (long.TryParse(Console.ReadLine(), out long ID))
        {
            Patient? queriedPatient = patientList.QueryById(patient => patient.ID == ID);

            if (queriedPatient != null)
            {
                Console.WriteLine($"ID: {queriedPatient.ID}, First Name: {queriedPatient.FirstName}, Last Name: {queriedPatient.LastName}");
            }
            else
            {
                Console.WriteLine("Patient not found.");
            }
        }
        else
        {
            Console.WriteLine("Invalid ID number entered.");
        }
        Console.ReadKey();
    }
}

Solution

  • As it is the code does not compile. If you remove the extra backslashes it will work. E.g. Node<T>? instead of Node\<T\>? and string[] instead of string\[\].

    Btw.: Circular lists make no sense for this kind of data. There is nothing circular about patients and doctors.

    The implementation can be improved. There should be no need to have Previous and Next properties in the data classes. These should only be in the Node<T> class. The nodes should have a Data property instead of a ID property:

    public class Node<T>
    {
        public required T Data { get; set; }
        public Node<T>? Previous { get; set; }
        public Node<T>? Next { get; set; }
    }
    

    Adding some required modifiers to the properties removes null warnings. The ID properties must not be nullable.

    public class Patient
    {
        public required long ID { get; set; }
        public required string FirstName { get; set; }
        public required string LastName { get; set; }
    
        public override string ToString() => $"{ID}. {FirstName} {LastName}";
    }
    
    public class Medicine
    {
        public required long ID { get; set; }
        public required string Name { get; set; }
    
        public override string ToString() => $"{ID}. {Name}";
    }
    
    public class Doctor
    {
        public required long ID { get; set; }
        public required string Name { get; set; }
        public required string Specialty { get; set; }
    
        public override string ToString() => $"{ID}. {Name} ({Specialty})";
    }
    
    public class Disease
    {
        public required long ID { get; set; }
        public required string Name { get; set; }
    
        public override string ToString() => $"{ID}. {Name}";
    }
    

    Adding the comments like // Medicine class to class Medicine adds no value. Comments should add information missing in the code. E.g. design decisions and other non obvious facts.

    public class DoublyLinkedCircularList<T>
    {
        private Node<T>? _head;
    
        public void Add(T element)
        {
            var newNode = new Node<T> { Data = element };
            if (_head == null) {
                _head = newNode;
                _head.Previous = _head;
                _head.Next = _head;
            } else {
                newNode.Previous = _head.Previous;
                newNode.Next = _head;
                _head.Previous!.Next = newNode;
                _head.Previous = newNode;
            }
        }
    
        public T? QueryById(Func<T, bool> query)
        {
            if (_head == null) {
                return default;
            }
    
            Node<T>? current = _head;
            do {
                if (query(current!.Data)) {
                    return current.Data;
                }
                current = current.Next;
            } while (current != _head);
    
            return default;
        }
    }
    

    By using primary constructors and getter-only properties we can simplify the classes:

    public class Patient(long id, string firstName, string lastName)
    {
        public long ID { get; } = id;
        public string FirstName { get; } = firstName;
        public string LastName { get; } = lastName;
    
        public override string ToString() => $"{ID}. {FirstName} {LastName}";
    }
    

    The patients are now initialized like this:

    patientList.Add(new Patient(46573806740, "Hiranur", "Sazak"));
    patientList.Add(new Patient(23746982347, "Nisa Nur", "Özdal"));
    patientList.Add(new Patient(89374938243, "Nisa Gül", "Ünal"));
    patientList.Add(new Patient(98723424674, "Berfin", "Geleş"));
    

    Since patientList.Add requires a Patient argument, C# can infer the type Patient and we can write:

    patientList.Add(new(46573806740, "Hiranur", "Sazak"));
    patientList.Add(new(23746982347, "Nisa Nur", "Özdal"));
    patientList.Add(new(89374938243, "Nisa Gül", "Ünal"));
    patientList.Add(new(98723424674, "Berfin", "Geleş"));
    

    Circular lists are rarely useful. Therefore lets implement a non circular linked list. Since we never move backwards through the list, the Previous link is not required and the list does not need to be doubly linked.

    The node class is never exposed publicly. Therefore, we can implement it as nested private class. To enable adding elements to the end of the list we also add a reference to the _tail node.

    public class LinkedList<T> : IEnumerable<T>
    {
        private class Node
        {
            public required T Data { get; set; }
            public Node? Next { get; set; }
            public override string ToString() => $"Data = [{Data}]";
        }
    
        private Node? _head, _tail;
    
        public void Add(T element)
        {
            var newNode = new Node { Data = element };
            if (_head == null) {
                _head = newNode;
                _tail = newNode;
            } else {
                _tail!.Next = newNode;
                _tail = newNode;
            }
        }
    
        ...
    }
    

    Note the : IEnumerable<T> in the class header. It means that the class must implement the IEnumerable<T> interface which is very useful. It enables us to use foreach to loop through the list and gives us access to a lot of IEnumerable<T> extension methods that are part of the Language INtegrated Query LINQ.

    Only one method needs to be implemented:

    IEnumerator<T> GetEnumerator()
    

    However, we must return an object which implements the IEnumerator<T> Interface. We implement it as nested private struct LinkedListEnumerator. To do this we replace the 3 dots (...) above with:

    private struct LinkedListEnumerator(LinkedList<T> linkedList) : IEnumerator<T>
    {
        // Enumerators are positioned before the first element until the first MoveNext() call.
        private Node? _currentNode = new() { Data = default!, Next = linkedList._head };
    
        public readonly T Current => _currentNode is null ? default! : _currentNode.Data;
    
        readonly object IEnumerator.Current => Current!;
    
        public readonly void Dispose() { }
    
        public bool MoveNext()
        {
            _currentNode = _currentNode?.Next;
            return _currentNode != null;
        }
    
        public void Reset()
        {
            _currentNode = new() { Data = default!, Next = linkedList._head };
        }
    }
    
    public IEnumerator<T> GetEnumerator() => new LinkedListEnumerator(this);
    
    IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    

    Now we can initialize the patients list by using a collection initializer:

    var patientList = new LinkedList<Patient>{
        new(46573806740, "Hiranur", "Sazak"),
        new(23746982347, "Nisa Nur", "Özdal"),
        new(89374938243, "Nisa Gül", "Ünal"),
        new(98723424674, "Berfin", "Geleş")
    };
    

    We can loop through the patients like this:

    foreach (Patient patient in patientList) {
        Console.WriteLine(patient);
    }
    

    And we can find a patient like this by using LINQ (you may need to add using System.Linq; at the top of your code):

    Patient? queriedPatient = patientList.FirstOrDefault(patient => patient.ID == ID);
    

    Note that we did not have to implement QueryById as LINQ implements it with the extension method FirstOrDefault. LINQ uses our IEnumerable<T> implementation to iterate the list.

    Another useful interface that we could implement (not shown here) is the ICollection<T> Interface. It is implemented by a lot of collections and lets us easily replace one collection by another.