¿Debería comenzar mi colección secuencial en el índice 0 o índice 1?

25

Estoy creando un modelo de objeto para un dispositivo que tiene múltiples canales. Los sustantivos utilizados entre el cliente y yo son Channely ChannelSet. ("Conjunto" no es semánticamente preciso, porque está ordenado y un conjunto adecuado no lo es. Pero eso es un problema para un momento diferente).

Estoy usando C #. Aquí hay un ejemplo de uso 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

Todo es elegante. Sin embargo, los clientes no son programadores y estarán absolutamente confundidos por la indexación cero: el primer canal es el canal 1 para ellos. Pero, en aras de la coherencia con C #, quiero mantener el ChannelSetíndice desde cero .

Esto seguramente causará algunas desconexiones entre mi equipo de desarrollo y los clientes cuando interactúan. Pero peor, cualquier inconsistencia en cómo se maneja esto dentro de la base de código es un problema potencial. Por ejemplo, aquí hay una pantalla de IU donde el usuario final ( que piensa en términos de indexación 1 ) está editando el canal 13:

Canal 13 o 12?

Ese Savebotón finalmente dará como resultado algún código. Si ChannelSetes 1 indexado:

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

o esto si es cero indexado:

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

No estoy realmente seguro de cómo manejar esto. Siento que es una buena práctica mantener una lista ordenada de cosas indexadas con enteros (the ChannelSet) consistentes con todas las otras interfaces de matriz y lista en el universo C # (por indexación cero ChannelSet). Pero entonces cada pieza de código entre la interfaz de usuario y el backend necesitará una traducción (restar por 1), y todos sabemos lo insidiosos y comunes que ya son los errores off-by-one.

Entonces, ¿alguna vez te ha mordido una decisión como esta? ¿Debo cero índice o un índice?

kdbanman
fuente
6060
"¿Deberían los índices de matriz comenzar en 0 o 1? Mi compromiso de 0.5 fue rechazado sin la consideración adecuada", pensé. - Stan Kelly-Bootle
mosquito
2
@gnat Es bueno que esté solo en la oficina: ¡las carcajadas mirando la pantalla de la computadora no suelen ser buenos indicadores de productividad!
kdbanman
44
¿Deben identificarse los canales por su posición en el conjunto? ¿No puedes identificarlos por otra cosa (por ejemplo, la frecuencia si esos eran canales de televisión)?
svick
@svick, ese es un gran punto. Puedo permitir el acceso al canal por diferentes identificadores más tarde, pero la jerga principal de los clientes realmente parece ser un número de canal indexado.
kdbanman
Según una lectura rápida, parece que no hay necesidad de usar índices como una cuestión de eficiencia, por lo que podría usar algún tipo de Mapa para vincular directamente los ID de los canales y los canales sin tener que pensar en las matrices. Creo que esto es a lo que se está refiriendo Rob, y también estoy de acuerdo con su solución si opta por usar índices.
Matthew leyó el

Respuestas:

24

Se siente como si estuvieras combinando el Identificador para el Channelcon su posición dentro de a ChannelSet. La siguiente es mi visualización de cómo se vería su código / comentarios en este momento:

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

Parece que ha decidido que debido a que las Channels dentro de a ChannelSetse identifican por números que tienen un límite superior e inferior, deben ser índices y, por lo tanto, como se basa en C #, 0. Si la forma natural de referirse a cada uno de los canales es por un número entre 1 y X, consúltelos por un número entre 1 y X. No intente forzarlos a ser índices.

Si realmente desea proporcionar una forma de acceder a ellos mediante un índice basado en 0 (¿qué beneficio le da esto a su usuario final o a los desarrolladores que consumen el código?), Implemente un indexador :

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];
    }
}
Robar
fuente
2
Esta es una respuesta realmente completa y concisa. Es exactamente el enfoque que terminé derivando de las otras respuestas.
kdbanman
77
Los transeúntes, he aceptado esta respuesta, pero este hilo está lleno de buenas respuestas, así que siga leyendo.
kdbanman
Muy bien, @Rob. "Sé cómo usar Indexers". - Neo
Jason P Sallinger
35

Muestre la IU con el índice 1, use el índice 0 en el código.

Dicho esto, trabajé con dispositivos de audio como este y usé el índice 1 para los canales y diseñé el código para nunca usar "Índice" o indexadores para ayudar a evitar la frustración. Algunos programadores todavía se quejaron, así que lo cambiamos. Luego otros programadores se quejaron.

Solo elige uno y quédate con él. Hay problemas más grandes que resolver en el gran esquema de sacar el software por la puerta.

Telastyn
fuente
12
La excepción a esto: si está utilizando un lenguaje basado en 1. Su código debe ser coherente con el resto de su código y su idioma, por lo tanto, basado en 0 en C #, 1 en VBA (!) O Lua. Su IU debe ser lo que los humanos esperan (que casi siempre se basa en 1).
user253751
1
@immibis: buen punto, no había pensado en eso.
Telastyn
3
Una cosa que extraño ocasionalmente acerca de la programación de antaño en BASIC es "BASE DE OPCIONES" ...
Brian Knoblauch
1
@ BlueRaja-DannyPflughoeft: Si bien siempre pensé que Option Baseera tonto, me gustó la característica que ofrecían algunos lenguajes de permitir que las matrices especifiquen individualmente límites inferiores arbitrarios. Si uno tiene una matriz de datos por año, por ejemplo, más allá de poder tener una matriz dimensionada [firstYear..lastYear]puede ser mejor que tener que acceder siempre al elemento [thisYear-firstYear].
supercat
1
@ BlueRaja-DannyPflughoeft El error de un hombre es la característica de otro hombre ...
Brian Knoblauch
21

Usa ambos.

No mezcle la interfaz de usuario con su código central. Internamente (como una biblioteca) debe codificar "sin saber" cómo el usuario final llamará a cada elemento de la matriz. Utilice las matrices y colecciones indexadas 0 "naturales".

La parte del programa que une los datos con la IU, la vista, debe tener cuidado de traducir correctamente los datos entre el Modelo mental del usuario y la 'Biblioteca' que hace el trabajo real.

¿Por qué es esto mejor?

  • El código puede ser más limpio, no hay trucos para traducir la indexación. Esto también ayuda a los programadores al no esperar que sigan y recuerden usar convenciones antinaturales.
  • Los usuarios utilizarán la indexación que esperan. No quieres molestarlos.
Dietr1ch
fuente
12

Puedes ver la colección desde dos ángulos diferentes.

(1) Es, en primer lugar, una colección secuencial regular , como una matriz o una lista. El índice de 0es obviamente la solución correcta entonces, siguiendo la convención. Usted asigna suficientes entradas y asigna el número de canal a los índices, lo cual es trivial (solo resta 1).

(2) Su colección es esencialmente una asignación entre identificadores de canal y objetos de información de canal. Simplemente sucede que los identificadores de canal son un rango secuencial de enteros; mañana podría ser algo así [1, 2, 3, 3a, 4, 4.1, 6, 8, 13]. Utiliza este conjunto ordenado como claves de mapeo.

Elija uno de los enfoques, documente y manténgalo. Desde el punto de vista de la flexibilidad, iría con (2), porque la representación del número de canal (al menos el nombre mostrado) es relativamente probable que cambie en el futuro.

9000
fuente
Realmente aprecio la parte 2 de tu respuesta. Creo que adoptaré algo así. El descriptor de acceso de índice ( channels[int]) será cero enteros indexados, y los descriptores de acceso Get GetChannelByFrequency, GetChannelByName, GetChannelByNumberserá flexible.
kdbanman
1
El segundo enfoque es definitivamente el mejor. Mis emisoras locales ofrecen 32 canales con LCN que van del 1 al 201. Esto requeriría una matriz dispersa (84% vacía). Es conceptualmente como almacenar una colección de personas en una matriz indexada por número de teléfono.
Kelly Thomas
3
Además, cualquier colección tan pequeña que esté representada por un menú desplegable de IU es extremadamente improbable que se beneficie de las características de rendimiento específicas de una matriz como estructura de datos. Por lo tanto, la cosa llamada "canal 1" por el usuario también podría llamarse "1" en el código, y usar un Dictionaryo SortedDictionary.
Steve Jessop
1
El principio de la menor sorpresa implicaría que operator [] (int index)debería estar basado en 0, mientras operator [] (IOrdered index)que no lo haría. (Disculpas por la sintaxis aproximada, mi C # está muy oxidado.)
9000
2
@kdbanman: personalmente, diría que tan pronto como se sobrecargue []en un lenguaje que use esta sobrecarga para buscar mediante una clave arbitraria, los programadores no deben asumir que las claves comienzan 0y son contiguas, y deberían dejar ese hábito de forma aguda. Pueden comenzar desde 0, o 1, o 1000, o la cadena "Channel1", ese es el objetivo de usar el operador []con claves arbitrarias. OTOH, si se tratara de C y alguien dijera "si dejara el elemento 0en una matriz sin usar para comenzar 1", entonces no diría "obviamente, sí", sería una decisión cercana, pero me inclinaría por "no".
Steve Jessop
3

Todos y sus perros usan índices basados ​​en cero. El uso de índices basados ​​en uno dentro de su aplicación le causará problemas de mantenimiento para siempre.

Ahora lo que muestra en una interfaz de usuario, eso depende totalmente de usted y su cliente. Simplemente muestre i + 1 como el número de canal, junto con el canal #i, si eso hace feliz a su cliente.

Si expones una clase, la expones a los programadores. Las personas que te confunden con un índice basado en cero no son programadores, por lo que aquí hay una desconexión.

Parece que le preocupa la conversión entre la interfaz de usuario y el código. Si eso le preocupa, cree una clase con la representación de interfaz de usuario de un número de canal. Una llamada que convierte el número 12 en la representación de la interfaz de usuario "Canal 13", y una llamada que convierte el "Canal 13" en el número 12. De todos modos, debe hacerlo, en caso de que el cliente cambie de opinión, por lo que solo hay dos líneas de código cambiar. Y funciona si el cliente solicita números romanos o letras de la A a la Z.

gnasher729
fuente
1
No puedo verificar tu respuesta ya que mi perro no me dice qué tipo de índices usa.
armani
3

Estás mezclando dos conceptos: indexación e identidad. No son lo mismo y no deben confundirse.

¿Cuál es el propósito de la indexación? Acceso aleatorio rápido. Si el rendimiento no es un problema, y ​​dada su descripción no lo es, entonces el uso de una colección que tiene un índice es innecesario y probablemente accidental.

Si usted (o su equipo) está confundido por el índice frente a la identidad, entonces puede resolver ese problema al no tener un índice. Use un diccionario o un enumerable y obtenga el canal por valor.

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

El primero es más claro, el segundo es más corto: pueden traducirse o no al mismo IL, pero de cualquier manera, dado que está usando esto para un menú desplegable, debería ser lo suficientemente rápido.

jmoreno
fuente
2

Está exponiendo una interfaz a través de su ChannelSetclase con la que espera que sus usuarios interactúen. Lo haría lo más natural posible para ellos usarlo como sea posible. Si espera que sus usuarios comiencen a contar desde 1 en lugar de 0, exponga esta clase y su uso teniendo en cuenta esa expectativa.

Bernardo
fuente
1
¡Ahí yace el problema! Mis usuarios finales esperan comenzar desde 1, y mis usuarios de desarrollo (incluido yo mismo) esperan comenzar desde 0.
kdbanman
1
Por lo tanto, sugiero adaptar su clase para satisfacer las necesidades de sus usuarios finales.
Bernard
2

La indexación basada en 0 fue popular y defendida por personas importantes, porque la declaración muestra el tamaño del objeto.

Con las computadoras modernas, con las computadoras que usarán sus usuarios, ¿es importante el tamaño del objeto?

Si su idioma (C #) no admite una forma limpia de declarar el primer y último elemento de una matriz, puede declarar una matriz más grande y usar constantes declaradas (o variable dinámica) para identificar el inicio y el final lógicos de El área activa de la matriz.

Para los objetos de IU, es casi seguro que puede permitirse usar un objeto de diccionario para su mapeo (si tiene 10 millones de objetos, tiene un problema de IU), y si el mejor objeto de diccionario resulta ser una lista con espacio desperdiciado en cada extremo, no has perdido nada importante.

david
fuente
En realidad, los índices basados ​​en cero y en uno tienen que ver con cómo se construye la matriz en la memoria. Los índices basados ​​en cero usan memoria externa (externa a la matriz) para determinar el tamaño, mientras que los índices basados ​​en una usan memoria interna (índice 0) para recordar el tamaño de la matriz ( generalmente, de todos modos ). No es popular para "el tamaño del objeto", pero generalmente tiene que ver con cómo se asigna internamente la memoria para las matrices. De todos modos, no recomendaría la filtración imprudente de bytes aquí y allá "solo porque". Pero los diccionarios suelen ser una mejor idea de todos modos.
phyrfox
@phyrfox: La abstracción que prefiero para la indexación basada en cero es decir que los índices identifican posiciones entre objetos; el primer objeto se encuentra entre los índices 0 y 1, el segundo entre 1 y 2, etc. Dado x <= y <= z, los rangos [x, y] y [y, z] serán consecutivos pero no se superpondrán, y ambos pueden estar vacíos sin que se requieran esquinas especiales Cuando se adopta el modelo de "índices entre elementos" con indexación basada en cero, una lista de seis elementos abarca el rango de 0 a 6; con una indexación basada en uno, se ubicaría del 1 al 7. Tenga en cuenta que esta abstracción encaja bien con punteros de "uno más allá".
supercat