سؤال

في وضع البحث عن موقع أنا المبنى ، قررت أن تذهب رخيصة و سريعة طريقة استخدام Microsoft Sql Server النص الكامل محرك البحث بدلا من شيء أكثر قوة مثل Lucene.Net.

واحدة من الميزات أود أن يكون ، على الرغم من جوجل سقو ذات الصلة الوثيقة قصاصات.أنا سرعان ما وجدت تحديد "ذات الصلة" قصاصات هو أكثر صعوبة مما كنت أدرك.

أريد أن اختيار مقتطفات استنادا إلى مصطلح البحث كثافة في العثور على النص.لذا, أساسا, أنا بحاجة إلى العثور على معظم مصطلح البحث مرور كثيفة في النص.حيث مرور بعض التعسفي عدد من الشخصيات (مثلا 200 -- ولكن أنها حقا لا يهم).

فكرتي الأولى هي استخدام .IndexOf() في حلقة وبناء مجموعة من مصطلح المسافات (طرح مؤشر وجدت الأجل من قبل الأجل) ، ثم ...ماذا ؟ إضافة أي اثنين, ثلاث, أربع, أي خمسة متتابعة مجموعة العناصر واستخدام واحد مع أصغر مبلغ (وبالتالي ، فإن أصغر مسافة بين مصطلحات البحث).

يبدو فوضوي.

هل هناك إنشاء أفضل أو أكثر وضوحا طريقة للقيام بذلك من خلال ما تأتي به ؟

هل كانت مفيدة؟

المحلول

وعلى الرغم من أن تنفيذه في جافا، يمكنك ان ترى نهج واحد لهذه المشكلة هنا: http://rcrezende.blogspot.com/2010/08 /smallest-relevant-text-snippet-for.html

نصائح أخرى

وأنا أعرف هذا الموضوع هو الطريق القديم، ولكن أعطى هذا الاسبوع في محاولة الماضي وكان الألم في الجانب الخلفي. هذا أبعد ما يكون عن الكمال، ولكن هذا هو ما خطرت لي.

ومولد المتكررة:

public static string SelectKeywordSnippets(string StringToSnip, string[] Keywords, int SnippetLength)
    {
        string snippedString = "";
        List<int> keywordLocations = new List<int>();

        //Get the locations of all keywords
        for (int i = 0; i < Keywords.Count(); i++)
            keywordLocations.AddRange(SharedTools.IndexOfAll(StringToSnip, Keywords[i], StringComparison.CurrentCultureIgnoreCase));

        //Sort locations
        keywordLocations.Sort();

        //Remove locations which are closer to each other than the SnippetLength
        if (keywordLocations.Count > 1)
        {
            bool found = true;
            while (found)
            {
                found = false;
                for (int i = keywordLocations.Count - 1; i > 0; i--)
                    if (keywordLocations[i] - keywordLocations[i - 1] < SnippetLength / 2)
                    {
                        keywordLocations[i - 1] = (keywordLocations[i] + keywordLocations[i - 1]) / 2;

                        keywordLocations.RemoveAt(i);

                        found = true;
                    }
            }
        }

        //Make the snippets
        if (keywordLocations.Count > 0 && keywordLocations[0] - SnippetLength / 2 > 0)
            snippedString = "... ";
        foreach (int i in keywordLocations)
        {
            int stringStart = Math.Max(0, i - SnippetLength / 2);
            int stringEnd = Math.Min(i + SnippetLength / 2, StringToSnip.Length);
            int stringLength = Math.Min(stringEnd - stringStart, StringToSnip.Length - stringStart);
            snippedString += StringToSnip.Substring(stringStart, stringLength);
            if (stringEnd < StringToSnip.Length) snippedString += " ... ";
            if (snippedString.Length > 200) break;
        }

        return snippedString;

    }

وظيفة والتي سوف تجد الرقم القياسي لجميع الكلمات الرئيسية في النص عينة

 private static List<int> IndexOfAll(string haystack, string needle, StringComparison Comparison)
    {
        int pos;
        int offset = 0;
        int length = needle.Length;
        List<int> positions = new List<int>();
        while ((pos = haystack.IndexOf(needle, offset, Comparison)) != -1)
        {
            positions.Add(pos);
            offset = pos + length;
        }
        return positions;
    }

وانها أخرق قليلا في تنفيذه. الطريقة التي يعمل بها هي من خلال إيجاد موقف جميع الكلمات الرئيسية في السلسلة. ثم التأكد من أن توجد كلمات هي أقرب إلى بعضها البعض من طول قصاصة المطلوب، بحيث قصاصات لا تتداخل (وهذا حيث انها قليلا غير محدد ...). ومن ثم الاستيلاء فرعية من الطول المطلوب تتمحور حول الموقف من الكلمات الرئيسية وغرز كل شيء معا.

وأنا أعلم أن هذه هي سنوات في وقت متأخر، ولكن نشرها فقط في حال قد تساعد شخص يتصادف هذا السؤال.

وهذه مشكلة لطيفة:)

وأنا أعتقد أن خلق ناقلات الفهرس: لكل كلمة، إنشاء إدخال 1 إذا كان مصطلح البحث أو 0. ثم العثور على ط مثل هذا المبلغ (indexvector [ط: I + MAXLENGTH]) إلى أقصى حد ممكن.

ويمكن في الواقع أن يتم ذلك بدلا بكفاءة. نبدأ مع عدد من searchterms على حد تعبير MAXLENGTH الأولى. ثم، وأنت تتحرك على إنقاص العداد الخاص بك إذا indexvector [ط] = 1 (أي الخاصة بك على وشك فقدان هذا المصطلح البحث كما يمكنك زيادة ط) وزيادته إذا indexvector [ط + MAXLENGTH + 1] = 1. كما تذهب، وتتبع ط وفقا لأعلى قيمة العداد.

وبمجرد أن حصلت ط المفضلة لديك، يمكنك لا تزال finetuning مثل معرفة ما اذا كان يمكن أن تقلل من الحجم الفعلي دون المساس العداد الخاص بك، على سبيل المثال من أجل العثور على حدود الجملة أو أيا كان. أو مثل اختيار الحق ط عدد من هو مع قيم العداد ما يعادلها.

ولست متأكدا إذا كان هذا هو نهج أفضل من يدكم - انها واحدة مختلفة

وقد ترغب أيضا للتحقق من هذه الورقة حول هذا الموضوع، والتي تأتي معه حتى الآن، آخر خط الأساس: <لأ href = "http://citeseerx.ist.psu.edu/viewdoc/download؟doi=10.1.1.72 0.4357 ومندوب = REP1 ونوع = قوات الدفاع الشعبي "يختلط =" نوفولو noreferrer "> http://citeseerx.ist.psu.edu/viewdoc/download؟doi=10.1.1.72.4357&rep=rep1&type=pdf

public class Highlighter
{        
    private class Packet
    {
        public string Sentence;
        public double Density;
        public int Offset;
    }

    public static string FindSnippet(string text, string query, int maxLength)
    {
        if (maxLength < 0)
        {
            throw new ArgumentException("maxLength");
        }
        var words = query.Split(' ').Where(w => !string.IsNullOrWhiteSpace(w)).Select(word => word.ToLower()).ToLookup(s => s);             
        var sentences = text.Split('.');
        var i = 0;
        var packets = sentences.Select(sentence => new Packet 
        { 
            Sentence = sentence, 
            Density = ComputeDensity(words, sentence),
            Offset = i++
        }).OrderByDescending(packet => packet.Density);
        var list = new SortedList<int, string>();            
        int length = 0;                
        foreach (var packet in packets)
        {
            if (length >= maxLength || packet.Density == 0)
            {
                break;
            }
            string sentence = packet.Sentence;
            list.Add(packet.Offset, sentence.Substring(0, Math.Min(sentence.Length, maxLength - length)));
            length += packet.Sentence.Length;
        }
        var sb = new List<string>();
        int previous = -1;
        foreach (var item in list)
        {
            var offset = item.Key;
            var sentence = item.Value;
            if (previous != -1 && offset - previous != 1)
            {
                sb.Add(".");
            }
            previous = offset;             
            sb.Add(Highlight(sentence, words));                
        }
        return String.Join(".", sb);
    }

    private static string Highlight(string sentence, ILookup<string, string> words)
    {
        var sb = new List<string>();
        var ff = true;
        foreach (var word in sentence.Split(' '))
        {
            var token = word.ToLower();
            if (ff && words.Contains(token))
            {
                sb.Add("[[HIGHLIGHT]]");
                ff = !ff;
            }
            if (!ff && !string.IsNullOrWhiteSpace(token) && !words.Contains(token))
            {
                sb.Add("[[ENDHIGHLIGHT]]");
                ff = !ff;
            }
            sb.Add(word);
        }
        if (!ff)
        {
            sb.Add("[[ENDHIGHLIGHT]]");
        }
        return String.Join(" ", sb);
    }

    private static double ComputeDensity(ILookup<string, string> words, string sentence)
    {            
        if (string.IsNullOrEmpty(sentence) || words.Count == 0)
        {
            return 0;
        }
        int numerator = 0;
        int denominator = 0;
        foreach(var word in sentence.Split(' ').Select(w => w.ToLower()))
        {
            if (words.Contains(word))
            {
                numerator++;
            }
            denominator++;
        }
        if (denominator != 0)
        {
            return (double)numerator / denominator;
        }
        else
        {
            return 0;
        }
    }
}

مثال:

<اقتباس فقرة>   ويعرف

وتسليط الضوء على "تدفق البصري كما تغير الضوء منظم في الصورة، على سبيل المثال على شبكية العين أو مستشعر الكاميرا، بسبب الحركة النسبية بين مقلة العين أو الكاميرا والمشهد. مزيد من التعريفات من الأدب تبرز خصائص مختلفة تدفق البصري "" التدفق البصري "

وإخراج:

[[HIGHLIGHT]] تدفق البصري [[ENDHIGHLIGHT]] ويعرف باسم تغيير منظم  ضوء في الصورة، والبريد ... مزيد من التعريفات من فرق الأدب تسليط الضوء خصائص erent من [[HIGHLIGHT]] تدفق البصري [[ENDHIGHLIGHT]]

حسنا، ها هي اخترق معا نسخة أدليت به باستخدام خوارزمية وصفتها أعلاه. أنا لا أعتقد أنه من كل هذا عظيم. ويستخدم ثلاثة (عد م، ثلاثة!) حلقات مجموعة وقائمتين. ولكن، أيضا، هو أفضل من لا شيء. أنا أيضا ضمنية الحد الأقصى لطول بدلا من تحويله إلى المعلمة.

private static string FindRelevantSnippets(string infoText, string[] searchTerms)
    {
        List<int> termLocations = new List<int>();
        foreach (string term in searchTerms)
        {
            int termStart = infoText.IndexOf(term);
            while (termStart > 0)
            {
                termLocations.Add(termStart);
                termStart = infoText.IndexOf(term, termStart + 1);
            }
        }

        if (termLocations.Count == 0)
        {
            if (infoText.Length > 250)
                return infoText.Substring(0, 250);
            else
                return infoText;
        }

        termLocations.Sort();

        List<int> termDistances = new List<int>();
        for (int i = 0; i < termLocations.Count; i++)
        {
            if (i == 0)
            {
                termDistances.Add(0);
                continue;
            }
            termDistances.Add(termLocations[i] - termLocations[i - 1]);
        }

        int smallestSum = int.MaxValue;
        int smallestSumIndex = 0;
        for (int i = 0; i < termDistances.Count; i++)
        {
            int sum = termDistances.Skip(i).Take(5).Sum();
            if (sum < smallestSum)
            {
                smallestSum = sum;
                smallestSumIndex = i;
            }
        }
        int start = Math.Max(termLocations[smallestSumIndex] - 128, 0);
        int len = Math.Min(smallestSum, infoText.Length - start);
        len = Math.Min(len, 250);
        return infoText.Substring(start, len);
    }

وبعض التحسينات كنت أفكر فيه سيكون للعودة متعددة "قصاصات" الذي يبلغ طوله أقصر أن تضيف ما يصل الى طول أطول - بهذه الطريقة أجزاء متعددة من المستند يمكن أخذ عينات

وأخذت نهج آخر، وربما أنها سوف تساعد شخص ما ...

والأولى أنه يبحث ما اذا كان كلمة يظهر في حالتي مع IgnoreCase (قمت بتغيير هذا بالطبع نفسك). بعد ذلك إنشاء قائمة من التعبيرات المنتظمة مباريات في كل فصل والبحث عن التواجد الأول من كلمة (السماح حالة جزئية مباريات حساسة). من أن مؤشر، وأحصل على 10 مباريات في الجبهة وراء كلمة، الأمر الذي يجعل المتكررة.

public static string GetSnippet(string text, string word)
{
    if (text.IndexOf(word, StringComparison.InvariantCultureIgnoreCase) == -1)
    {
        return "";
    }

    var matches = new Regex(@"\b(\S+)\s?", RegexOptions.Singleline | RegexOptions.Compiled).Matches(text);

    var p = -1;
    for (var i = 0; i < matches.Count; i++)
    {
        if (matches[i].Value.IndexOf(word, StringComparison.InvariantCultureIgnoreCase) != -1)
        {
            p = i;
            break;
        }
    }

    if (p == -1) return "";
    var snippet = "";
    for (var x = Math.Max(p - 10, 0); x < p + 10; x++)
    {
        snippet += matches[x].Value + " ";
    }
    return snippet;
}

إذا كنت تستخدم CONTAINSTABLE ستحصل على RANK الظهر، وهذا هو في جوهره قيمة الكثافة - أعلى قيمة RANK، وارتفاع الكثافة. بهذه الطريقة، يمكنك فقط تشغيل استعلام للحصول على النتائج التي تريدها و لا يجب أن يؤدي إلى تلفيق البيانات عندما عاد لها.

كتب دالة على فعل هذا الآن فقط.كنت تريد أن تمر في:

المدخلات:

نص الوثيقة
هذا هو النص الكامل للوثيقة كنت أخذ مقتطف من.على الأرجح سوف تحتاج إلى غزة أي BBCode/HTML من هذه الوثيقة.

البحث الأصلي
سلسلة المستخدم إدخال البحث

مقتطف طول
طول المقتطف ترغب في عرضها.

قيمة الإرجاع:

بدء مؤشر نص المستند إلى اتخاذ مقتطف من.للحصول على مقتطف ببساطة documentText.Substring(returnValue, snippetLength).هذا له ميزة أنه يمكنك أن تعرف إذا كان مقتطف من البداية/النهاية/الوسط بحيث يمكنك إضافة بعض الزخارف مثل ... إذا كنت ترغب في مقتطف البداية/النهاية.

الأداء

A resolution تعيين 1 سوف تجد أفضل مقتطف ولكن التحركات النافذة على طول 1 شار في وقت واحد.تعيين هذه القيمة أعلى لتسريع التنفيذ.

القرص

يمكنك العمل بها score ولكن تريد.في هذا المثال قمت به Math.pow(wordLength, 2) لصالح كلمات أطول.

private static int GetSnippetStartPoint(string documentText, string originalQuery, int snippetLength)
{
    // Normalise document text
    documentText = documentText.Trim();
    if (string.IsNullOrWhiteSpace(documentText)) return 0;

    // Return 0 if entire doc fits in snippet
    if (documentText.Length <= snippetLength) return 0;

    // Break query down into words
    var wordsInQuery = new HashSet<string>();
    {
        var queryWords = originalQuery.Split(' ');
        foreach (var word in queryWords)
        {
            var normalisedWord = word.Trim().ToLower();
            if (string.IsNullOrWhiteSpace(normalisedWord)) continue;
            if (wordsInQuery.Contains(normalisedWord)) continue;
            wordsInQuery.Add(normalisedWord);
        }
    }

    // Create moving window to get maximum trues
    var windowStart = 0;
    double maxScore = 0;
    var maxWindowStart = 0;

    // Higher number less accurate but faster
    const int resolution = 5;

    while (true)
    {
        var text = documentText.Substring(windowStart, snippetLength);

        // Get score of this chunk
        // This isn't perfect, as window moves in steps of resolution first and last words will be partial.
        // Could probably be improved to iterate words and not characters.
        var words = text.Split(' ').Select(c => c.Trim().ToLower());
        double score = 0;
        foreach (var word in words)
        {
            if (wordsInQuery.Contains(word))
            {
                // The longer the word, the more important.
                // Can simply replace with score += 1 for simpler model.
                score += Math.Pow(word.Length, 2);
            }                   
        }
        if (score > maxScore)
        {
            maxScore = score;
            maxWindowStart = windowStart;
        }

        // Setup next iteration
        windowStart += resolution;

        // Window end passed document end
        if (windowStart + snippetLength >= documentText.Length)
        {
            break;
        }
    }

    return maxWindowStart;
}

الكثير يمكنك إضافة إلى ذلك ، على سبيل المثال بدلا من مقارنة الكلمات ربما قد ترغب في محاولة مقارنة SOUNDEX حيث يمكنك الوزن soundex مباريات أقل من الدقيقة المباريات.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top