Devo usar uma lista ou uma matriz?


22

Estou trabalhando em um formulário do Windows para calcular UPC para números de item.

Criei com êxito um que lida com um número de item / UPC por vez, agora quero expandi-lo e fazer isso para vários números de item / UPCs.

Comecei e tentei usar uma lista, mas continuo preso. Eu criei uma classe auxiliar:

public class Codes
{
    private string incrementedNumber;
    private string checkDigit;
    private string wholeNumber;
    private string wholeCodeNumber;
    private string itemNumber;

    public Codes(string itemNumber, string incrementedNumber, string checkDigit, string wholeNumber, string wholeCodeNumber)
    {
        this.incrementedNumber = incrementedNumber;
        this.checkDigit = checkDigit;
        this.wholeNumber = wholeNumber;
        this.wholeCodeNumber = wholeCodeNumber;
        this.itemNumber = itemNumber;
    }

    public string ItemNumber
    {
        get { return itemNumber; }
        set { itemNumber = value; }
    }

    public string IncrementedNumber
    { 
        get { return incrementedNumber; }
        set { incrementedNumber = value; } 
    }

    public string CheckDigit
    {
        get { return checkDigit; }
        set { checkDigit = value; }
    }

    public string WholeNumber
    {
        get { return wholeNumber; }
        set { wholeNumber = value; }
    }

    public string WholeCodeNumber
    {
        get { return wholeCodeNumber; }
        set { wholeCodeNumber = value; }
    }

}

Em seguida, iniciei meu código, mas o problema é que o processo é incremental, ou seja, eu obtenho o número do item em uma visualização em grade por meio de caixas de seleção e os coloco na lista. Então eu pego o último UPC do banco de dados, retiro o dígito de verificação, depois incremento o número por um e o coloco na lista. Depois, calculo o dígito de verificação para o novo número e o coloco na lista. E aqui eu já recebo uma exceção de falta de memória. Aqui está o código que tenho até agora:

List<Codes> ItemNumberList = new List<Codes>();


    private void buttonSearch2_Click(object sender, EventArgs e)
    {
        //Fill the datasets
        this.immasterTableAdapter.FillByWildcard(this.alereDataSet.immaster, (textBox5.Text));
        this.upccodeTableAdapter.FillByWildcard(this.hangtagDataSet.upccode, (textBox5.Text));
        this.uPCTableAdapter.Fill(this.uPCDataSet.UPC);
        string searchFor = textBox5.Text;
        int results = 0;
        DataRow[] returnedRows;
        returnedRows = uPCDataSet.Tables["UPC"].Select("ItemNumber = '" + searchFor + "2'");
        results = returnedRows.Length;
        if (results > 0)
        {
            MessageBox.Show("This item number already exists!");
            textBox5.Clear();
            //clearGrids();
        }
        else
        {
            //textBox4.Text = dataGridView1.Rows[0].Cells[1].Value.ToString();
            MessageBox.Show("Item number is unique.");
        }
    }

    public void checkMarks()
    {

        for (int i = 0; i < dataGridView7.Rows.Count; i++)
        {
            if ((bool)dataGridView7.Rows[i].Cells[3].FormattedValue)
            {
                {
                    ItemNumberList.Add(new Codes(dataGridView7.Rows[i].Cells[0].Value.ToString(), "", "", "", ""));
                }
            }
        }
    }

    public void multiValue1()
    {
        _value = uPCDataSet.UPC.Rows[uPCDataSet.UPC.Rows.Count - 1]["UPCNumber"].ToString();//get last UPC from database
        _UPCNumber = _value.Substring(0, 11);//strip out the check-digit
        _UPCNumberInc = Convert.ToInt64(_UPCNumber);//convert the value to a number

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            _UPCNumberInc = _UPCNumberInc + 1;
            _UPCNumberIncrement = Convert.ToString(_UPCNumberInc);//assign the incremented value to a new variable
            ItemNumberList.Add(new Codes("", _UPCNumberIncrement, "", "", ""));//**here I get the OutOfMemoreyException**
        }

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            long chkDigitOdd;
            long chkDigitEven;
            long chkDigitSubtotal;
            chkDigitOdd = Convert.ToInt64(_UPCNumberIncrement.Substring(0, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(2, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(4, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(6, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(8, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(10, 1));
            chkDigitOdd = (3 * chkDigitOdd);
            chkDigitEven = Convert.ToInt64(_UPCNumberIncrement.Substring(1, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(3, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(5, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(7, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(9, 1));
            chkDigitSubtotal = (300 - (chkDigitEven + chkDigitOdd));
            _chkDigit = chkDigitSubtotal.ToString();
            _chkDigit = _chkDigit.Substring(_chkDigit.Length - 1, 1);
            ItemNumberList.Add(new Codes("", "",_chkDigit, "", ""));
        }

É este o caminho certo para fazer isso, usando uma lista, ou devo procurar uma maneira diferente?


Usar uma lista é bom. Iterar a lista enquanto a adiciona é uma maneira infalível de explodir seu código e indica um problema na sua lógica (ou na escrita do código). Além disso, esse é o seu erro e provavelmente não ajudará futuros visitantes. Votação para fechar.
Telastyn

2
Como observação lateral, todos esses campos particulares (na sua Codeclasse) são redundantes e nada além de ruído, na verdade, { get; private set; }seria suficiente.
21913 Konrad Morawski

5
O título da pergunta para isso é realmente preciso? Isso realmente não parece uma questão de lista versus matriz , mas sim como eu posso melhorar minha pergunta de implementação . Dito isto, se você estiver adicionando ou removendo elementos, deseja uma lista (ou outra estrutura de dados flexível). As matrizes são realmente muito boas quando você sabe exatamente quantos elementos você precisa no início.
KChaloux

@KChaloux, é praticamente o que eu queria saber. É uma lista o caminho certo para fazer isso, ou eu deveria ter procurado uma maneira diferente de fazer isso? Parece que uma lista é uma boa maneira, só preciso ajustar minha lógica.
Campagnolo_1

1
@ Telastyn, eu não estava pedindo tanto para melhorar meu código como para mostrar o que estou tentando fazer.
Campagnolo_1

Respostas:


73

Vou expandir meu comentário:

... se você estiver adicionando ou removendo elementos, deseja uma lista (ou outra estrutura de dados flexível). As matrizes são realmente muito boas quando você sabe exatamente quantos elementos você precisa no início.

Uma análise rápida

As matrizes são boas quando você tem um número fixo de elementos que é improvável que seja alterado e deseja acessá-lo de maneira não sequencial.

  • Tamanho fixo
  • Acesso Rápido - O (1)
  • Redimensionamento lento - O (n) - precisa copiar todos os elementos para uma nova matriz!

As Listas Vinculadas são otimizadas para adições e remoções rápidas em cada extremidade, mas seu acesso é lento no meio.

  • Tamanho variável
  • Acesso lento no meio - O (n)
    • Precisa atravessar cada elemento a partir da cabeça para alcançar o índice desejado
  • Acesso Rápido na Cabeça - O (1)
  • Acesso potencialmente rápido na cauda
    • O (1) se uma referência for armazenada no final da cauda (como em uma lista duplamente vinculada)
    • O (n) se nenhuma referência for armazenada (a mesma complexidade de acessar um nó no meio)

As listas de matrizes (como List<T>em C #!) São uma mistura das duas, com adições bastante rápidas e acesso aleatório. List<T> geralmente será sua coleção preferida quando você não tiver certeza do que usar.

  • Usa uma matriz como uma estrutura de suporte
  • É inteligente quanto ao redimensionamento - aloca o dobro de seu espaço atual quando fica sem espaço.
    • Isso leva a O (log n) redimensionado, o que é melhor do que redimensionar toda vez que adicionamos / removemos
  • Acesso Rápido - O (1)

Como matrizes funcionam

A maioria das linguagens de modelo de matrizes é como dados contíguos na memória, dos quais cada elemento tem o mesmo tamanho. Digamos que tivemos uma matriz de ints (mostrada como [endereço: valor], usando endereços decimais porque sou preguiçoso)

[0: 10][32: 20][64: 30][96: 40][128: 50][160: 60]

Cada um desses elementos é um número inteiro de 32 bits; portanto, sabemos quanto espaço ele ocupa na memória (32 bits!). E sabemos o endereço de memória do ponteiro para o primeiro elemento.

É trivialmente fácil obter o valor de qualquer outro elemento nessa matriz:

  • Pegue o endereço do primeiro elemento
  • Tome o deslocamento de cada elemento (seu tamanho na memória)
  • Multiplique o deslocamento pelo índice desejado
  • Adicione seu resultado ao endereço do primeiro elemento

Digamos que nosso primeiro elemento seja '0'. Sabemos que nosso segundo elemento está em '32' (0 + (32 * 1)), e nosso terceiro elemento está em 64 (0 + (32 * 2)).

O fato de podermos armazenar todos esses valores próximos um do outro na memória significa que nossa matriz é o mais compacta possível. Isso também significa que todos os nossos elementos precisam permanecer juntos para que as coisas continuem funcionando!

Assim que adicionamos ou removemos um elemento, precisamos pegar todo o resto e copiá-los para algum novo local na memória, para garantir que não haja lacunas entre os elementos e que tudo tenha espaço suficiente. Isso pode ser muito lento , especialmente se você estiver fazendo isso toda vez que quiser adicionar um único elemento.

Listas Vinculadas

Diferentemente das matrizes, as Listas Vinculadas não precisam que todos os seus elementos estejam próximos um do outro na memória. Eles são compostos de nós, que armazenam as seguintes informações:

Node<T> {
    Value : T      // Value of this element in the list
    Next : Node<T> // Reference to the node that comes next
}

A lista em si mantém uma referência à cabeça e à cauda (primeiro e último nós) na maioria dos casos, e às vezes acompanha seu tamanho.

Se você quiser adicionar um elemento ao final da lista, tudo que você precisa fazer é obter a cauda , e alterar o seu Nextpara fazer referência a um novo Nodecontendo o seu valor. Remover do final é igualmente simples - basta desreferenciar o Nextvalor do nó anterior.

Infelizmente, se você possui um LinkedList<T>com 1000 elementos e deseja o elemento 500, não há uma maneira fácil de pular direto para o 500º elemento, como ocorre com uma matriz. Você precisa começar na cabeça , e continue indo ao Nextnó, até que você tenha feito isso 500 vezes.

É por isso que adicionar e remover de um LinkedList<T>é rápido (quando se trabalha nas extremidades), mas o acesso ao meio é lento.

Edit : Brian ressalta nos comentários que as Listas Vinculadas correm o risco de causar uma falha na página, por não serem armazenadas na memória contígua. Isso pode ser difícil de comparar e pode tornar as Listas vinculadas ainda mais lentas do que você imagina, dadas as complexidades de tempo.

Melhor dos dois mundos

List<T>compromete os dois T[]e LinkedList<T>cria uma solução razoavelmente rápida e fácil de usar na maioria das situações.

Internamente, List<T>é uma matriz! Ele ainda tem que pular os bastidores da cópia de seus elementos ao redimensionar, mas faz alguns truques legais.

Para iniciantes, adicionar um único elemento geralmente não faz com que a matriz copie. List<T>garante que sempre haja espaço suficiente para mais elementos. Quando acabar, em vez de alocar uma nova matriz interna com apenas um novo elemento, ele alocará uma nova matriz com vários novos elementos (geralmente o dobro do que atualmente possui!).

As operações de cópia são caras, List<T>reduzindo-as o máximo possível, enquanto ainda permitem acesso aleatório rápido. Como efeito colateral, pode acabar desperdiçando um pouco mais de espaço do que uma matriz direta ou uma lista vinculada, mas geralmente vale a pena a troca.

TL; DR

Use List<T>. Normalmente é o que você deseja e parece correto para você nessa situação (onde você está chamando .Add ()). Se você não tiver certeza do que precisa, List<T>é um bom lugar para começar.

Matrizes são boas para coisas de alto desempenho, "eu sei que preciso exatamente de X elementos". Como alternativa, eles são úteis para estruturas rápidas e únicas "Eu preciso agrupar essas X coisas que eu já defini juntas para poder fazer um loop sobre elas".

Existem várias outras classes de coleção. Stack<T>é como uma lista vinculada que opera apenas de cima. Queue<T>funciona como uma lista primeiro a entrar, primeiro a sair. Dictionary<T, U>é um mapeamento associativo não ordenado entre chaves e valores. Brinque com eles e conheça os pontos fortes e fracos de cada um. Eles podem criar ou quebrar seus algoritmos.


2
Em alguns casos, pode haver vantagens em usar uma combinação de uma matriz e uma intindicação do número de elementos utilizáveis. Entre outras coisas, é possível copiar em massa vários elementos de uma matriz para outra, mas copiar entre listas geralmente requer elementos de processamento individualmente. Além disso, elementos de matriz podem ser passados refpara coisas como Interlocked.CompareExchange, enquanto itens de lista não podem.
Supercat

2
Eu gostaria de poder dar mais de um voto positivo. Eu conhecia as diferenças de casos de uso e como matrizes / listas vinculadas funcionavam, mas nunca soube ou pensei sobre como List<>funcionava sob o capô.
22413 Bobson

1
A adição de um único elemento a uma Lista <T> é amortizada O (1); a eficiência da adição de elementos normalmente não é uma justificativa suficiente para usar uma lista vinculada (e uma Lista circular permite adicionar à frente E atrás no O (1) amortizado). As listas vinculadas têm muitas idiossincrasias de desempenho. Por exemplo, não ser armazenado contiguamente na memória significa que a iteração sobre uma lista vinculada inteira tem mais chances de causar uma falha de página ... e isso é difícil de comparar. A maior justificativa para o uso de uma lista vinculada é quando você deseja concatenar duas listas (pode ser feita em O (1)) ou adicionar elementos ao meio.
21713 Brian

1
Eu deveria esclarecer. Quando eu disse lista circular, quis dizer uma lista de matriz circular, não uma lista vinculada circular. O termo correto seria deque (fila dupla). Eles geralmente são implementados da mesma maneira que uma Lista (matriz sob o capô), com uma exceção: existe um valor inteiro interno, "first", que indica qual índice da matriz é o primeiro elemento. Para adicionar um elemento à parte traseira, basta subtrair 1 do "primeiro" (contornando o comprimento da matriz, se necessário). Para acessar um elemento, basta acessar (index+first)%length.
21713 Brian

2
Existem algumas coisas que você não pode fazer com uma lista que você pode fazer com uma matriz simples, por exemplo, passando um elemento de índice como um parâmetro ref.
Ian Goldby

6

Embora a resposta do KChaloux seja ótima, eu gostaria de destacar outra consideração: List<T>é muito mais poderosa que uma matriz. Os métodos de List<T>são muito úteis em muitas circunstâncias - uma matriz não possui esses métodos e você pode gastar muito tempo para implementar soluções alternativas.

Portanto, de uma perspectiva de desenvolvimento, quase sempre uso List<T>porque, quando existem requisitos adicionais, eles costumam ser muito mais fáceis de implementar quando você usa um List<T>.

Isso leva a um problema final: meu código (não sei o seu) contém 90% List<T>, portanto, as matrizes não se encaixam muito bem. Quando as passo a passo, preciso chamar o .toList()método delas e convertê-las em uma lista. é irritante e é tão lento que qualquer ganho de desempenho ao usar uma matriz é perdido.


Isso é verdade, esse é outro bom motivo para usar a Lista <T> - fornece muito mais funcionalidades para você criar diretamente na classe.
KChaloux

1
O LINQ não nivela o campo adicionando muita dessa funcionalidade a qualquer IEnumerable (incluindo uma matriz)? Ainda resta alguma coisa no C # moderno (4+) que você possa fazer apenas com uma Lista <T> e não com uma matriz?
31414 Dave

1
@ Dave Estender a matriz / lista parece uma coisa dessas. Além disso, eu diria que a sintaxe para construir / manipular uma lista é muito melhor do que para matrizes.
Christian Sauer

2

No entanto, ninguém mencionou esta parte: "E aqui eu já recebo uma exceção de falta de memória". O que é inteiramente devido a

for (int i = 0; i < ItemNumberList.Count; i++)
{
    ItemNumberList.Add(new item());
}

É fácil ver o porquê. Não sei se você pretendia adicionar a uma lista diferente ou deveria apenas armazenar ItemNumberList.Countcomo uma variável antes do loop para obter o resultado desejado, mas isso é simplesmente quebrado.

Programmers.SE é para "... interessado em perguntas conceituais sobre desenvolvimento de software ...", e as outras respostas o trataram como tal. Tente http://codereview.stackexchange.com , onde essa pergunta se encaixaria. Mas mesmo lá é horrível, pois podemos apenas assumir que esse código começa em _Click, que não tem chamada para multiValue1onde você diz que o erro acontece.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.