Frage

Standortsuche Bei der Entwicklung ich baue, habe ich beschlossen, den billigen und schnellen Weg zu gehen und Microsoft SQL Servers Volltext-Suchmaschine anstatt etwas robusten wie Lucene.Net verwenden.

Eines des Merkmals würde Ich mag zwar haben, ist, Google-artige relevantes Dokument Schnipsel. Ich habe schnell die Bestimmung „relevant“ Schnipsel gefunden ist schwieriger, als ich realisiert.

Ich mag Schnipsel wählen, basierend auf Suchbegriffs Dichte in dem gefundenen Text. Also, im Wesentlichen, muß ich die meisten Suchbegriffs dichte Passage im Text finden. Wo eine Passage einige beliebige Anzahl von Zeichen ist (sagen wir 200 - aber es spielt keine Rolle).

Mein erster Gedanke ist .IndexOf () in einer Schleife zu verwenden, und eine Reihe von Begriff Entfernungen (subtrahieren Sie den Index des gefundenen Begriffs aus dem zuvor gefundenen Begriff) zu bauen, dann ... was? Fügen Sie bis alle zwei, drei beliebige, alle vier, alle fünf, aufeinanderfolgende Array-Elemente und verwenden Sie die mit der kleinsten Summe (also der kleinste Abstand zwischen den Suchbegriffen).

Das scheint chaotisch.

Gibt es einen etablierten, besser, oder offensichtlichen Weg, dies zu tun, als das, was ich habe kommen mit?

War es hilfreich?

Lösung

Obwohl es in Java implementiert ist, können Sie einen Ansatz für dieses Problem ist hier zu sehen: http://rcrezende.blogspot.com/2010/08 /smallest-relevant-text-snippet-for.html

Andere Tipps

Ich weiß, dass dieser Thread Weg alt ist, aber ich habe einen Versuch diese letzte Woche und es war in der Rückseite ein Schmerz. Dies ist bei weitem nicht perfekt, aber das ist, was ich kam mit.

Der Snippet-Generator:

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;

    }

Die Funktion, die den Index aller Keywords in dem Beispieltext findet

 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;
    }

Es ist ein bisschen ungeschickt in seiner Ausführung. Die Funktionsweise ist durch die Position aller Keywords in der Zeichenfolge zu finden. Dann prüfen, ob keine Schlüsselwörter einander näher sind als die Schnipsel Länge gewünscht wird, so dass Schnipsel nicht überlappen (das ist, wo es ein bisschen iffy ist ...). Und dann packt Teil der um die Position der Schlüsselwörter zentriert gewünschte Länge und näht das Ganze zusammen.

Ich weiß, das Jahr zu spät ist, aber nur für den Fall zu veröffentlichen, es könnte jemanden helfen, über diese Frage kommen.

Dies ist ein schönes Problem:)

ich glaube, ich einen Indexvektor schaffen würde: Für jedes Wort, erstellen Sie einen Eintrag 1, wenn das Suchwort oder sonst 0. Dann die ich finde, dass Summe (indexvector [i: i + maxlength]) maximiert wird.

Dies kann tatsächlich ziemlich effizient durchgeführt werden. Beginnen Sie mit der Anzahl der Suchbegriffe in den ersten maxlength Worte. wie Sie dann bewegen sich auf, verringern Sie die Zähler, wenn indexvector [i] = 1 (das heißt Ihr über diesen Suchbegriff zu verlieren, wie Sie i erhöhen), und es zu erhöhen, wenn indexvector [i + maxlength + 1] = 1. Wie Sie gehen, halten Sie den Überblick über die i mit dem höchsten Zählerwert.

Wenn Sie Ihre Lieblings-i erhalten haben, können Sie immer noch Feinstimm wie sehen, wenn Sie die tatsächliche Größe reduzieren können Ihre Zähler ohne Kompromisse, z.B. um Satzgrenzen oder was auch immer zu finden. Oder wie die Auswahl der richtigen i aus einer Anzahl von mit äquivalenten Zählwerte.

Nicht sicher, ob dies ein besserer Ansatz ist als Sie -. Es ist ein anderer

Sie mögen vielleicht auch auf dem Thema dieses Papier überprüfen, die mit noch einer anderen-Basislinie kommt: 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;
        }
    }
}

Beispiel:

  

highlight „Optic Strom wird als die Veränderung von strukturiertem Licht in dem Bild definiert ist, beispielsweise auf der Netzhaut oder die Sensor der Kamera aufgrund einer Relativbewegung zwischen dem Augapfel oder der Kamera und der Bildfläche. Weitere Definitionen aus der Literatur markierten unterschiedliche Eigenschaften von optischem Fluss“ "optic flow"

Ausgabe:

[[highlight]] Optic Flow [[ENDHIGHLIGHT]] wird als die Veränderung der strukturierten definiert  Licht in dem Bild, e ... Weitere Definitionen aus der Literatur Highlight diff Erent Eigenschaften von [[HIGHLIGHT]] optischem Fluss [[ENDHIGHLIGHT]]

Nun, hier ist die gehackt zusammen Version ich den Algorithmus unter Verwendung ich oben beschrieben. Ich glaube nicht, es ist alles so toll ist. Es nutzt drei (count em, drei!) Schleifen ein Array und zwei Listen. Aber, na ja, ist es besser als nichts. Ich fest einprogrammiert auch die maximale Länge, anstatt sie in einen Parameter drehen.

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);
    }

Einige Verbesserungen, die ich denken konnte, wären mehrere „Schnipsel“ zurückzukehren mit einer kürzeren Länge, die die längeren Länge summieren sich -. Diese Weise mehr Teile des Dokuments abgetastet werden können

habe ich einen anderen Ansatz, vielleicht wird es jemand helfen ...

Zuerst sucht er, wenn er Wort mit IgnoreCase in meinem Fall erscheint (Sie diese Texte selbst natürlich ändern). Dann erstelle ich eine Liste von Regex auf jedem Separator entspricht und für das erste Vorkommen des Wortes suchen (so dass teilweise Groß- und Kleinschreibung Ursachen). Von diesem Index, erhalte ich die 10 Spiele vor und hinter dem Wort, das das Snippet macht.

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;
}

Wenn Sie CONTAINS verwenden Sie ein RANK zurückbekommen, ist dies ein Dichtewert im Wesentlichen - höher der RANK-Wert, desto höher die Dichte. Auf diese Weise, die Sie gerade eine Abfrage ausführen, um die Ergebnisse zu erhalten, die Sie wünschen und nicht die Daten führen, müssen massiert, wenn seine zurückgeführt.

Schrieb eine Funktion dies gerade jetzt zu tun. Sie wollen passieren in:

Eingänge:

Dokumenttext
Dies ist der vollständige Text des Dokuments Sie einen Ausschnitt einnehmen. Höchstwahrscheinlich werden Sie aus diesem Dokument alle BBCode / HTML entfernen.

Original Abfrage
Die Zeichenfolge der Benutzer eingegeben als ihre Suche

Snippet Länge
Länge des Snippets Sie anzeigen möchten.

Rückgabewert:

Startindex des Dokuments, das auf das Snippet zu nehmen. Um das Snippet einfach tun documentText.Substring(returnValue, snippetLength). Dies hat den Vorteil, dass Sie wissen, ob das Snippet von Anfang / Ende / Mitte ist nehmen, so dass Sie eine Dekoration wie ... hinzufügen können, wenn Sie an dem Schnipsel Start / Ende wünschen.

Performance

Ein resolution auf 1 den besten Schnipsel findet , sondern bewegt sich das Fenster entlang 1 Zeichen zu einem Zeitpunkt. Setzen Sie diesen Wert höher zu beschleunigen Ausführung.

Tweaks

Sie können ausrechnen score immer Sie wollen. In diesem Beispiel habe ich Math.pow(wordLength, 2) getan mehr Worte zu begünstigen.

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;
}

Lots mehr können Sie diese hinzufügen, zum Beispiel anstelle von genauen Worte vielleicht vergleichen Sie versuchen könnten, wollen die SOUNDEX zu vergleichen, wo man Gewicht soundex entspricht weniger als exakte Übereinstimmungen.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top