Alle Wörter (und Anzahl) auflisten - Evtl Filtern

  • VB.NET

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von RodFromGermany.

    Alle Wörter (und Anzahl) auflisten - Evtl Filtern

    Hallo Zusammen.
    Ich hatte vor kurzem eine Idee für ein kleines Programm, bin aber selbst noch nicht wirklich in der Lage das zu realisieren.
    Als erstes müsste ich es irgendwie schaffen, dass eine Liste von allen Wörtern eines Textes, welchen der Benutzer vorher in eine Textbox einfügen kann, und deren Anzahl automatisch angefertigt wird.
    Später sollen dann alle Wörter die mehr als 1 mal im Text vorkommen herausgefiltert, und nur noch eine Liste mit allen Wörtern die lediglich 1 mal im gesamten Text vorkommen angezeigt werden. So weit erst mal der Plan.
    Ich hoffe, dass irgendjemand eine Idee hat, wie man dies realisieren könnte.
    Und bitte steinigt mich nicht, wenn eine Ähnliche Frage schon einmal gestellt wurde, über die SuFu konnte ich leider nichts finden...

    Danke schon mal für eventuelle Antworten und hilfreiche Beiträge!

    MfG
    also eine sehr mächtige Lösung gibts in DataExpressions im "Wortvergleich" - Projekt.
    Ist viel Stoff für einen Anfänger, aber mit jedem anneren Ansatz hast du mindestens genauso viel Arbeit.
    Denn es besteht ja nicht nur das Problem, den Text zu filtern, sondern du musst ja den Text speichern / laden können, und auch eine Eingabe haben für verschiedenen Filter, und die wollen auch gespeichert werden.
    @Scaphonyx:: Willkommen im Forum. :thumbup:
    Vielleicht fängst Du etwas kleiner an, das Datenbanking von @ErfinderDesRades: kriegen wir später.
    1. kannst Du einen Text in seine Worte zerlegen:

    VB.NET-Quellcode

    1. Dim text = "uiafk?hjuasd lkjasdv lj. kjasdfhvl kajsdf: hvlk.janhs!dfvklöhjivklj"
    2. Dim parts() = text.Split({" "c, "."c, "!"c, "?"c, ";"c, ":"c, ","c}, StringSplitOptions.RemoveEmptyEntries)
    Hier wird an allen Leer- und Satzzeichen gesplittet, leere Einträge werden entfernt.
    Sortieren kannst Du das Array ganz easy:

    VB.NET-Quellcode

    1. Array.Sort(parts)
    So. Nun verarbeite das, spiel ein wenig rum und bekomme selbst heraus, wie man die Anzahl der Wörter bestimmt. ;)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Hi
    hab' dazu auch mal 'nen Code verfasst. Wenn's dir zu kompliziert ist, lass es aber weg (ist fortgeschritten):

    Folgende Funktion habe ich zum Zählen geschrieben:

    VB.NET-Quellcode

    1. Public Class Snippets
    2. Private Sub New()
    3. End Sub
    4. #Region "Words"
    5. Shared Function GetWords(input As String) As IEnumerable(Of String)
    6. If input Is Nothing Then Throw New ArgumentNullException("input")
    7. Dim wordFilter As Func(Of String, Integer, Boolean) = AddressOf Char.IsWhiteSpace 'das hier definiert den Filter, der für die Wort-Trennzeichen verwendet wird, es wird in meinem Fall nur an Leerzeichen (Tab, Zeilenumbruch, usw.) gesplittet
    8. Return GetWordPositions(input, wordFilter).Select(Function(range) input.Substring(range.StartIndex, range.EndIndex - range.StartIndex))
    9. End Function
    10. 'gibt Start- und End-Indices aller in input gefundenen Wörter zurück. separator gibt eben eine Funktion an, die zurückgibt, ob das Zeichen im String ein Separator ist, oder nicht (z.B. Leerzeichen in meinem Fall)
    11. Private Shared Iterator Function GetWordPositions(input As String, separatorFilter As Func(Of String, Integer, Boolean)) As IEnumerable(Of IndexRange)
    12. Dim prevInd As Integer = 0 'Den Index des vorhergehenden Worts merken
    13. Dim cind As Integer = prevInd 'Der Index, an dem momentan untersucht wird
    14. Dim ln As Integer = input.Length 'Die Länge des Strings (wird nur zwischengespeichert)
    15. While cind < ln 'solange weitere Buchstaben vorhanden sind
    16. If separatorFilter(input, cind) Then 'ist der Buchstabe ein Trennzeichen
    17. If cind > prevInd Then Yield New IndexRange(prevInd, cind) 'gib die Indices zurück, sofern die Länge des Worts > 0 ist (die Länge des Wortes ergibt sich aus cind - prevInd)
    18. cind += If(Char.IsSurrogate(input, cind), 2, 1) 'nächsten Buchstaben auswählen und Surrogaten-Paare beachten
    19. prevInd = cind 'das neue Wort wird nach dem vorhin untersuchten Trennzeichen erwartet (aufeinanderfolgende Trennzeichen werden ignoriert, da dann das Wort die Länge 0 hätte)
    20. Else
    21. cind += If(Char.IsSurrogate(input, cind), 2, 1) 'nächsten Buchstaben auswählen und Surrogaten-Paare beachten
    22. End If
    23. End While
    24. If cind > prevInd Then Yield New IndexRange(prevInd, cind) 'letztes Wort zurückgeben, sofern vorhanden
    25. End Function
    26. Private Structure IndexRange
    27. Public StartIndex As Integer
    28. Public EndIndex As Integer
    29. Public Sub New(startIndex As Integer, endIndex As Integer)
    30. Me.StartIndex = startIndex
    31. Me.EndIndex = endIndex
    32. End Sub
    33. End Structure
    34. #End Region
    35. End Class


    Der Code für ein Beispiel als Konsolenanwendung wäre:

    VB.NET-Quellcode

    1. Shared Sub Main()
    2. Do 'solange eine Zeile einlesen, bis der eingegebene Text leer ist
    3. Console.Write(">")
    4. Dim ln As String = Console.ReadLine()
    5. If ln.Length = 0 Then Exit Do
    6. For Each v As String In Snippets.GetWords(ln) 'und alle darin enthaltenen Wörter ausgeben
    7. Console.WriteLine(" " & v)
    8. Next
    9. Loop
    10. End Sub


    Nach Kriterien kannst du dann wunderbar filtern. Das hier zählt die Wörter:

    VB.NET-Quellcode

    1. Shared Function GetWordCount(input As String) As IEnumerable(Of KeyValuePair(Of String, Integer))
    2. Return GetWordCount(input, StringComparer.Ordinal)
    3. End Function
    4. 'der comparer gibt an, unter welchen Kritieren sortiert werden soll (siehe bspw. StringComparer-Klasse). So kann man bspw. Groß-und Kleinschreibung ignorieren.
    5. Shared Function GetWordCount(input As String, comparer As IEqualityComparer(Of String)) As IEnumerable(Of KeyValuePair(Of String, Integer))
    6. Dim dtc As New Dictionary(Of String, Integer)(comparer)
    7. For Each w As String In GetWords(input)
    8. Dim cv As Integer = 0
    9. dtc(w) = If(dtc.TryGetValue(w, cv), cv + 1, 1) 'Ist das Wort bereits im Dictionary vorhanden, inkrementiere dessen Anzahl, sonst setze ihn auf 1
    10. Next
    11. Return dtc
    12. End Function


    Anfangs hab' ich noch eine IEnumerable(Of T)-Extension geschrieben, aber die hab' ich nach der letzten Änderung nicht mehr gebraucht. Ggf. ist die ja noch für den einen oder anderen interessant:
    Der erwartete Unterschied zwischen LazySelect und Enumerable.Select ist, dass LazySelect nur dann den Wert aus dem darunterliegenden Enumerator generiert, wenn er benötigt wird.
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Module EnumerableExtension
    2. 'Selektiert einen Wert aus dem eingehenden TIn und konvertiert diesen, sobald er benötigt wird, also Current aufgerufen wird, anhand des übergebenen Generators.
    3. <Extension()>
    4. Public Function LazySelect(Of TIn, TOut)(underlyingEnumerable As IEnumerable(Of TIn), generator As Func(Of TIn, TOut)) As IEnumerable(Of TOut)
    5. If underlyingEnumerable Is Nothing Then Throw New ArgumentNullException("underlyingEnumerable")
    6. If generator Is Nothing Then Throw New ArgumentNullException("generator")
    7. Return New DelegateEnumerable(Of TOut)(Function() New LazyEnumerator(Of TIn, TOut)(underlyingEnumerable, generator))
    8. End Function
    9. Private NotInheritable Class DelegateEnumerable(Of T)
    10. Implements IEnumerable(Of T)
    11. Private _generator As Func(Of IEnumerator(Of T))
    12. Public Sub New(generator As Func(Of IEnumerator(Of T)))
    13. If generator Is Nothing Then Throw New ArgumentNullException("generator")
    14. _generator = generator
    15. End Sub
    16. Public Function GetEnumerator() As IEnumerator(Of T) Implements IEnumerable(Of T).GetEnumerator
    17. Return _generator()
    18. End Function
    19. Private Function GetEnumeratorObj() As IEnumerator Implements IEnumerable.GetEnumerator
    20. Return GetEnumerator()
    21. End Function
    22. End Class
    23. Private NotInheritable Class LazyEnumerator(Of TIn, TOut)
    24. Implements IEnumerator(Of TOut)
    25. Private _value As TOut
    26. Private _valueGenerated As Boolean
    27. Private _generator As Func(Of TIn, TOut)
    28. Private _disposed As Boolean
    29. Private _underlyingEnumerator As IEnumerator(Of TIn)
    30. Public Sub New(underlyingEnumerable As IEnumerable(Of TIn), generator As Func(Of TIn, TOut))
    31. If underlyingEnumerable Is Nothing Then Throw New ArgumentNullException("underlyingEnumerable")
    32. If generator Is Nothing Then Throw New ArgumentNullException("generator")
    33. _underlyingEnumerator = underlyingEnumerable.GetEnumerator()
    34. _generator = generator
    35. End Sub
    36. Public ReadOnly Property Current As TOut Implements IEnumerator(Of TOut).Current
    37. Get
    38. If _disposed Then Throw New ObjectDisposedException(Me.GetType().Name)
    39. If Not _valueGenerated Then 'Wert dann aus dem darunterliegenden Enumerable generieren, wenn er benötigt wird, sonst nicht
    40. _value = _generator(_underlyingEnumerator.Current)
    41. _valueGenerated = True
    42. End If
    43. Return _value
    44. End Get
    45. End Property
    46. Private ReadOnly Property CurrentObj As Object Implements IEnumerator.Current
    47. Get
    48. Return Current
    49. End Get
    50. End Property
    51. Public Function MoveNext() As Boolean Implements IEnumerator.MoveNext
    52. _value = Nothing
    53. _valueGenerated = False
    54. Return _underlyingEnumerator.MoveNext()
    55. End Function
    56. Public Sub Reset() Implements IEnumerator.Reset
    57. If _disposed Then Throw New ObjectDisposedException(Me.GetType().Name)
    58. _value = Nothing
    59. _valueGenerated = False
    60. _underlyingEnumerator.Reset()
    61. End Sub
    62. Private Sub Dispose(disposing As Boolean)
    63. If Not _disposed Then
    64. _underlyingEnumerator.Dispose()
    65. _disposed = True
    66. End If
    67. End Sub
    68. Public Sub Dispose() Implements IDisposable.Dispose
    69. Dispose(False)
    70. GC.SuppressFinalize(Me)
    71. End Sub
    72. End Class
    73. End Module


    Gruß
    ~blaze~
    @~blaze~
    Danke für deine Mühe. Ich glaube ich bin dann doch mehr Anfänger als der allgemeine Eindruck hier zu sein scheint :D
    Aber ich habe mit deinen Code mal durchgelesen, habe einiges verstanden, einiges aber auch nicht.
    Am ehesten stelle ich mir die Frage, wie ich diesen Code jetzt für mein Vorhaben verwenden kann.
    So wie ich das verstanden habe, wird die Variable "input" für den im optimalfall dann vom Benutzer eingegebenen Text verwendet. Nur wüsste ich nicht genau, wie ich das auch deklarieren kann. Da müsste dann ja so etwas wie

    Quellcode

    1. Dim input as integer = textbox1.text
    stehen oder nicht? *unwissend* :whistling:
    Das wäre ein Parameter eines Funktionsaufrufs:

    VB.NET-Quellcode

    1. For Each word In GetWordCount(TextBox1.Text)
    2. MessageBox.Show(word.ToString())
    3. Next

    TextBox1.Text fungiert hier als Wert, der in Form eines Parameters übergeben wird. Der Rückgabewert von GetWordCount wird hier von einer For-Each-Schleife direkt weiterverarbeitet.
    Alternativ könnte man bspw. auch folgendes machen:

    VB.NET-Quellcode

    1. MessageBox.Show(String.Join(vbCrLf, GetWords(TextBox1.Text))


    Achja: Wichtig ist auch, dass du dir die Codes durchschaust und fragst, wenn was nicht klar ist. IntelliSense bietet dir da zwar erstklassige Hilfe, auch mit den Beschreibungen und dem Zeug, aber ich glaube, dass einiges klarer wird, wenn man's erklärt bekommt, sofern du es nicht bereits kennst (könnte mir bspw. IEnumerable(Of T) oder IEqualityComparer(Of T) als mögliche Frage-Quellen vorstellen). Bist natürlich nicht der einzige, der fragen darf ;).

    Gruß
    ~blaze~

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „~blaze~“ ()

    Also in .Net gibt's ja Namensräume, Typen und Member. Namensräume sind in den Standardbibliotheken eben bspw. System, System.Drawing, System.Windows.Forms, usw. Typen hingegen sind bspw. System.Windows.Forms.Form, System.Drawing.Color, aber auch IEnumerable(Of T), usw. (der Namensraum kann weggelassen werden, da die enthaltenden Namensräume importiert wurden). Damit hat jeder Typ einen absoluten Pfad, wie oben für Form. Wenn du jetzt auf Member von Typen zugreifst, musst du eben genau so einen gültigen Pfad angeben. D.h.
    <Typpfad>.<Memberpfad>
    Typpfad ist in deinem Fall Snippet, Memberpfad eben GetWords oder GetWordCount, welches logisch eigentlich GetWordCounts heißen müsste.

    Gruß
    ~blaze~
    Danke dir!
    Also ich hab das jetzt halbwegs geschafft, stehe momentan aber vor einem anderen Problem :/
    Ich versuche, die enstandene Wörterliste zu filtern. Es wird eine .txt datei eingelesen und alle Wörter, die dort enthalten sind, sollen aus der Liste herausgelöscht werden.
    Das habe ich so versucht umzusetzen:

    Quellcode

    1. Dim pfad As String = My.Computer.FileSystem.SpecialDirectories.Desktop & "\words.txt"
    2. Dim zeilen() As String = IO.File.ReadAllLines(pfad)
    3. Dim ln As Integer = zeilen.Length 'diese hab ich nur zur überprüfung definiert
    4. Dim i As Integer = 0


    So, dann habe ich eine Textbox erstellt und einen button daneben, welcher wie folgt beauftragt wurde:

    Quellcode

    1. Dim eingabe As String = RichTextBox2.Text
    2. Dim x As Integer = 0
    3. Do Until x = ln
    4. eingabe = eingabe.Replace(zeilen(x), "")
    5. x = x + 1
    6. Loop
    7. RichTextBox2.Text = eingabe


    Wenn ich das dann aber mit einem einer Testliste mache, sagt er mir "Die Zeichenfolge kann keine Länge von 0(null) haben."
    Dabei weist er mich auf die Zeile "eingabe = eingabe.replace(zeilen(x), "") hin.

    Was hat das zu bedeuten?
    @Scaphonyx:: Probier mal so etwas:

    VB.NET-Quellcode

    1. Dim ll As New List(Of String)
    2. ll.AddRange({"aa", "bb", "cc", "dd"})
    3. ll.AddRange(IO.File.ReadLines("c:\Temp\Test.txt").ToList)
    4. ll.Remove("bb")
    5. ll.RemoveAt(0)
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!