Recientemente tuve una prueba en mi clase. Uno de los problemas fue el siguiente:
Dado un número n , escriba una función en C / C ++ que devuelva la suma de los dígitos del número al cuadrado . (Lo siguiente es importante). El rango de n es [- (10 ^ 7), 10 ^ 7]. Ejemplo: si n = 123, su función debería devolver 14 (1 ^ 2 + 2 ^ 2 + 3 ^ 2 = 14).
Esta es la función que escribí:
int sum_of_digits_squared(int n)
{
int s = 0, c;
while (n) {
c = n % 10;
s += (c * c);
n /= 10;
}
return s;
}
Me pareció bien. Así que ahora regresó la prueba y descubrí que el maestro no me dio todos los puntos por una razón que no entiendo. Según él, para que mi función esté completa, debería haber agregado el siguiente detalle:
int sum_of_digits_squared(int n)
{
int s = 0, c;
if (n == 0) { //
return 0; //
} //
// THIS APPARENTLY SHOULD'VE
if (n < 0) { // BEEN IN THE FUNCTION FOR IT
n = n * (-1); // TO BE CORRECT
} //
while (n) {
c = n % 10;
s += (c * c);
n /= 10;
}
return s;
}
El argumento para esto es que el número n está en el rango [- (10 ^ 7), 10 ^ 7], por lo que puede ser un número negativo. Pero no veo dónde falla mi propia versión de la función. Si entiendo correctamente, el significado de while(n)
es while(n != 0)
, no while (n > 0)
, así que en mi versión de la función, el número n no dejaría de entrar en el bucle. Funcionaría igual.
Luego, probé ambas versiones de la función en mi computadora en casa y obtuve exactamente las mismas respuestas para todos los ejemplos que probé. Entonces, sum_of_digits_squared(-123)
es igual a sum_of_digits_squared(123)
(que de nuevo, es igual a 14
) (incluso sin los detalles que aparentemente debería haber agregado). De hecho, si trato de imprimir en la pantalla los dígitos del número (de menor a mayor importancia), en el 123
caso que obtengo 3 2 1
y en el -123
caso que obtengo -3 -2 -1
(lo que en realidad es algo interesante). Pero en este problema no importaría ya que cuadramos los dígitos.
Entonces, ¿quién está equivocado?
EDITAR : Mi mal, olvidé especificar y no sabía que era importante. La versión de C utilizada en nuestra clase y pruebas tiene que ser C99 o más reciente . Así que supongo (al leer los comentarios) que mi versión obtendría la respuesta correcta de cualquier manera.
n = n * (-1)
es una forma ridícula de escribirn = -n
; Solo un académico lo pensaría. Y mucho menos agregar los paréntesis redundantes.n = n * (-1)
? Wut ??? Lo que busca tu profesor es esto: `n = -n '. El lenguaje C tiene un operador unario menos.Respuestas:
Resumiendo una discusión que se ha filtrado en los comentarios:
n == 0
. Lawhile(n)
prueba manejará ese caso perfectamente.%
los operandos negativos se definió de manera diferente. En algunos sistemas antiguos (incluido, en particular, Unix temprano en un PDP-11, donde Dennis Ritchie desarrolló originalmente C), el resultado siemprea % b
estuvo en el rango , lo que significa que -123% 10 fue 7. En dicho sistema, la prueba de antemano para sería necesario.[0 .. b-1]
n < 0
Pero la segunda viñeta se aplica solo a tiempos anteriores. En las versiones actuales de los estándares C y C ++, la división de enteros se define para truncarse hacia 0, por lo que
n % 10
se garantiza que le dará el último dígito (posiblemente negativo)n
incluso cuandon
es negativo.Entonces, la respuesta a la pregunta "¿Cuál es el significado de
while(n)
?" es "Exactamente igual quewhile(n != 0)
" y la respuesta a "¿Funcionará este código correctamente tanto para negativo como para positivon
?" es "Sí, en cualquier compilador moderno que cumpla con los estándares". La respuesta a la pregunta "¿Entonces por qué el instructor lo marcó?" es probable que no estén al tanto de una redefinición significativa del lenguaje que le sucedió a C en 1999 y a C ++ en 2010 más o menos.fuente
n == 0
menos hace que sea inmediatamente y completamente obvio para cualquier lector lo que sucede en ese caso. Sin ella, el lector debe asegurarse de que el bucle se omite y que el valor predeterminado des
return es el correcto.n=0
. Introducir ramificaciones y complicaciones innecesarias no hace que el código sea más fácil, lo hace más difícil, ya que ahora no solo tiene que demostrar que el algoritmo general es correcto, sino que también debe pensar en todos los casos especiales por separado.Tu código está perfectamente bien
Tienes toda la razón y tu maestro está equivocado. No hay absolutamente ninguna razón para agregar esa complejidad adicional, ya que no afecta el resultado en absoluto. Incluso introduce un error. (Vea abajo)
Primero, la verificación por separado si
n
es cero es obviamente completamente innecesaria y esto es muy fácil de realizar. Para ser honesto, en realidad cuestiono la competencia de tus maestros si tiene objeciones al respecto. Pero supongo que todos pueden tener un pedo cerebral de vez en cuando. Sin embargo, creo quewhile(n)
debería cambiarsewhile(n != 0)
porque agrega un poco más de claridad sin siquiera costar una línea adicional. Sin embargo, es algo menor.El segundo es un poco más comprensible, pero todavía está equivocado.
Esto es lo que dice el estándar C11 6.5.5.p6 :
La nota al pie dice esto:
El truncamiento hacia cero significa que el valor absoluto para
a/b
es igual al valor absoluto(-a)/b
para todosa
yb
, lo que a su vez significa que su código está perfectamente bien.El módulo es matemática fácil, pero puede ser contradictorio
Sin embargo, tu maestro tiene un punto que debes tener cuidado, porque el hecho de que estés cuadrando el resultado es realmente crucial aquí. Calcular de
a%b
acuerdo con la definición anterior es matemática fácil, pero puede ir en contra de su intuición. Para multiplicación y división, el resultado es positivo si los operandos tienen el mismo signo. Pero cuando se trata de módulo, el resultado tiene el mismo signo que el primer operando. El segundo operando no afecta el signo en absoluto. Por ejemplo,7%3==1
pero(-7)%(-3)==(-1)
.Aquí hay un fragmento que lo demuestra:
Entonces, irónicamente, tu maestro demostró su punto al estar equivocado.
El código de tu maestro es defectuoso
Sí, de hecho lo es. Si la entrada es
INT_MIN
Y, la arquitectura es el complemento de dos Y el patrón de bits donde el bit de signo es 1 y todos los bits de valor son 0 NO es un valor de trampa (el uso del complemento de dos sin valores de trampa es muy común), entonces el código de su maestro producirá un comportamiento indefinido en la linean = n * (-1)
. Su código es, aunque sea ligeramente, mejor que el suyo. Y considerando la introducción de un pequeño error al hacer que el código sea innecesario complejo y obtener un valor absolutamente cero, diría que su código es MUCHO mejor.En otras palabras, en compilaciones donde INT_MIN = -32768 (aunque la función resultante no puede recibir una entrada que sea <-32768 o> 32767), la entrada válida de -32768 provoca un comportamiento indefinido, porque el resultado de - (- 32768i16) no se puede expresar como un entero de 16 bits. (En realidad, -32768 probablemente no causaría un resultado incorrecto, porque - (- 32768i16) generalmente se evalúa a -32768i16, y su programa maneja los números negativos correctamente.) (SHRT_MIN podría ser -32768 o -32767, dependiendo del compilador).
Pero su maestro declaró explícitamente que
n
puede estar en el rango [-10 ^ 7; 10 ^ 7]. Un número entero de 16 bits es demasiado pequeño; tendrías que usar [al menos] un número entero de 32 bits. El usoint
podría hacer que su código sea seguro, excepto queint
no es necesariamente un número entero de 32 bits. Si compila para una arquitectura de 16 bits, ambos fragmentos de código tienen fallas. Pero su código sigue siendo mucho mejor porque este escenario reintroduce el errorINT_MIN
mencionado anteriormente con su versión. Para evitar esto, puede escribir enlong
lugar deint
, que es un número entero de 32 bits en cualquier arquitectura. Selong
garantiza que A puede mantener cualquier valor en el rango [-2147483647; 2147483647]. C11 Standard 5.2.4.2.1LONG_MIN
es a menudo-2147483648
pero el valor máximo permitido (sí, máximo, es un número negativo) paraLONG_MIN
es2147483647
.¿Qué cambios haría en su código?
Su código está bien tal como está, por lo que estas no son realmente quejas. Es más así si realmente necesito decir algo sobre su código, hay algunas pequeñas cosas que podrían aclararlo un poco.
n
an!=0
. Semánticamente, es 100% equivalente, pero lo hace un poco más claro.c
(a la que cambié el nombredigit
) dentro del ciclo while ya que solo se usa allí.long
para asegurarse de que pueda manejar todo el conjunto de entrada.En realidad, esto puede ser un poco engañoso porque, como se mencionó anteriormente, la variable
digit
puede obtener un valor negativo, pero un dígito en sí mismo nunca es positivo o negativo. Hay algunas maneras de evitar esto, pero esto es REALMENTE quisquilloso, y no me importarían esos pequeños detalles. Especialmente la función separada para el último dígito lo lleva demasiado lejos. Irónicamente, esta es una de las cosas que el código de tu maestro realmente resuelve.sum += (digit * digit)
asum += ((n%10)*(n%10))
y omitir la variabledigit
por completo.digit
si es negativo. Pero recomendaría encarecidamente no hacer que el código sea más complejo solo para que un nombre de variable tenga sentido. Ese es un olor a código MUY fuerte.int last_digit(long n) { int digit=n%10; if (digit>=0) return digit; else return -digit; }
Esto es útil si desea utilizar esa función en otro lugar.c
como lo hace originalmente. Ese nombre de variable no proporciona información útil, pero por otro lado, tampoco es engañoso.Pero para ser honesto, en este punto debes pasar a un trabajo más importante. :)
fuente
INT_MIN
y la arquitectura está usando el complemento de dos (que es muy común), entonces el código de su maestro producirá un comportamiento indefinido. Ay. Eso dejará una marca. ;-)(a/b)*b + a%b ≡ a
, el código del OP también depende del hecho de que se/
redondea hacia cero, y eso(-c)*(-c) ≡ c*c
. Se podría argumentar que las verificaciones adicionales están justificadas a pesar de que el estándar garantiza todo eso, porque no es lo suficientemente obvio. (Por supuesto que también se podría argumentar que no debería ser más bien un comentario que une las secciones estándar pertinentes, pero las directrices de estilo puede variar.)%
y los/
operadores puedan compilar solo en unidiv
x86, osdiv
en ARM o lo que sea. Sin embargo, eso no está relacionado code-gen más rápido para divisores constantes de tiempo de compilación)No me gusta por completo ni tu versión ni la de tu profesor. La versión de tu profesor hace innecesarios los exámenes adicionales que señalas correctamente. El operador mod de C no es un mod matemático apropiado: un número negativo mod 10 producirá un resultado negativo (el módulo matemático adecuado siempre es no negativo). Pero como lo estás cuadrando de todos modos, no hay diferencia.
Pero esto está lejos de ser obvio, por lo que agregaría a su código no los controles de su maestro, sino un gran comentario que explica por qué funciona. P.ej:
/ * NOTA: Esto funciona para valores negativos, porque el módulo se eleva al cuadrado * /
fuente
%
se llama el mejor de un resto , porque es que, incluso para este tipo firmados.-7 % 10
realidad será en-7
lugar de 3.%
operador de C no es.NOTA: Mientras escribía esta respuesta, usted aclaró que está usando C. La mayoría de mi respuesta es sobre C ++. Sin embargo, dado que su título todavía tiene C ++ y la pregunta todavía está etiquetada como C ++, he elegido responder de todos modos en caso de que esto sea útil para otras personas, especialmente porque la mayoría de las respuestas que he visto hasta ahora son en su mayoría insatisfactorias.
En C ++ moderno (Nota: Realmente no sé dónde se encuentra C en esto), su profesor parece estar equivocado en ambos aspectos.
Primero es esta parte aquí:
En C ++, esto es básicamente lo mismo que :
Eso significa que tu while es equivalente a algo como esto:
Eso significa que dado que simplemente está saliendo de su if cuando el while no se ejecuta de todos modos, realmente no hay una razón para poner esto si aquí, ya que lo que está haciendo después del ciclo y en el if son equivalentes de todos modos. Aunque debería decir que es por alguna razón, estos eran diferentes, necesitarías tener esto si.
Entonces, realmente, esta declaración if no es particularmente útil a menos que me equivoque.
La segunda parte es donde las cosas se ponen difíciles:
El corazón del problema es cuál es la salida del módulo de un número negativo.
En C ++ moderno, esto parece estar bien definido :
Y después:
Como el cartel de la respuesta citada señala correctamente, la parte importante de esta ecuación aquí:
Tomando un ejemplo de su caso, obtendría algo como esto:
El único inconveniente es esa última línea:
Eso significa que en un caso como este, solo el signo parece estar definido por la implementación. Eso no debería ser un problema en su caso porque, porque de todos modos está cuadrando este valor.
Dicho esto, tenga en cuenta que esto no necesariamente se aplica a versiones anteriores de C ++ o C99. Si eso es lo que está usando su profesor, ese podría ser el motivo.
EDITAR: No, estoy equivocado. Este parece ser el caso para C99 o posterior también :
Y otro lugar :
Así que sí. Incluso en C99, esto no parece afectarlo. La ecuación es la misma.
fuente
(-1)%10
podría producir-1
o1
; significa que podría producir-1
o9
, y en este último caso(-1)/10
producirá-1
y el código de OP nunca terminará.(a/b)*b + a%b == a
, luego dejaa=-1; b=10
, dando(-1/10)*10 + (-1)%10 == -1
. Ahora, si de-1/10
hecho se redondea hacia abajo (hacia -inf), entonces lo tenemos(-1/10)*10 == -10
, y usted necesita tener(-1)%10 == 9
para que la primera ecuación coincida. Como el otro estado de respuestas , no es así como funciona dentro de los estándares actuales, pero es como solía funcionar. No es realmente acerca de la señal del resto como tal, sino cómo las rondas de división y de lo que el resto continuación, tiene que ser para satisfacer la ecuación.(-1)*10+9=-1
, por lo que la elección(-1)/10=-1
y(-1)%10=9
no viola la ecuación de gobierno. Por otro lado, la elección(-1)%10=1
no puede satisfacer la ecuación de gobierno sin importar cómo(-1)/10
se elija; no hay un enteroq
tal queq*10+1=-1
.Como otros han señalado, el tratamiento especial para n == 0 no tiene sentido, ya que para cada programador de C serio es obvio que "while (n)" hace el trabajo.
El comportamiento para n <0 no es tan obvio, por eso preferiría ver esas 2 líneas de código:
o al menos un comentario:
Honestamente, ¿a qué hora comenzaste a considerar que n podría ser negativo? ¿Al escribir el código o al leer los comentarios de tu maestro?
fuente
Esto me recuerda una tarea que fallé
Allá por los años 90. El profesor había estado surgiendo sobre bucles y, para resumir, nuestra tarea consistía en escribir una función que devolviera el número de dígitos para cualquier entero> 0.
Entonces, por ejemplo, el número de dígitos
321
sería3
.Aunque la tarea simplemente decía que escribiera una función que devolviera el número de dígitos, la expectativa era que usaríamos un bucle que se dividiera por 10 hasta que ... lo obtenga, como lo cubre la conferencia .
Pero el uso de bucles no se indicó explícitamente, así que yo:
took the log, stripped away the decimals, added 1
y posteriormente fui criticado frente a toda la clase.El punto es que el propósito de la tarea era evaluar nuestra comprensión de lo que habíamos aprendido durante las conferencias . De la conferencia que recibí, supe que el profesor de informática era un poco idiota (¿pero quizás un idiota con un plan?)
En su situación:
Definitivamente habría proporcionado dos respuestas:
fuente
En general, en las asignaciones no todas las marcas se dan solo porque el código funciona. También obtiene calificaciones por hacer que una solución sea fácil de leer, eficiente y elegante. Estas cosas no siempre son mutuamente excluyentes.
Uno que no puedo entender lo suficiente es "usar nombres de variables significativos" .
En su ejemplo, no hace mucha diferencia, pero si está trabajando en un proyecto con un millón de líneas de legibilidad de código se vuelve muy importante.
Otra cosa que tiendo a ver con el código C es que las personas intentan parecer inteligentes. En lugar de usar while (n! = 0), les mostraré a todos lo inteligente que soy al escribir while (n) porque significa lo mismo. Bueno, lo hace en el compilador que tiene, pero como sugirió, la versión anterior del profesor no lo ha implementado de la misma manera.
Un ejemplo común es hacer referencia a un índice en una matriz mientras lo incrementa al mismo tiempo; Números [i ++] = iPrime;
Ahora, el próximo programador que trabaja en el código tiene que saber si me incrementan antes o después de la tarea, solo para que alguien pueda presumir.
Un megabyte de espacio en disco es más barato que un rollo de papel higiénico. Si busca claridad en lugar de tratar de ahorrar espacio, sus compañeros programadores estarán más felices.
fuente
++i
aumenta antes de la evaluación ei++
incrementos después.while(n)
También es una característica común del lenguaje. Basado en una lógica como esta, he visto muchos códigos similaresif (foo == TRUE)
. Estoy de acuerdo en re: nombres de variables, sin embargo.while(n)
no es el peor ejemplo para eso ("me gusta"if(strcmp(one, two))
más)i++
y++i
modifique el código C que se debe usar en la producción.No discutiría si la definición original o moderna de '%' es mejor, pero cualquiera que escriba dos declaraciones de retorno en una función tan corta no debería enseñar la programación en C. El retorno adicional es una declaración de goto y no usamos goto en C. Además, el código sin la verificación de cero tendría el mismo resultado, el retorno adicional lo hizo más difícil de leer.
fuente
int findChar(char *str, char c) { if(!str) return -1; int i=0; while(str[i]) { if(str[i] == c) return i; i++; } return -1; }
El enunciado del problema es confuso, pero el ejemplo numérico aclara el significado de la suma de los dígitos del número al cuadrado . Aquí hay una versión mejorada:
La función que escribió está bien, excepto por 2 detalles:
long
para acomodar todos los valores en el rango especificado ya quelong
el estándar C garantiza que el tipo tiene al menos 31 bits de valor, por lo tanto, un rango suficiente para representar todos los valores en [-10 7 , 10 7 ] . (Tenga en cuenta que el tipoint
es suficiente para el tipo de retorno, cuyo valor máximo es568
).%
los operandos negativos no es intuitivo y su especificación varía entre el C99 Standard y las ediciones anteriores. Debe documentar por qué su enfoque es válido incluso para entradas negativas.Aquí hay una versión modificada:
La respuesta del profesor tiene múltiples defectos:
int
puede tener un rango de valores insuficiente.0
.n = INT_MIN
.Dadas las restricciones adicionales en la declaración del problema (C99 y rango de valores para
n
), solo el primer defecto es un problema. El código adicional aún produce las respuestas correctas.Debería obtener una buena nota en esta prueba, pero la explicación se requiere en una prueba escrita para mostrar su comprensión de los problemas negativos
n
, de lo contrario, el maestro puede suponer que no estaba al tanto y que tuvo suerte. En un examen oral, habría recibido una pregunta y su respuesta la habría clavado.fuente