Minha coleção seqüencial deve começar no índice 0 ou no índice 1?

25

Estou criando um modelo de objeto para um dispositivo que possui vários canais. Os substantivos usados ​​entre o cliente e eu são Channele ChannelSet. ("Conjunto" não é semanticamente preciso, porque é pedido e um conjunto adequado não é. Mas isso é um problema para um tempo diferente.)

Eu estou usando c #. Aqui está um exemplo de uso de ChannelSet:

// load a 5-channel ChannelSet
ChannelSet channels = ChannelSetFactory.FromFile("some_5_channel_set.json");

Console.Write(channels.Count);
// -> 5

foreach (Channel channel in channels) {
    Console.Write(channel.Average);
    Console.Write(",  ");
}
// -> 0.3,  0.3,  0.9,  0.1,  0.2

Tudo é dândi. No entanto, os clientes não são programadores e absolutamente ficarão confusos com a indexação zero - o primeiro canal é o canal 1 para eles. Mas, por uma questão de consistência com o C #, quero manter o ChannelSetindexado do zero .

Isso certamente causará algumas desconexões entre minha equipe de desenvolvimento e os clientes quando eles interagem. Porém, pior ainda, qualquer inconsistência em como isso é tratado na base de código é um problema em potencial. Por exemplo, aqui está uma tela da interface do usuário em que o usuário final ( que pensa em termos de 1 indexação ) está editando o canal 13:

Canal 13 ou 12?

Esse Savebotão acabará resultando em algum código. Se ChannelSetfor 1 indexado:

channels.GetChannel(13).SomeProperty = newValue;  // notice: 13

ou isso se for zero indexado:

channels.GetChannel(12).SomeProperty = newValue;  // notice: 12

Não tenho muita certeza de como lidar com isso. Eu sinto que é uma boa prática manter uma lista ordenada e indexada por números inteiros ( ChannelSetconsistente) com todas as outras interfaces de matriz e lista no universo C # (por indexação zero ChannelSet). Mas todo código entre a interface do usuário e o back-end precisará de uma tradução (subtrair por 1), e todos sabemos como os erros off-by-one comuns e insidiosos já são.

Então, uma decisão como essa já te mordeu? Devo zerar o índice ou um índice?

kdbanman
fonte
60
"Os índices da matriz devem começar em 0 ou 1? Meu compromisso de 0,5 foi rejeitado sem, pensei, consideração adequada." - Stan Kelly-Bootle
mosquito
2
@gnat É uma coisa boa que estou sozinha no escritório - gargalhadas olhando para a tela do computador geralmente não são bons indicadores de produtividade!
Kdbanman
4
Os canais precisam ser identificados por sua posição no aparelho? Você não pode identificá-los por outra coisa (por exemplo, frequência, se esses forem canais de TV)?
svick
@ Rick, esse é um ótimo ponto. Posso permitir o acesso ao canal por diferentes identificadores posteriormente, mas o idioma principal dos clientes realmente parece ser um número de canal indexado.
Kdbanman
De uma leitura rápida, parece que não há necessidade de usar índices por uma questão de eficiência; portanto, você pode usar algum tipo de mapa para vincular diretamente IDs de canal e canais sem precisar pensar em matrizes. Acho que é isso que Rob está chegando, e também concordo com a solução dele, se você optar por usar índices.
Matthew Leia

Respostas:

24

Parece que você está confundindo o Identificador Channelcom a sua posição dentro de a ChannelSet. A seguir, é minha visualização de como seu código / comentários ficaria no momento:

public sealed class ChannelSet
{
    private Channel[] channels;
    /// <summary>Retrieves the specified channel</summary>
    /// <param name="channelId">The id of the channel to return</param>
    Channel GetChannel(int channelId)
    {
        return channels[channelId-1];
    }
}

Parece que você decidiu que, como Channels dentro de ChannelSetsão identificados por números que têm um limite superior e inferior, eles devem ser índices e, portanto, como é baseado em C #, 0. Se a maneira natural de se referir a cada um dos canais for por um número entre 1 e X, consulte-os por um número entre 1 e X. Não tente forçá-los a serem índices.

Se você realmente deseja fornecer uma maneira de acessá-los por índice baseado em 0 (que benefício isso oferece ao usuário final ou aos desenvolvedores que consomem o código?), Implemente um Indexador :

public sealed class ChannelSet
{
    private Channel[] channels;
    /// <summary>Retrieves the specified channel</summary>
    /// <param name="channelId">The id of the channel to return</param>
    public Channel GetChannel(int channelId)
    {
        return channels[channelId-1];
    }

    /// <summary>Return the channel at the specified index</summary>
    public Channel this[int index]
    {
        return channels[index];
    }
}
Roubar
fonte
2
Esta é uma resposta realmente completa e concisa. É exatamente a abordagem que acabei derivando das outras respostas.
kdbanman
7
Transeuntes, aceitei esta resposta, mas este tópico está cheio de boas respostas, então continue lendo.
kdbanman
Muito bom, @Rob. "Eu sei como usar indexadores." - Neo
Jason P Sallinger
35

Mostre a interface do usuário com o índice 1, use o índice 0 no código.

Dito isso, trabalhei com dispositivos de áudio como esse e usei o índice 1 para os canais e projetei o código para nunca usar "Index" ou indexadores para ajudar a evitar frustrações. Alguns programadores ainda reclamaram, então nós mudamos. Outros programadores reclamaram.

Basta escolher um e ficar com ele. Existem problemas maiores a serem resolvidos no grande esquema de obter software pela porta.

Telastyn
fonte
12
A exceção a isso: se você estiver usando um idioma baseado em 1. Seu código deve ser consistente com o restante do seu código e seu idioma, portanto, com base em 0 em C #, em 1 em VBA (!) Ou Lua. Sua interface do usuário deve ser o que os humanos esperam (que quase sempre é baseado em 1).
user253751
1
@immibis - bom ponto, eu não tinha pensado nisso.
Telastyn
3
Uma coisa que ocasionalmente sinto falta dos velhos tempos de programação no BASIC é "OPTION BASE" ...
Brian Knoblauch
1
@ BlueRaja-DannyPflughoeft: Embora eu sempre tenha pensado Option Basebobo, eu gostei do recurso de alguns idiomas oferecidos para permitir que as matrizes especifiquem individualmente limites inferiores arbitrários. Se alguém possui uma matriz de dados por ano, por exemplo, além da capacidade de dimensionar uma matriz [firstYear..lastYear]pode ser melhor do que ter que sempre acessar o elemento [thisYear-firstYear].
11123
1
@ BlueRaja-DannyPflughoeft bug um homem é o recurso de outro homem ...
Brian Knoblauch
21

Use ambos.

Não misture a interface do usuário com seu código principal. Internamente (como uma biblioteca), você deve codificar "sem saber" como cada elemento na matriz será chamado pelo usuário final. Use as matrizes e coleções "naturais" indexadas em 0.

A parte do programa que une os dados à interface do usuário, a visualização, deve ter o cuidado de traduzir corretamente os dados entre o Modelo Mental do Usuário e a 'Biblioteca' que faz o trabalho real.

Por que isso é melhor?

  • O código pode ser mais limpo, sem hacks para traduzir a indexação. Isso também ajuda os programadores por não esperar que eles sigam e lembre-se de usar convenções não naturais.
  • Os usuários usarão a indexação esperada. Você não quer incomodá-los.
Dietr1ch
fonte
12

Você pode ver a coleção de dois ângulos diferentes.

(1) É, em primeiro lugar, uma coleção seqüencial regular , como uma matriz ou uma lista. O índice de 0é obviamente a solução certa, seguindo a convenção. Você aloca entradas suficientes e mapeia o número do canal para índices, o que é trivial (apenas subtraia 1).

(2) Sua coleção é essencialmente um mapeamento entre identificadores de canal e objetos de informação de canal. Acontece que os identificadores de canal são um intervalo seqüencial de números inteiros; amanhã pode ser algo parecido [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. Você usa esse conjunto ordenado como chaves de mapeamento.

Escolha uma das abordagens, documente-a e cumpra-a. Do ponto de vista da flexibilidade, eu usaria (2), porque a representação do número do canal (pelo menos o nome exibido) é relativamente provável de mudar no futuro.

9000
fonte
Eu realmente aprecio a parte 2 da sua resposta. Eu acho que vou adotar algo assim. O índice de assessor ( channels[int]) será zero de inteiros indexados, e os assessores Obter GetChannelByFrequency, GetChannelByName, GetChannelByNumberserá flexível.
Kdbanman
1
A segunda abordagem é definitivamente a melhor. Minhas emissoras locais oferecem 32 canais com LCNs que variam de 1 a 201. Isso exigiria uma matriz esparsa (84% vazia). É conceitualmente como armazenar uma coleção de pessoas em uma matriz indexada por número de telefone.
557 Thomas Kelly
3
Além disso, qualquer coleção tão pequena que seja representada por uma lista suspensa da interface do usuário é extremamente improvável de se beneficiar das características específicas de desempenho de uma matriz como a estrutura de dados. Portanto, a coisa chamada "canal 1" pelo usuário também pode ser chamada "1" no código e usar um DictionaryouSortedDictionary .
21715 Steve Jobs (
1
O princípio da menor surpresa implicaria que operator [] (int index) deveria ser baseado em 0, enquanto operator [] (IOrdered index)não seria. (Desculpas para a sintaxe aproximada, o meu C # é muito enferrujado.)
9000
2
@kdbanman: pessoalmente, eu diria que, assim que você estiver sobrecarregando []em uma linguagem que usa essa sobrecarga para pesquisa por chave arbitrária, os programadores não devem assumir que as chaves iniciam 0e são contíguas e devem abandonar esse hábito. Eles podem começar a partir de 0, ou 1, ou 1000, ou a cadeia de caracteres "Channel1", esse é o objetivo de usar o operador []com chaves arbitrárias. OTOH, se esse fosse C e alguém estivesse dizendo "devo deixar um elemento 0em uma matriz sem uso para começar 1", então eu não diria "obviamente, sim", seria por um triz, mas eu preferiria "não".
21715 Steve Joplin
3

Todos e seus cães usam índices baseados em zero. O uso de índices baseados em um aplicativo só causará problemas de manutenção para sempre.

Agora, o que você exibe em uma interface do usuário depende inteiramente de você e seu cliente. Apenas exiba i + 1 como o número do canal, junto com o canal #i, se isso deixar seu cliente feliz.

Se você expõe uma classe, expõe-a aos programadores. As pessoas confundidas com um índice baseado em zero não são programadores, portanto, há uma desconexão aqui.

Você parece se preocupar com a conversão entre interface do usuário e código. Se isso lhe preocupa, crie uma classe com a representação da interface do usuário de um número de canal. Uma chamada transformando o número 12 na representação da interface do usuário "Canal 13" e uma chamada transformando "Canal 13" no número 12. Você deve fazer isso de qualquer maneira, caso o cliente mude de idéia, para que haja apenas duas linhas de código mudar. E funciona se o cliente solicitar números romanos ou letras de A a Z.

gnasher729
fonte
1
Não consigo verificar sua resposta, pois meu cachorro não me diz que tipo de índices ele usa.
Armani
3

Você está misturando dois conceitos: indexação e identidade. Eles não são a mesma coisa e não devem ser confundidos.

Qual é o objetivo da indexação? Acesso aleatório rápido. Se o desempenho não for um problema e, dada a sua descrição, não for necessário, usar uma coleção com um índice é desnecessário e provavelmente acidental.

Se você (ou sua equipe) estiver confuso com o índice versus a identidade, poderá resolver esse problema sem ter um índice. Use um dicionário ou um enumerável e obtenha o canal por valor.

channels.First(c=> c.Number == identity).SomeProperty = newValue;
channels[identity].SomeProperty = newValue;

O primeiro é mais claro, o segundo é mais curto - eles podem ou não ser traduzidos para a mesma IL, mas, de qualquer forma, dado que você está usando isso para uma lista suspensa, deve ser rápido o suficiente.

jmoreno
fonte
2

Você está expondo uma interface através de sua ChannelSetclasse com a qual espera que seus usuários interajam. Eu faria o mais natural possível para eles usarem. Se você espera que seus usuários comecem a contar de 1 em vez de 0, exponha essa classe e seu uso com essa expectativa em mente.

Bernard
fonte
1
Ai que está o problema! Meus usuários finais estão esperando para começar a partir de 1, e meus usuários dev (eu incluído) está esperando para começar a partir de 0.
kdbanman
1
Por isso, sugiro adaptar sua classe para atender às necessidades de seus usuários finais.
Bernard
2

A indexação baseada em 0 era popular e defendida por pessoas importantes, porque a declaração mostra o tamanho do objeto.

Nos computadores modernos, com os computadores que seus usuários estarão usando, o tamanho do objeto é importante?

Se o seu idioma (C #) não suportar uma maneira limpa de declarar o primeiro e o último elemento de uma matriz, você poderá declarar uma matriz maior e usar constantes declaradas (ou variáveis ​​dinâmicas) para identificar o início e o fim lógicos de uma matriz. a área ativa da matriz.

Para objetos de interface do usuário, você quase certamente pode se dar ao luxo de usar um objeto de dicionário para seu mapeamento (se você tiver 10 milhões de objetos, um problema de interface do usuário) e se o melhor objeto de dicionário for uma lista com espaço desperdiçado em cada extremidade, você não perdeu nada importante.

david
fonte
Na verdade, os índices baseados em zero e um têm a ver com a maneira como a matriz é construída na memória. Os índices baseados em zero usam memória externa (externa à matriz) para determinar o tamanho, enquanto os índices baseados em zero usam memória interna (índice 0) para lembrar o tamanho da matriz ( geralmente, de qualquer maneira ). Não é popular para "o tamanho do objeto", mas geralmente tem a ver com a maneira como a memória é alocada internamente para matrizes. Independentemente disso, eu não recomendaria vazar imprudentemente bytes aqui e ali "apenas porque". Mas os dicionários geralmente são uma idéia melhor de qualquer maneira.
Phyrfox
@ phyrfox: A abstração que eu prefiro para indexação baseada em zero é dizer que os índices identificam posições entre objetos; o primeiro objeto está entre os índices 0 e 1, o segundo entre 1 e 2, etc. Dado x <= y <= z, os intervalos [x, y] e [y, z] serão consecutivos, mas não se sobrepõem, e ou ambos podem estar vazios sem a necessidade de caixas de canto especiais. Ao adotar o modelo "índices entre os itens" com indexação com base em zero, uma lista de seis itens abrange o intervalo de 0 a 6; com a indexação baseada em uma, ficaria de 1 a 7. Observe que essa abstração se encaixa bem com ponteiros "um além".
11123