La mia raccolta sequenziale dovrebbe iniziare dall'indice 0 o dall'indice 1?


25

Sto creando un modello a oggetti per un dispositivo con più canali. I nomi usati tra il cliente e io sono Channele ChannelSet. ("Set" non è semanticamente accurato, perché è ordinato e non è un set corretto. Ma questo è un problema per un altro tempo.)

Sto usando C #. Ecco un esempio di utilizzo di 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

Tutto è dandy. Tuttavia, i client non sono programmatori e saranno assolutamente confusi dall'indicizzazione zero: il primo canale è il canale 1 per loro. Ma, per coerenza con C #, voglio mantenere l' ChannelSetindicizzato da zero .

Questo sicuramente causerà alcune disconnessioni tra il mio team di sviluppo e i client quando interagiscono. Ma peggio, qualsiasi incoerenza nel modo in cui questo viene gestito all'interno della base di codice è un potenziale problema. Ad esempio, ecco una schermata dell'interfaccia utente in cui l'utente finale ( che pensa in termini di 1 indicizzazione ) sta modificando il canale 13:

Canale 13 o 12?

SaveAlla fine quel pulsante genererà del codice. Se ChannelSetè 1 indicizzato:

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

o questo se è indicizzato zero:

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

Non sono davvero sicuro di come gestirlo. Sento che è buona prassi mantenere un elenco di cose ordinate e indicizzate per intero (le ChannelSet) coerenti con tutte le altre interfacce di array e liste nell'universo C # (a zero indicizzazione ChannelSet). Ma poi ogni parte di codice tra l'interfaccia utente e il backend avrà bisogno di una traduzione (sottrazione per 1) e sappiamo tutti quanto siano già insidiosi e comuni errori off-by-one.

Quindi, una decisione come questa ti ha mai morso? Devo zero indice o un indice?


60
"Gli indici di array dovrebbero iniziare da 0 o 1? Il mio compromesso di 0,5 è stato respinto senza, ho pensato, un'adeguata considerazione." - Stan Kelly-Bootle
moscerino del

2
@gnat È una buona cosa che sia solo in ufficio: scoppi di risate che fissano lo schermo del computer non sono di solito buoni indicatori di produttività!
kdbanman,

4
I canali devono essere identificati dalla loro posizione nel set? Non puoi identificarli con qualcos'altro (ad es. Frequenza se fossero canali TV)?
svick,

@svick, è un ottimo punto. Potrei consentire l'accesso al canale da identificatori diversi in seguito, ma il gergo principale dei client sembra davvero essere un numero di canale indicizzato.
kdbanman,

Da una lettura veloce sembra che non sia necessario utilizzare gli indici come una questione di efficienza, quindi è possibile utilizzare un qualche tipo di mappa per collegare direttamente gli ID e i canali dei canali senza dover pensare alle matrici. Penso che sia quello a cui sta arrivando Rob, e sono anche d'accordo con la sua soluzione se optate per l'uso degli indici.
Matteo Leggi il

Risposte:


24

Sembra che tu stia confondendo l'identificatore Channelcon la sua posizione all'interno di a ChannelSet. Di seguito è la mia visualizzazione di come il tuo codice / commenti apparirebbe al 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];
    }
}

Sembra che tu abbia deciso che, poiché Channelall'interno di a ChannelSetsono identificati da numeri che hanno un limite superiore e inferiore, devono essere indici e quindi poiché sono basati su C #, 0. Se il modo naturale di fare riferimento a ciascuno dei canali è di un numero compreso tra 1 e X, fare riferimento a loro con un numero compreso tra 1 e X. Non cercare di forzarli a essere indici.

Se vuoi davvero fornire un modo per accedervi con un indice basato su 0 (che vantaggio offre questo utente finale o gli sviluppatori che consumano il codice?), Allora implementa un indicizzatore :

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];
    }
}

2
Questa è una risposta davvero completa e concisa. È esattamente l'approccio che ho finito derivando dalle altre risposte.
kdbanman,

7
Passanti, ho accettato questa risposta, ma questa discussione è piena di buone risposte, quindi continuate a leggere.
kdbanman,

Molto bello, @Rob. "So usare gli indicizzatori." - Neo
Jason P Sallinger,

35

Mostra l'interfaccia utente con l'indice 1, usa l'indice 0 nel codice.

Detto questo, ho lavorato con dispositivi audio come questo e ho usato l'indice 1 per i canali e progettato il codice per non utilizzare mai "Index" o indicizzatori per evitare la frustrazione. Alcuni programmatori si sono ancora lamentati, quindi l'abbiamo cambiato. Quindi altri programmatori si sono lamentati.

Basta sceglierne uno e attenersi ad esso. Ci sono problemi più grandi da risolvere nel grande schema di ottenere software fuori dalla porta.


12
L'eccezione a questo: se stai usando una lingua basata su 1. Il codice deve essere coerente con il resto del codice e della lingua, quindi basato su 0 in C #, basato su 1 in VBA (!) O Lua. La tua UI dovrebbe essere qualunque cosa gli umani si aspettino (che è quasi sempre basata su 1).
user253751

1
@immibis - buon punto, non ci avevo pensato.
Telastyn,

3
Una cosa che mi manca di tanto in tanto dei vecchi tempi di programmazione in BASIC è "OPTION BASE" ...
Brian Knoblauch,

1
@ BlueRaja-DannyPflughoeft: anche se ho sempre pensato che Option Basefosse sciocco, mi è piaciuta la funzionalità che alcune lingue offrivano di consentire agli array di specificare individualmente limiti inferiori arbitrari. Se uno ha un array di dati per anno, ad esempio, oltre ad avere un array dimensionato [firstYear..lastYear]può essere più bello che dover sempre accedere all'elemento [thisYear-firstYear].
supercat

1
@ BlueRaja-DannyPflughoeft Il bug di un uomo è la caratteristica di un altro uomo ...
Brian Knoblauch

21

Utilizza entrambi.

Non mescolare l'interfaccia utente con il codice principale. Internamente (come libreria) è necessario codificare "senza sapere" come ciascun elemento dell'array verrà chiamato dall'utente finale. Utilizzare le matrici e le raccolte "naturali" con indice 0.

La parte del programma che unisce i dati con l'interfaccia utente, la vista, dovrebbe occuparsi di tradurre correttamente i dati tra il modello mentale dell'utente e la 'Libreria' che svolge il lavoro effettivo.

Perché è meglio?

  • Il codice può essere più pulito, nessun trucco per tradurre l'indicizzazione. Questo aiuta anche i programmatori non aspettandosi che seguano e ricordino di usare convenzioni innaturali.
  • Gli utenti useranno l'indicizzazione che si aspettano. Non vuoi infastidirli.

12

Puoi vedere la collezione da due diverse angolazioni.

(1) È, in primo luogo, una raccolta sequenziale regolare , come una matrice o un elenco. L'indice da 0è ovviamente la soluzione giusta allora, seguendo la convenzione. Allocare abbastanza voci e mappare il numero del canale agli indici, il che è banale (basta sottrarre 1).

(2) La tua raccolta è essenzialmente una mappatura tra identificatori di canale e oggetti info canale. Accade semplicemente che gli identificatori di canale siano un intervallo sequenziale di numeri interi; domani potrebbe essere qualcosa di simile [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. Si utilizza questo set ordinato come chiavi di mappatura.

Scegli uno degli approcci, documentalo e seguilo. Dal punto di vista della flessibilità, andrei con (2), perché la rappresentazione del numero del canale (almeno il nome visualizzato) è relativamente probabile che cambi in futuro.


Apprezzo molto la seconda parte della tua risposta. Penso che adotterò qualcosa del genere. La funzione di accesso indice ( channels[int]) sarà pari a zero interi indicizzati, e accesso Get GetChannelByFrequency, GetChannelByName, GetChannelByNumbersarà flessibile.
kdbanman,

1
Il secondo approccio è decisamente il migliore. Le mie emittenti locali offrono 32 canali con LCN che vanno da 1 a 201. Ciò richiederebbe un array sparse (84% vuoto). È concettualmente come archiviare una raccolta di persone in un array indicizzato dal numero di telefono.
Kelly Thomas,

3
Inoltre, qualsiasi raccolta così piccolo che è rappresentato da una tendina interfaccia utente è estremamente improbabile beneficiare delle caratteristiche delle prestazioni di un array come struttura dati. Quindi la cosa chiamata "canale 1" dall'utente potrebbe anche essere chiamata "1" nel codice e usare un Dictionaryo SortedDictionary.
Steve Jessop,

1
Il principio della minima sorpresa implicherebbe che operator [] (int index)dovrebbe essere basato su 0, mentre operator [] (IOrdered index)non lo sarebbe. (Mi scuso per la sintassi approssimativa, il mio C # è molto arrugginito.)
9000

2
@kdbanman: personalmente, direi che non appena stai sovraccaricando []in una lingua che utilizza questo sovraccarico per la ricerca con chiave arbitraria, i programmatori non devono presumere che le chiavi inizino 0e siano contigue, e dovrebbero dare un tono acuto all'abitudine. Potrebbero iniziare da 0, o 1, o 1000, o dalla stringa "Channel1", questo è il punto centrale dell'uso dell'operatore []con chiavi arbitrarie. OTOH, se questo fosse C e qualcuno stesse dicendo "dovrei lasciare un elemento 0in un array inutilizzato per iniziare da 1", quindi non direi "ovviamente, sì", sarebbe una chiamata ravvicinata ma mi spingerei verso "no".
Steve Jessop,

3

Tutti e il loro cane usano indici a base zero. L'uso di indici a base singola all'interno dell'applicazione causerà problemi di manutenzione per sempre.

Ora ciò che visualizzi in un'interfaccia utente, dipende interamente da te e dal tuo cliente. Visualizza i + 1 come numero di canale, insieme al canale #i, se questo rende felice il tuo cliente.

Se esponi una classe, la esponi ai programmatori. Le persone che sono confuse da un indice a base zero non sono programmatori, quindi qui c'è una disconnessione.

Sembra che ti preoccupi della conversione tra interfaccia utente e codice. Se questo ti preoccupa, crea una classe con la rappresentazione dell'interfaccia utente di un numero di canale. Una chiamata che trasforma il numero 12 nella rappresentazione dell'interfaccia utente "Canale 13" e una chiamata che trasforma "Canale 13" nel numero 12. Dovresti farlo comunque, nel caso in cui il cliente cambi idea, quindi ci sono solo due righe di codice cambiare. E funziona se il cliente richiede numeri romani o lettere dalla A alla Z.


1
Non riesco a verificare la tua risposta poiché il mio cane non mi dirà quale tipo di indici utilizza.
Armani,

3

Stai mescolando due concetti: indicizzazione e identità. Non sono la stessa cosa e non devono essere confusi.

Qual è lo scopo dell'indicizzazione? Accesso casuale veloce. Se la prestazione non è un problema e, data la tua descrizione, non lo è, l'utilizzo di una raccolta con un indice non è necessario e probabilmente accidentale.

Se tu (o la tua squadra) siete confusi dall'indice rispetto all'identità, allora potete risolvere quel problema non avendo un indice. Utilizzare un dizionario o un enumerabile e ottenere il canale in base al valore.

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

Il primo è più chiaro, il secondo è più breve: possono o meno essere tradotti nello stesso IL, ma in entrambi i casi, dato che lo stai usando per un menu a discesa, dovrebbe essere abbastanza veloce.


2

Stai esponendo un'interfaccia attraverso la tua ChannelSetclasse con cui ti aspetti che i tuoi utenti interagiscano. Lo renderei il più naturale possibile per loro. Se prevedete che i vostri utenti inizieranno a contare da 1 invece di 0, esponete questa classe e il suo utilizzo tenendo presente tale aspettativa.


1
Qui sta il problema! I miei utenti finali si aspettano di iniziare da 1 e i miei utenti dev (me compreso) si aspettano di iniziare da 0.
kdbanman,

1
Quindi suggerisco di adattare la tua classe per soddisfare le esigenze dei tuoi utenti finali.
Bernard,

2

L'indicizzazione basata su 0 era popolare e difesa da persone importanti, poiché la dichiarazione mostra le dimensioni dell'oggetto.

Con i computer moderni, con i computer che verranno utilizzati dagli utenti, la dimensione dell'oggetto è importante?

Se il tuo langauge (C #) non supporta un modo chiaro di dichiarare il primo e l'ultimo elemento di un array, puoi semplicemente dichiarare un array più grande e usare costanti dichiarate (o variabili dinamiche) per identificarti all'inizio e alla fine logici di l'area attiva dell'array.

Per gli oggetti dell'interfaccia utente, puoi quasi certamente permetterti di utilizzare un oggetto dizionario per la tua mappatura (se hai 10 milioni di oggetti, hai un problema con l'interfaccia utente) e se il miglior oggetto dizionario risulta essere un elenco con spazio sprecato alle due estremità, non hai perso nulla di importante.


In realtà gli indici a base zero e a base singola hanno a che fare con il modo in cui l'array è costruito in memoria. Gli indici a base zero utilizzano la memoria esterna (esterna all'array) per determinare la dimensione, mentre gli indici a base singola utilizzano la memoria interna (indice 0) per ricordare la dimensione dell'array (in genere, comunque ). Non è popolare per "la dimensione dell'oggetto", ma di solito ha a che fare con il modo in cui la memoria è allocata internamente per le matrici. Indipendentemente da ciò, non consiglierei sconsideratamente perdite di byte qua e là "solo perché". Ma i dizionari sono generalmente un'idea migliore comunque.
phyrfox,

@phyrfox: L'astrazione che preferisco per l'indicizzazione a base zero è quella di dire che gli indici identificano le posizioni tra gli oggetti; il primo oggetto si trova tra gli indici 0 e 1, il secondo tra 1 e 2, ecc. Dato x <= y <= z, gli intervalli [x, y] e [y, z] saranno consecutivi ma non si sovrappongono e entrambi possono essere vuoti senza necessità di custodie angolari speciali. Quando si adotta il modello "indici tra elementi" con indicizzazione in base zero, un elenco di sei elementi abbraccia l'intervallo da 0 a 6; con l'indicizzazione su base singola si collocherebbe da 1 a 7. Si noti che questa astrazione si adatta bene ai puntatori "one-beyond".
supercat
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.