我的顺序收集应该从索引0还是索引1开始?


25

我正在为具有多个通道的设备创建对象模型。客户和我之间使用的名词是ChannelChannelSet。(“集合”在语义上不准确,因为它是有序的,而正确的集合不是。但这在不同的时间是个问题。)

我正在使用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索引从零开始

当我的开发团队和客户进行交互时,这肯定会导致他们之间的脱节。但更糟糕的是,在代码库中如何处理此问题的任何不一致都是潜在的问题。例如,这是一个UI屏幕,最终用户(根据1个索引进行思考)正在编辑频道13:

频道13或12?

Save按钮最终将产生一些代码。如果ChannelSet索引为1:

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

如果它的索引为零:

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

我不确定如何处理。我觉得这是个好习惯,即使有序的,整数索引的事物列表(ChannelSet)与C#Universe中的所有其他数组和列表接口保持一致(通过零索引ChannelSet)。但是,然后,UI和后端之间的每一段代码都需要翻译(减1),我们都知道隐患和常见的一对一错误已经是多么的隐秘。

那么,这样的决定有没有咬过你?我应该零索引还是一个索引?


60
“数组索引应该从0还是1开始?我认为,在没有适当考虑的情况下,我对0.5的折衷被拒绝了。” - 斯坦·凯利·布特尔
蚊蚋

2
@gnat我一个人在办公室里是一件好事-凝视着电脑屏幕的笑声通常不是生产力的好指标!
kdbanman

4
是否必须通过频道在集合中的位置来标识频道?您是否可以通过其他方式识别它们(例如,如果是电视频道则为频率)?
2015年

@svick,这很不错。稍后我可能会允许通过不同的标识符进行频道访问,但是客户端的主要术语确实确实是一个索引频道号。
kdbanman

通过快速阅读,似乎不需要使用索引来提高效率,因此您可以使用某种Map直接链接通道ID和通道,而无需考虑数组。我认为这就是Rob的想法,如果您选择使用索引,我也同意他的解决方案。
马修(Matthew)

Answers:


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

确实感觉您已经决定,因为Channela ChannelSet中的s 由具有上限和下限的数字标识,因此它们必须是索引,因此它是基于C#的0。如果引用每个通道的自然方法是使用1到X之间的数字,请使用1到X之间的数字来引用它们。请勿尝试强迫它们成为索引。

如果您真的想提供一种方法,可以通过基于0的索引访问它们(这对最终用户或使用代码的开发人员有什么好处?),则可以实现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];
    }
}

2
这是一个非常完整而简洁的答案。这正是我最终从其他答案中得出的方法。
kdbanman 2015年

7
路人,我已经接受了这个答案,但是这个主题里充满了很好的答案,因此请继续阅读。
kdbanman 2015年

很好,@ Rob。“我知道如何使用索引器。” -Neo
Jason P Sallinger 2015年

35

显示具有索引1的UI,在代码中使用索引0。

就是说,我使用了这样的音频设备,并在通道中使用了索引1,并且设计的代码从不使用“索引”或索引器来避免沮丧。一些程序员仍然抱怨,所以我们更改了它。然后其他程序员抱怨。

只需选择一个并坚持下去即可。在使软件脱颖而出的宏伟计划中,还有更大的问题需要解决。


12
例外:如果您使用的是基于1的语言。您的代码应与其余代码和语言保持一致,因此在C#中从0开始,在VBA(!)或Lua中从1开始。您的UI应该是人类期望的(几乎总是基于1)。
user253751

1
@immibis-好点,我没有想到这一点。
Telastyn

3
关于BASIC过去的编程,我偶尔会错过的一件事是“ OPTION BASE” ...
Brian Knoblauch 2015年

1
@ BlueRaja-DannyPflughoeft:虽然我一直认为这Option Base很愚蠢,但我确实喜欢某些语言提供的功能,该功能允许数组分别指定任意下限。例如,如果一个人有一个按年份排列的数据数组,[firstYear..lastYear]那么比必须始终访问element更好的是,除了能够对数组进行尺寸标注之外,它还会更好[thisYear-firstYear]
2015年

1
@ BlueRaja-DannyPflughoeft一个人的错误是另一个人的特征……
Brian Knoblauch

21

同时使用。

请勿将UI与核心代码混用。在内部(作为库),您应该“不知道”编码,最终用户将如何调用数组中的每个元素。使用“自然的” 0索引数组和集合。

将程序与UI结合在一起的程序部分,即视图,应注意在用户的心理模型和执行实际工作的“库”之间正确转换数据。

为什么这样更好?

  • 代码可以更整洁,没有黑客可以转换索引。通过不期望他们遵循并记住使用不自然的约定,这也有助于程序员。
  • 用户将使用他们期望的索引。你不想惹恼他们。

12

您可以从两个不同的角度查看集合。

(1)首先,它是一个常规的顺序集合,例如数组或列表。0那么,遵循惯例,从索引显然是正确的解决方案。您分配了足够的条目并将通道号映射到索引,这是微不足道的(只需减去即可1)。

(2)您的集合本质上是一个映射通道标识符和通道信息对象之间。碰巧通道标识符是整数的连续范围;明天可能像[1, 2, 3, 3a, 4, 4.1, 6, 8, 13]。您可以将此有序集用作映射键。

选择一种方法,记录下来并坚持下去。从灵活性的角度来看,我会选择(2),因为通道号的表示(至少显示的名称)将来可能会发生变化。


我非常感谢您回答的第二部分。我想我会采用类似的方法。索引访问(channels[int])将是零个索引的整数,而get访问GetChannelByFrequencyGetChannelByNameGetChannelByNumber将是灵活的。
kdbanman 2015年

1
第二种方法绝对是最好的。我的本地广播公司提供32个频道,LCN的范围从1到201。这将需要一个稀疏数组(84%为空)。从概念上讲,这就像将人的集合存储在按电话号码索引的数组中。
凯利·托马斯

3
此外,任何集合如此之小,它是由一个UI下拉表示是非常不太可能从数组作为数据结构的特定性能特征中获益。因此,用户在代码中也将其称为“通道1”,并使用DictionarySortedDictionary
史蒂夫·杰索普

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一起显示为频道号。

如果公开一个类,则将其公开给程序员。从零开始的索引使您感到困惑的人不是程序员,因此这里没有联系。

您似乎担心UI和代码之间的转换。如果您对此感到担心,请创建一个包含频道号的用户界面表示形式的类。一个调用将数字12转换为UI表示的“通道13”,一个调用将“通道13”转换为数字12。无论如何,如果客户改变了主意,则应该这样做,因此只有两行代码改变。如果客户要求输入罗马数字或字母A到Z,它也可以使用。


1
我无法验证您的答案,因为我的狗不会告诉我他使用的索引类型。
阿玛尼

3

您正在混淆两个概念:索引和身份。它们不是同一件事,不应混淆。

索引的目的是什么?快速随机访问。如果性能不是问题,并且没有您的描述,那么使用具有索引的集合是不必要的,并且可能是偶然的。

如果您(或您的团队)对索引和身份感到困惑,那么可以通过没有索引来解决该问题。使用字典或枚举数并按值获取通道。

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

第一个更清晰,第二个更短-它们可能转换为或不转换为相同的IL,但是无论哪种方式,鉴于您正在使用它进行下拉,它应该足够快。


2

您正在通过ChannelSet类公开一个希望与用户进行交互的界面。我将使它们尽可能自然地使用。如果您希望用户将从1开始计数,而不是从0开始计数,则请牢记该期望公开此类及其用法。


1
问题就出在这里!我的最终用户期望从1开始,而我的开发用户(包括我自己)期望从0开始
。– kdbanman

1
因此,我建议调整您的班级以满足最终用户的需求。
伯纳德

2

基于0的索引很受欢迎,并且受到重要人物的拥护,因为声明显示了对象的大小。

对于现代计算机以及用户将要使用的计算机,对象的大小根本不重要吗?

如果您的语言(C#)不支持声明数组的第一个元素和最后一个元素的干净方法,则可以声明一个更大的数组,并使用声明的常量(或动态变量)来标识逻辑的开始和结束。数组的活动区域。

对于UI对象,几乎可以肯定可以使用字典对象进行映射(如果您有1000万个对象,则存在UI问题),并且如果最好的字典对象竟然是包含以下内容的列表:两端都浪费了空间,您没有丢失任何重要信息。


实际上,从零开始的索引和从一开始的索引都与数组在内存中的构造方式有关。从零开始的索引使用外部存储器(在数组外部)来确定大小,而从零开始的索引使用内部存储器(索引为0)来记住数组的大小(通常无论如何)。“对象的大小”并不流行,但通常与内部为数组分配内存的方式有关。无论如何,我不建议“只是因为”在这里到那里乱丢字节。但是无论如何,字典通常是一个更好的主意。
phyrfox

@phyrfox:我更喜欢基于零的索引,该抽象是说索引标识对象之间的位置。第一个对象位于索引0和1之间,第二个对象位于索引1和2之间,依此类推。给定x <= y <= z,范围[x,y]和[y,z]将是连续的,但不重叠,或者两者都可以是空的,不需要特殊的特殊情况。当采用基于零的索引的“索引位于项目之间”时,六个项目的列表的范围从0到6;使用基于一个的索引时,它将位于1到7之间。请注意,此抽象非常适合“一个超越”的指针。
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.