Lista dinâmica duplamente encadeada – C#

Autor: Emerson Shigueo Sugimoto 08/03/2014

Criei este projeto para resolver um problema que envolvia LinquedList (C#), que por definição é uma lista duplamente ligada. O objetivo do projeto é o desenvolvimento de uma biblioteca de objetos genéricos armazenados em uma lista duplamente ligada com inserção ordenada.

Exemplo de Entrada:

LinkedList lqDt = new LinkedList();
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now));
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(-1)));
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(-4)));
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(10)));

txt1.Text = UtilLinquedList.Print(lqDt) + Environment.NewLine + Environment.NewLine;

LinkedList lqInt = new LinkedList();
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(3));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(20));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(14));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(-7));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(1));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(3));

txt1.Text += UtilLinquedList.Print(lqInt) + Environment.NewLine + Environment.NewLine;

LinkedList lqPessoa = new LinkedList();
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Emerson", Idade = 10 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Maria", Idade = 7 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Alana", Idade = 20 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Denise", Idade = 20 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Kurtis", Idade = 15 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Paula", Idade = 9 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Gogh", Idade = 7 }));
UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Four", Idade = 4 }));

txt1.Text += UtilLinquedList.Print(lqPessoa) + Environment.NewLine + Environment.NewLine;

Exemplo de saída do sistema:

SaidaDoubleLinquedList

Estrutura do projeto:

projeto_linqued_list

Classe AbstLinquedList

A classe AbstLinquedList representa um item da lista duplamente encadeada. Os métodos necessários a ordenação dos itens na lista duplamente encadeada são:

  • CompareTo
  • AbsDiff
  • int operator -
using System;

namespace DoubleLinquedList.code.linqued {

    /// <summary>
    /// Classe abstrata - representa um item da linquedlist
    /// 
    /// Desenvolvido por:
    /// Emerson Shigueo Sugimoto 08/03/2014
    /// </summary>
    public abstract class AbstLinquedList : IComparable {
        public abstract int CompareTo(object obj);

        public abstract int AbsDiff(AbstLinquedList obj);
        public static int operator -(AbstLinquedList x, AbstLinquedList y) {
            return x.AbsDiff(y);
        }

        public abstract override string ToString();
    }

}

O método CompareTo é responsável por comparar dois objetos AbstLinquedList (estude a interface IComparablehttp://msdn.microsoft.com/pt-br/library/system.icomparable(v=vs.110).aspx). Sendo que se a função retornar 0, os objetos são “iguais”, se retornar -1, o obj é “menor” do que o objeto da classe AbstLinquedList e se retorna 1, é “maior”. Parece ser confuso para quem está aprendendo, mais é um conceito extremamente simples e amplamente utilizado.

Uso os termos comparativos entre aspas: “maior”, “menor” e “iguais” de forma genérica, pois para dois números, objetos do tipo DateTime, etc., é compreensível dizer que um número é maior, menor ou igual a outro, porém para objetos mais complexos, como um struct ou classe que representa uma entidade, é estranho pensar que uma classe é “maior”, “menor” ou “igual” (não pense no termo Equals, mais em igualdade númerica) que outra. Na verdade o “maior”, “menor” ou “igual” se aplica a função de comparação que você vai usar. Como exemplo este artigo define a classe Pessoa, com propriedades Nome (string) e Idade (int), para comparar dois objetos Pessoa eu uso o atributo Idade.

O método AbsDiff é responsável por realizar a diferença absoluta (Math.Abs) entre dois objetos do tipo AbstLinquedList. O número absoluto de uma diferença numérica pode ser traduzido pela função ternária:

int a = 3;
int b = 7;
return a > b ? a - b : b - a;

A sobrecarga do operador ‘-’ é feita pela função:

        public static int operator -(AbstLinquedList x, AbstLinquedList y) {
            return x.AbsDiff(y);
        }

O método ToString() é usado para imprimir o objeto AbstLinquedList através do método Print(LinkedList lq) da classe UtilLinquedList.

Classe UtilLinquedList

É nesta classe que a inserção de forma ordenada é feita.

using System;
using System.Collections.Generic;
using System.Text;

namespace DoubleLinquedList.code.linqued {

    /// <summary>
    /// Funções úteis a linked list
    /// 
    /// Desenvolvido por:
    /// Emerson Shigueo Sugimoto 08/03/2014
    /// </summary>
    public static class UtilLinquedList {

        /// <summary>
        /// Adiciona os itens na Linqued List de forma ordenada
        /// </summary>
        /// <param name="lq">LinkedList</param>
        /// <param name="valor">AbstLinquedList</param>
        public static void AddLinkedList(ref LinkedList<AbstLinquedList> lq, AbstLinquedList valor) {
            if (lq == null) { lq = new LinkedList<AbstLinquedList>(); }
            if (lq.Count <= 0) { lq.AddFirst(valor); return; }

            LinkedListNode<AbstLinquedList> first = lq.First;
            LinkedListNode<AbstLinquedList> last = lq.Last;

            if (first.Value.CompareTo(valor) == 1) {
                lq.AddFirst(valor);
                return;
            }
            if (last.Value.CompareTo(valor) == -1) {
                lq.AddLast(valor);
                return;
            }

            //esta mais próximo do inicio ou do fim ?
            int diffInicio = first.Value - valor;
            int diffFim = last.Value - valor;

            if (diffInicio <= diffFim) { //mais próximo do início
                while (first.Next.Value.CompareTo(valor) == -1) {
                    first = lq.Find(first.Value).Next;
                }
                lq.AddBefore(first.Next, valor);
            } else { //mais próximo do fim
                while (last.Previous.Value.CompareTo(valor) == 1) {
                    last = lq.Find(last.Value).Previous;
                }
                lq.AddAfter(last.Previous, valor);
            }
        }

        /// <summary>
        /// Imprime objetos AbstLinquedList, observe a sobrecarga ToString()
        /// </summary>
        /// <param name="lq">LinkedList</param>
        /// <returns></returns>
        public static string Print(LinkedList<AbstLinquedList> lq) {
            StringBuilder rt = new StringBuilder();
            foreach (AbstLinquedList nd in lq) {
                if (rt.Length > 0) { rt.Append(", "); }
                rt.Append(nd.ToString());
            }
            return rt.ToString();
        }

    }
}
/*
-----------------------
    Exemplo de uso:
-----------------------

    LinkedList<AbstLinquedList> lqDt = new LinkedList<AbstLinquedList>();
    UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now));
    UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(-1)));
    UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(-4)));
    UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(10)));

    txt1.Text = UtilLinquedList.Print(lqDt) + Environment.NewLine + Environment.NewLine;

    LinkedList<AbstLinquedList> lqInt = new LinkedList<AbstLinquedList>();
    UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(3));
    UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(20));
    UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(14));
    UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(-7));
    UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(1));
    UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(3));

    txt1.Text += UtilLinquedList.Print(lqInt) + Environment.NewLine + Environment.NewLine;

    LinkedList<AbstLinquedList> lqPessoa = new LinkedList<AbstLinquedList>();
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Emerson", Idade = 10 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Maria", Idade = 7 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Alana", Idade = 20 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Denise", Idade = 20 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Kurtis", Idade = 15 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Paula", Idade = 9 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Gogh", Idade = 7 }));
    UtilLinquedList.AddLinkedList(ref lqPessoa, new LQPessoa(new Pessoa() { Nome = "Four", Idade = 4 }));

    txt1.Text += UtilLinquedList.Print(lqPessoa) + Environment.NewLine + Environment.NewLine; 
*/

O método Print retorna a sobrecarga ToString() dos objetos AbstLinquedList.

O método AddLinkedList(ref LinkedList lq, AbstLinquedList valor) adiciona de forma ordenada os objetos AbstLinquedList na lista LinkedList lq. A palavra chave ref indica que a lista será passada por referência, pode-se ler, de forma simplista, que um ponteiro para a variável será passado. Descrição do método:

Caso a lista seja nula, crie:

if (lq == null) { lq = new LinkedList(); }

Se está vazia, adicione o objeto:

if (lq.Count <= 0) { lq.AddFirst(valor); return; }

Primeiro e último itens da lista:

LinkedListNode first = lq.First;
LinkedListNode last = lq.Last;

Se o primeiro item da lista é “maior” do que o item a ser adicionado, significa que o item a ser adicionado será o primeiro item.

            if (first.Value.CompareTo(valor) == 1) {
                lq.AddFirst(valor);
                return;
            }

Se o último item da lista é “menor” do que o item a ser adicionado, significa que o item a ser adicionado será o último item.

            if (last.Value.CompareTo(valor) == -1) {
                lq.AddLast(valor);
                return;
            }

Neste ponto do código, eu preciso “varrer” a lista encadeada até encontrar o ponto de inserção. Sabendo que eu tenho o primeiro item e o último item da lista, eu preciso saber a distância do item em relação ao primeiro item e em relação ao último item, se ele estiver mais próximo do início, eu “varro” a lista de frente para trás, se ele estiver mais próximo do fim da lista, eu “varro” a lista de trás para frente.
Uso o termo chulo “varrer” para explicar de forma direta a intenção de percorrer os itens da lista.

//esta mais próximo do inicio ou do fim ?
int diffInicio = first.Value - valor;
int diffFim = last.Value - valor;

Se o item está mais próximo do início ou equidistante (diffInicio <= diffFim):

if (diffInicio <= diffFim) { //mais próximo do início

Enquanto os itens da lista forem “menores” do que o item a ser adicionado, vá para o próximo.

                while (first.Next.Value.CompareTo(valor) == -1) {
                    first = lq.Find(first.Value).Next;
                }

Adiciona o item na sequência da lista encadedada:

                lq.AddBefore(first.Next, valor);
            }

Se o ponto está mais próximo do final:

else { //mais próximo do fim

Enquanto os itens da lista forem “maiores” do que o item a ser adicionado, vá para o anterior.

                while (last.Previous.Value.CompareTo(valor) == 1) {
                    last = lq.Find(last.Value).Previous;
                }

Adiciona o item na sequência da lista encadedada:

                lq.AddAfter(last.Previous, valor);
            }

Classe LQInt

Exemplo de implementação da classe AbstLinquedList.

using System;

namespace DoubleLinquedList.code.linqued.implement {

    /// <summary>
    /// Exemplo de implementação de AbstLinquedList para objetos do tipo int
    /// 
    /// Desenvolvido por:
    /// Emerson Shigueo Sugimoto 08/03/2014
    /// </summary>
    public class LQInt : AbstLinquedList {

        public int Valor { get; set; }

        public LQInt(int valor) {
            Valor = valor;
        }

        public override int CompareTo(object obj) {
            if (obj == null) { return 1; }
            return ((LQInt)obj).Valor > Valor ? -1 : 1;
        }

        public override string ToString() {
            return Valor.ToString();
        }

        public override int AbsDiff(AbstLinquedList obj) {
            return Math.Abs(Valor - ((LQInt)obj).Valor);
        }
    }

}

A implementação é feita para objetos do tipo int.
A propriedade Valor:

public int Valor { get; set; }

É a base dos métodos da classe. CompareTo é dado por:

return ((LQInt)obj).Valor > Valor ? -1 : 1;

E AbsDiff:

return Math.Abs(Valor - ((LQInt)obj).Valor);

Exemplo de uso:

LinkedList lqInt = new LinkedList();
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(3));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(20));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(14));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(-7));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(1));
UtilLinquedList.AddLinkedList(ref lqInt, new LQInt(3));

txt1.Text += UtilLinquedList.Print(lqInt) + Environment.NewLine + Environment.NewLine;

Classe LQData

using System;

namespace DoubleLinquedList.code.linqued.implement {

    /// <summary>
    /// Exemplo de implementação de AbstLinquedList para objetos do tipo DateTime
    /// 
    /// Desenvolvido por:
    /// Emerson Shigueo Sugimoto 08/03/2014
    /// </summary>
    public class LQData : AbstLinquedList {

        public DateTime Valor { get; set; }

        public LQData(DateTime valor) {
            Valor = valor;
        }

        public override int CompareTo(object obj) {
            if (obj == null) { return 1; }
            return Valor.CompareTo(((LQData)obj).Valor);
        }

        public override string ToString() {
            return Valor.ToString("dd/MM/yyyy dd:mm:ss.fff");
        }

        public override int AbsDiff(AbstLinquedList obj) {
            return Math.Abs((Valor - ((LQData)obj).Valor).Milliseconds);
        }
    }

}

Exemplo de uso:

LinkedList lqDt = new LinkedList();
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now));
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(-1)));
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(-4)));
UtilLinquedList.AddLinkedList(ref lqDt, new LQData(DateTime.Now.AddDays(10)));

txt1.Text = UtilLinquedList.Print(lqDt) + Environment.NewLine + Environment.NewLine;

Classe Pessoa

Criado para representar a ordenação de entidades.

namespace DoubleLinquedList.code.entidades {

    /// <summary>
    /// Classe Pessoa
    /// 
    /// Desenvolvido por:
    /// Emerson Shigueo Sugimoto 08/03/2014
    /// </summary>
    public class Pessoa {

        public string Nome { get; set; }
        public int Idade { get; set; }

        //.. etc

        public override string ToString() {
            return Nome + " " + Idade;
        }

    }

}

Classe LQPessoa

Ordena objetos do tipo Pessoa.

using DoubleLinquedList.code.entidades;
using System;

namespace DoubleLinquedList.code.linqued.implement {

    /// <summary>
    /// Exemplo de implementação de AbstLinquedList para objetos do tipo Pessoa
    /// 
    /// Desenvolvido por:
    /// Emerson Shigueo Sugimoto 08/03/2014
    /// </summary>
    public class LQPessoa : AbstLinquedList {
        public Pessoa Valor { get; set; }

        public LQPessoa(Pessoa valor) {
            Valor = valor;
        }

        /// <summary>
        /// Observe a sobrecarga, aqui compara-se a idade.
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public override int CompareTo(object obj) {
            if (obj == null) { return 1; }
            return ((LQPessoa)obj).Valor.Idade > Valor.Idade ? -1 : 1;
        }

        /// <summary>
        /// Sobrecarga usada no método UtilLinquedList.Print
        /// </summary>
        /// <returns></returns>
        public override string ToString() {
            return Valor.ToString();
        }

        public override int AbsDiff(AbstLinquedList obj) {
            return Math.Abs(Valor.Idade - ((LQPessoa)obj).Valor.Idade);
        }

    }

}

Esta foi a solução genérica que encontrei para ordenar objetos em uma estrutura de linkedlist. Possui sugestões ou melhorias? favor comentar no artigo.

O download do código fonte pode ser feito em:

https://mega.co.nz/#!EVIR1AKA!iUZcrrqrb4TKX9gmOFZsUGdol__ZfmyBtMoUSTULILQ

Deixe um comentário