शब्दों की सूची में रिक्त स्थान के बिना पाठ को कैसे विभाजित करें?


106

इनपुट: "tableapplechairtablecupboard..." कई शब्द

ऐसे टेक्स्ट को शब्दों की सूची में विभाजित करने और प्राप्त करने के लिए एक कुशल एल्गोरिदम क्या होगा:

आउटपुट: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]

पहली बात जो दिमाग में आती है वह है सभी संभव शब्दों (पहले अक्षर से शुरू) से गुजरना और सबसे लंबे शब्द को ढूंढना संभव है, से जारी रखें position=word_position+len(word)

पुनश्च
हमारे पास सभी संभावित शब्दों की एक सूची है।
शब्द "अलमारी" "कप" और "बोर्ड" हो सकता है, सबसे लंबे समय तक चयन करें।
भाषा: अजगर, लेकिन मुख्य बात एल्गोरिथ्म ही है।


14
क्या आप सुनिश्चित हैं कि स्ट्रिंग "टैब" और "लीप" शब्दों से शुरू नहीं होती है?
रोब ह्रस्का

हाँ, ऐसा लगता है कि यह अस्पष्ट तरीके से नहीं किया जा सकता है।
डेमोलेक

@RobHruska, उस मामले में मैंने लिखा, सबसे लंबे समय तक संभव का चयन करना।
सर्गेई

2
@Sergey - आपकी "सबसे लंबे समय तक संभव" मानदंड का अर्थ है कि यह यौगिक शब्दों के लिए था। और उस स्थिति में, यदि स्ट्रिंग "कालीन" होती तो क्या होता। क्या यह "कालीन", या "पेट्रेल" होगा?
रोब ह्रस्का

2
आपके तार में कई तानाशाही शब्द हैं:['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up']
पुनः प्रकाशित

जवाबों:


200

वास्तविक दुनिया डेटा पर लागू होने पर एक भोली एल्गोरिथ्म अच्छे परिणाम नहीं देगा। यहां एक 20-लाइन एल्गोरिथ्म है जो वास्तविक शब्द पाठ के लिए सटीक परिणाम देने के लिए सापेक्ष शब्द आवृत्ति का शोषण करता है।

(यदि आप अपने मूल प्रश्न का उत्तर चाहते हैं, जो शब्द आवृत्ति का उपयोग नहीं करता है, तो आपको यह परिष्कृत करने की आवश्यकता है कि वास्तव में "सबसे लंबे शब्द" का क्या अर्थ है: क्या 20-अक्षर का शब्द और दस 3-अक्षर का शब्द होना बेहतर है, या है पांच-अक्षर शब्दों का होना बेहतर है। एक बार जब आप एक सटीक परिभाषा पर बस जाते हैं, तो आपको बस परिभाषित लाइन को बदलना होगा wordcost अर्थ को प्रतिबिंबित लिए ।)

विचार

आगे बढ़ने का सबसे अच्छा तरीका आउटपुट का वितरण मॉडल है। एक अच्छा पहला अनुमान यह है कि सभी शब्द स्वतंत्र रूप से वितरित किए गए हैं। फिर आपको केवल सभी शब्दों की सापेक्ष आवृत्ति जानने की आवश्यकता है। यह मानना ​​उचित है कि वे जिपफ के नियम का पालन करते हैं, यह रैंक n वाला शब्द है शब्दों की सूची में शब्द है प्रायिकता 1 / ( n log N ) है जहाँ N शब्दकोष में शब्दों की संख्या है।

एक बार जब आप मॉडल तय कर लेते हैं, तो आप रिक्त स्थान की स्थिति का पता लगाने के लिए गतिशील प्रोग्रामिंग का उपयोग कर सकते हैं। सबसे अधिक संभावित वाक्य वह है जो प्रत्येक व्यक्तिगत शब्द की संभाव्यता के उत्पाद को अधिकतम करता है, और इसे गतिशील प्रोग्रामिंग के साथ गणना करना आसान है। प्रायिकता का उपयोग करने के बजाय हम ओवरफ्लो से बचने के लिए प्रायिकता के विलोम के रूप में परिभाषित लागत का उपयोग करते हैं।

कोड

from math import log

# Build a cost dictionary, assuming Zipf's law and cost = -math.log(probability).
words = open("words-by-frequency.txt").read().split()
wordcost = dict((k, log((i+1)*log(len(words)))) for i,k in enumerate(words))
maxword = max(len(x) for x in words)

def infer_spaces(s):
    """Uses dynamic programming to infer the location of spaces in a string
    without spaces."""

    # Find the best match for the i first characters, assuming cost has
    # been built for the i-1 first characters.
    # Returns a pair (match_cost, match_length).
    def best_match(i):
        candidates = enumerate(reversed(cost[max(0, i-maxword):i]))
        return min((c + wordcost.get(s[i-k-1:i], 9e999), k+1) for k,c in candidates)

    # Build the cost array.
    cost = [0]
    for i in range(1,len(s)+1):
        c,k = best_match(i)
        cost.append(c)

    # Backtrack to recover the minimal-cost string.
    out = []
    i = len(s)
    while i>0:
        c,k = best_match(i)
        assert c == cost[i]
        out.append(s[i-k:i])
        i -= k

    return " ".join(reversed(out))

जिसका आप उपयोग कर सकते हैं

s = 'thumbgreenappleactiveassignmentweeklymetaphor'
print(infer_spaces(s))

परिणाम

मैं इस त्वरित और गंदे 125k- शब्द शब्दकोश का उपयोग कर रहा हूं जिसे मैंने एक साथ रखा है जिसे विकिपीडिया के एक छोटे उपसमूह से एक ।

से पहले: थंबग्रीनैप्लेक्टीवीसाइनमेंटवीकलीमेटाफोर।
के बाद: अंगूठे हरे सेब सक्रिय असाइनमेंट साप्ताहिक रूपक।

इससे पहले: thereismassesoftextinformationofpeoplescommentswhichisparsedfromhtmlbuttherearen odelimitedcharactersinthemforexamplethumbgreenappleactiveassignmentweeklymetmetho rapparentlytherearethumbumbreenlelecinestringthroblining मुखरता से पुनर्प्राप्त करना

के बाद: लोगों की टिप्पणियों की टेक्स्ट जानकारी का एक समूह है जो html से पार्स किया गया है, लेकिन उनमें कोई सीमांकित अक्षर नहीं हैं उदाहरण के लिए अंगूठे हरे सेब सक्रिय असाइनमेंट साप्ताहिक रूपक स्पष्ट रूप से अंगूठे हरे सेब आदि हैं स्ट्रिंग में भी एक बड़ा शब्दकोश है क्वेरी कि क्या यह शब्द उचित है, इसलिए निष्कर्षण का सबसे तेज़ तरीका क्या है।

इससे पहले: itwasadarkandstormynthertherainfellintorrentsexceptatoccasionalintervalswhenitwascheckedbyaviolentgustofwindwhichsweptupthestreetsforitisinlondhatourourscelerrattlinglattrentlingongonghhhhhhhhhhhhThttpenthttiglivefankerhttl

इसके बाद: यह एक अंधेरी और तूफानी रात थी, बारिश सामयिक अंतरालों को छोड़कर मूसलधार बारिश में गिर गई, जब इसे हवा के एक हिंसक झोंके ने चेक किया, जो इसके लिए सड़कों पर बह गया, लंदन में है कि हमारा दृश्य घर के अंदर झुनझुना और भयंकर रूप से उत्तेजित होता है अँधेरे से जूझते हुए चिरागों की भीषण ज्वाला।

जैसा कि आप देख सकते हैं कि यह अनिवार्य रूप से निर्दोष है। सबसे महत्वपूर्ण हिस्सा यह सुनिश्चित करना है कि आपकी शब्द सूची को एक कॉर्पस के समान प्रशिक्षित किया गया था कि आप वास्तव में क्या करेंगे, अन्यथा परिणाम बहुत खराब होंगे।


अनुकूलन

कार्यान्वयन समय और स्मृति की एक रैखिक राशि का उपभोग करता है, इसलिए यह उचित रूप से कुशल है। यदि आपको और अधिक स्पीडअप की आवश्यकता है, तो आप उम्मीदवारों के सेट के आकार को कम करने के लिए शब्द सूची से एक प्रत्यय का पेड़ बना सकते हैं।

यदि आपको एक बहुत बड़ी लगातार स्ट्रिंग को संसाधित करने की आवश्यकता है तो अत्यधिक मेमोरी उपयोग से बचने के लिए स्ट्रिंग को विभाजित करना उचित होगा। उदाहरण के लिए, आप 10000 वर्णों के ब्लॉक में पाठ को संसाधित कर सकते हैं और सीमा प्रभावों से बचने के लिए दोनों ओर 1000 वर्णों के मार्जिन को जोड़ सकते हैं। यह स्मृति उपयोग को न्यूनतम रखेगा और गुणवत्ता पर लगभग निश्चित रूप से कोई प्रभाव नहीं पड़ेगा।


1
दो लाइनों के पाठ के बारे में क्या?
पत्ती

11
इस कोड ने मुझे सुन्न कर दिया है। मुझे कुछ समझ नहीं आया। मैं लॉग इन चीजों को नहीं समझता। लेकिन मैंने इस कोड का परीक्षण अपने कंप्यूटर पर किया। आप एक प्रतिभाशाली हैं।
आदित्य सिंह

1
इस एल्गोरिथ्म का रन टाइम क्या है? आप अहोकारसिक का उपयोग क्यों नहीं करते हैं?
रेट्रोकोड

8
यह उत्कृष्ट है। मैंने इसे एक पाइप पैकेज में बदल दिया है: pypi.python.org/pypi/wordninja pip install wordninja
keredson

2
@wittrup में आपके words.txt"COMP": `` $ grep "^ COMP $" words.txt COMP `` `शामिल हैं और यह वर्णानुक्रम में क्रमबद्ध है। यह कोड मानता है कि यह उपस्थिति की घटती आवृत्ति में क्रमबद्ध है (जो इस तरह n-ग्राम सूचियों के लिए आम है)। यदि आप ठीक से सॉर्ट की गई सूची का उपयोग करते हैं, तो आपकी स्ट्रिंग ठीक निकलती है: `` >>> wordninja.split ('namethecompanywherebonniewasemployedwhenwestarteddating') ['नाम', '', 'कंपनी', 'जहाँ', 'बोनी', ' था ',' नियोजित ',' जब ',' हम ',' शुरू ',' डेटिंग '] `` ``
केरेडसन

50

शीर्ष उत्तर में उत्कृष्ट कार्य के आधार पर , मैंने pipआसान उपयोग के लिए एक पैकेज बनाया है ।

>>> import wordninja
>>> wordninja.split('derekanderson')
['derek', 'anderson']

स्थापित करने के लिए, भागो pip install wordninja

केवल अंतर मामूली हैं। यह एक के listबजाय एक रिटर्न देता है str, यह इसमें काम करता है python3, इसमें शब्द सूची शामिल है और गैर-अल्फा वर्ण (जैसे अंडरस्कोर, डैश, आदि) होने पर भी ठीक से विभाजित होती है।

जेनेरिक ह्यूमन को फिर से धन्यवाद!

https://github.com/keredson/wordninja


2
इसे बनाने के लिए धन्यवाद।
मोहित भाटिया

1
धन्यवाद! मैं आप इसे एक पैकेज बनाया प्यार करता हूँ। अंतर्निहित विधि मेरे लिए बहुत अच्छी तरह से काम नहीं करती थी। उदाहरण के लिए "लाउंजर्स" को "लाउंज" और "आरएस" में विभाजित किया गया था
हैरी एम

@keredson - सबसे पहले, समाधान के लिए धन्यवाद। यह अच्छा व्यवहार करता है। हालांकि, यह "-" आदि जैसे विशेष वर्णों को स्ट्रिप्स करता है। यह कभी-कभी उचित विभाजन नहीं देता है जैसे कि एक लंबी स्ट्रिंग का कहना है - "वेदरिंगप्रोपरेटी मैमेट ट्रेड नेम ग्राफ 2-1। रंग परिवर्तन, ई, एरिजोना, फ्लोरिडा, साइक्लोल® / के बाद। Geloy® Resin Systems PVC की तुलना में। [१५] २५ २० १५ 10E १० ५ ० PVC, व्हाइट PVC, ब्राउन C / G, ब्राउनसी / G। Capstock वह सामग्री है जिसका उपयोग किसी प्रोफाइल की बाहरी सतह पर लागू सतह परत के रूप में किया जाता है। बाहर निकालना। एक Cycolac® सब्सट्रेट पर जेलॉय® राल कैपस्टॉक बकाया अपंगता प्रदान करता है। [25] "
राकेश लैम्प स्टैक

क्या आप GH में कोई समस्या खोल सकते हैं?
कीरडसन

1
अच्छा काम, प्रयास के लिए धन्यवाद। इसने मुझे वास्तव में बहुत समय बचाया।
Jan Zeiseweis

17

यहाँ पुनरावर्ती खोज का उपयोग कर समाधान है:

def find_words(instring, prefix = '', words = None):
    if not instring:
        return []
    if words is None:
        words = set()
        with open('/usr/share/dict/words') as f:
            for line in f:
                words.add(line.strip())
    if (not prefix) and (instring in words):
        return [instring]
    prefix, suffix = prefix + instring[0], instring[1:]
    solutions = []
    # Case 1: prefix in solution
    if prefix in words:
        try:
            solutions.append([prefix] + find_words(suffix, '', words))
        except ValueError:
            pass
    # Case 2: prefix not in solution
    try:
        solutions.append(find_words(suffix, prefix, words))
    except ValueError:
        pass
    if solutions:
        return sorted(solutions,
                      key = lambda solution: [len(word) for word in solution],
                      reverse = True)[0]
    else:
        raise ValueError('no solution')

print(find_words('tableapplechairtablecupboard'))
print(find_words('tableprechaun', words = set(['tab', 'table', 'leprechaun'])))

पैदावार

['table', 'apple', 'chair', 'table', 'cupboard']
['tab', 'leprechaun']

"बॉक्स से बाहर" काम करता है, धन्यवाद! मुझे लगता है कि तिकड़ी संरचना का भी उपयोग करना चाहिए क्योंकि मिकू ने कहा, न कि सभी शब्दों का सेट। फिर भी धन्यवाद!
सर्जेई

11

एक त्रिकोणीय डेटा संरचना का उपयोग करना , जो संभव शब्दों की सूची रखता है, यह निम्नलिखित करने के लिए बहुत जटिल नहीं होगा:

  1. एडवांस पॉइंटर (कॉन्टेनेटेड स्ट्रिंग में)
  2. लुकअप और तिकड़ी में इसी नोड को स्टोर करें
  3. यदि त्रिक नोड में बच्चे हैं (जैसे कि लंबे शब्द हैं), तो 1 पर जाएं।
  4. अगर नोड तक कोई बच्चे नहीं हैं, तो सबसे लंबा शब्द मिलान हुआ; परिणाम सूची में शब्द जोड़ें (नोड या ट्राइ ट्रावेल के दौरान संक्षिप्त) में, पॉइंटर को रीसेट करें (या संदर्भ रीसेट करें), और शुरू करें

3
यदि लक्ष्य पूरे स्ट्रिंग का उपभोग करना है, "tableprechaun"तो आपको पीछे हटना होगा, फिर बाद में विभाजित करना होगा "tab"
डैनियल फिशर

इसके अलावा तीनों का उल्लेख करने के लिए, लेकिन मैं डैनियल के साथ भी सहमत हूं, कि पीछे हटने की जरूरत है।
सर्जेई

@ डैनियल, सबसे लंबे समय तक मैच खोज को पीछे हटने की आवश्यकता नहीं है, नहीं। आप ऐसा क्यों सोचते हैं? और ऊपर के एल्गोरिथ्म में क्या गलत है?
डेविन जीनपिएरे

1
@ ड्विन तथ्य यह है कि "tableprechaun"शुरुआत से सबसे लंबे समय तक मैच के लिए "table", छोड़ना है "prechaun", जिसे शब्दकोश शब्दों में विभाजित नहीं किया जा सकता है। इसलिए आपको अपने साथ "tab"छोड़ते हुए छोटा मैच लेना होगा "leprechaun"
डैनियल फिशर

@ डैनियल, सॉरी, हाँ। मैंने समस्या को गलत समझा। सही किए गए एल्गोरिदम को एक ही बार में सभी संभावित पेड़ों की स्थिति का ट्रैक रखना चाहिए - AKA रैखिक-समय NFA खोज। या फिर पीछे, यकीन है, लेकिन यह सबसे खराब मामला है।
डेविन जीनपिएरे

9

Unutbu का समाधान काफी करीब था लेकिन मुझे कोड को पढ़ना मुश्किल लगता है, और इससे अपेक्षित परिणाम नहीं मिला। जेनेरिक ह्यूमन के समाधान में यह दोष है कि उसे शब्द आवृत्तियों की आवश्यकता होती है। सभी उपयोग के मामले के लिए उपयुक्त नहीं है।

यहाँ डिवाइड और कॉन्कर एल्गोरिथ्म का उपयोग करके एक सरल समाधान है

  1. यह करने की कोशिश करता शब्दों की संख्या को कम उदाहरण find_words('cupboard')वापस आ जाएगी ['cupboard']बजाय ['cup', 'board'](यह सोचते हैं कि cupboard, cupऔर boarddictionnary में हैं)
  2. इष्टतम समाधान अद्वितीय नहीं है , नीचे दिया गया कार्यान्वयन एक समाधान देता हैfind_words('charactersin')लौट सकता है ['characters', 'in']या शायद यह वापस आ जाएगा ['character', 'sin'](जैसा कि नीचे देखा गया है)। आप सभी इष्टतम समाधानों को वापस करने के लिए एल्गोरिदम को काफी आसानी से संशोधित कर सकते हैं।
  3. इस कार्यान्वयन में समाधानों को याद किया जाता है ताकि यह उचित समय में चले

कोड:

words = set()
with open('/usr/share/dict/words') as f:
    for line in f:
        words.add(line.strip())

solutions = {}
def find_words(instring):
    # First check if instring is in the dictionnary
    if instring in words:
        return [instring]
    # No... But maybe it's a result we already computed
    if instring in solutions:
        return solutions[instring]
    # Nope. Try to split the string at all position to recursively search for results
    best_solution = None
    for i in range(1, len(instring) - 1):
        part1 = find_words(instring[:i])
        part2 = find_words(instring[i:])
        # Both parts MUST have a solution
        if part1 is None or part2 is None:
            continue
        solution = part1 + part2
        # Is the solution found "better" than the previous one?
        if best_solution is None or len(solution) < len(best_solution):
            best_solution = solution
    # Remember (memoize) this solution to avoid having to recompute it
    solutions[instring] = best_solution
    return best_solution

यह मेरे 3GHz मशीन पर लगभग 5sec लेगा:

result = find_words("thereismassesoftextinformationofpeoplescommentswhichisparsedfromhtmlbuttherearenodelimitedcharactersinthemforexamplethumbgreenappleactiveassignmentweeklymetaphorapparentlytherearethumbgreenappleetcinthestringialsohavealargedictionarytoquerywhetherthewordisreasonablesowhatsthefastestwayofextractionthxalot")
assert(result is not None)
print ' '.join(result)

लोगों की टिप्पणियों की पाठ्य सूचनाओं की रीस जनता, जिसे html से पार्स किया गया है, लेकिन कोई सीमांकित वर्ण पाप नहीं हैं, उदाहरण के लिए अंगूठे हरे सेब सक्रिय असाइनमेंट साप्ताहिक रूपक जाहिर है कि अंगूठे में हरे रंग के सेब आदि हैं, मेरे पास क्वेरी के लिए एक बड़ा शब्दकोश है या नहीं यह शब्द वाजिब है इसलिए निष्कर्षण का सबसे तेज़ तरीका है thxa लॉट


यह मानने का कोई कारण नहीं है कि कोई पाठ एक अक्षर वाले शब्द में समाप्त नहीं हो सकता है। आपको एक विभाजन पर अधिक विचार करना चाहिए।
पांडा -34

7

Https://stackoverflow.com/users/1515832/generic-human द्वारा उत्तर महान है। लेकिन इसके सबसे अच्छे क्रियान्वयन को मैंने कभी अपनी पुस्तक 'ब्यूटीफुल डेटा' में खुद पीटर नोरविग को लिखा था।

इससे पहले कि मैं उसका कोड पेस्ट करूं, मुझे इस पर विस्तार करना चाहिए कि नॉरविग का तरीका अधिक सटीक क्यों है (हालांकि कोड के संदर्भ में थोड़ा धीमा और लंबा)।

1) डेटा थोड़ा बेहतर है - आकार के संदर्भ में और सटीकता के संदर्भ में (वह एक साधारण रैंकिंग के बजाय एक शब्द गणना का उपयोग करता है) 2) अधिक महत्वपूर्ण बात, यह n- ग्राम के पीछे तर्क है जो वास्तव में दृष्टिकोण को इतना सटीक बनाता है ।

अपनी पुस्तक में उन्होंने जो उदाहरण दिया है, वह एक स्ट्रिंग 'सिटडाउन' को विभाजित करने की समस्या है। अब स्ट्रिंग विभाजन की एक गैर-बिग्राम विधि पी () सिट ’) * पी (-डाउन’) पर विचार करेगी, और यदि यह पी (down सिटडाउन ’) से कम है - जो कि अक्सर होगा - तो यह विभाजित नहीं होगा यह है, लेकिन हम इसे (ज्यादातर समय) चाहते हैं।

हालाँकि जब आपके पास बिग्राम मॉडल होता है तो आप p ('बैठ जाओ') को एक bigram बनाम p ('सिटडाउन') और पूर्व जीत के रूप में मान सकते हैं। मूल रूप से, यदि आप bigrams का उपयोग नहीं करते हैं, तो यह उन शब्दों की संभावना का इलाज करता है जिन्हें आप स्वतंत्र रूप से विभाजित कर रहे हैं, जो कि मामला नहीं है, कुछ शब्द एक के बाद एक दिखाई देने की अधिक संभावना है। दुर्भाग्य से वे शब्द भी हैं जो अक्सर कई उदाहरणों में एक साथ अटक जाते हैं और अलग हो जाते हैं।

यहां डेटा का लिंक है (यह 3 अलग-अलग समस्याओं के डेटा है और विभाजन केवल एक है। कृपया विवरण के लिए अध्याय पढ़ें): http://norvig.com/ngrams/

और यहाँ कोड का लिंक है: http://norvig.com/ngrams/ngrams.py

इन लिंक को कुछ समय हो गया है, लेकिन मैं कोड के विभाजन भाग को वैसे भी कॉपी करूँगा

import re, string, random, glob, operator, heapq
from collections import defaultdict
from math import log10

def memo(f):
    "Memoize function f."
    table = {}
    def fmemo(*args):
        if args not in table:
            table[args] = f(*args)
        return table[args]
    fmemo.memo = table
    return fmemo

def test(verbose=None):
    """Run some tests, taken from the chapter.
    Since the hillclimbing algorithm is randomized, some tests may fail."""
    import doctest
    print 'Running tests...'
    doctest.testfile('ngrams-test.txt', verbose=verbose)

################ Word Segmentation (p. 223)

@memo
def segment(text):
    "Return a list of words that is the best segmentation of text."
    if not text: return []
    candidates = ([first]+segment(rem) for first,rem in splits(text))
    return max(candidates, key=Pwords)

def splits(text, L=20):
    "Return a list of all possible (first, rem) pairs, len(first)<=L."
    return [(text[:i+1], text[i+1:]) 
            for i in range(min(len(text), L))]

def Pwords(words): 
    "The Naive Bayes probability of a sequence of words."
    return product(Pw(w) for w in words)

#### Support functions (p. 224)

def product(nums):
    "Return the product of a sequence of numbers."
    return reduce(operator.mul, nums, 1)

class Pdist(dict):
    "A probability distribution estimated from counts in datafile."
    def __init__(self, data=[], N=None, missingfn=None):
        for key,count in data:
            self[key] = self.get(key, 0) + int(count)
        self.N = float(N or sum(self.itervalues()))
        self.missingfn = missingfn or (lambda k, N: 1./N)
    def __call__(self, key): 
        if key in self: return self[key]/self.N  
        else: return self.missingfn(key, self.N)

def datafile(name, sep='\t'):
    "Read key,value pairs from file."
    for line in file(name):
        yield line.split(sep)

def avoid_long_words(key, N):
    "Estimate the probability of an unknown word."
    return 10./(N * 10**len(key))

N = 1024908267229 ## Number of tokens

Pw  = Pdist(datafile('count_1w.txt'), N, avoid_long_words)

#### segment2: second version, with bigram counts, (p. 226-227)

def cPw(word, prev):
    "Conditional probability of word, given previous word."
    try:
        return P2w[prev + ' ' + word]/float(Pw[prev])
    except KeyError:
        return Pw(word)

P2w = Pdist(datafile('count_2w.txt'), N)

@memo 
def segment2(text, prev='<S>'): 
    "Return (log P(words), words), where words is the best segmentation." 
    if not text: return 0.0, [] 
    candidates = [combine(log10(cPw(first, prev)), first, segment2(rem, first)) 
                  for first,rem in splits(text)] 
    return max(candidates) 

def combine(Pfirst, first, (Prem, rem)): 
    "Combine first and rem results into one (probability, words) pair." 
    return Pfirst+Prem, [first]+rem 

यह अच्छी तरह से काम करता है, लेकिन जब मैं अपने संपूर्ण डेटासेट पर इसे लागू करने की कोशिश करता हूं, तो यह कहता हैRuntimeError: maximum recursion depth exceeded in cmp
हैरी एम

ngrams निश्चित रूप से आपको एक सटीकता को बढ़ावा देंगे w / एक घातीय रूप से बड़ा आवृत्ति तानाशाही, स्मृति और गणना उपयोग। btw ज्ञापन समारोह वहाँ एक छलनी की तरह स्मृति लीक है। कॉल के बीच इसे साफ करना चाहिए।
केर्डसन

3

यहां जावास्क्रिप्ट में अनुवादित उत्तर स्वीकृत है ( https://github.com/keredson/wordninja से फाइल "wordninja_words.txt" की आवश्यकता है ):

var fs = require("fs");

var splitRegex = new RegExp("[^a-zA-Z0-9']+", "g");
var maxWordLen = 0;
var wordCost = {};

fs.readFile("./wordninja_words.txt", 'utf8', function(err, data) {
    if (err) {
        throw err;
    }
    var words = data.split('\n');
    words.forEach(function(word, index) {
        wordCost[word] = Math.log((index + 1) * Math.log(words.length));
    })
    words.forEach(function(word) {
        if (word.length > maxWordLen)
            maxWordLen = word.length;
    });
    console.log(maxWordLen)
    splitRegex = new RegExp("[^a-zA-Z0-9']+", "g");
    console.log(split(process.argv[2]));
});


function split(s) {
    var list = [];
    s.split(splitRegex).forEach(function(sub) {
        _split(sub).forEach(function(word) {
            list.push(word);
        })
    })
    return list;
}
module.exports = split;


function _split(s) {
    var cost = [0];

    function best_match(i) {
        var candidates = cost.slice(Math.max(0, i - maxWordLen), i).reverse();
        var minPair = [Number.MAX_SAFE_INTEGER, 0];
        candidates.forEach(function(c, k) {
            if (wordCost[s.substring(i - k - 1, i).toLowerCase()]) {
                var ccost = c + wordCost[s.substring(i - k - 1, i).toLowerCase()];
            } else {
                var ccost = Number.MAX_SAFE_INTEGER;
            }
            if (ccost < minPair[0]) {
                minPair = [ccost, k + 1];
            }
        })
        return minPair;
    }

    for (var i = 1; i < s.length + 1; i++) {
        cost.push(best_match(i)[0]);
    }

    var out = [];
    i = s.length;
    while (i > 0) {
        var c = best_match(i)[0];
        var k = best_match(i)[1];
        if (c == cost[i])
            console.log("Alert: " + c);

        var newToken = true;
        if (s.slice(i - k, i) != "'") {
            if (out.length > 0) {
                if (out[-1] == "'s" || (Number.isInteger(s[i - 1]) && Number.isInteger(out[-1][0]))) {
                    out[-1] = s.slice(i - k, i) + out[-1];
                    newToken = false;
                }
            }
        }

        if (newToken) {
            out.push(s.slice(i - k, i))
        }

        i -= k

    }
    return out.reverse();
}

2

यदि आप शब्दसूची को डीएफए (जो बहुत धीमी गति से होगा) में दबा देते हैं , तो किसी इनपुट से मेल खाने में लगने वाला समय स्ट्रिंग की लंबाई के लिए आनुपातिक होगा (वास्तव में, स्ट्रिंग के ऊपर घूमने की तुलना में केवल थोड़ा धीमा)।

यह प्रभावी रूप से ट्राइ एल्गोरिथ्म का अधिक सामान्य संस्करण है जो पहले उल्लेख किया गया था। मैं केवल पूर्ण के लिए इसका उल्लेख करता हूं - अभी तक, कोई डीएफए कार्यान्वयन नहीं है जिसका आप उपयोग कर सकते हैं। RE2 काम करेगा, लेकिन मुझे नहीं पता कि पायथन बाइंडिंग ने आपको धुन दी कि आप एक DFA को कितना बड़ा होने देंगे, इससे पहले कि वह संकलित DFA डेटा को फेंक देता है और NFA खोज करता है।


विशेष रूप से प्लस 2 के लिए, इससे पहले इसका इस्तेमाल नहीं किया था
सर्गेई

0

ऐसा लगता है कि काफी सांसारिक पीछे कर देगा। स्ट्रिंग की शुरुआत पर शुरू करें। एक शब्द होने तक सही स्कैन करें। फिर, बाकी स्ट्रिंग पर फ़ंक्शन को कॉल करें। फ़ंक्शन "झूठे" देता है अगर यह एक शब्द को पहचानने के बिना दाईं ओर सभी तरह से स्कैन करता है। अन्यथा, पाया गया शब्द वापस कर देता है और पुनरावर्ती कॉल के द्वारा लौटाए गए शब्दों की सूची।

उदाहरण: "झांकी"। "टैब" ढूँढता है, फिर "लीप", लेकिन "ple" में कोई शब्द नहीं। "छलांग" में कोई दूसरा शब्द नहीं। "टेबल" ढूँढता है, फिर "ऐप"। "ले" एक शब्द नहीं है, इसलिए सेब की कोशिश करता है, पहचानता है, रिटर्न करता है।

सबसे लंबे समय तक संभव प्राप्त करने के लिए, चलते रहें, केवल समाधान के बजाय उत्सर्जित (लौटने के बजाय) करें; फिर, आपके द्वारा चुने गए किसी भी मानदंड (अधिकतम, न्यूनतम, औसत, आदि) द्वारा इष्टतम को चुनें।


अच्छा एल्गोरिथ्म, इसके बारे में सोच रहा था। unutbu ने कोड भी लिखा था।
सर्जेई

@Sergey, backtracking खोज एक घातीय-समय एल्गोरिथ्म है। इसके बारे में "अच्छा" क्या है?
डेविन जीनपिएरे

1
यह सिर्फ आसान है, यह कहना है कि यह तेज नहीं है
सर्गेई

0

Unutbu के समाधान के आधार पर मैंने जावा संस्करण लागू किया है:

private static List<String> splitWordWithoutSpaces(String instring, String suffix) {
    if(isAWord(instring)) {
        if(suffix.length() > 0) {
            List<String> rest = splitWordWithoutSpaces(suffix, "");
            if(rest.size() > 0) {
                List<String> solutions = new LinkedList<>();
                solutions.add(instring);
                solutions.addAll(rest);
                return solutions;
            }
        } else {
            List<String> solutions = new LinkedList<>();
            solutions.add(instring);
            return solutions;
        }

    }
    if(instring.length() > 1) {
        String newString = instring.substring(0, instring.length()-1);
        suffix = instring.charAt(instring.length()-1) + suffix;
        List<String> rest = splitWordWithoutSpaces(newString, suffix);
        return rest;
    }
    return Collections.EMPTY_LIST;
}

इनपुट: "tableapplechairtablecupboard"

आउटपुट: [table, apple, chair, table, cupboard]

इनपुट: "tableprechaun"

आउटपुट: [tab, leprechaun]



0

एक का उपयोग करने के लिए @ miku के सुझाव पर विस्तार करते हुए Trie, एक परिशिष्ट केवल Trieइसे लागू करने के लिए अपेक्षाकृत सीधे-आगे है python:

class Node:
    def __init__(self, is_word=False):
        self.children = {}
        self.is_word = is_word

class TrieDictionary:
    def __init__(self, words=tuple()):
        self.root = Node()
        for word in words:
            self.add(word)

    def add(self, word):
        node = self.root
        for c in word:
            node = node.children.setdefault(c, Node())
        node.is_word = True

    def lookup(self, word, from_node=None):
        node = self.root if from_node is None else from_node
        for c in word:
            try:
                node = node.children[c]
            except KeyError:
                return None

        return node

फिर हम Trieशब्दों के एक सेट से एक शब्दकोश का निर्माण कर सकते हैं :

dictionary = {"a", "pea", "nut", "peanut", "but", "butt", "butte", "butter"}
trie_dictionary = TrieDictionary(words=dictionary)

जो एक पेड़ का उत्पादन करेगा जो इस तरह दिखता है ( *एक शब्द की शुरुआत या अंत इंगित करता है):

* -> a*
 \\\ 
  \\\-> p -> e -> a*
   \\              \-> n -> u -> t*
    \\
     \\-> b -> u -> t*
      \\             \-> t*
       \\                 \-> e*
        \\                     \-> r*
         \
          \-> n -> u -> t*

हम इसे शब्दों के चयन के बारे में अनुमान के साथ जोड़कर एक समाधान में शामिल कर सकते हैं। उदाहरण के लिए हम छोटे शब्दों पर लंबे समय तक शब्द पसंद कर सकते हैं:

def using_trie_longest_word_heuristic(s):
    node = None
    possible_indexes = []

    # O(1) short-circuit if whole string is a word, doesn't go against longest-word wins
    if s in dictionary:
        return [ s ]

    for i in range(len(s)):
        # traverse the trie, char-wise to determine intermediate words
        node = trie_dictionary.lookup(s[i], from_node=node)

        # no more words start this way
        if node is None:
            # iterate words we have encountered from biggest to smallest
            for possible in possible_indexes[::-1]:
                # recurse to attempt to solve the remaining sub-string
                end_of_phrase = using_trie_longest_word_heuristic(s[possible+1:])

                # if we have a solution, return this word + our solution
                if end_of_phrase:
                    return [ s[:possible+1] ] + end_of_phrase

            # unsolvable
            break

        # if this is a leaf, append the index to the possible words list
        elif node.is_word:
            possible_indexes.append(i)

    # empty string OR unsolvable case 
    return []

हम इस फ़ंक्शन का उपयोग इस तरह कर सकते हैं:

>>> using_trie_longest_word_heuristic("peanutbutter")
[ "peanut", "butter" ]

क्योंकि हम में हमारी स्थिति को बनाए रखने Trieहम अब और लंबे समय तक शब्द के लिए खोज के रूप में, हम पार trieसंभव समाधान प्रति एक बार (बजाय ज्यादा से ज्यादा 2के लिए समय peanut: pea, peanut)। अंतिम शॉर्ट-सर्किट हमें सबसे खराब स्थिति में स्ट्रिंग के माध्यम से चार-वार चलने से बचाता है।

अंतिम परिणाम निरीक्षण का एक मुट्ठी भर है:

'peanutbutter' - not a word, go charwise
'p' - in trie, use this node
'e' - in trie, use this node
'a' - in trie and edge, store potential word and use this node
'n' - in trie, use this node
'u' - in trie, use this node
't' - in trie and edge, store potential word and use this node
'b' - not in trie from `peanut` vector
'butter' - remainder of longest is a word

इस समाधान का एक लाभ इस तथ्य में है कि आप बहुत जल्दी जानते हैं यदि लंबे समय तक किसी दिए गए उपसर्ग के साथ शब्द मौजूद हैं, जो एक शब्दकोश के खिलाफ अनुक्रम अनुक्रमों को पूरी तरह से जांचने की आवश्यकता है। यह unsolvableअन्य कार्यान्वयन के लिए तुलनात्मक रूप से सस्ते होने का जवाब भी देता है ।

इस समाधान के डाउनसाइड्स एक बड़ी मेमोरी फुटप्रिंट हैं trieऔर trieअप-फ्रंट के निर्माण की लागत ।


0

यदि आपके पास स्ट्रिंग के भीतर निहित शब्दों की एक विस्तृत सूची है:

word_list = ["table", "apple", "chair", "cupboard"]

शब्द का पता लगाने के लिए सूची पर पुनरावृति करने के लिए सूची समझ का उपयोग करना और यह कितनी बार प्रकट होता है।

string = "tableapplechairtablecupboard"

def split_string(string, word_list):

    return ("".join([(item + " ")*string.count(item.lower()) for item in word_list if item.lower() in string])).strip()

फ़ंक्शन stringसूची के क्रम में शब्दों का एक आउटपुट देता हैtable table apple chair cupboard


0

Https://github.com/keredson/wordninja/ में मदद के लिए बहुत धन्यवाद

मेरी तरफ से जावा में उसी का एक छोटा सा योगदान।

सार्वजनिक विधि splitContiguousWordsको कक्षा में अन्य 2 विधियों के साथ एम्बेड किया जा सकता है जिनके पास निंजा_words.txt एक ही निर्देशिका में है (या कोडर की पसंद के अनुसार संशोधित किया गया है)। और विधि splitContiguousWordsका उपयोग उद्देश्य के लिए किया जा सकता है।

public List<String> splitContiguousWords(String sentence) {

    String splitRegex = "[^a-zA-Z0-9']+";
    Map<String, Number> wordCost = new HashMap<>();
    List<String> dictionaryWords = IOUtils.linesFromFile("ninja_words.txt", StandardCharsets.UTF_8.name());
    double naturalLogDictionaryWordsCount = Math.log(dictionaryWords.size());
    long wordIdx = 0;
    for (String word : dictionaryWords) {
        wordCost.put(word, Math.log(++wordIdx * naturalLogDictionaryWordsCount));
    }
    int maxWordLength = Collections.max(dictionaryWords, Comparator.comparing(String::length)).length();
    List<String> splitWords = new ArrayList<>();
    for (String partSentence : sentence.split(splitRegex)) {
        splitWords.add(split(partSentence, wordCost, maxWordLength));
    }
    log.info("Split word for the sentence: {}", splitWords);
    return splitWords;
}

private String split(String partSentence, Map<String, Number> wordCost, int maxWordLength) {
    List<Pair<Number, Number>> cost = new ArrayList<>();
    cost.add(new Pair<>(Integer.valueOf(0), Integer.valueOf(0)));
    for (int index = 1; index < partSentence.length() + 1; index++) {
        cost.add(bestMatch(partSentence, cost, index, wordCost, maxWordLength));
    }
    int idx = partSentence.length();
    List<String> output = new ArrayList<>();
    while (idx > 0) {
        Pair<Number, Number> candidate = bestMatch(partSentence, cost, idx, wordCost, maxWordLength);
        Number candidateCost = candidate.getKey();
        Number candidateIndexValue = candidate.getValue();
        if (candidateCost.doubleValue() != cost.get(idx).getKey().doubleValue()) {
            throw new RuntimeException("Candidate cost unmatched; This should not be the case!");
        }
        boolean newToken = true;
        String token = partSentence.substring(idx - candidateIndexValue.intValue(), idx);
        if (token != "\'" && output.size() > 0) {
            String lastWord = output.get(output.size() - 1);
            if (lastWord.equalsIgnoreCase("\'s") ||
                    (Character.isDigit(partSentence.charAt(idx - 1)) && Character.isDigit(lastWord.charAt(0)))) {
                output.set(output.size() - 1, token + lastWord);
                newToken = false;
            }
        }
        if (newToken) {
            output.add(token);
        }
        idx -= candidateIndexValue.intValue();
    }
    return String.join(" ", Lists.reverse(output));
}


private Pair<Number, Number> bestMatch(String partSentence, List<Pair<Number, Number>> cost, int index,
                      Map<String, Number> wordCost, int maxWordLength) {
    List<Pair<Number, Number>> candidates = Lists.reverse(cost.subList(Math.max(0, index - maxWordLength), index));
    int enumerateIdx = 0;
    Pair<Number, Number> minPair = new Pair<>(Integer.MAX_VALUE, Integer.valueOf(enumerateIdx));
    for (Pair<Number, Number> pair : candidates) {
        ++enumerateIdx;
        String subsequence = partSentence.substring(index - enumerateIdx, index).toLowerCase();
        Number minCost = Integer.MAX_VALUE;
        if (wordCost.containsKey(subsequence)) {
            minCost = pair.getKey().doubleValue() + wordCost.get(subsequence).doubleValue();
        }
        if (minCost.doubleValue() < minPair.getKey().doubleValue()) {
            minPair = new Pair<>(minCost.doubleValue(), enumerateIdx);
        }
    }
    return minPair;
}

क्या होगा अगर हमारे पास शब्दों की सूची नहीं है?
शिराज़ी

यदि मैंने क्वेरी को सही ढंग से समझा है: इसलिए उपरोक्त दृष्टिकोण में, publicविधि एक प्रकार के वाक्य को स्वीकार करती है Stringजो regex के साथ पहले स्तर पर विभाजित है। और इसकी सूची ninja_wordsgit रेपो से डाउनलोड के लिए उपलब्ध है।
अर्नब दास


-1

आपको अपनी शब्दावली की पहचान करने की आवश्यकता है - शायद कोई भी मुफ्त शब्द सूची करेगा।

एक बार हो जाने पर, उस प्रत्यय के पेड़ का निर्माण करने के लिए उस शब्दावली का उपयोग करें, और उस के खिलाफ इनपुट की अपनी धारा से मिलान करें: http://en.wikipedia.org/wiki/Suffix_tree


व्यवहार में यह कैसे काम करेगा? प्रत्यय के पेड़ के निर्माण के बाद, आपको कैसे पता चलेगा कि क्या मेल खाना है?
जॉन कुरलक

@JohnKurlak किसी अन्य निर्धारक परिमित ऑटोमोटन की तरह - एक पूर्ण शब्द का अंत एक स्वीकृत स्थिति है।
मैरिन

क्या उस दृष्टिकोण को पीछे जाने की आवश्यकता नहीं है? आपने अपने उत्तर में बैकट्रैकिंग का उल्लेख नहीं किया ...
जॉन कुर्लाक

क्यों नहीं? यदि आपके पास "टेबलपरचून" है, तो नीचे बताए अनुसार क्या होता है? यह सबसे लंबे शब्द से मेल खाएगा, जो "टेबल" कर सकता है, और फिर इसे दूसरा शब्द नहीं मिलेगा। इसे "टैब" पर वापस जाना होगा और फिर "लेप्रेचुन" से मिलान करना होगा।
जॉन कुर्लाक

@JohnKurlak मल्टीपल "ब्रांच" को एक ही समय में लाइव किया जा सकता है। वास्तव में, आप प्रत्येक अक्षर के लिए एक टोकन को पेड़ में धकेलते हैं जो एक संभव शब्द है, और वही पत्र अन्य जीवित टोकन को आगे बढ़ा सकता है।
मार्सिन डे
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.