2012-01-19 9 views
7

Así que estoy verde como la hierba y aprendiendo programación de How to think like a computer scientist: Learn python 3. Puedo responder la pregunta (ver más abajo) pero temo que me estoy perdiendo la lección.Escribir una función generalizada para ambas cadenas y listas en python

Escribir una función (llamada insert_at_end) que pasará (devolver la negrita dados los dos argumentos antes) para los tres:

test(insert_at_end(5, [1, 3, 4, 6]), **[1, 3, 4, 6, 5]**) 
test(insert_at_end('x', 'abc'), **'abcx'**) 
test(insert_at_end(5, (1, 3, 4, 6)), **(1, 3, 4, 6, 5)**) 

El libro da este consejo: "Estos ejercicios ilustran muy bien que la abstracción secuencia es general, (porque el corte, la indexación y la concatenación son muy generales), por lo que es posible escribir funciones generales que funcionen en todos los tipos de secuencia ".

Esta versión no tiene soluciones en línea (que he podido encontrar) pero en la que encontré las respuestas de alguien a una versión anterior del texto (para Python 2.7) y lo hicieron de esta manera:

def encapsulate(val, seq): 
    if type(seq) == type(""): 
     return str(val) 
    if type(seq) == type([]): 
     return [val] 
    return (val,) 

def insert_at_end(val, seq): 
    return seq + encapsulate(val, seq) 

Lo que parece resolver la cuestión al distinguir entre listas y cadenas ... yendo en contra de la sugerencia. Entonces, ¿qué tal hay una manera de responder la pregunta (y alrededor de 10 más similares) sin distinguir? es decir, no se usa "tipo()"

+1

No creo que aprenda algo útil al intentar resolver este problema. –

Respuesta

1

Esta es no una solución sino más bien una explicación de por qué una solución realmente elegante no parece posible.

  • + concatena secuencias, pero solo secuencias del mismo tipo.
  • Los valores pasados ​​como primer argumento para insert_at_end son 'escalares', por lo que debe convertirlos al tipo de secuencia que tiene el segundo argumento.
  • para hacer eso, no puede simplemente llamar a un constructor de secuencia con un argumento escalar y crear una secuencia de un elemento de ese tipo: tuple(1) no funciona.
  • str funciona de forma diferente que otros tipos de secuencias: tuple(["a"])("a",) es, list(["a"]) es ["a"], pero es str(["a"]))"['a']" y no "a".

Esto hace + inútil en esta situación, a pesar de que se puede construir fácilmente una secuencia de tipo dado limpiamente, sin instanceof, sólo mediante el uso type().

No puede usar la asignación de división, ya que solo las listas son mutables.

En esta situación, la solución de @Hamish parece más limpia.

+0

La solución de Hamish es bastante limpia, pero creo que va muy mal si intenta insertar una cadena de caracteres múltiples al final de una lista de cadenas. – Duncan

+0

@Duncan: bueno, con las listas, puede agregar una lista anidada, pero no existe una cadena anidada. Para prevenirlo adecuadamente, debe comprobar explícitamente por 'str' (lo que frustra el propósito) o tener un sistema de tipo estático lo suficientemente potente como para prohibir listas anidadas (que toma un lenguaje diferente al de Python). – 9000

+0

@ 9000 Excelente Gracias, esencial (más que una respuesta específica). Estaba buscando confirmar que, como usted dice, "una solución elegante no parece posible". Me preocupaba que me estaba perdiendo un concepto subyacente que más tarde podría perseguirme. –

-1

Mientras que encapsular depende del tipo, el código directamente en insert_at_end no funciona, y depende de + lo que significa cosas relacionadas para los 3 tipos, y en ese sentido, encaja con la sugerencia .

+0

Debe haber una respuesta más agradable. La respuesta probablemente podría usar tipo, pero no tipos particulares. –

+0

¡No puedes simplemente tirar el código ofensivo en una función diferente y llamarlo una victoria! : D – Hamish

+0

Me interesaría ver eso también, pero no he visto a nadie sugerir uno todavía. –

2

yo diría que el ejemplo no es simétrica, es decir, que pide al lector manejar dos casos diferentes:

  • int, lista
  • str, str

En mi opinión, el ejercicio debe pedir para implementar esta:

  • lista, lista: insert_at_end ([5], [1, 3, 4, 6])
  • str, str: insert_at_end ('x', 'abc')

En este caso, el lector tiene que trabajar sólo con dos parámetros que utilizan el mismo tipo de secuencia y la indirecta tendría mucho más sentido .

+0

No estoy de acuerdo, no es el tipo de secuencia un problema, es si el nuevo valor es iterable o no. – Hamish

+1

Creo que todo el problema es que el str (o unicode) funciona de forma diferente a la lista, tupla o lo que sea. De lo contrario, str (['a', 'b', 'c']) produciría 'abc' y no "['a', 'b', 'c']" ... –

0

El desafío con esta pregunta (en Python 2.7, estoy probando 3.2 en este momento para verificar) es que dos de los posibles tipos de entrada para seq son inmutables, y se espera que devuelva el mismo tipo que se pasó . en Para cadenas, esto es un problema menor, ya que podría hacer esto:

return seq + char 

Como que devolverá una nueva cadena que es el encadenamiento de la secuencia de entrada y el carácter anexado, pero eso no funciona para listas o tuplas Solo puede concatenar una lista a una lista o una tupla a una tupla. Si usted quiere evitar el "tipo" de cheques, usted podría llegar con algo como esto:

if hasattr(seq, 'append'): # List input. 
    seq.append(char) 
elif hasattr(seq, 'strip'): # String input. 
    seq = seq + char 
else: # Tuple 
    seq = seq + (char,) 

return seq 

Eso no es realmente muy diferente de los tipos de cheques en realidad, pero sí evitar el uso de la función directamente type.

1

Ese problema es uno de una larga lista y la sugerencia se aplica a todos ellos. Creo que es razonable que, habiendo escrito la función encapsulate que se puede volver a utilizar para cosas como insert_at_front, el resto de la implementación sea de tipo independiente.

Sin embargo, creo que una mejor aplicación de encapsulate podría ser:

def encapsulate(val, seq): 
    if isinstance(seq, basestring): 
     return val 
    return type(seq)([val]) 

que se ocupa de una gama más amplia de tipos con menos código.

+0

Al revés: esta solución funciona. Desventaja: no es una buena solución agnóstica de tipo que utiliza la interfaz común de secuencias; en cambio, es un caso especial de alguna clase mágica. – 9000

+1

@ 9000, las cadenas a menudo necesitan ser encapsuladas especiales, son la única secuencia que solo contiene objetos del mismo tipo que ellos mismos y que estropea una gran cantidad de código tipado de otro modo. De lo contrario, maneja 'list',' tuple', sus subclases y cualquier otra secuencia que siga el modelo general que se puede construir a partir de una lista. – Duncan

+0

sí, las cadenas deben ser especiales, y esta es la cruda realidad que evita que este problema tenga una solución elegante, totalmente independiente de la agnóstico de tipo, totalmente basada en la interfaz. – 9000

0

Esta solución aún requiere un código separado para cadenas en lugar de listas/tuplas, pero es más conciso y no hace ninguna comprobación de tipos específicos.

def insert_at_end(val, seq): 
    try: 
     return seq + val 
    except TypeError: # unsupported operand type(s) for + 
     return seq + type(seq)([val]) 
+0

¿qué tal: afirmar insert_at_end (['val'], ['seq']) == ['seq', ['val']]? –

2

Mi mejor esfuerzo:

def insert_at_end(val, seq): 
    t = type(seq) 
    try: 
     return seq + t(val) 
    except TypeError: 
     return seq + t([val]) 

Este intentará crear la secuencia de type(seq) y si no es val iterables produce una lista y concatena.

+0

'test (insert_at_end ('xyz', ['abc']), ** ['abc', 'xyz'] **)' no pasaría. – Duncan

0

Tal vez esto está más cerca de la respuesta:

def genappend(x, s): 
    if isinstance(s, basestring): 
     t = s[0:0].join 
    else: 
     t = type(s) 
    lst = list(s) 
    lst.append(x) 
    return t(lst) 

print genappend(5, [1,2,3,4])  
print genappend(5, (1,2,3,4)) 
print genappend('5', '1234') 

También podría ser completamente definidas por el usuario tipos de secuencias. También funcionarán siempre y cuando se puedan convertir en una lista. Esto también funciona:

print genappend('5', set('1234')) 
0

Estoy de acuerdo que el punto es que si item es iterable o no.

Así que mi solución sería la siguiente:

def iterate(seq, item): 
    for i in seq: 
     yield i 
    yield item 

def insert_at_end(seq, item): 
    if hasattr(item, '__iter__'): 
     return seq + item 
    else: 
     return type(seq)(iterate(seq, item)) 

Ejemplo:

>>> insert_at_end('abc', 'x') 
'abcx' 
>>> insert_at_end([1, 2, 4, 6], 5) 
[1, 2, 4, 6, 5] 
>>> insert_at_end((1, 2, 4, 6), 5) 
(1, 2, 4, 6, 5) 

Desde insert_at_end puede manejar iterable y no, funciona bien incluso con:

>>> insert_at_end('abc', 'xyz') 
'abcxyz' 
>>> insert_at_end([1, 2, 4, 6], [5, 7]) 
[1, 2, 4, 6, 5, 7] 
>>> insert_at_end((1, 2, 4, 6), (5, 7)) 
(1, 2, 4, 6, 5, 7) 
+0

Esto es completamente incorrecto. insert_at_end ([1, 2, 4, 6], [5]) debe ser [1, 2, 4, 6, [5]] por toda la lógica. –

+0

@RomanSusi: Si quiere manejar cadena y lista de la misma manera, diría que no es lo que esperaba. Por otro lado, por el sentido común, estaría de acuerdo contigo; así que soy el primero en estar un poco confundido y no discutiré demasiado porque parece que el autor del ejercicio también tenía su propia lógica ... –

Cuestiones relacionadas