Python: разбить строку юникода на границы слов - PullRequest
10 голосов
/ 15 ноября 2009

Мне нужно взять строку и сократить ее до 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 ]

7 голосов
/ 15 ноября 2009

Китайский язык обычно не имеет пробелов между словами, и символы могут иметь разные значения в зависимости от контекста Вам нужно будет понять текст, чтобы разбить его на границе слова. Другими словами, то, что вы пытаетесь сделать, не так просто.

5 голосов
/ 16 ноября 2009

Для сегментации слов на китайском языке и других сложных задач при обработке естественного языка, NLTK считается хорошей отправной точкой, если не полным решением - это богатый набор инструментов на основе Python, особенно полезный для обучения о методах обработки NL (и нередко достаточно хороших, чтобы предложить вам жизнеспособное решение некоторых из этих проблем).

3 голосов
/ 17 ноября 2009

флаг 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']
2 голосов
/ 21 января 2010

Я опробовал решение с 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')
1 голос
/ 03 февраля 2012

В основном, в CJK (кроме корейского языка с пробелами) вам нужен словарь для правильного сегментирования слов. В зависимости от вашего точного определения слова, японский язык может быть более сложным, так как не все измененные варианты слова (то есть "行 こ う" против "" っ た ") появятся в словаре. Стоит ли усилий, зависит от вашей заявки.

1 голос
/ 17 ноября 2009

После разговора с некоторыми носителями кантонского, мандаринского и японского языков кажется, что правильно сделать это сложно, но мой текущий алгоритм все еще имеет смысл для них в контексте интернет-публикаций.

Это означает, что они используются для обработки "разбить на пространство и добавить ... в конце".

Так что я буду лениться и придерживаться этого, пока не получу жалобы от людей, которые этого не понимают.

Единственное изменение в моей первоначальной реализации состояло в том, чтобы не вводить пробел в последнем слове, поскольку оно не требуется ни в одном языке (и использовать символ Unicode… &#x2026 вместо ... three dots для сохранения 2 символов )

0 голосов
/ 17 ноября 2009

Сохраните два символа и используйте вместо трех точек эллипсис (, 0x2026 )!

0 голосов
/ 16 ноября 2009

Это переносит решение о разрыве слов в модуль re, но оно может работать достаточно хорошо для вас.

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