Ma collection séquentielle doit-elle commencer à l'index 0 ou à l'index 1?

25

Je crée un modèle d'objet pour un appareil qui a plusieurs canaux. Les noms utilisés entre le client et moi sont Channelet ChannelSet. ("Ensemble" n'est pas sémantiquement précis, car il est ordonné et un ensemble approprié ne l'est pas. Mais c'est un problème pour une autre période.)

J'utilise C #. Voici un exemple d'utilisation 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

Tout est dandy. Cependant, les clients ne sont pas des programmeurs et ils seront absolument déroutés par l'indexation zéro - le premier canal est le canal 1 pour eux. Mais, par souci de cohérence avec C #, je veux garder l' ChannelSetindex à partir de zéro .

Cela est sûr de provoquer des déconnexions entre mon équipe de développement et les clients lorsqu'ils interagissent. Mais pire, toute incohérence dans la façon dont cela est géré dans la base de code est un problème potentiel. Par exemple, voici un écran d'interface utilisateur où l'utilisateur final ( qui pense en termes de 1 indexation ) édite le canal 13:

Canal 13 ou 12?

Ce Savebouton va finalement aboutir à du code. Si ChannelSetest 1 indexé:

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

ou ceci s'il est indexé zéro:

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

Je ne sais pas vraiment comment gérer cela. Je pense que c'est une bonne pratique de garder une liste ordonnée et indexée d'entiers (les ChannelSet) cohérente avec toutes les autres interfaces de tableau et de liste dans l'univers C # (par indexation zéro ChannelSet). Mais alors, chaque morceau de code entre l'interface utilisateur et le backend aura besoin d'une traduction (soustrayez de 1), et nous savons tous à quel point les erreurs insidieuses et courantes de coup par coup sont déjà.

Alors, une décision comme celle-ci vous a-t-elle déjà mordu? Dois-je zéro index ou un index?

kdbanman
la source
60
"Les indices matriciels devraient-ils commencer à 0 ou 1? Mon compromis de 0,5 a été rejeté sans, je pense, une considération appropriée." - Stan Kelly-Bootle
moucher
2
@gnat C'est une bonne chose que je sois seul au bureau - des éclats de rire en regardant l'écran d'ordinateur ne sont généralement pas de bons indicateurs de productivité!
kdbanman
4
Les chaînes doivent-elles être identifiées par leur position dans l'ensemble? Ne pouvez-vous pas les identifier par autre chose à la place (par exemple, la fréquence s'il s'agit de chaînes de télévision)?
svick
@svick, c'est un excellent point. Je peux autoriser l'accès aux canaux par différents identifiants plus tard, mais le jargon principal des clients semble vraiment être un numéro de canal indexé.
kdbanman
À partir d'une lecture rapide, il semble qu'il n'y ait pas besoin d'utiliser des indices par souci d'efficacité, vous pouvez donc utiliser une sorte de carte pour relier directement les ID de canal et les canaux sans avoir à penser aux tableaux. Je pense que c'est ce à quoi Rob veut en venir, et je suis également d'accord avec sa solution si vous optez pour l'utilisation d'indices.
Matthew Read

Réponses:

24

On a l'impression que vous confondez l'identifiant pour le Channelavec sa position dans a ChannelSet. Voici ma visualisation de l'apparence actuelle de votre code / commentaires:

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

Il semble que vous ayez décidé que parce que les Channels dans un ChannelSetsont identifiés par des nombres qui ont une limite supérieure et inférieure, ils doivent être des index et donc comme il est basé sur C #, 0. Si la façon naturelle de faire référence à chacun des canaux est un nombre entre 1 et X, faites-leur référence par un nombre entre 1 et X. N'essayez pas de les forcer à être des index.

Si vous voulez vraiment fournir un moyen d'y accéder par un index basé sur 0 (quel avantage cela donne-t-il à votre utilisateur final ou aux développeurs qui consomment le code?), Alors implémentez un indexeur :

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];
    }
}
Rob
la source
2
C'est une réponse vraiment complète et concise. C'est exactement l'approche que j'ai fini par tirer des autres réponses.
kdbanman
7
Passants, j'ai accepté cette réponse, mais ce fil est plein de bonnes réponses, alors lisez la suite.
kdbanman
Très sympa, @Rob. "Je sais comment utiliser les indexeurs." - Neo
Jason P Sallinger
35

Affichez l'interface utilisateur avec l'index 1, utilisez l'index 0 dans le code.

Cela dit, j'ai travaillé avec des périphériques audio comme celui-ci et utilisé l'index 1 pour les canaux et conçu le code pour ne jamais utiliser "Index" ou indexeurs pour éviter la frustration. Certains programmeurs se plaignaient toujours, alors nous l'avons changé. Ensuite, d'autres programmeurs se sont plaints.

Choisissez-en un et respectez-le. Il y a de plus gros problèmes à résoudre dans le grand schéma consistant à sortir les logiciels.

Telastyn
la source
12
L'exception à cela: si vous utilisez une langue basée sur 1. Votre code doit être cohérent avec le reste de votre code et votre langue, donc basé sur 0 en C #, basé sur 1 en VBA (!) Ou Lua. Votre interface utilisateur doit être celle que les humains attendent (qui est presque toujours basée sur 1).
user253751
1
@immibis - bon point, je n'y avais pas pensé.
Telastyn
3
Une chose qui me manque occasionnellement à propos de la programmation d'antan en BASIC est "OPTION BASE" ...
Brian Knoblauch
1
@ BlueRaja-DannyPflughoeft: Bien que j'ai toujours pensé que Option Basec'était idiot, j'ai aimé la fonctionnalité offerte par certains langages permettant aux tableaux de spécifier individuellement des limites inférieures arbitraires. Si l'on a un tableau de données par année, par exemple, au-delà de la possibilité d'avoir un tableau dimensionné [firstYear..lastYear]peut être plus agréable que d'avoir toujours accès à l'élément [thisYear-firstYear].
supercat
1
@ BlueRaja-DannyPflughoeft Le bug d'un homme est la caractéristique d'un autre homme ...
Brian Knoblauch
21

Utilise les deux.

Ne mélangez pas l'interface utilisateur avec votre code principal. En interne (en tant que bibliothèque), vous devez coder "sans savoir" comment chaque élément du tableau sera appelé par l'utilisateur final. Utilisez les tableaux et collections «naturels» indexés sur 0.

La partie du programme qui joint les données à l'interface utilisateur, la vue, doit veiller à traduire correctement les données entre le modèle mental de l'utilisateur et la «bibliothèque» qui effectue le travail réel.

Pourquoi est-ce mieux?

  • Le code peut être plus propre, pas de piratage pour traduire l'indexation. Cela aide également les programmeurs en ne s'attendant pas à ce qu'ils suivent et se souviennent d'utiliser des conventions contre nature.
  • Les utilisateurs utiliseront l'indexation qu'ils attendent. Vous ne voulez pas les ennuyer.
Dietr1ch
la source
12

Vous pouvez voir la collection sous deux angles différents.

(1) Il s'agit, en premier lieu, d'une collection séquentielle régulière , comme un tableau ou une liste. L'index de 0est donc évidemment la bonne solution, suivant la convention. Vous allouez suffisamment d'entrées et mappez le numéro de canal aux indices, ce qui est trivial (il suffit de soustraire 1).

(2) Votre collection est essentiellement un mappage entre les identifiants de canal et les objets d'informations de canal. Il se trouve que les identifiants de canal sont une plage séquentielle d'entiers; demain ce pourrait être quelque chose comme [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. Vous utilisez cet ensemble ordonné comme clés de mappage.

Choisissez l'une des approches, documentez-la et respectez-la. Du point de vue de la flexibilité, j'irais avec (2), car la représentation du numéro de canal (au moins le nom affiché) est relativement susceptible de changer à l'avenir.

9000
la source
J'apprécie vraiment la partie 2 de votre réponse. Je pense que je vais adopter quelque chose comme ça. L'accesseur index ( channels[int]) sera zéro entiers indexés, et les accesseurs Get GetChannelByFrequency, GetChannelByName, GetChannelByNumbersera flexible.
kdbanman
1
La deuxième approche est certainement la meilleure. Mes diffuseurs locaux offrent 32 canaux avec des LCN allant de 1 à 201. Cela nécessiterait une matrice clairsemée (84% vide). C'est comme conceptuellement stocker une collection de personnes dans un tableau indexé par numéro de téléphone.
Kelly Thomas
3
En outre, toute collection si petite qu'elle est représentée par une liste déroulante d'interface utilisateur a très peu de chances de bénéficier des caractéristiques de performance spécifiques d'un tableau en tant que structure de données. Donc, la chose appelée "canal 1" par l'utilisateur pourrait aussi bien être appelée "1" dans le code, et utiliser un Dictionaryou SortedDictionary.
Steve Jessop
1
Le principe de la moindre surprise impliquerait qu'il operator [] (int index)devrait être basé sur 0, alors operator [] (IOrdered index)que non. (Toutes mes excuses pour la syntaxe approximative, mon C # est très rouillé.)
9000
2
@kdbanman: personnellement, je dirais que dès que vous surchargez []dans un langage qui utilise cette surcharge pour la recherche par clé arbitraire, les programmeurs ne doivent pas supposer que les clés commencent à partir 0et sont contiguës, et devraient mettre fin à cette habitude. Ils peuvent commencer par 0, ou 1, ou 1000, ou la chaîne "Channel1", c'est tout l'intérêt d'utiliser l'opérateur []avec des clés arbitraires. OTOH, si c'était C et que quelqu'un disait "devrais-je laisser un élément 0dans un tableau inutilisé pour commencer à partir de 1" alors je ne dirais pas "évidemment, oui", ce serait un appel serré mais je m'appuierais sur "non".
Steve Jessop
3

Tout le monde et son chien utilisent des index à base zéro. L'utilisation d'index à base unique dans votre application vous causera des problèmes de maintenance pour toujours.

Maintenant, ce que vous affichez dans une interface utilisateur, cela dépend entièrement de vous et de votre client. Affichez simplement i + 1 comme numéro de canal, ainsi que le canal #i, si cela rend votre client heureux.

Si vous exposez une classe, vous l'exposez aux programmeurs. Les personnes que vous confondez avec un index de base zéro ne sont pas des programmeurs, il y a donc une déconnexion ici.

Vous semblez vous inquiéter de la conversion entre l'interface utilisateur et le code. Si cela vous inquiète, créez une classe portant la représentation de l'interface utilisateur d'un numéro de canal. Un appel transformant le numéro 12 en représentation d'interface utilisateur "Canal 13", et un appel transformant "Canal 13" en numéro 12. Vous devez le faire de toute façon, au cas où le client changerait d'avis, il n'y a donc que deux lignes de code changer. Et cela fonctionne si le client demande des chiffres romains ou des lettres de A à Z.

gnasher729
la source
1
Je ne peux pas vérifier votre réponse car mon chien ne me dira pas quel type d'index il utilise.
armani
3

Vous mélangez deux concepts: l'indexation et l'identité. Ils ne sont pas la même chose et ne doivent pas être confondus.

Quel est le but de l'indexation? Accès aléatoire rapide. Si les performances ne sont pas un problème, et compte tenu de votre description, ce n'est pas le cas, alors l'utilisation d'une collection qui a un index est inutile et probablement accidentelle.

Si vous (ou votre équipe) êtes confus par l'index par rapport à l'identité, vous pouvez résoudre ce problème en n'ayant pas d'index. Utilisez un dictionnaire ou un énumérable et obtenez le canal par valeur.

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

Le premier est plus clair, le second est plus court - ils peuvent ou non être traduits dans le même IL, mais de toute façon, étant donné que vous l'utilisez pour une liste déroulante, cela devrait être assez rapide.

jmoreno
la source
2

Vous exposez une interface à travers votre ChannelSetclasse avec laquelle vous attendez de vos utilisateurs qu'ils interagissent. Je le rendrais aussi naturel pour eux que possible. Si vous pensez que vos utilisateurs commenceront à compter à partir de 1 au lieu de 0, exposez cette classe et son utilisation en gardant cette attente à l'esprit.

Bernard
la source
1
C'est là que réside le problème! Mes utilisateurs finaux s'attendent à commencer à partir de 1, et mes utilisateurs de développement (moi y compris) s'attendent à commencer à partir de 0.
kdbanman
1
Et je suggère donc d'adapter votre classe pour répondre aux besoins de vos utilisateurs finaux.
Bernard
2

L'indexation basée sur 0 était populaire et défendue par des personnes importantes, car la déclaration indique la taille de l'objet.

Avec les ordinateurs modernes, avec les ordinateurs que vos utilisateurs utiliseront, la taille de l'objet est-elle importante?

Si votre langauge (C #) ne prend pas en charge une manière propre de déclarer le premier et le dernier élément d'un tableau, vous pouvez simplement déclarer un tableau plus grand et utiliser des constantes déclarées (ou une variable dynamique) pour identifier le début et la fin logique de la zone active de la matrice.

Pour les objets d'interface utilisateur, vous pouvez presque certainement vous permettre d'utiliser un objet dictionnaire pour votre mappage (si vous avez 10 millions d'objets, vous avez un problème d'interface utilisateur) et si le meilleur objet dictionnaire se révèle être une liste avec gaspillé d'espace à chaque extrémité, vous n'avez rien perdu d'important.

David
la source
En fait, les index à base zéro et à base un ont à voir avec la façon dont le tableau est construit en mémoire. Les index à base zéro utilisent la mémoire externe (externe au tableau) pour déterminer la taille, tandis que les index à base unique utilisent la mémoire interne (index 0) pour se souvenir de la taille du tableau ( généralement, de toute façon ). Ce n'est pas populaire pour «la taille de l'objet», mais cela a généralement à voir avec la façon dont la mémoire est allouée en interne pour les tableaux. Quoi qu'il en soit, je ne recommanderais pas des octets qui fuient imprudemment ici et là «juste parce que». Mais les dictionnaires sont généralement une meilleure idée de toute façon.
phyrfox
@phyrfox: L'abstraction que je préfère pour l'indexation à base zéro est de dire que les index identifient les positions entre les objets; le premier objet se situe entre les indices 0 et 1, le second entre 1 et 2, etc. Étant donné x <= y <= z, les plages [x, y] et [y, z] seront consécutives mais ne se chevaucheront pas, et soit ou les deux peuvent être vides sans nécessiter de cas d'angle spéciaux. Lors de l'adoption du modèle "les indices se situent entre les éléments" avec une indexation à base zéro, une liste de six éléments s'étend de 0 à 6; avec une indexation à base unique, elle se situerait de 1 à 7. Notez que cette abstraction correspond bien aux pointeurs "au-delà".
supercat