Python: разбить строку юникода на границы слов
Мне нужно взять строку и сократить ее до 140 символов.
В настоящее время я занимаюсь:
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
Так что это прекрасно работает для английского языка и английского языка, как строки, но не подходит для китайской строки, потому что tweet.split()
просто возвращает один массив:
>>> 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']
Как я должен сделать это, чтобы он обрабатывал I18N? Имеет ли это смысл на всех языках?
Я на Python 2.5.4, если это имеет значение.
8 ответов
Поговорив с некоторыми носителями кантонского, мандаринского и японского языков, кажется, что правильно сделать это сложно, но мой текущий алгоритм все еще имеет смысл для них в контексте интернет-сообщений.
Это означает, что они привыкли к "разделению пространства и добавлению… в конце" лечения.
Поэтому я буду ленивым и придерживаться этого, пока не получу жалобы от людей, которые этого не понимают.
Единственное изменение в моей первоначальной реализации состояло бы в том, чтобы не ставить пробел в последнем слове, так как оно не нужно ни в каком языке (и использовать символ юникода… …
вместо... three dots
сохранить 2 символа)
У китайцев обычно нет пробелов между словами, и символы могут иметь разные значения в зависимости от контекста. Вам нужно будет понять текст, чтобы разбить его на границе слова. Другими словами, то, что вы пытаетесь сделать, не так просто.
Для сегментации слов на китайском языке и других сложных задач при обработке естественного языка, NLTK считается хорошей отправной точкой, если не полным решением - это богатый набор инструментов на основе Python, особенно полезный для изучения методов обработки NL (и не редко достаточно, чтобы предложить вам жизнеспособное решение некоторых из этих проблем).
re.U
флаг будет лечить \s
согласно базе данных свойств символов Unicode.
Однако данная строка, по-видимому, не содержит пробельных символов в соответствии с базой данных Unicode в 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']
Я опробовал решение с PyAPNS для push-уведомлений и просто хотел поделиться тем, что сработало для меня. Проблема, с которой я столкнулся, заключается в том, что усечение до 256 байтов в UTF-8 приведет к удалению уведомления. Я должен был убедиться, что уведомление было закодировано как "unicode_escape", чтобы заставить его работать. Я предполагаю, что это потому, что результат отправляется как JSON, а не как необработанный UTF-8. В любом случае вот функция, которая работала для меня:
def unicode_truncate(s, length, encoding='unicode_escape'):
encoded = s.encode(encoding)[:length]
return encoded.decode(encoding, 'ignore')
В основном, в CJK (кроме корейского языка с пробелами), вам нужен словарь для правильного сегментирования слов. В зависимости от вашего точного определения "слова", японский язык может быть более сложным, так как не все варианты слова со словом (например, "行こう" против "行った") появятся в словаре. Стоит ли это усилий, зависит от вашего приложения.
Вам нужны инструменты сегментации китайских слов. Сегментация слов - непростая задача, и в настоящее время она полностью не решена. Есть несколько инструментов:
Разработано Academia Sinica, Тайвань.
Разработано Сунь Джуньи, инженером Baidu.
Разработано Группой языковых вычислений и машинного обучения Пекинского университета
Если вам нужна сегментация символов, это можно сделать, хотя и не очень полезно.
>>> s = u"简讯:新華社報道,美國總統奧巴馬乘坐的「空軍一號」專機晚上10時42分進入上海空域,預計約30分鐘後抵達浦東國際機場,開展他上任後首次訪華之旅。"
>>> chars = list(s)
>>> chars
[u'\u7b80', u'\u8baf', u'\uff1a', u'\u65b0', u'\u83ef', u'\u793e', u'\u5831', u'\u9053', u'\uff0c', u'\u7f8e', u'\u570b', u'\u7e3d', u'\u7d71', u'\u5967', u'\u5df4', u'\u99ac', u'\u4e58', u'\u5750', u'\u7684', u'\u300c', u'\u7a7a', u'\u8ecd', u'\u4e00', u'\u865f', u'\u300d', u'\u5c08', u'\u6a5f', u'\u665a', u'\u4e0a', u'1', u'0', u'\u6642', u'4', u'2', u'\u5206', u'\u9032', u'\u5165', u'\u4e0a', u'\u6d77', u'\u7a7a', u'\u57df', u'\uff0c', u'\u9810', u'\u8a08', u'\u7d04', u'3', u'0', u'\u5206', u'\u9418', u'\u5f8c', u'\u62b5', u'\u9054', u'\u6d66', u'\u6771', u'\u570b', u'\u969b', u'\u6a5f', u'\u5834', u'\uff0c', u'\u958b', u'\u5c55', u'\u4ed6', u'\u4e0a', u'\u4efb', u'\u5f8c', u'\u9996', u'\u6b21', u'\u8a2a', u'\u83ef', u'\u4e4b', u'\u65c5', u'\u3002']
>>> print('/'.join(chars))
简/讯/:/新/華/社/報/道/,/美/國/總/統/奧/巴/馬/乘/坐/的/「/空/軍/一/號/」/專/機/晚/上/1/0/時/4/2/分/進/入/上/海/空/域/,/預/計/約/3/0/分/鐘/後/抵/達/浦/東/國/際/機/場/,/開/展/他/上/任/後/首/次/訪/華/之/旅/。
Это предупреждает о важном решении для повторного модуля, но оно может работать достаточно хорошо для вас.
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
Сохраните два символа и используйте elipsis (…
, 0x2026) вместо трех точек!