2012-01-19 14 views
7

Quindi sono verde come l'erba e la programmazione di apprendimento da How to think like a computer scientist: Learn python 3. Sono in grado di rispondere alla domanda (vedi sotto) ma temo di perdere la lezione.Scrittura di una funzione generalizzata per stringhe e liste in python

Scrivere una funzione (chiamata insert_at_end) che passerà (restituire il grassetto visti i due argomenti prima) per tutti e tre:

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)**) 

Il libro dà questo suggerimento: "Queste esercitazioni illustrano bene che l'astrazione successione è generale, (perché slicing, indexing e concatenazione sono così generali), quindi è possibile scrivere funzioni generali che funzionano su tutti i tipi di sequenza. ".

Questa versione non ha soluzioni on-line (che ho potuto trovare), ma in ho trovato le risposte di qualcuno a una versione precedente del testo (per Python 2.7) e hanno fatto in questo modo:

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) 

Quale sembra risolvere la domanda distinguendo tra elenchi e stringhe ... andando contro il suggerimento. Quindi, c'è un modo per rispondere alla domanda (e circa 10 più simili) senza distinguere? cioè non usando "type()"

+1

Non penso che imparerai qualcosa di utile dal tentativo di risolvere questo problema. –

risposta

1

Questo è non una soluzione, ma piuttosto una spiegazione perché a la soluzione veramente elegante non sembra possibile.

  • + concatena le sequenze, ma solo le sequenze dello stesso tipo.
  • i valori passati come primo argomento a insert_at_end sono 'scalari', quindi è necessario convertirli nel tipo di sequenza che ha il secondo argomento.
  • per fare ciò, non è sufficiente chiamare un costruttore di sequenze con un argomento scalare e creare una sequenza di un elemento di quel tipo: tuple(1) non funziona.
  • str funziona in modo diverso rispetto ad altri tipi di sequenze: tuple(["a"]) è ("a",), list(["a"]) è ["a"], ma str(["a"])) è "['a']" e non "a".

Questo rende + inutile in questa situazione, anche se è possibile costruire facilmente una sequenza di dato tipo pulito, senza instanceof, semplicemente utilizzando type().

Non è possibile utilizzare l'assegnazione di sezioni, poiché solo gli elenchi sono modificabili.

In questa situazione, la soluzione di @Hamish sembra più pulita.

+0

La soluzione di Hamish è abbastanza pulita, ma penso che vada male se si tenta di inserire una stringa multi-carattere alla fine di un elenco di stringhe. – Duncan

+0

@Duncan: beh, con le liste, è possibile aggiungere un elenco annidato, ma non esiste una stringa nidificata. Per prevenirlo correttamente, è necessario controllare esplicitamente 'str' (che vanifica lo scopo) o avere un sistema di tipo statico abbastanza potente da proibire le liste annidate (che richiede una lingua diversa da Python). – 9000

+0

@ 9000 Eccellente Grazie, essenziale (più che una risposta specifica) volevo confermare che, come dici tu, "una soluzione elegante non sembra possibile". Ero preoccupato che mi mancasse un concetto di fondo che potrebbe poi inseguirmi. –

-1

Mentre incapsula si basa sul tipo, il codice direttamente in insert_at_end non lo fa, e fa affidamento su + significato di cose correlate per tutti e 3 i tipi, e quindi in questo senso, si adatta al suggerimento .

+0

Ci deve essere una risposta migliore. La risposta potrebbe probabilmente usare il tipo, ma non i tipi particolari. –

+0

Non puoi semplicemente lanciare il codice incriminato in una funzione diversa e chiamarla vittoria! : D – Hamish

+0

Sarei interessato a vedere anche questo, ma non ho ancora visto nessuno suggerirne uno. –

2

direi che l'esempio non è simmetrica, nel senso che chiede al lettore di gestire due casi diversi:

  • int, elenco
  • str, str

Nella mia opinione, l'esercizio dovrebbe chiedere per implementare questo:

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

In questo caso, il lettore deve funzionare solo con due parametri che utilizzano lo stesso tipo di sequenza e il suggerimento avrebbe molto più senso .

+0

Non sono d'accordo - non è il tipo di sequenza che è un problema - è se il nuovo valore è iterabile o meno. – Hamish

+1

Penso che l'intero problema sia che lo str (o unicode) funzioni in modo diverso da lista, tupla o altro. Altrimenti str (['a', 'b', 'c']) produrrebbe 'abc' e non "['a', 'b', 'c']" ... –

0

La sfida con questa domanda (in Python 2.7, sto testando 3.2 in questo momento per verificare) è che due dei possibili tipi di input per seq sono immutabili e che è necessario restituire lo stesso tipo di quello passato . in Per le stringhe, questo è un problema minore, perché si potrebbe fare questo:

return seq + char 

Come che restituisce una nuova stringa che è la concatenazione della sequenza di input e il carattere aggiunto, ma che non funziona per liste o tuple. È possibile solo concatenare una lista a una lista o una tupla a una tupla. Se si voleva evitare "tipo" il controllo, si potrebbe arrivare con qualcosa di simile:

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 

che non è proprio molto diverso da tipi in realtà di controllo, ma non evitare di utilizzare direttamente la funzione type.

1

Questo problema è uno di una lunga lista e il suggerimento si applica a tutti. Penso che sia ragionevole che, dopo aver scritto la funzione encapsulate che può essere riutilizzata per cose come insert_at_front, il resto dell'implementazione sia di tipo agnostico.

Tuttavia, penso che una migliore attuazione della encapsulate potrebbe essere:

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

che gestisce una gamma più ampia di tipi con meno codice.

+0

Upside: questa soluzione funziona. Lato negativo: non è una buona soluzione agnostica di tipo che utilizza l'interfaccia comune delle sequenze; invece, è un caso speciale una classe magica. – 9000

+1

@ 9000, le stringhe spesso hanno bisogno di un involucro speciale, sono l'unica sequenza che contiene solo oggetti dello stesso tipo di se stessi e che danneggia un sacco di codice digitato anatra altrimenti pulito. Altrimenti gestisce 'list',' tuple', le loro sottoclassi e qualsiasi altra sequenza che segue il modello generale che può essere costruita da una lista. – Duncan

+0

sì, le stringhe devono essere speciali, e questa è la triste verità che impedisce a questo problema di avere un'elegante soluzione indipendente dal tipo e completamente basata sull'interfaccia. – 9000

0

Questa soluzione richiede ancora un codice separato per le stringhe anziché elenchi/tuple, ma è più conciso e non esegue alcun controllo per tipi specifici.

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

come su: assert insert_at_end (['val'], ['seq']) == ['seq', ['val']]? –

2

mio meglio:

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

Questo tenterà di creare la sequenza di type(seq) e se val non è iterabile produce una lista e concatena.

+0

'test (insert_at_end ('xyz', ['abc']), ** ['abc', 'xyz'] **)' non passano. – Duncan

0

Forse questo è più vicina la risposta:

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') 

Ci potrebbe anche essere completamente tipi di sequenze definiti dall'utente. Funzioneranno anche finché convertibili da e verso una lista. Questo funziona anche:

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

Sono d'accordo che il punto è che se item è iterabile o meno.

Così la mia soluzione potrebbe essere questa:

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)) 

Esempio:

>>> 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) 

Dal insert_at_end in grado di gestire iterabile e non, funziona bene anche 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

Questo è assolutamente sbagliato. insert_at_end ([1, 2, 4, 6], [5]) dovrebbe essere [1, 2, 4, 6, [5]] di tutta la logica. –

+0

@RomanSusi: Se vuoi gestire string e list allo stesso modo, direi che non è quello che mi aspetterei. D'altra parte, per buon senso, sarei d'accordo con te; quindi sono il primo a essere un po 'confuso e non voglio discutere troppo perché sembra che anche l'autore dell'esercizio abbia la sua logica ... –

Problemi correlati