2015-06-17 11 views
8

W języku Python 3 ciągi Unicode powinny podać liczbę znaków Unicode, ale nie wiem, jak uzyskać końcową szerokość tekstu, biorąc pod uwagę, że niektóre znaki się łączą.Jak uzyskać szerokość wyświetlania połączonych znaków Unicode w Pythonie 3?

Genesis 1: 1 - בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ

>>> len('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ') 
60 

Ale ciąg jest szerokość tylko 37 znaków. Normalizacja nie rozwiązuje problemu, ponieważ samogłoski (kropki pod większymi znakami) są odrębnymi znakami.

>>> len(unicodedata.normalize('NFC', 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')) 
60 

Na marginesie: moduł textwrap jest całkowicie złamany w tym względzie agresywnie zawijania, gdzie nie powinny. str.format wydaje się podobnie zepsuty.

+0

liczące klastry grafem może nie wystarczyć np [różne czcionki mogą prowadzić do różnych rozmiarów tekst] (http://stackoverflow.com/q/2922295/4279) – jfs

+0

Nawet jeśli mamy zagwarantowane czcionkę o stałej szerokości ? –

+0

Skorzystaj z linku, wypróbuj kod i przekonaj się sam. – jfs

Odpowiedz

3

Kilka rozwiązań z wykorzystaniem strony trzeciej uniseg, jak sugeruje @bobince:

>>> from uniseg.graphemecluster import grapheme_cluster_breakables 
>>> sum(grapheme_cluster_breakables('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ')) 
37 
>>> 
>>> from uniseg.graphemecluster import grapheme_clusters 
>>> list(grapheme_clusters('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְ הָאָרֶץ')) 
['בְּ', 'רֵ', 'א', 'שִׁ', 'י', 'ת', ',', ' ', 'בָּ', 'רָ', 'א', ' ', 'אֱ', 'לֹ', 'הִ', 'י', 'ם', ',', ' ', 'אֵ', 'ת', ' ', 'הַ', 'שָּׁ', 'מַ', 'יִ', 'ם', ',', ' ', 'וְ', 'אֵ', 'ת', ' ', 'הָ', 'אָ', 'רֶ', 'ץ'] 
>>> len(list(grapheme_clusters('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַי , ואֵת הָאָרֶץ'))) 
37 

To wygląda właściwy sposób to zrobić.

Oto przykład, który łata się textwrap. Rozwiązania do łatania innych modułów powinny być podobne.

>>> import textwrap 
>>> text = 'בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשּׁמַיִם, וְאֵת הָאָרֶץ' 
>>> print(textwrap.fill(text, width=40)) # bad, aggressive wrapping 
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת 
הַשָּׁמַיִם, וְאֵת הָאָרֶץ 
>>> import uniseg.graphemecluster 
>>> def new_len(x): 
...  if isinstance(x, str): 
...   return sum(1 for _ in uniseg.graphemecluster.grapheme_clusters(x)) 
...  return len(x) 
>>> textwrap.len = new_len 
>>> print(textwrap.fill(text, width=40)) # Good wrapping 
בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ 
+1

Można również użyć modułu 'regex':' count_user_perceived_characters = lambda text: len (regex.findall (r '\ X', text)) ' – jfs

+0

@ J.F.Sebastian Neat! Ten projekt mówi, że zamierza zastąpić 're'. Czy masz jakiś pomysł, czy rzeczywiście? –

+1

Nie wiem. [Wydaje się mało prawdopodobne] (http://bugs.python.org/issue2636). – jfs

3

Problemem jest łączenie znaków, które Python liczy się jako odrębny podczas obliczania __len__, ale łączy się w jedną drukowaną postać.

Aby dowiedzieć się, czy postać jest łączenie znaków, możemy użyć unicodedata module:

unicodedata.combining(unichr)

Zwraca kanoniczną łącząc klasy przypisanej do znaku Unicode unichr jako liczba całkowita. Zwraca 0, jeśli nie zdefiniowano żadnej klasy łączącej.

Naiwnym rozwiązaniem jest po prostu pozbawienie dowolnych postaci niezerowej klasy łączącej. Pozostawia to znaki, które stoją same i powinny dać nam ciąg znaków z mapowaniem 1-do-1 między widocznymi i ukrytymi postaciami. (Jestem nowicjuszem Unicode i jest to prawdopodobnie bardziej skomplikowane, istnieją subtelności z łączeniem postaci i extenderów grafem, których tak naprawdę nie rozumiem, ale nie wydają się być istotne dla tego konkretnego ciągu znaków.)

Więc wymyśliłem tej funkcji:

import unicodedata 

def visible_length(unistr): 
    '''Returns the number of printed characters in a Unicode string.''' 
    return len([char for char in unistr if unicodedata.combining(char) == 0]) 

która zwraca odpowiednią długość za wyrażenie:

>>> visible_length('בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ') 
37 

prawdopodobnie nie jest to kompletna solutio n dla wszystkich ciągów Unicode, ale w zależności od tego, z jakiego podzestawu Unicode pracujesz, może to wystarczyć dla twoich potrzeb.

+3

Jeśli potrzebujesz pełnego algorytmu segmentacji grafik Unicode lub dzielenia linii, to jest to nieco bardziej skomplikowane - zobacz moduły stron trzecich, takie jak uniseg. – bobince

+0

+1. To zdarzyło mi się, ale kiedy grałem z unicodedata.com i widziałem, że zwróciło to wiele wartości, byłem bardzo zastraszony, ale może nadaje się do moich celów. Dzięki. Mam nadzieję, że ktoś może zaproponować jeszcze bardziej niezawodne rozwiązanie. –