Sollte meine sequentielle Sammlung bei Index 0 oder Index 1 beginnen?

25

Ich erstelle ein Objektmodell für ein Gerät mit mehreren Kanälen. Die zwischen dem Klienten und mir verwendeten Substantive sind Channelund ChannelSet. ("Menge" ist semantisch nicht genau, weil es geordnet ist und eine richtige Menge nicht. Aber das ist ein Problem für eine andere Zeit.)

Ich benutze C #. Hier ist ein Anwendungsbeispiel für 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

Alles ist gut. Die Clients sind jedoch keine Programmierer und werden durch die Nullindizierung absolut verwirrt - der erste Kanal ist für sie Kanal 1. Aus Gründen der Konsistenz mit C # möchte ich jedoch den ChannelSetIndex von Null halten .

Dies wird sicher einige Verbindungsabbrüche zwischen meinem Entwicklerteam und den Clients verursachen, wenn diese interagieren. Schlimmer noch, jegliche Inkonsistenz in der Handhabung innerhalb der Codebasis ist ein potenzielles Problem. Hier ist zum Beispiel ein UI-Bildschirm, in dem der Endbenutzer ( der an 1 Index denkt ) Kanal 13 bearbeitet:

Kanal 13 oder 12?

Dieser SaveKnopf wird irgendwann einen Code ergeben. Wenn ChannelSet1 indiziert ist:

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

oder dies, wenn es null indiziert ist:

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

Ich bin mir nicht sicher, wie ich damit umgehen soll. Ich halte es für eine gute Praxis, eine geordnete, ganzzahlig indizierte Liste von Dingen zu führen, die ChannelSetmit allen anderen Array- und Listenschnittstellen im C # -Universum konsistent sind (durch Null-Indizierung ChannelSet). Aber dann muss jeder Code zwischen der Benutzeroberfläche und dem Backend übersetzt werden (abzüglich 1), und wir alle wissen, wie heimtückisch und häufig Fehler sind, die nur einmal vorkommen.

Hat Sie eine solche Entscheidung jemals gebissen? Soll ich einen Index oder einen Nullindex haben?

kdbanman
quelle
60
"Sollten Array-Indizes bei 0 oder 1 beginnen? Mein Kompromiss von 0,5 wurde abgelehnt, ohne dass ich es in Betracht gezogen hätte." - Stan Kelly-Bootle
Mücke
2
@gnat Es ist gut, dass ich allein im Büro bin - Lachanfälle auf dem Computerbildschirm sind normalerweise keine guten Indikatoren für die Produktivität!
kdbanman
4
Müssen Kanäle anhand ihrer Position im Set identifiziert werden? Können Sie sie nicht durch etwas anderes identifizieren (z. B. Frequenz, wenn es sich um Fernsehsender handelte)?
Svick
@svick, das ist ein großartiger Punkt. Möglicherweise erlaube ich später den Kanalzugriff durch verschiedene Bezeichner, aber der primäre Jargon der Clients scheint wirklich eine indizierte Kanalnummer zu sein.
kdbanman,
Beim schnellen Lesen scheint es nicht notwendig zu sein, Indizes aus Effizienzgründen zu verwenden. Sie könnten also eine Art Karte verwenden, um Kanal-IDs und Kanäle direkt zu verknüpfen, ohne über Arrays nachdenken zu müssen. Ich denke, das ist es, worauf Rob hinausläuft, und ich stimme seiner Lösung auch zu, wenn Sie sich für die Verwendung von Indizes entscheiden.
Matthew Read

Antworten:

24

Es fühlt sich an, als würdest du den Bezeichner für Channelmit seiner Position innerhalb von in Konflikt bringen ChannelSet. Das Folgende ist meine Visualisierung, wie Ihr Code / Ihre Kommentare im Moment aussehen würden:

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

Es fühlt sich an, als hätten Sie sich entschieden , dass Channels innerhalb von a ChannelSetdurch Zahlen identifiziert werden, die eine obere und untere Schranke haben. Sie müssen Indizes sein und daher auf C #, 0 basieren. Wenn die natürliche Art, auf jeden der Kanäle zu verweisen, eine Zahl zwischen 1 und X ist, verweisen Sie auf sie mit einer Zahl zwischen 1 und X. Versuchen Sie nicht, sie als Indizes zu erzwingen.

Wenn Sie wirklich eine Möglichkeit bereitstellen möchten, über einen 0-basierten Index auf sie zuzugreifen (welchen Vorteil bietet dies Ihrem Endbenutzer oder Entwicklern, die den Code verwenden?), Implementieren Sie einen Indexer :

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];
    }
}
rauben
quelle
2
Dies ist eine wirklich vollständige und präzise Antwort. Es ist genau der Ansatz, den ich aus den anderen Antworten abgeleitet habe.
kdbanman
7
Passanten, ich habe diese Antwort akzeptiert, aber dieser Thread ist voller guter Antworten. Lesen Sie also weiter.
kdbanman
Sehr nett, @Rob. "Ich weiß, wie man Indexer benutzt." - Neo
Jason P Sallinger
35

Zeigen Sie die Benutzeroberfläche mit Index 1 an, verwenden Sie Index 0 im Code.

Trotzdem habe ich mit Audiogeräten wie diesen gearbeitet und Index 1 für die Kanäle verwendet und den Code so entworfen, dass er niemals "Index" oder Indexer verwendet, um Frustrationen zu vermeiden. Einige Programmierer haben sich immer noch beschwert, also haben wir es geändert. Dann haben sich andere Programmierer beschwert.

Einfach eins auswählen und dabei bleiben. Es gibt größere Probleme zu lösen, wenn es darum geht, Software aus der Tür zu bekommen.

Telastyn
quelle
12
Ausnahme: Wenn Sie eine 1-basierte Sprache verwenden. Ihr Code sollte mit dem Rest Ihres Codes und Ihrer Sprache konsistent sein, also 0-basiert in C #, 1-basiert in VBA (!) Oder Lua. Ihre Benutzeroberfläche sollte das sein, was Menschen erwarten (was fast immer 1-basiert ist).
user253751
1
@immibis - guter Punkt, daran hatte ich nicht gedacht.
Telastyn
3
Eine Sache, die ich gelegentlich an den alten Programmtagen in BASIC vermisse, ist "OPTION BASE" ...
Brian Knoblauch
1
@ BlueRaja-DannyPflughoeft: Obwohl ich es immer für Option Basedumm gehalten habe, mochte ich die Funktion, die in einigen Sprachen angeboten wird, Arrays die Möglichkeit zu geben, willkürliche Untergrenzen individuell festzulegen. Wenn man beispielsweise ein Array von Daten pro Jahr hat, [firstYear..lastYear]kann es besser sein , über die Fähigkeit hinaus ein Array zu dimensionieren , als immer auf das Element zugreifen zu müssen [thisYear-firstYear].
Superkatze
1
@ BlueRaja-DannyPflughoeft Der Fehler eines Mannes ist ein Merkmal eines anderen Mannes ...
Brian Knoblauch
21

Verwende beide.

Mischen Sie die Benutzeroberfläche nicht mit Ihrem Kerncode. Intern (als Bibliothek) sollten Sie "ohne zu wissen" codieren, wie jedes Element im Array vom Endbenutzer aufgerufen wird. Verwenden Sie die "natürlichen" 0-indizierten Arrays und Auflistungen.

Der Teil des Programms, der die Daten mit der Benutzeroberfläche verbindet, die Ansicht, sollte darauf achten, dass die Daten zwischen dem mentalen Modell des Benutzers und der Bibliothek, die die eigentliche Arbeit leistet, korrekt übersetzt werden.

Warum ist das besser?

  • Der Code kann sauberer sein, keine Hacks zum Übersetzen der Indizierung. Dies hilft den Programmierern auch, indem sie nicht erwarten, dass sie folgen, und daran denken, unnatürliche Konventionen zu verwenden.
  • Die Benutzer verwenden die erwartete Indizierung. Du willst sie nicht ärgern.
Dietr1ch
quelle
12

Sie können die Sammlung aus zwei verschiedenen Blickwinkeln betrachten.

(1) Es ist in erster Linie eine reguläre sequentielle Sammlung , wie ein Array oder eine Liste. Der Index von 0ist dann offensichtlich die richtige Lösung, wenn man die Konvention befolgt. Sie ordnen den Indizes genügend Einträge zu und ordnen die Kanalnummer zu, was trivial ist (einfach subtrahieren)1 ).

(2) Ihre Sammlung ist im Wesentlichen eine Zuordnung zwischen Kanalbezeichnern und Kanalinformationsobjekten. Es kommt nur vor, dass Kanalbezeichner ein sequentieller Bereich von ganzen Zahlen sind. morgen könnte es so etwas wie sein[1, 2, 3, 3a, 4, 4.1, 6, 8, 13] . Sie verwenden dieses geordnete Set als Zuordnungsschlüssel.

Wählen Sie einen der Ansätze, dokumentieren Sie ihn und halten Sie sich daran. Unter dem Gesichtspunkt der Flexibilität würde ich mit (2) fortfahren, da sich die Darstellung der Kanalnummer (zumindest des angezeigten Namens) in Zukunft wahrscheinlich ändern wird.

9000
quelle
Ich weiß Teil 2 Ihrer Antwort wirklich zu schätzen. Ich denke, ich werde so etwas übernehmen. Der Index Accessor ( channels[int]) wird Null indiziert ganze Zahlen, und die Get - Accessoren GetChannelByFrequency, GetChannelByName, GetChannelByNumberwird flexibel sein.
kdbanman
1
Der zweite Ansatz ist auf jeden Fall am besten. Meine lokalen Sender bieten 32 Kanäle mit LCNs von 1 bis 201 an. Dies würde ein spärliches Array erfordern (84% leer). Es ist konzeptionell wie das Speichern einer Sammlung von Personen in einem Array, das nach Telefonnummer indiziert ist.
Kelly Thomas
3
Darüber hinaus ist es äußerst unwahrscheinlich, dass eine Sammlung, die so klein ist, dass sie durch ein Dropdown-Menü der Benutzeroberfläche dargestellt wird , von den spezifischen Leistungsmerkmalen eines Arrays als Datenstruktur profitiert. Das Ding, das vom Benutzer "Kanal 1" genannt wird, kann auch im Code "1" genannt werden und ein Dictionaryoder verwenden SortedDictionary.
Steve Jessop
1
Das Prinzip der geringsten Überraschung würde bedeuten, operator [] (int index)dass 0-basiert sein sollte, während operator [] (IOrdered index)dies nicht der Fall wäre. (Entschuldigung für die ungefähre Syntax, mein C # ist sehr rostig.)
9000
2
@kdbanman: Ich persönlich würde argumentieren, dass, sobald Sie []in einer Sprache überladen sind, die diese Überladung für das Nachschlagen mit beliebigen Schlüsseln verwendet, die Programmierer nicht davon ausgehen dürfen, dass die Schlüssel von 0einander ausgehen und aufeinander folgen, und diese Angewohnheit schärfer werden sollten. Sie können von 0oder 1oder 1000oder der Zeichenfolge ausgehen. Dies "Channel1"ist der springende Punkt bei der Verwendung eines Operators []mit beliebigen Schlüsseln. OTOH, wenn dies C wäre und jemand sagen würde "sollte ich ein Element 0in einem Array ungenutzt lassen, um von vorne zu beginnen 1", dann würde ich nicht "offensichtlich ja" sagen, es wäre ein enger Anruf, aber ich würde mich lehnen "Nein".
Steve Jessop
3

Jeder und sein Hund verwenden nullbasierte Indizes. Wenn Sie in Ihrer Anwendung einseitige Indizes verwenden, treten für immer Wartungsprobleme auf.

Was Sie nun auf einer Benutzeroberfläche anzeigen, liegt ganz bei Ihnen und Ihrem Kunden. Zeigen Sie einfach i + 1 als Kanalnummer zusammen mit Kanal #i an, wenn dies Ihren Kunden glücklich macht.

Wenn Sie eine Klasse verfügbar machen, machen Sie sie Programmierern zugänglich. Leute, die Sie durch einen Null-basierten Index verwirren, sind keine Programmierer, daher gibt es hier eine Unterbrechung.

Sie scheinen sich Sorgen um die Konvertierung zwischen Benutzeroberfläche und Code zu machen. Wenn Sie sich Sorgen machen, erstellen Sie eine Klasse, die die Darstellung einer Kanalnummer auf der Benutzeroberfläche enthält. Ein Anruf, der die Nummer 12 in die UI-Darstellung "Channel 13" verwandelt, und ein Anruf, der "Channel 13" in die Nummer 12 verwandelt. Sie sollten dies trotzdem tun, falls der Kunde seine Meinung ändert, sodass nur zwei Codezeilen vorhanden sind wechseln. Und es funktioniert, wenn der Kunde nach römischen Ziffern oder Buchstaben von A bis Z fragt.

gnasher729
quelle
1
Ich kann Ihre Antwort nicht bestätigen, da mein Hund mir nicht sagt, welche Art von Index er verwendet.
Armani
3

Sie verwechseln zwei Konzepte: Indizierung und Identität. Sie sind nicht dasselbe und sollten nicht verwechselt werden.

Was ist der Zweck der Indizierung? Schneller wahlfreier Zugriff. Wenn die Leistung kein Problem ist und Ihre Beschreibung es nicht ist, ist die Verwendung einer Auflistung mit einem Index unnötig und wahrscheinlich versehentlich.

Wenn Sie (oder Ihr Team) durch den Index mit der Identität verwechselt werden, können Sie dieses Problem lösen, indem Sie keinen Index haben. Verwenden Sie entweder ein Wörterbuch oder eine Aufzählung, und ermitteln Sie den Kanal nach Wert.

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

Die erste ist klarer, die zweite ist kürzer - sie können oder können nicht in die gleiche IL übersetzt werden, aber so oder so, wenn Sie dies für ein Dropdown verwenden, sollte es schnell genug sein.

jmoreno
quelle
2

Sie legen eine Schnittstelle in Ihrer ChannelSetKlasse offen, mit der Ihre Benutzer interagieren sollen. Ich würde es ihnen so natürlich wie möglich machen. Wenn Sie erwarten, dass Ihre Benutzer von 1 statt von 0 zählen, dann machen Sie diese Klasse und ihre Verwendung unter Berücksichtigung dieser Erwartung sichtbar.

Bernard
quelle
1
Darin liegt das Problem! Meine Endbenutzer erwarten einen Start ab 1, und meine Entwickler (ich selbst eingeschlossen) erwarten einen Start ab 0.
kdbanman
1
Deshalb empfehle ich Ihnen, Ihre Klasse an die Bedürfnisse Ihrer Endbenutzer anzupassen.
Bernard
2

Die 0-basierte Indizierung war beliebt und wurde von wichtigen Personen verteidigt, da die Deklaration die Größe des Objekts angibt.

Ist bei modernen Computern und den Computern, die Ihre Benutzer verwenden, die Größe des Objekts überhaupt wichtig?

Wenn Ihre Sprache (C #) keine saubere Deklaration des ersten und letzten Elements eines Arrays unterstützt, können Sie einfach ein größeres Array deklarieren und deklarierte Konstanten (oder dynamische Variablen) verwenden, um den logischen Anfang und das logische Ende von zu identifizieren der aktive Bereich des Arrays.

Bei UI-Objekten können Sie es sich mit ziemlicher Sicherheit leisten, ein Dictionary-Objekt für Ihre Zuordnung zu verwenden (bei 10 Millionen Objekten liegt ein UI-Problem vor), und wenn sich herausstellt, dass das beste Dictionary-Objekt eine Liste mit ist Verschwendeter Platz an beiden Enden, Sie haben nichts Wichtiges verloren.

David
quelle
Tatsächlich haben der nullbasierte und der einsbasierte Index damit zu tun, wie das Array im Speicher aufgebaut ist. Auf Null basierende Indizes verwenden externen Speicher (außerhalb des Arrays), um die Größe zu bestimmen, während auf Eins basierende Indizes internen Speicher (Index 0) verwenden, um die Größe des Arrays zu speichern ( normalerweise sowieso ). Es ist nicht beliebt für "die Größe des Objekts", hat aber normalerweise damit zu tun, wie der Speicher intern für Arrays zugewiesen wird. Unabhängig davon würde ich nicht empfehlen, hier und da rücksichtslos Bytes zu verlieren, "nur weil". Aber Wörterbücher sind in der Regel sowieso eine bessere Idee.
Phyrfox
@phyrfox: Die Abstraktion, die ich für die nullbasierte Indizierung bevorzuge, ist zu sagen, dass Indizes Positionen zwischen Objekten identifizieren . Das erste Objekt liegt zwischen den Indizes 0 und 1, das zweite zwischen 1 und 2 usw. Bei x <= y <= z sind die Bereiche [x, y] und [y, z] aufeinanderfolgend, überschneiden sich jedoch nicht und entweder oder beide können leer sein, ohne dass spezielle Eckkoffer erforderlich sind. Bei der Übernahme des Modells "Indizes liegen zwischen Elementen" mit nullbasierter Indizierung erstreckt sich eine Liste von sechs Elementen über den Bereich von 0 bis 6; Bei einer einbasierten Indizierung würde sie zwischen 1 und 7 liegen. Beachten Sie, dass diese Abstraktion gut zu Ein-Jenseits-Zeigern passt.
Superkatze