শব্দের তালিকায় ফাঁকা ছাড়াই পাঠ্যকে কীভাবে বিভক্ত করবেন?


106

ইনপুট: "tableapplechairtablecupboard..." অনেক শব্দ

এই জাতীয় পাঠ্যকে শব্দের তালিকায় বিভক্ত করতে এবং পাওয়ার জন্য একটি কার্যকর অ্যালগরিদম কী হবে:

আউটপুট: ["table", "apple", "chair", "table", ["cupboard", ["cup", "board"]], ...]

প্রথম জিনিসটি যা মনে মনে আসে তা হ'ল সমস্ত সম্ভাব্য শব্দের মধ্য দিয়ে যাওয়া (প্রথম অক্ষর দিয়ে শুরু করে) এবং দীর্ঘতম সম্ভাব্য শব্দটি সন্ধান করা, থেকে চালিয়ে যাওয়া position=word_position+len(word)

পিএস
আমাদের কাছে সমস্ত সম্ভাব্য শব্দের একটি তালিকা রয়েছে।
শব্দ "আলমারি" "কাপ" এবং "বোর্ড" হতে পারে, দীর্ঘতম নির্বাচন করুন।
ভাষা: পাইথন, তবে মূল জিনিসটি নিজেই অ্যালগরিদম।


14
আপনি কি নিশ্চিত যে স্ট্রিংটি "ট্যাব" এবং "লিপ" শব্দ দিয়ে শুরু হয় না?
রব হুড়স্কা

হ্যাঁ, মনে হচ্ছে এটি পরিষ্কারভাবে করা যায় না।
deهيলেক্স

@ রবহ্রুস্কা, সেক্ষেত্রে আমি লিখেছি, দীর্ঘতম সম্ভাব্য নির্বাচন করে।
সের্গেই

2
@ সের্গে - আপনার "দীর্ঘতম সম্ভব" মাপদণ্ডটি বোঝায় যে এটি যৌগিক শব্দের জন্য। এবং সেক্ষেত্রে স্ট্রিংটি "কার্পেট্রেল" হলে কী হবে। এটি "কার্পেট", বা "পেট্রেল" হবে?
রব হুশকা

2
: আপনার স্ট্রিং অনেক dictitonary শব্দ হয়['able', 'air', 'apple', 'boa', 'boar', 'board', 'chair', 'cup', 'cupboard', 'ha', 'hair', 'lea', 'leap', 'oar', 'tab', 'table', 'up']
reclosedev

উত্তর:


200

রিয়েল-ওয়ার্ল্ড ডেটা প্রয়োগ করার সময় একটি নির্লজ্জ অ্যালগরিদম ভাল ফলাফল দেয় না। এখানে একটি 20-লাইনের অ্যালগরিদম যা সত্য-শব্দ পাঠ্যের জন্য সঠিক ফলাফল দিতে আপেক্ষিক শব্দ ফ্রিকোয়েন্সি ব্যবহার করে explo

(আপনি যদি নিজের আসল প্রশ্নের উত্তর চান যা শব্দ ফ্রিকোয়েন্সি ব্যবহার করে না, আপনি "দীর্ঘতম শব্দ" বলতে যা বোঝায় তা অবশ্যই পরিমার্জন করতে হবে: 20-অক্ষরের শব্দ এবং দশ-অক্ষরের দশটি শব্দ ব্যবহার করা আরও ভাল বা হয় পাঁচটি দশ অক্ষরের শব্দের সাথে থাকা ভাল? আপনি যখন একটি সুনির্দিষ্ট সংজ্ঞাটি স্থির করেন, কেবলমাত্র wordcostউদ্দেশ্যে বর্ণিত অর্থটি প্রতিফলিত করার জন্য আপনাকে অবশ্যই রেখাটি পরিবর্তন করতে হবে ))

বুদ্ধিটা

এগিয়ে যাওয়ার সর্বোত্তম উপায় হ'ল আউটপুট বিতরণের মডেল । একটি ভাল প্রথম অনুমান করা সমস্ত শব্দ স্বতন্ত্রভাবে বিতরণ করা অনুমান করা হয়। তারপরে আপনাকে কেবল সমস্ত শব্দের আপেক্ষিক ফ্রিকোয়েন্সি জানতে হবে। এটি ধরে নেওয়া যুক্তিসঙ্গত যে তারা জিপফের আইন অনুসরণ করে, শব্দের তালিকায় র‌্যাঙ্ক এন সহ এই শব্দটি প্রায় 1 / ( 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-শব্দ অভিধানটি ব্যবহার করছি ।

পূর্বে: থাম্বগ্রিন অ্যাপ্লিকটিভ অ্যাসাইনমেন্ট উইক্লিমেটফোর।
এর পরে: থাম্ব সবুজ আপেল সক্রিয় অ্যাসাইনমেন্ট সাপ্তাহিক রূপক।

পূর্বে: ThereismassesoftextinificationsofpeoplescommentsWichisparsedfromhtmlbuttherearen odelimitedcharactersinthemfirexamplethumbgreenappleactiveassignmentweeklymetapho rapparentlytherearethumbgreenappleetcinthestringialialhahaalarged शब्दकोয় থ্রোস্টাইরিচেসফ্রাস্টিফেরেশনস্রোতস্রোতস্রোতস্রোতস্রোতস্রোতসংশ্লিষ্ট।

এর পরে: পিপলস মন্তব্যের টেক্সট তথ্য রয়েছে যা এইচটিএমএল থেকে বিশ্লেষণ করা হয়েছে তবে তাদের মধ্যে কোনও সীমিত অক্ষর নেই উদাহরণস্বরূপ থাম্ব সবুজ আপেল সক্রিয় নিয়োগের সাপ্তাহিক রূপক স্পষ্টতই স্ট্রিংয়ে থাম্ব গ্রিন আপেল ইত্যাদি রয়েছে আমার কাছে একটি বড় অভিধানও রয়েছে শব্দটি যুক্তিসঙ্গত কিনা তা ক্যোয়ারী করুন যাতে প্রচুর পরিমাণে এক্সট্রাকশন করার দ্রুততম উপায় কী।

পূর্বে: ইটওয়াসাদারক্যান্ডস্টোরমাইটারিথেরেনফেলিনটোরেন্টসেক্সেপটোকসেকশনালইন্টারভালস্নেথওয়াসচেকডবাইভিল্যান্টগাস্টফুইন্ড উইচস সুইপটপ স্ট্রিটস স্টোরসফাইটিসাইনলন্ডোন্টসোরস্নিলেস্লট্লিংং হাউজটোপস্ফ্যান্ডস্লাইগ্যাটিটিং স্টেজেডেজেডেচেনগেইডেজেডে।

এরপরে: এটি একটি অন্ধকার এবং ঝড়ো রাত ছিল মাঝে মধ্যে বিরতি ব্যতীত বৃষ্টি প্রবাহিত হয়েছিল যখন রাস্তাগুলি বয়ে গেছে এমন লন্ডনে যে আমাদের দৃশ্যটি বাড়ির ছাদে ছড়িয়ে-ছিটিয়ে এবং মারাত্মকভাবে আন্দোলন করছে তা লন্ডনে রয়েছে checked অন্ধকারের বিরুদ্ধে লড়াই করে এমন প্রদীপের অল্প শিখা।

আপনি দেখতে পারেন এটি মূলত ত্রুটিহীন। সর্বাধিক গুরুত্বপূর্ণ অংশটি হ'ল আপনার শব্দের তালিকাটি আপনি যা মুখোমুখি হবেন তার অনুরূপ কর্পাসকে প্রশিক্ষণ দেওয়া হয়েছিল তা নিশ্চিত করা, অন্যথায় ফলাফলগুলি খুব খারাপ হবে।


অপ্টিমাইজেশান

বাস্তবায়ন সময় এবং মেমরির একটি রৈখিক পরিমাণ গ্রাস করে, তাই এটি যুক্তিসঙ্গতভাবে দক্ষ। আপনার যদি আরও স্পিডআপের প্রয়োজন হয় তবে আপনি প্রার্থীদের সেটের আকার কমাতে শব্দ তালিকা থেকে একটি প্রত্যয় গাছ তৈরি করতে পারেন।

আপনার যদি খুব বড় ক্রমাগত স্ট্রিং প্রক্রিয়া করার প্রয়োজন হয় তবে অতিরিক্ত মেমরির ব্যবহার এড়াতে স্ট্রিংটি বিভক্ত করা যুক্তিসঙ্গত হবে। উদাহরণস্বরূপ আপনি সীমানা প্রভাব এড়ানোর জন্য 10000 অক্ষরগুলির ব্লক এবং 1000 টির একটি মার্জিনের ব্লকগুলিতে পাঠ্য প্রক্রিয়া করতে পারেন। এটি মেমরির ব্যবহারকে সর্বনিম্ন রাখবে এবং গুণমানের উপরে অবশ্যই কোনও প্রভাব ফেলবে।


1
দুই লাইনের পাঠ্য কি?
লিফিয়ে

11
এই কোডটি আমাকে অসাড় করে দিয়েছে। আমি কিছু বুঝতে পারিনি। আমি লগ জিনিস বুঝতে পারি না। তবে আমি এই কোডটি আমার কম্পিউটারে পরীক্ষা করেছি। তুমি একজন প্রতিভাবান.
আদিত্য সিং

1
এই অ্যালগরিদমের রান সময় কী? আপনি কেন অহোকরাসিক ব্যবহার করবেন না?
রেট্রো কোড

8
এটি দুর্দান্ত। আমি এটিকে একটি পাইপ প্যাকেজে পরিণত করেছি: pypi.python.org/pypi/wordninjapip install wordninja
keredson

2
@ আপনার words.txt" কমপ্লেক্স ": itt `` $ গ্রেপ "^ কমপ $" words.txt কমপ `` `ধারণ করে এবং এটি বর্ণানুক্রমিকভাবে বাছাই করা হয়েছে w এই কোডটি ধরে নিয়েছে যে এটির উপস্থিতি হ্রাসকারী ফ্রিকোয়েন্সি অনুসারে বাছাই করা হয়েছে (এটি এন-গ্রাম তালিকার পক্ষে সাধারণ)। আপনি যদি সঠিকভাবে বাছাই করা তালিকা ব্যবহার করেন তবে আপনার স্ট্রিংটি ভালভাবে বেরিয়ে আসে: `` `>>> wordninja.split ('namethecompanywherebonniewasemp কাজেরWenwest সূত্রের তারিখ') ['নাম', 'the', 'সংস্থা', 'যেখানে', 'বনি', ' ',' নিয়োগকৃত ',' কখন ',' আমরা ',' শুরু ',' ডেটিং '] ছিল `` `
কেরডসন

50

শীর্ষ উত্তরের চমৎকার কাজের ভিত্তিতে , আমি pipসহজেই ব্যবহারের জন্য একটি প্যাকেজ তৈরি করেছি ।

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

ইনস্টল করতে, চালান pip install wordninja

একমাত্র পার্থক্য নাবালিকাগুলি। এটি একটি listপরিবর্তে পরিবর্তিত strহয় python3, এটি এতে কাজ করে , এতে শব্দের তালিকা অন্তর্ভুক্ত রয়েছে এবং অ-আলফা অক্ষর (যেমন আন্ডারস্কোর, ড্যাশ ইত্যাদির মতো) থাকলেও সঠিকভাবে বিভক্ত হয়।

জেনেরিক হিউম্যানকে আবার ধন্যবাদ!

https://github.com/keredson/wordninja


2
এটি তৈরি করার জন্য ধন্যবাদ।
মোহিত ভাটিয়া

1
ধন্যবাদ! আপনি এটি একটি প্যাকেজ তৈরি করেছেন আমি পছন্দ করি। অন্তর্নিহিত পদ্ধতিটি আমার পক্ষে খুব ভাল কাজ করে নি। উদাহরণস্বরূপ "লাউঞ্জারগুলি" "লাউঞ্জ" এবং "আরএস" তে বিভক্ত হয়েছিল
হ্যারি এম

@ কোরেডসন - সবার আগে সমাধানের জন্য ধন্যবাদ। এটা ভাল আচরণ করে। তবে এটি "-" ইত্যাদির মতো বিশেষ চরিত্রগুলি সরিয়ে দেয় এটি কখনও কখনও যথাযথ বিভাজন দেয় না যেমন দীর্ঘ স্ট্রিং বলে - "ওয়েদারিংপ্রোপার্টিসমেটারিয়াল ট্রেড নেম গ্রাফ 2-1। কালার চেঞ্জ, ই, আরিজোনা, ফ্লোরিডা, সাইকোলেস / এর পরে জেলোই রজন সিস্টেমগুলি পিভিসির সাথে তুলনা করে। [15] 25 20 15 10E 10 5 0 পিভিসি, হোয়াইট পিভিসি, ব্রাউন সি / জি, ব্রাউনসি / জি। ক্যাপস্টক এমন উপাদান যা পৃষ্ঠের স্তরটি কোনও প্রোফাইলের বাহ্যিক পৃষ্ঠের জন্য প্রয়োগ করা হয় সাইকোলাক্স সাবস্ট্রেটের উপর জেলোয়ে রজন ক্যাপস্টক অসামান্য পরিমানের উপলব্ধ করে তোলে। [25] "
রাকেশ ল্যাম্প স্ট্যাক

আপনি জিএইচ একটি সমস্যা খুলতে পারেন?
কেরডসন

1
চমৎকার কাজ, চেষ্টা করার জন্য ধন্যবাদ। এটি সত্যিই আমার অনেক সময় সাশ্রয় করেছে।
জান জেইসওয়েস

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"
ড্যানিয়েল ফিশার

ট্রাই উল্লেখ করার জন্য প্লাস, তবে আমি ড্যানিয়েলের সাথেও একমত, যে ব্যাকট্র্যাকিং করা দরকার।
সের্গেই

@ ড্যানিয়েল, দীর্ঘতম ম্যাচের অনুসন্ধানের জন্য ব্যাকট্র্যাকিংয়ের দরকার নেই, না। এটা চিন্তা করছো কেন? এবং উপরের অ্যালগরিদমের সাথে কী সমস্যা?
ডেভিন জিনপিয়ের 15'12

1
@ ডেভিন প্রথম "tableprechaun"থেকেই দীর্ঘতম ম্যাচের জন্য "table"ছেড়ে যাচ্ছেন "prechaun", যা অভিধানের শব্দগুলিতে বিভক্ত হতে পারে না। তাই আপনাকে "tab"একটি রেখে ছোট খাটো ম্যাচ নিতে হবে "leprechaun"
ড্যানিয়েল ফিশার

@ ড্যানিয়েল, দুঃখিত, হ্যাঁ আমি সমস্যাটি ভুল বুঝেছি। সংশোধিত অ্যালগরিদমটি একবারে সমস্ত সম্ভাব্য গাছের অবস্থানের ট্র্যাক রাখতে হবে - একেএ রৈখিক-সময় এনএফএ অনুসন্ধান। অথবা অন্যথায় ব্যাকট্র্যাক, অবশ্যই, তবে এটি সবচেয়ে খারাপ ক্ষেত্রে ক্ষণস্থায়ী সময়।
ডেভিন জিনপিয়ের

9

আনটবুর সমাধানটি বেশ কাছাকাছি ছিল তবে আমি কোডটি পড়তে অসুবিধা পেয়েছি এবং এটি প্রত্যাশিত ফলাফল দেয় নি। জেনেরিক হিউম্যানের দ্রবণের মধ্যে একটি অপূর্ণতা রয়েছে যে এটির শব্দ ফ্রিকোয়েন্সি দরকার। সমস্ত ব্যবহারের ক্ষেত্রে উপযুক্ত নয়।

ডিভাইড এবং কনকয়ের অ্যালগরিদম ব্যবহার করে এখানে একটি সহজ সমাধান ।

  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 মেশিনে প্রায় 5 সেকেন্ড লাগবে:

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

এইচটিএমএল থেকে বিশ্লেষণ করা হয়েছে এমন মন্তব্যগুলির পাঠ্য তথ্যের রিস গণ শব্দটি যুক্তিসঙ্গত তাই কীভাবে এক্সট্রাকশনটির দ্রুততম উপায় থেক্সা লট


একটি পাঠের একক অক্ষরের শব্দের মধ্যে শেষ হতে পারে না বিশ্বাস করার কোনও কারণ নেই। আপনার একটি বিভক্তিকে আরও বিবেচনা করা উচিত।
পান্ডা-34

7

Https://stackoverflow.com/users/1515832/generic-human এর উত্তর দুর্দান্ত। তবে আমি এর সেরা প্রয়োগটি দেখেছি পিটার নরভিগ নিজেই তাঁর বই 'বিউটিফুল ডেটা' লিখেছিলেন।

আমি তার কোডটি পেস্ট করার আগে, কেন নরভিগের পদ্ধতিটি আরও নির্ভুল (যদিও কোডের দিক দিয়ে কিছুটা ধীর এবং দীর্ঘতর) তার উপরে আমাকে প্রসারিত করুন।

1) ডেটা কিছুটা ভাল - আকারের দিক থেকে এবং নির্ভুলতার ক্ষেত্রে (তিনি সাধারণ র্যাঙ্কিংয়ের পরিবর্তে একটি শব্দ গণনা ব্যবহার করেন) 2) আরও গুরুত্বপূর্ণ, এটি এন-গ্রামের পিছনে যুক্তি যা সত্যই পদ্ধতির এত সঠিক করে তোলে ।

তিনি তাঁর বইয়ে যে উদাহরণ সরবরাহ করেছেন তা হ'ল স্ট্রিং 'সিটডাউন' বিভক্ত করার সমস্যা। এখন স্ট্রিং বিভাজনের একটি অ-বিগ্রাম পদ্ধতি পি ('সিট') * পি ('ডাউন') বিবেচনা করবে এবং এটি পি ('সিটডাউন') এর চেয়ে কম হলে - যা প্রায়শই ঘটবে - এটি বিভক্ত হবে না এটি, তবে আমরা এটি (বেশিরভাগ সময়) চাই।

তবে যখন আপনার কাছে বিগ্রাম মডেল রয়েছে আপনি পি ('সিট ডাউন') কে বিগ্রাম বনাম পি ('সিটডাউন') এবং প্রাক্তন জয়ের হিসাবে মূল্য দিতে পারেন। মূলত, আপনি যদি বিগ্রামগুলি ব্যবহার না করেন, তবে এটি আপনার স্বাধীন হিসাবে বিভক্ত হওয়া শব্দের সম্ভাব্যতা বিবেচনা করে, যা কিছু নয়, কিছু শব্দ একের পর এক প্রদর্শিত হওয়ার সম্ভাবনা বেশি থাকে। দুর্ভাগ্যক্রমে সেগুলিও এমন শব্দ যা প্রায়শই একসাথে অনেকগুলি ঘটনায় আটকে থাকে এবং বিভাজনকে বিভ্রান্ত করে।

এখানে ডেটাতে লিংক (এটি 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
হ্যারি এম

এনজিআরএম অবশ্যই আপনাকে ডাব্লু / একটি তাত্পর্যপূর্ণ বৃহত্তর ফ্রিকোয়েন্সি ডেক, স্মৃতি এবং গণনার ব্যবহারের সঠিকতা দেবে। বিটিডব্লিউ মেমো ফাংশন সেখানে একটি চালনী মত মেমরি ফাঁস হয়। কলগুলির মধ্যে এটি পরিষ্কার করা উচিত।
কের্ডসন

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

আপনি যদি শব্দের তালিকাটি ডিএফএ-তে প্রম্পম্পাইল করেন (যা খুব ধীর হয়ে যাবে), তবে কোনও ইনপুট মেলতে যে সময় লাগে তা স্ট্রিংয়ের দৈর্ঘ্যের সমানুপাতিক হবে (আসলে, কেবল স্ট্রিংটির উপরে পুনরাবৃত্তি হওয়ার চেয়ে কিছুটা ধীর)।

এটি কার্যকরভাবে ট্রাই অ্যালগরিদমের আরও সাধারণ সংস্করণ যা আগে উল্লেখ করা হয়েছিল। আমি কেবল এটি সম্পূর্ণরূপে উল্লেখ করেছি - এখনও পর্যন্ত, আপনি কেবল ব্যবহার করতে পারেন এমন কোনও ডিএফএ বাস্তবায়ন নেই। আরই 2 কাজ করবে, তবে আমি জানি না পাইথন বাইন্ডিংগুলি আপনাকে কোনও ডিএফএর সংকলনকারী ডিএফএ ডেটা সরিয়ে ফেলে এনএফএ অনুসন্ধান করার আগে আপনাকে কতটা বড় হতে দেয় তা সুর দেয়।


বিশেষত প্লাস পুনর্নির্মাণের জন্য, আগে এটি ব্যবহার করেনি
সের্গেই

0

দেখে মনে হচ্ছে মোটামুটি জাগ্রত ব্যাকট্র্যাকিং করবে। স্ট্রিংয়ের ভিক্ষা শুরু করুন। আপনার কোনও শব্দ না হওয়া পর্যন্ত ডান স্ক্যান করুন। তারপরে, বাকী স্ট্রিংয়ে ফাংশনটি কল করুন। কোনও শব্দ সনাক্ত না করে যদি ডানদিকে সমস্ত দিক থেকে স্ক্যান করে তবে ফাংশন "মিথ্যা" দেয়। অন্যথায়, এটি পাওয়া শব্দটি এবং পুনরাবৃত্তির কল দ্বারা শব্দের তালিকাটি ফিরে আসে।

উদাহরণ: "টেবিল অ্যাপল"। "ট্যাব", তারপরে "লাফানো" সন্ধান করে তবে "পিলে" তে কোনও শব্দ নেই। "লিপ্পল" এর অন্য কোনও শব্দ নেই। "সারণী", তারপরে "অ্যাপ" সন্ধান করে। "লে" কোনও শব্দ নয়, তাই আপেল চেষ্টা করে, স্বীকৃতি দেয়, ফিরে আসে।

দীর্ঘতম সম্ভব পেতে, চালিয়ে যান, কেবল নির্গমন করা (ফিরে আসার চেয়ে) সঠিক সমাধানগুলি; তারপরে, আপনি যে কোনও মানদণ্ড চয়ন করেন তা বেছে নিন (ম্যাক্সম্যাক্স, মিনম্যাক্স, গড় ইত্যাদি)


ভাল অ্যালগরিদম, এটি সম্পর্কে চিন্তা ছিল। unutbu এমনকি কোড লিখেছেন।
সের্গেই

@ সের্গে, ব্যাকট্র্যাকিং অনুসন্ধান হ'ল এক্সফেনশনাল-টাইম অ্যালগরিদম। এটি সম্পর্কে "ভাল" কী?
ডেভিন জিনপিয়ের 15'12

1
এটি কেবল সহজ, এটি দ্রুত বলে না
সের্গেই

0

আনটবুর সমাধানের ভিত্তিতে আমি একটি জাভা সংস্করণ প্রয়োগ করেছি:

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

@ মিকুর একটি ব্যবহারের পরামর্শকে প্রসারিত করে 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একই ডিরেক্টরিতে (বা কোডারের পছন্দ অনুসারে সংশোধিত) ninja_words.txt থাকা শ্রেণীর অন্যান্য 2 টি পদ্ধতির সাথে এম্বেড করা যেতে পারে। এবং পদ্ধতিটি 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যা রেজেক্সের সাথে প্রথম স্তরের ভিত্তিতে বিভক্ত হয়। এবং এর তালিকার ninja_wordsজন্য গিট রেপো থেকে ডাউনলোডের জন্য উপলব্ধ।
অর্ণব দাস


-1

আপনাকে আপনার শব্দভাণ্ডার সনাক্ত করতে হবে - সম্ভবত কোনও নিখরচায় শব্দের তালিকা কাজ করবে।

একবার হয়ে গেলে, প্রত্যয় গাছ তৈরি করতে সেই শব্দভাণ্ডারটি ব্যবহার করুন এবং আপনার ইনপুটটির প্রবাহটি এর বিপরীতে মেলে: http://en.wikedia.org/wiki/Suffix_tree


এটি অনুশীলনে কীভাবে কাজ করবে? প্রত্যয় গাছ তৈরির পরে কীভাবে মিলবে তা আপনি কীভাবে জানবেন?
জন কুর্লাক

@ জোহানকুরলাক অন্য যে কোনও নির্বিচারবাদী সসীম অটোমেটনের মতো - একটি সম্পূর্ণ শব্দের সমাপ্তি একটি গ্রহণযোগ্য রাষ্ট্র।
মার্সিন

এই পদ্ধতির জন্য কি ব্যাকট্র্যাকিংয়ের প্রয়োজন নেই? আপনি নিজের উত্তরে ব্যাকট্রাকিংয়ের কথা উল্লেখ করেন নি ...
জন কুর্লাক

কেন না? আপনার নীচে উল্লিখিত "টেবিলপ্রেচান" থাকলে কী হবে? এটি সবচেয়ে দীর্ঘতম শব্দের সাথে মিলবে, এটি "টেবিল" এবং তারপরে এটি আর একটি শব্দ খুঁজে পাবে না। এটি "ট্যাব" এ ফিরে ব্যাকট্র্যাক করতে হবে এবং তারপরে "লিপ্রেচান" মিলবে।
জন কুরলাক

@ জনকুরলাক একাধিক "শাখা" একই সাথে লাইভ হতে পারে। বাস্তবে, আপনি প্রতিটি অক্ষরের জন্য গাছের মধ্যে একটি টোকেন চাপুন যা একটি সম্ভাব্য শব্দ শুরু এবং একই অক্ষরটি অন্যান্য লাইভ টোকেনকে অগ্রসর করতে পারে।
মার্সিন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.