Czy moja sekwencyjna kolekcja powinna zaczynać się od indeksu 0 czy indeksu 1?

25

Tworzę model obiektowy dla urządzenia, które ma wiele kanałów. Rzeczowniki używane między klientem a mną to Channeli ChannelSet. („Zestaw” nie jest semantycznie dokładny, ponieważ jest uporządkowany, a prawidłowy zestaw nie. Ale to problem na inny czas.)

Używam C #. Oto przykład użycia 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

Wszystko jest eleganckie. Jednak klienci nie są programistami i absolutnie będą myleni przez indeksowanie zerowe - pierwszy kanał to dla nich kanał 1. Ale ze względu na spójność z C # chcę utrzymać ChannelSetindeksowanie od zera .

To z pewnością spowoduje pewne rozłączenia między moim zespołem deweloperów a klientami podczas interakcji. Co gorsza, każda niespójność w sposobie postępowania w bazie kodu jest potencjalnym problemem. Oto na przykład ekran interfejsu użytkownika, na którym użytkownik końcowy ( który myśli o indeksowaniu 1 ) edytuje kanał 13:

Kanał 13 czy 12?

Ten Saveprzycisk ostatecznie spowoduje powstanie kodu. Jeśli ChannelSetindeksowany jest 1:

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

lub jeśli indeks jest zerowy:

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

Nie jestem pewien, jak sobie z tym poradzić. Wydaje mi się, że dobrą praktyką jest utrzymywanie uporządkowanej, indeksowanej liczbami całkowitymi listy rzeczy (the ChannelSet) zgodnych ze wszystkimi innymi interfejsami tablicowymi i listowymi we wszechświecie C # (przez indeksowanie zerowe ChannelSet). Ale wtedy każdy fragment kodu między interfejsem użytkownika a backendem będzie wymagał tłumaczenia (odejmij 1), a wszyscy wiemy, jak podstępne i powszechne są już błędy podstępne.

Czy taka decyzja kiedykolwiek cię ugryzła? Czy powinienem zerować indeks czy jeden indeks?

kdbanman
źródło
60
„Czy indeksy tablicowe powinny zaczynać się od 0 czy 1? Mój kompromis 0,5 został odrzucony bez, jak sądzę, odpowiedniego uwzględnienia.” - Stan Kelly-Bootle
gnat
2
@gnat To dobrze, że jestem sam w biurze - wybuchy śmiechu wpatrzone w ekran komputera nie są zwykle dobrymi wskaźnikami wydajności!
kdbanman,
4
Czy kanały muszą być identyfikowane według ich pozycji w zestawie? Czy nie możesz ich zidentyfikować za pomocą czegoś innego (np. Częstotliwości, jeśli były to kanały telewizyjne)?
sick
@svick, to świetny punkt. Mogę później zezwolić na dostęp do kanału za pomocą różnych identyfikatorów, ale głównym językiem klientów naprawdę wydaje się być jeden indeksowany numer kanału.
kdbanman,
Z szybkiego czytania wynika, że ​​nie ma potrzeby używania indeksów ze względu na wydajność, więc możesz użyć pewnego rodzaju mapy, aby bezpośrednio połączyć identyfikatory kanałów i kanały bez konieczności myślenia o tablicach. Myślę, że do tego zmierza Rob, i zgadzam się również z jego rozwiązaniem, jeśli zdecydujesz się na użycie indeksów.
Matthew przeczytał

Odpowiedzi:

24

Wygląda na to, że identyfikujesz identyfikator Channelz jego pozycją w ChannelSet. Oto moja wizualizacja tego, jak w tej chwili wyglądałby Twój kod / komentarze:

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

Wydaje się, że zdecydowałeś, że ponieważ Channelw obrębie ChannelSetsą identyfikowane przez liczby, które mają górną i dolną granicę, muszą być indeksami, a zatem ponieważ są oparte na C #, 0. Jeśli naturalnym sposobem odwoływania się do każdego z kanałów jest liczba od 1 do X, odnieś się do nich liczbą od 1 do X. Nie próbuj narzucać im indeksów.

Jeśli naprawdę chcesz zapewnić sposób dostępu do nich za pomocą indeksu opartego na 0 (jakie korzyści daje to użytkownikowi końcowemu lub programistom korzystającym z kodu?), Wówczas zaimplementuj narzędzie indeksujące :

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];
    }
}
Obrabować
źródło
2
To jest naprawdę kompletna i zwięzła odpowiedź. To właśnie podejście, które skończyło się na innych odpowiedziach.
kdbanman,
7
Przechodniów, zaakceptowałem tę odpowiedź, ale ten wątek jest pełen dobrych odpowiedzi, więc czytaj dalej.
kdbanman,
Bardzo fajnie, @Rob. „Wiem, jak korzystać z indeksatorów”. - Neo
Jason P Sallinger
35

Pokaż interfejs użytkownika z indeksem 1, użyj indeksu 0 w kodzie.

To powiedziawszy, pracowałem z takimi urządzeniami audio i użyłem indeksu 1 dla kanałów i zaprojektowałem kod, aby nigdy nie używał „indeksu” lub indeksatorów, aby uniknąć frustracji. Niektórzy programiści wciąż narzekali, więc zmieniliśmy to. Potem narzekali inni programiści.

Wybierz jeden i trzymaj się go. Istnieją większe problemy do rozwiązania w wielkim schemacie wyprowadzania oprogramowania z domu.

Telastyn
źródło
12
Wyjątek: jeśli używasz języka opartego na 1. Twój kod powinien być spójny z resztą kodu i językiem, więc 0 w C #, 1 w VBA (!) Lub Lua. Twój interfejs powinien być zgodny z oczekiwaniami ludzi (który prawie zawsze opiera się na 1).
user253751,
1
@immibis - dobra uwaga, nie myślałem o tym.
Telastyn,
3
Jedną rzeczą, której czasami brakuje mi w programowaniu w BASIC za dawnych czasów, jest „OPCJA PODSTAWA” ...
Brian Knoblauch,
1
@ BlueRaja-DannyPflughoeft: Chociaż zawsze uważałem, że Option Baseto głupie, podobała mi się funkcja niektórych języków, polegająca na umożliwieniu tablicom indywidualnego określania dowolnych dolnych granic. Na przykład, jeśli mamy tablicę danych według roku, wykraczanie poza tablicę [firstYear..lastYear]może być ładniejsze niż konieczność zawsze dostępu do elementu [thisYear-firstYear].
supercat
1
@ BlueRaja-DannyPflughoeft Błąd jednego człowieka jest cechą drugiego człowieka ...
Brian Knoblauch
21

Używać obu.

Nie mieszaj interfejsu użytkownika z kodem podstawowym. Wewnętrznie (jako biblioteka) powinieneś kodować „nie wiedząc”, jak każdy element w tablicy będzie wywoływany przez użytkownika końcowego. Użyj „naturalnych” tablic i kolekcji z indeksowaniem 0.

Ta część programu, która łączy dane z interfejsem użytkownika, widok, powinna zadbać o prawidłowe tłumaczenie danych między modelem mentalnym użytkownika a „biblioteką”, która faktycznie działa.

Dlaczego to jest lepsze?

  • Kod może być czystszy, bez włamań do tłumaczenia indeksowania. Pomaga to również programistom, nie oczekując od nich przestrzegania i pamiętania o nienaturalnych konwencjach.
  • Użytkownicy będą korzystać z oczekiwanego indeksowania. Nie chcesz ich drażnić.
Dietr1ch
źródło
12

Możesz zobaczyć kolekcję pod dwoma różnymi kątami.

(1) Jest to przede wszystkim regularny zbiór sekwencyjny , taki jak tablica lub lista. Indeks z 0jest oczywiście właściwym rozwiązaniem, zgodnie z konwencją. Przydzielasz wystarczającą liczbę wpisów i mapujesz numer kanału do indeksów, co jest banalne (wystarczy odjąć 1).

(2) Twoja kolekcja jest zasadniczo mapowaniem między identyfikatorami kanałów a obiektami informacji o kanale. Zdarza się, że identyfikatory kanałów to sekwencyjny zakres liczb całkowitych; jutro może być coś takiego [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. Używasz tego uporządkowanego zestawu jako kluczy mapujących.

Wybierz jedno z podejść, udokumentuj je i trzymaj się go. Z punktu widzenia elastyczności wybrałbym (2), ponieważ reprezentacja numeru kanału (przynajmniej wyświetlanej nazwy) prawdopodobnie zmieni się w przyszłości.

9000
źródło
Naprawdę doceniam część 2 twojej odpowiedzi. Myślę, że adoptuję coś takiego. Akcesor index ( channels[int]) będzie zerowe indeksowane liczbami całkowitymi, i akcesorów dostać GetChannelByFrequency, GetChannelByName, GetChannelByNumberbędzie elastyczny.
kdbanman,
1
Drugie podejście jest zdecydowanie najlepsze. Moi lokalni nadawcy oferują 32 kanały z LCN w zakresie od 1 do 201. Wymagałoby to rzadkiej tablicy (84% pustej). Koncepcyjnie przypomina to przechowywanie kolekcji osób w tablicy indeksowanej według numeru telefonu.
Kelly Thomas,
3
Co więcej, jakakolwiek kolekcja tak mała, że ​​jest reprezentowana przez menu rozwijane interfejsu użytkownika, jest bardzo mało prawdopodobne, aby skorzystała ze specyficznych właściwości wydajności tablicy jako struktury danych. Tak więc rzecz nazywana przez użytkownika „kanałem 1” równie dobrze może być nazywana „1” w kodzie i użyć Dictionarylub SortedDictionary.
Steve Jessop,
1
Zasada najmniejszej niespodzianki oznaczałaby, że operator [] (int index)powinna być oparta na 0, a operator [] (IOrdered index)nie. (Przepraszamy za przybliżoną składnię, mój C # jest bardzo zardzewiały.)
9000
2
@kdbanman: osobiście argumentowałbym, że gdy tylko przeciążasz się []w języku, który używa tego przeciążenia do wyszukiwania według dowolnego klucza, programiści nie mogą zakładać, że klucze zaczynają się od 0i są ciągłe, i powinni rzucić ten nawyk ostry. Mogą zaczynać się od 0, lub 1, lub 1000łańcucha "Channel1", to jest cały sens używania operatora []z dowolnymi kluczami. OTOH, gdyby to był C, a ktoś powiedziałby: „czy powinienem pozostawić element 0w tablicy nieużywany, aby zacząć od 1”, to nie powiedziałbym „oczywiście, tak”, byłoby to bliskie wezwanie, ale skłaniam się do "Nie".
Steve Jessop,
3

Wszyscy i ich pies używają indeksów zerowych. Korzystanie z indeksów opartych na jednostkach w aplikacji spowoduje problemy z konserwacją na zawsze.

Teraz to, co wyświetlasz w interfejsie użytkownika, zależy wyłącznie od Ciebie i Twojego klienta. Wystarczy wyświetlić i + 1 jako numer kanału wraz z kanałem #i, jeśli to uszczęśliwi twojego klienta.

Jeśli wystawisz klasę, wystawisz ją programistom. Ludzie, którzy są zdezorientowani indeksem zerowym, nie są programistami, więc tutaj jest rozłączenie.

Wygląda na to, że martwisz się konwersją interfejsu użytkownika i kodu. Jeśli to Cię martwi, utwórz klasę reprezentującą interfejs użytkownika numeru kanału. Jedno połączenie zamienia liczbę 12 w reprezentację interfejsu użytkownika „Kanał 13”, a jedno połączenie zamienia „Kanał 13” w numer 12. Powinieneś to zrobić tak czy inaczej, na wypadek, gdyby klient zmienił zdanie, więc są tylko dwa wiersze kodu zmienić. I działa, jeśli klient poprosi o cyfry rzymskie lub litery od A do Z.

gnasher729
źródło
1
Nie mogę zweryfikować twojej odpowiedzi, ponieważ mój pies nie powie mi, jakiego rodzaju indeksów używa.
armani
3

Mieszacie dwie koncepcje: indeksowanie i tożsamość. Nie są tym samym i nie należy ich mylić.

Jaki jest cel indeksowania? Szybki losowy dostęp. Jeśli wydajność nie jest problemem, a biorąc pod uwagę Twój opis, nie jest, to użycie kolekcji z indeksem jest niepotrzebne i prawdopodobnie przypadkowe.

Jeśli Ty (lub Twój zespół) jesteś zdezorientowany przez indeks a tożsamość, możesz rozwiązać ten problem, nie mając indeksu. Użyj słownika lub wyliczenia i uzyskaj kanał według wartości.

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

Pierwszy jest bardziej przejrzysty, drugi jest krótszy - mogą zostać przetłumaczone na tę samą IL, ale w obu przypadkach, biorąc pod uwagę, że używasz tego do rozwijania, powinno to być wystarczająco szybkie.

jmoreno
źródło
2

Udostępniasz interfejs w swojej ChannelSetklasie, z którym oczekujesz, że użytkownicy będą wchodzić w interakcje. Zrobiłbym, aby korzystanie z nich było jak najbardziej naturalne. Jeśli oczekujesz, że Twoi użytkownicy zaczną liczyć od 1 zamiast 0, ujawnij tę klasę i jej użycie, mając na uwadze to oczekiwanie.

Bernard
źródło
1
Na tym polega problem! Moi użytkownicy końcowi oczekują, że zaczną od 1, a użytkownicy
deweloperów
1
Dlatego sugeruję dostosowanie swojej klasy do potrzeb użytkowników końcowych.
Bernard,
2

Indeksowanie na podstawie 0 było popularne i bronione przez ważne osoby, ponieważ deklaracja pokazuje rozmiar obiektu.

Czy w przypadku nowoczesnych komputerów, z których będą korzystać Twoi użytkownicy, czy rozmiar obiektu jest w ogóle ważny?

Jeśli Twój język (C #) nie obsługuje czystego sposobu deklarowania pierwszego i ostatniego elementu tablicy, możesz po prostu zadeklarować większą tablicę i użyć zadeklarowanych stałych (lub zmiennej dynamicznej) do identyfikacji logicznego początku i końca aktywny obszar tablicy.

W przypadku obiektów interfejsu użytkownika prawie na pewno stać Cię na użycie obiektu słownika do mapowania (jeśli masz 10 milionów obiektów, masz problem z interfejsem użytkownika), a jeśli najlepszym obiektem słownika jest lista z zmarnowane miejsce na obu końcach, nie straciłeś nic ważnego.

David
źródło
W rzeczywistości indeksy zerowe i indeksy jednokierunkowe mają związek z tym, jak tablica jest konstruowana w pamięci. Indeksy oparte na zerach używają pamięci zewnętrznej (zewnętrznej względem tablicy) do określania rozmiaru, podczas gdy indeksy oparte na jednostkach używają pamięci wewnętrznej (indeks 0) do zapamiętywania wielkości tablicy ( zwykle w każdym razie ). Nie jest popularny ze względu na „rozmiar obiektu”, ale zwykle ma związek z wewnętrznym przydzielaniem pamięci dla tablic. Niezależnie od tego nie polecałbym lekkomyślnie przeciekających bajtów tu i tam „tylko dlatego”. Ale słowniki są zazwyczaj lepszym pomysłem.
phyrfox,
@phyrfox: Abstrakcja, którą preferuję dla indeksowania zerowego, polega na tym, że indeksy identyfikują pozycje między obiektami; pierwszy obiekt leży między indeksami 0 i 1, drugi między 1 a 2 itd. Biorąc pod uwagę, że x <= y <= z, zakresy [x, y] i [y, z] będą następować kolejno, ale nie będą się nakładać, i albo oba mogą być puste bez specjalnych skrzynek narożnych. Przyjmując model „indeksy siedzą między pozycjami” z indeksowaniem od zera, lista sześciu pozycji obejmuje zakres od 0 do 6; z indeksowaniem opartym na jednym, mieści się w przedziale od 1 do 7. Zauważ, że ta abstrakcja dobrze pasuje do wskaźników „jeden poza”.
supercat