10

Devo prendere una stringa e accorciarla a 140 caratteri.Python: Dividere la stringa unicode sui limiti delle parole

Attualmente sto facendo:

if len(tweet) > 140: 
    tweet = re.sub(r"\s+", " ", tweet) #normalize space 
    footer = "… " + utils.shorten_urls(post['url']) 
    avail = 140 - len(footer) 
    words = tweet.split() 
    result = "" 
    for word in words: 
     word += " " 
     if len(word) > avail: 
      break 
     result += word 
     avail -= len(word) 
    tweet = (result + footer).strip() 
    assert len(tweet) <= 140 

Così Questa grande opera per l'inglese, e l'inglese come corde, ma fallisce per una stringa cinese, perché tweet.split() solo restituisce un array:

>>> s = u"简讯:新華社報道,美國總統奧巴馬乘坐的「空軍一號」專機晚上10時42分進入上海空域,預計約30分鐘後抵達浦東國際機場,開展他上任後首次訪華之旅。" 
>>> s 
u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002' 
>>> s.split() 
[u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002'] 

Come dovrebbe Lo faccio in modo che gestisca I18N? Ha senso in tutte le lingue?

Sono su Python 2.5.4 se questo è importante.

+0

+1 per la domanda interessante –

risposta

1

Dopo aver parlato con alcuni nativi cantonese, mandarino, e gli altoparlanti giapponesi sembra che la cosa giusta da fare è difficile, ma il mio algoritmo attuale rende ancora senso per loro nel contesto di tutti i messaggi di internet.

Significato, sono utilizzati per il trattamento "split on space e add ... alla fine".

Quindi sarò pigro e lo seguirò fino a quando non ricevo lamentele da persone che non lo capiscono.

L'unica modifica alla mia implementazione originale potrebbe essere quella di non forzare uno spazio sul l'ultima parola in quanto è non necessario in qualsiasi lingua (e utilizzare il carattere unicode ... &#x2026 invece di ... three dots per salvare 2 caratteri)

+0

È un'entità denominata in HTML: '& hellip;', ellissi orizzontali. – ephemient

7

In genere il cinese non ha spazi bianchi tra le parole e i simboli possono avere significati diversi a seconda del contesto. Dovrai capire il testo per dividerlo in un confine di parole. In altre parole, quello che stai cercando di fare non è facile in generale.

+0

Ha un sottostringa di stringa cinese? Come se fossi 's [: 120]' sarà ancora leggibile? –

+4

Si può finire con una mezza parola che potrebbe cambiare totalmente il significato. Immagina di dividere "assistenza" alle prime tre lettere. –

+0

ok, grazie. "..." significa la stessa cosa in altre lingue, oppure c'è un carattere alternativo "ellissi" –

5

Per la segmentazione delle parole in cinese e altri compiti avanzati nell'elaborazione del linguaggio naturale, considerare NLTK come un buon punto di partenza se non una soluzione completa: si tratta di un ricco toolkit basato su Python, particolarmente utile per l'apprendimento delle tecniche di elaborazione NL (e non raramente abbastanza buono da offrire una soluzione praticabile ad alcuni di questi problemi).

+3

"non di rado" == di solito, a volte, qualcos'altro? –

+0

@Laurence, dipende da quanto siano spigolose le tue tipiche attività NL, e dal modo in cui è necessario che il codice sia indurito dalla produzione e ottimizzato per le prestazioni. Se hai a che fare con terabyte di testo o hai bisogno di una risposta a bassa latenza, quindi devi implementare un cluster parallelo altamente scalabile e di grandi dimensioni, NLTK ti permetterà, nel migliore dei casi, di disegnare un prototipo, non di offrire una soluzione praticabile alle tue esigenze; per le attività a volume ridotto e più tolleranti nel tempo, in particolare quelli noti come la segmentazione, "di solito" si applica - ma ci sono tutti i tipi di bisogni intermedi e problemi di problemi speciali! -) –

+2

Non voglio davvero addestrare una soluzione NLP per la scoperta dell'intervallo di parole. Sono sicuro che qualcuno lo abbia già fatto e voglia solo uno splitter di wordbreak pre-confezionato. –

0

Questo punisce la decisione rompicapo sul modulo re, ma potrebbe funzionare abbastanza bene per te.

import re 

def shorten(tweet, footer="", limit=140): 
    """Break tweet into two pieces at roughly the last word break 
    before limit. 
    """ 
    lower_break_limit = limit/2 
    # limit under which to assume breaking didn't work as expected 

    limit -= len(footer) 

    tweet = re.sub(r"\s+", " ", tweet.strip()) 
    m = re.match(r"^(.{,%d})\b(?:\W|$)" % limit, tweet, re.UNICODE) 
    if not m or m.end(1) < lower_break_limit: 
     # no suitable word break found 
     # cutting at an arbitrary location, 
     # or if len(tweet) < lower_break_limit, this will be true and 
     # returning this still gives the desired result 
     return tweet[:limit] + footer 
    return m.group(1) + footer 
+0

grazie. Ho aggiunto un controllo se non ci sono limiti di parole. Per le stringhe in inglese funziona molto bene, ma per il mio esempio cinese (raddoppiarlo per renderlo lungo), finisco con una stringa lunga 137 caratteri, non 140. 'len (accorcia (s * 2," ... fine "))' –

+0

Ciò significa che funziona come previsto, poiché si interrompe all'ultimo \ b \ W. Tuttavia, non conosco il cinese per sapere se si tratta in realtà di un'interruzione di parole in quel testo. Prova 'abbrevia (" abcde "* 3," ", 13)' per un altro esempio di come si rompa più corto del limite. –

3

il re.U flag tratterà \s in base al database delle proprietà dei caratteri Unicode.

La data stringa, tuttavia, non sembra contenere caratteri di spazio vuoto in base alla banca dati unicode di Python:

>>> x = u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002' 
>>> re.compile(r'\s+', re.U).split(x) 
[u'\u7b80\u8baf\uff1a\u65b0\u83ef\u793e\u5831\u9053\uff0c\u7f8e\u570b\u7e3d\u7d71\u5967\u5df4\u99ac\u4e58\u5750\u7684\u300c\u7a7a\u8ecd\u4e00\u865f\u300d\u5c08\u6a5f\u665a\u4e0a10\u664242\u5206\u9032\u5165\u4e0a\u6d77\u7a7a\u57df\uff0c\u9810\u8a08\u7d0430\u5206\u9418\u5f8c\u62b5\u9054\u6d66\u6771\u570b\u969b\u6a5f\u5834\uff0c\u958b\u5c55\u4ed6\u4e0a\u4efb\u5f8c\u9996\u6b21\u8a2a\u83ef\u4e4b\u65c5\u3002'] 
+0

Giusto, ma "spazio bianco" in inglese significa separatori di parole, dove non ci sono separatori di parole in cinese, solo spazi bianchi come separatori di frasi. –

-1

Salva due personaggi e utilizzare un elipsis (, 0x2026) invece di tre punti!

+1

In ellissi UTF-8 richiede 3 byte quindi non c'è molto da salvare lì :) –

+2

Ho usato la parola "caratteri" invece di "byte" di proposito. :) –

+1

Adam intendeva: si salvano due caratteri Unicode, ma in UTF-8, U + 2026 richiede 3 byte e tre punti prendono 1 byte ciascuno, quindi non c'è alcun salvataggio quando lo si memorizza. La mia nota: concettualmente è meglio usare un carattere ellittico. –

2

Ho provato la soluzione con PyAPNS per le notifiche push e volevo solo condividere ciò che ha funzionato per me. Il problema che ho riscontrato è che il troncamento a 256 byte in UTF-8 avrebbe comportato la caduta della notifica. Dovevo assicurarmi che la notifica fosse codificata come "unicode_escape" per farlo funzionare. Presumo che ciò avvenga perché il risultato viene inviato come JSON e non come UTF-8 grezzo.Comunque qui è la funzione che ha funzionato per me:

def unicode_truncate(s, length, encoding='unicode_escape'): 
    encoded = s.encode(encoding)[:length] 
    return encoded.decode(encoding, 'ignore') 
1

In sostanza, in CJK (tranne coreano con spazi), è necessario dizionario di look-up alle parole del segmento correttamente. A seconda della tua esatta definizione di "parola", il giapponese può essere più difficile di così, dal momento che non tutte le varianti flesse di una parola (ad esempio "行 こ う" vs. "行 っ た") appariranno nel dizionario. Se vale la pena, lo sforzo dipende dalla tua applicazione.

Problemi correlati