Чи повинен мій послідовний збірник починатися з індексу 0 або індексу 1?


25

Я створюю об’єктну модель для пристрою, який має кілька каналів. Іменники, що вживаються між клієнтом і мною, є Channelі ChannelSet. ("Набір" не є семантично точним, оскільки він впорядкований, а правильний набір - не. Але це проблема в інший час.)

Я використовую C #. Ось приклад використання 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

Все денді. Однак клієнти не є програмістами, і їх абсолютно збиває з пантелику нульова індексація - перший канал для них - канал 1. Але, для узгодженості з C #, я хочу тримати ChannelSetіндексування від нуля .

Це обов'язково може призвести до відключення між моєю командою розробників та клієнтами, коли вони взаємодіють. Але що ще гірше, будь-яка невідповідність того, як це обробляється в кодовій базі, є потенційною проблемою. Наприклад, ось екран інтерфейсу користувача, де кінцевий користувач ( який думає з точки зору 1 індексації ) редагує канал 13:

13 або 12 канал?

Ця Saveкнопка в кінцевому підсумку призведе до деякого коду. Якщо ChannelSetіндексовано 1:

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

або це, якщо він індексується нулем:

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

Я не дуже впевнений, як з цим впоратися. Я відчуваю, що є хорошою практикою зберігати впорядкований цілочисельний список речей ( ChannelSet), який відповідає всім іншим інтерфейсам масиву та списку у всесвіті C # (за нульовою індексуванням ChannelSet). Але тоді кожен фрагмент коду між користувальницьким інтерфейсом та бекендером потребуватиме перекладу (віднімаємо на 1), і всі ми знаємо, наскільки вже є підступні та поширені помилки один за одним.

Отже, чи колись таке укусило вас таке рішення? Чи повинен я нульовий індекс або один індекс?


60
"Чи повинні індекси масиву починатися з 0 або 1? Мої компроміси 0,5 були відхилені без, я вважав, належного розгляду." - Стен Келлі-Бутл
гнат

2
@gnat Це добре, що я один в офісі - сплески сміху, що дивиться на екран комп'ютера, зазвичай не є хорошими показниками продуктивності!
kdbanman

4
Чи повинні ідентифікувати канали за їхньою позицією в наборі? Чи не можете ви їх ідентифікувати за допомогою чогось іншого (наприклад, частоти, якщо це були телеканали)?
svick

@svick, це чудовий момент. Пізніше я можу дозволити доступ до каналу за допомогою різних ідентифікаторів, але основним мовою клієнтів справді є один індексований номер каналу.
kdbanman

З швидкого читання здається, що немає необхідності використовувати показники як ефективність, тому ви можете використовувати якусь карту для прямого зв’язку ідентифікаторів каналів та каналів, не замислюючись про масиви. Я думаю, що до цього стикається Роб, і я також погоджуюся з його рішенням, якщо ви вирішите використовувати індекси.
Матвій

Відповіді:


24

Схоже, ви плутаєте Ідентифікатор для Channelз його положенням в межах ChannelSet. Далі йде моя візуалізація того, як виглядатиме ваш код / ​​коментарі в даний момент:

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

Схоже, ви вирішили, що оскільки Channels в межах ChannelSetідентифікуються за номерами, які мають верхню та нижню межі, вони повинні бути індексами, а тому, як це базується на C #, 0. Якщо природний спосіб посилатися на кожен з каналів - це число між 1 і X, посилайтеся на них числом між 1 і X. Не намагайтеся примушувати їх бути індексами.

Якщо ви дійсно хочете надати спосіб доступу до них за допомогою індексу на основі 0 (яку користь це дає вашому кінцевому користувачеві або розробникам, які споживають код?), То застосуйте індексатор :

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
Це дійсно повна і стисла відповідь. Саме такий підхід я закінчив, виходячи з інших відповідей.
kdbanman

7
Перехожі, я прийняв цю відповідь, але ця тема сповнена хороших відповідей, тому читайте далі.
kdbanman

Дуже приємно, @Rob. "Я знаю, як користуватися індексаторами." - Нео
Джейсон П Саллінгер

35

Покажіть інтерфейс користувача з індексом 1, використовуйте індекс 0 у коді.

З цього приводу я працював із такими аудіопристроями і використовував індекс 1 для каналів і розробляв код, щоб ніколи не використовувати "Index" або індексатори, щоб уникнути розладу. Деякі програмісти все-таки скаржилися, тому ми її змінили. Потім інші програмісти скаржилися.

Просто виберіть один і дотримуйтесь його. У грандіозній схемі виведення програмного забезпечення у двері виникають більші проблеми.


12
Виняток із цього: якщо ви використовуєте мову на основі 1. Ваш код повинен відповідати решті коду та вашій мові, тому 0 на базі C #, 1 на базі VBA (!) Або Lua. Ваш інтерфейс повинен бути таким, що очікують люди (що майже завжди базується на 1).
користувач253751

1
@immibis - хороший момент, я про це не думав.
Теластин

3
Одне, що я іноді сумую про програмування давніх часів у BASIC, це "ОСНОВА ОПЦІЙ" ...
Брайан Кноблауч

1
@ BlueRaja-DannyPflughoeft: Хоча я завжди думав, що Option Baseце нерозумно, мені сподобалась функція деяких мов, що дозволяють дозволити масивам окремо задавати довільні нижчі межі. Якщо у вас є масив даних за роком, наприклад, виходячи з того, щоб мати розмірний масив, це [firstYear..lastYear]може бути приємніше, ніж завжди мати доступ до елемента [thisYear-firstYear].
supercat

1
@ BlueRaja-DannyPflughoeft Помилка одного чоловіка - особливість іншого чоловіка ...
Brian Knoblauch

21

Використовуйте обидва.

Не змішуйте інтерфейс користувача з основним кодом. Внутрішньо (як бібліотека) ви повинні кодувати "не знаючи", як кінцевий користувач буде називати кожен елемент масиву. Скористайтеся "природними" 0-індексованими масивами та колекціями.

Частина програми, яка об'єднує дані з інтерфейсом користувача, перегляд, повинна подбати про правильний переклад даних між ментальною моделлю користувача та "Бібліотекою", яка виконує фактичну роботу.

Чому це краще?

  • Код може бути більш чистим, без хакі для перекладу індексації. Це також допомагає програмістам, не сподіваючись, що вони слідуватимуть і не пам'ятають використовувати неприродні умовності.
  • Користувачі будуть використовувати індексацію, яку вони очікують. Ви не хочете їх дратувати.

12

Ви можете побачити колекцію з двох різних кутів.

(1) Це, в першу чергу, звичайна послідовна колекція , як масив чи список. Індекс з 0, очевидно, є правильним рішенням після дотримання конвенції. Ви виділяєте достатню кількість записів та номер каналу карти в індексах, що тривіально (просто віднімайте 1).

(2) Ваша колекція - це по суті відображення між ідентифікаторами каналів та інформаційними об'єктами каналу. Просто буває так, що ідентифікатори каналів - це послідовний діапазон цілих чисел; завтра це може бути щось на кшталт [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. Ви використовуєте цей упорядкований набір як відображення ключів.

Виберіть один із підходів, задокументуйте його та дотримуйтесь його. З точки зору гнучкості, я б пішов з (2), оскільки подання номера каналу (принаймні відображуваної назви) порівняно ймовірно зміниться в майбутньому.


Я дуже ціную частину 2 вашої відповіді. Я думаю, що прийму щось подібне. Індекс-аксесуар ( channels[int]) буде нульовими індексованими цілими числами, а Get accessors GetChannelByFrequency, GetChannelByName- GetChannelByNumberгнучким.
kdbanman

1
Другий підхід, безумовно, найкращий. Мої місцеві мовники пропонують 32 канали з LCN, розмірами від 1 до 201. Для цього знадобиться розріджений масив (порожній 84%). Це концептуально як зберігання колекції людей у ​​масиві, індексованому телефонним номером.
Келлі Томас

3
Крім того, будь-яка колекція, настільки мала, що вона представлена ​​розкритим інтерфейсом інтерфейсу, вкрай малоймовірна для користі від конкретних характеристик продуктивності масиву як структури даних. Отже, те, що користувач називає "канал 1", може також називатися "1" в коді, і використовувати a Dictionaryабо SortedDictionary.
Стів Джессоп

1
Принцип найменшого сюрпризу передбачає, що він operator [] (int index)повинен базуватися на 0, тоді як operator [] (IOrdered index)не буде. (Вибачте за приблизний синтаксис, мій C # дуже іржавий.)
9000

2
@kdbanman: особисто я б заперечував, що як тільки ви перевантажуєтесь []мовою, яка використовує це перевантаження для пошуку довільним ключем, програмісти не повинні вважати, що ключі починаються 0і є суміжними, і повинні вибивати цю звичку різко. Вони можуть починатися з 0, або 1, або 1000, або рядка "Channel1", у цьому вся суть використання оператора []з довільними ключами. OTOH, якби це був C і хтось сказав: "Якщо я повинен залишити елемент 0у масиві невикористаним, щоб почати з 1", тоді я б не сказав "очевидно, так", це був би близький дзвінок, але я схиляюся до "ні".
Стів Джессоп

3

Усі та їх собака використовують нульові індекси. Використання одноосновних індексів у вашій програмі назавжди викличе проблеми з технічним обслуговуванням.

Тепер те, що ви відображаєте в інтерфейсі користувача, повністю залежить від вас та вашого клієнта. Просто покажіть i + 1 як номер каналу разом з каналом #i, якщо це робить вашого клієнта щасливим.

Якщо ви виставляєте клас, то ви піддаєте його програмістам. Люди, яких вас бентежить індекс, що базується на нулі, - це не програмісти, тому тут є відключення.

Ви, мабуть, переживаєте за перетворення між інтерфейсом користувача та кодом. Якщо це вас хвилює, створіть клас, який містить представлення користувальницьким інтерфейсом номера каналу. Один дзвінок, який перетворює номер 12 на представлення інтерфейсу "13 каналу", і один виклик перетворює "13 канал" на номер 12. Ви повинні це зробити все одно, якщо клієнт передумає, тому існує лише два рядки коду змінювати. І це працює, якщо замовник запитує римські цифри або букви від А до Я.


1
Я не можу підтвердити вашу відповідь, оскільки моя собака не скаже мені, який тип індексів він використовує.
armani

3

Ви змішуєте два поняття: індексацію та ідентичність. Вони не одне і те ж і їх не слід плутати.

Яка мета індексації? Швидкий випадковий доступ. Якщо продуктивність не є проблемою, а якщо її опис не є, то використання колекції, що має індекс, є непотрібним і, ймовірно, випадковим.

Якщо ви (або ваша команда) плутаєте індекс з ідентичністю, ви можете вирішити цю проблему, не маючи індексу. Скористайтеся словником або числом і отримайте канал за значенням.

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

Перший є більш зрозумілим, другий - коротшим - вони можуть або не можуть бути переведені в один і той же ІЛ, але в будь-якому випадку, якщо ви використовуєте це для спадання, він повинен бути досить швидким.


2

Ви відкриваєте інтерфейс через свій ChannelSetклас, з яким очікуєте взаємодії ваших користувачів. Я би зробив їх максимально природним для використання. Якщо ви очікуєте, що ваші користувачі почнуть рахувати від 1, а не 0, то відкрийте цей клас та його використання з урахуванням цього очікування.


1
У цьому криється проблема! Мої кінцеві користувачі розраховують почати з 1, а мої розробники (включаючи мене) очікують, що стартують з 0.
kdbanman

1
І тому я пропоную адаптувати свій клас відповідно до потреб ваших кінцевих користувачів.
Бернар

2

Індексація на основі 0 була популярною та захищеною важливими людьми, оскільки декларація показує розмір об'єкта.

З сучасними комп’ютерами, з комп’ютерами, якими користуватимуться ваші користувачі, чи важливий розмір об'єкта взагалі?

Якщо ваш langauge (C #) не підтримує чіткий спосіб оголошення першого і останнього елемента масиву, ви можете просто оголосити більший масив і використовувати оголошені константи (або динамічну змінну) для ідентифікації до логічного початку та кінця активна область масиву.

Для об’єктів інтерфейсу ви майже напевно можете дозволити собі використовувати об’єкт словника для свого відображення (якщо у вас є 10 мільйонів об'єктів, у вас є проблема з інтерфейсом користувача), а якщо найкращим об’єктом словника є список із даремно витрачений простір на будь-якому кінці, ви нічого не втратили важливого.


Насправді нульові та одноосновні індекси мають відношення до того, як масив побудований у пам'яті. Нульові індекси використовують зовнішню пам'ять (зовнішню для масиву) для визначення розміру, тоді як одноіндексні індекси використовують внутрішню пам'ять (індекс 0) для запам'ятовування розміру масиву ( як правило, у будь-якому випадку ). Це не популярно для "розміру об'єкта", але зазвичай пов'язане з тим, як пам'ять внутрішньо розподіляється для масивів. Незважаючи на те, я б не рекомендував безрозсудно просочувати байти тут і там "просто тому". Але словники, як правило, краще ідея.
фірфокс

@phyrfox: абстракція, яку я віддаю перевагу для нульової індексації, полягає в тому, щоб сказати, що індекси ідентифікують позиції між об'єктами; перший об'єкт лежить між індексами 0 і 1, другий між 1 і 2 і т. д. Дані x <= y <= z, діапазони [x, y] і [y, z] будуть послідовними, але не перетинатимуться, і або обидва можуть бути порожніми, не вимагаючи спеціальних кутових корпусів. При прийнятті моделі "індекси сидіти між елементами" з нульовою індексацією список шести предметів охоплює діапазон від 0 до 6; при одноосновному індексуванні вона складе від 1 до 7. Зауважте, що ця абстракція добре відповідає вказівникам "поза межами".
supercat
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.