Splitzeichen in Array lassen

  • Allgemein

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

    Es ist halt die Frage, was wartungsfreundlicher ist. Sofern das nicht eh schon lange klar ist: Wenn jetzt die Anforderungen in späteren Programmen an anderen Stellen verändert wird, gilt diese Art der Eingabe nicht mehr. Wenn das Programm keine Trennung in mehrere Teilbereiche hat, ist das Ansteuern von Code ggf. sehr zeitaufwändig, daher achte ich bei meinem Programmaufbau darauf, dass solche Funktionen einfach in eine Klasse ausgelagert werden, die statische Funktionen (oder Extensions) enthält. Daher gilt eigentlich, dass der Code immer für alle Fälle gültig sein muss. Meist werden die Funktionen dann in einer Art und Weise programmiert, die den Vorgang stark abstrahieren, da dies zur Folge hat, dass die Programmcodes an anderen Stellen wieder verwendet werden können; die Abstraktion wird dabei den zukünftigen Komponenten entnommen. Durch diese Überlegung wird eben auch das Testing weniger zeitaufwändig. Der Programmumfang bei meinen Programmen ist allerdings meist auch darauf ausgelegt, später stark erweitert zu werden. Klar, die Codezeilenzahl wird dadurch um einen nicht unbeachtlichen Faktor multipliziert, aber ich habe in den meisten Fällen wenige Probleme, den Code tatsächlich zum Laufen zu bringen.
    Ich habe halt keine wirkliche Erfahrung, was Programmierung angeht. An der Universität hatten wir mal ein Modul über Programmierung in Java im 1. Semester, das war mehr oder weniger alles, was wir an echter Programmierpraxis betrieben haben.

    Ich bin außerdem einer, der seine Quellcodes meist so programmiert, dass einzelne "Passes" in einem einzigen Rutsch durchlaufen können. str.Replace(...).Replace(...)....Replace(...) z.B. sind n Replace-Aufrufe, die im Code so verankert sind. Schöner wäre es, wenn es bspw. str.Replace(...) wäre, da das dann ein einziger Rutsch ist, der alles erledigt, wie bei meiner Funktion oben. Damit wird halt quasi "optimale" Verwendbarkeit ermöglicht. Ich finde eh, dass die String-Programmierung im .Net-Framework dürftig ausgefallen ist, da wäre schon mehr gegangen.

    Gruß
    ~blaze~
    guck dir mal die Down-Tugend bei CleanCode-Developers an.

    KISS und YAGNI: wiederverwenbaren Code erst schreiben, wenn Wiederverwendbarkeit sich auch als dringend(!) notwendig erweist. U.U. sogar auch mal Copy-Paste in Kauf nehmen, um zu vermeiden, dass die einzubindenden Super-Verwendbaren Bibliotheken ins Uferlose wachsen.
    Ich mach ja auch nix ohne meine Helpers-Projekte, aber ich empfinde das immer als problematisch, wenn ich einen Upload mache, wo ein Problem auch dank der Helpers elegant gelöst ist, aber im Helper-Projekt fahren halt noch endlos annere Methoden und Klassen rum, die im konkreten Fall garnicht gebraucht werden.
    Weil Code, der nicht gebraucht wird, ist oft sehr schwer zu verstehen (und stört auch das Verständnis der gebrauchten Codes).
    Und womöglich wird das Helpers mal erweitert, und dabei das Rad neu erfunden, einfach weil die eiglich vorgesehene Lösung nicht verstanden und erkannt wurde.

    Vor allem bezweifel ich hier stark, dass diese Anforderung öfter mal auftritt. Womöglich ist sie nichtmal hier wirklich notwendig, und ein hierarchischer Split (also erst nach '.' in Sätze splitten, dann die Sätze nach ',' in Nebensätze) wäre dem TE dienlicher als die Merkwürdigkeit, die er jetzt erhält.
    Schon beachtlich wie solche kleinen Themen so viel Diskusionsstoff bieten können.

    Aber... Im Bereich der Datenbank gibt es hin und wieder Fälle wo man bei redundanten Daten ein Auge zudrückt, ja manchmal sind diese sogar gewollt. Im Bereich der Webentwicklung schreien viele "OOP hier, OOP da", aber wozu, wenn es sich eh nur um paar Zeilen Code handelt, die in kommender Zeit eh nicht mehr werden. In sämtlichen Sprachen sagt man, es sei falsch Fehler zu unterdrücken, wie zb mittels TryCatch oder dem @-Zeichen in PHP, aber auch die werden hin und wieder absichtlich benutzt.

    Worauf ich hinaus will.. mein "Scherz", der mit der "Mega-Funktion" hatte auch nen ernstgemeinten Kern... Man könnte jetzt auch den Text (Den der TE splitten will), oder die FUnktion selbst, nochma auf Maschinencode-Ebene übertragen, wovon nicht nur ich, sondern ich wette die meisten anderen hier keine Ahnung haben, aber die Frage ist, wozu? Ärger mit ner Mücke lässt sich schnell beheben... bei nem Elefanten wirds etwas schwieriger... Ich denke dem TE gings nur darum schnell eine Lösung für sein Problem zu finden. Und ich wetter er definiert "gute Lösung" ganz anders als andere ... nämlich ganz nach seiner eigenen Problemstellung.

    Naja, zielführend ist es ja schon, aber ich denk', eine alternative Lösung ist für andere schon ganz hilfreich, bei denen das nicht so gelöst werden kann. Ich schreibe meinen Code eh meist so, dass er übertrieben exakt arbeitet, auch wenn's den meisten hier im Forum egal ist, hauptsache, er macht seinen Job.
    Im Prinzip ist's schon eine elegante Lösung, also warum nicht ;). Try-Catch ist immer so eine Sache. Z.B. bei IDictionary.Add ist vorgesehen, dass ein Fehler fliegt, wenn bereits ein Eintrag vorhanden ist. Das ist gewollt so und ein Ersatz für IDictionary.Contains und IDictionary.this (also den Default-Setter meine ich damit), da das zwei Operationen wären. Daher ist es hier sogar gut, Try-Catch zu verwenden, da so auch ICollection<KeyValuePair<TKey, TValue>> korrekt unterstützt werden. Naja, lange Rede, kurzer Sinn: Es gibt mehr oder weniger kein echtes "Pauschalrezept", das vorschreibt, wie eine Lösung für ein Problem auszusehen hat. Je nach Fall ist die Lösung eines Problems in einer bestimmten Art und Weise angemessen. Für einen Parser würde ich auf die String-Operationen abgesehen von Substring, Remove, etc. gänzlich verzichten, im alltäglichen Gebrauch kann man sich schon mit Replace, etc. was hinmurksen, aber ich würde es nur auf oberster Ebene machen, also GUI-bezogene Dinge, etc. aber nicht in Daten selber oder gar in Programmbibliotheken.

    Gruß
    ~blaze~

    VB1963 schrieb:

    SplittyDev schrieb:

    Die String.Replace-Funktion akzeptiert ein Char-Array als ersten Parameter

    Das hast du wohl mit der Split-Funktion verwechselt...


    Ups :whistling: Sorry
    Hi,

    Ich habe seit langem das Projekt mal wieder ausgegraben und musste feststellen, dass es für längere Splitter Käse produziert. Hier mal meine Anpassung soweit ich sie habe:

    VB.NET-Quellcode

    1. Public Function NewSplit(ByVal term As String, ByVal splitter As String()) As List(Of String)
    2. Dim final_list As New List(Of String)
    3. Dim last_found As Integer = 0
    4. For u = 0 To term.Length - 1
    5. For Each seperator As String In splitter
    6. If u + seperator.Length < term.Length Then
    7. If term.Substring(u, seperator.Length) = seperator Then
    8. final_list.Add(term.Substring(last_found, u - last_found))
    9. final_list.Add(term.Substring(u, seperator.Length))
    10. 'Hier muss last_found verschoben werden
    11. End If
    12. End If
    13. Next
    14. Next
    15. Return final_list
    16. End Function
    17. Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    18. MessageBox.Show(String.Join(Environment.NewLine, NewSplit("sin(2*3+4)", {"+", "*", "sin(", "(", ")"})))
    19. End Sub


    Wie bereits oben erwähnt muss last_found jedesmal verschoben werden. Ich blicke allerdings selbst nicht so ganz durch warum last_found = u + seperator.Length falsch ist...

    Jetzt an alle die nörgeln: Ich kann kein Yielding verwenden weil ich es in Java schreiben will/muss und ich mich noch nicht soweit damit auskenne. Deswegen muss ich mit der Idee zurechtkommen.

    8-) faxe1008 8-)

    faxe1008 schrieb:

    "sin(2*3+4)"
    Gugst Du Formelparser.
    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!
    Nein, es geht mir nur darum den Ausdruck zu zerlegen verwerten kann ich ihn schon.
    Das mit dem Replacen ist zwar schön und gut aber doch ein wenig arg dreckig. Wenn jemand den Fehler im oberen Algo findet kann er ihn gerne aufzeigen.

    8-) faxe1008 8-)
    Hi
    gehe jedes Zeichen des Strings durch (über eine For-Schleife, keine For-Each-Schleife) und markiere den Anfang eines "Tokens" ("sin", "(", "3", "+", usw.). Sobald das Ende des Tokens erreicht wurde, gibst du es zurück bzw., fügst du es in eine entsprechende Klasse ein (normalerweise gibt man das dann einfach als Token-Stream zurück, dafür wäre Yielding halt perfekt geeignet). Die Klasse könnte z.B. immer den linken Operanden, den rechten Operanden und den entsprechenden Operator bereithalten (None, +, -, *, /, ^, ...). Ist der Operator None, ist es das Element ganz links, d.h. es wird keine binäre Operation ausgeführt. Ein Operand sollte optimalerweise Teil eines Kompositums sein, damit du die Operatorpräzedenzen danach entsprechend sortieren kannst. + hätte die gleiche Präzendenz, wie -, * die gleiche wie /, aber eine höhere, als +, ^ eben nochmal eine höhere, als *, usw. (habe ich mit Comparison(Of Operator) implementiert, bei meinen Interpretern/bisher wegs Lust nicht beendeten Compilerversuchen). Damit das anständig läuft, brauchst du aber einen Stapel, d.h. entweder du verwendest Rekursion oder die Stack(Of T)-Klasse/einer dafür angepassten Stack-Klasse. Ich würde zu Zweiterem (und zwar zur Eigenimplementation) tendieren. Die Sortierung funktioniert nach einem einfachen Prinzip: Wenn die Präzendenz äquivalent zum obersten Element des Stapels ist, werden die danach gelesenen Operation mit gleich hoher Präzendenz ans Ende des obersten Elements angefügt und das neue Element ersetzt das oberste Stapelelement. Sobald ein Element mit kleinerer Präzendenz kommt, wird das oberste Stapelelement entfernt, bis eine gleich-hohe Präzedenz gegeben ist (dann wird verfahren, wie vorher beschrieben) oder, falls der Stapel leer ist, wird das vormals oberste Stapelelement (also das letzte) als linker Operand verschachtelt. Verschachteln bedeutet, dass der Operand kein Leaf des Kompositums ist, sondern zu einem Kompositum gemacht wird, das das Leaf als erstes Element enthält. Jetzt kommt der "tricky" part: Wenn eine höhere Präzedenz des Operators da ist, bezieht sich das auch auf das letzte Element der bisherigen Operandenliste. Das letzte Element ist logischerweise das, das oben auf dem Stapel liegt. Da ich Leaf und Kompositum als extra Klasse modellieren würde, würde ich die Operation jeweils verzögern und das letzte Element des Stapels in einer Variablen behalten und zwar in einer Klasse, die ähnlich aufgebaut ist, wie die Operand-Klasse, aber sowohl Wert, als auch eine Variablen enthält (die möglicherweise Nothing ist), die den Wert als Kompositum bereitstellt. Wenn der Wert Nothing ist, handelt es sich um ein Leaf, ansonsten um ein Kompositum.
    Ggf. gibt's auch eine wesentlich einfachere Lösung des Dilemmas, die sich mir gerade entzieht, was gut möglich ist.

    Gruß
    ~blaze~

    ~blaze~ schrieb:

    Hi
    gehe jedes Zeichen des Strings durch (über eine For-Schleife, keine For-Each-Schleife) und markiere den Anfang eines "Tokens" ("sin", "(", "3", "+", usw.). Sobald das Ende des Tokens erreicht wurde, gibst du es zurück bzw., fügst du es in eine entsprechende Klasse ein (normalerweise gibt man das dann einfach als Token-Stream zurück, dafür wäre Yielding halt perfekt geeignet).


    Das meinte ich danke. Auch der Rest deines Beitrages enthält sehr interessante Ideen :thumbup: , die ich vielleicht in der .Net Version einbauen möchte. Die Verwertung der Ausdrücke funktioniert schon ausgezeichnet es ging nur um die Aufteilung des Strings ;D, aber noch mals danke für deine Mühen.

    Gruß Fabian

    8-) faxe1008 8-)
    Hier wäre übrigens noch meine Variante für die Separate-Funktion:

    VB.NET-Quellcode

    1. Shared Function Separate(input As String, separator As String(), count As Integer, splitOptions As StringSplitOptions) As String()
    2. If input Is Nothing Then Throw New ArgumentNullException("input")
    3. If separator Is Nothing Then Throw New ArgumentNullException("separator")
    4. If count < 0 Then Throw New ArgumentOutOfRangeException("count", "Non-negative value expected for count.")
    5. If splitOptions <> StringSplitOptions.None AndAlso
    6. splitOptions <> StringSplitOptions.RemoveEmptyEntries Then
    7. Throw New ArgumentException("Unknown split options.", "splitOptions")
    8. End If
    9. 'Keine Trennzeichen oder Leerstring führt zu entsprechendem Resultat (splitOptions berücksichtigen)
    10. If count = 0 OrElse separator.Length = 0 Then
    11. If splitOptions = StringSplitOptions.RemoveEmptyEntries AndAlso String.Empty = input Then
    12. Return New String() {}
    13. Else
    14. Return New String() {input}
    15. End If
    16. End If
    17. count -= 1 'Platz für ein zusätzliches Element reservieren (durch splitOptions erzeugtes Fehlverhalten nicht möglich)
    18. Dim buffer As New List(Of String)()
    19. Dim index As Integer = 0
    20. Dim marker As Integer = -1 'marker = -1 signalisiert, dass zuletzt ein Split-Element war
    21. Dim length As Integer = input.Length
    22. While count >= 0 AndAlso index < length
    23. For Each s As String In separator
    24. Dim ind As Integer = index 'Index merken, falls ein Inhalt zwischen zwei Trennzeichen sein sollte
    25. If IsStr(input, s, index) Then
    26. If marker = -1 Then
    27. 'Leere Stringsequenz vorschalten, wenn sonst Separator auf Separator folgen würde
    28. If splitOptions <> StringSplitOptions.RemoveEmptyEntries Then
    29. buffer.Add(String.Empty)
    30. End If
    31. Else
    32. buffer.Add(input.Substring(marker, ind - marker))
    33. marker = -1
    34. End If
    35. buffer.Add(s)
    36. Continue While
    37. End If
    38. Next
    39. If marker = -1 Then marker = index
    40. index += 1
    41. End While
    42. If marker = -1 Then
    43. 'Leere Stringsequenz hinterherschieben, wenn sonst Separator auf Separator folgen würde
    44. If splitOptions <> StringSplitOptions.RemoveEmptyEntries Then
    45. buffer.Add(String.Empty)
    46. End If
    47. Else
    48. buffer.Add(input.Substring(marker, index - marker))
    49. marker = -1
    50. End If
    51. Return buffer.ToArray()
    52. End Function
    53. 'Überprüft, ob der Text in input am eingehenden index äquivalent mit dem Text aus comp ist. Falls das der Fall ist, wird index um die Länge von comp erhöht
    54. Private Shared Function IsStr(input As String, comp As String, ByRef index As Integer) As Boolean
    55. 'index springt als marker ein
    56. If input.Length < index + comp.Length Then Return False '
    57. Dim ind As Integer = 0
    58. Dim dest As Integer = comp.Length
    59. 'Gibt's noch weitere Buchstaben, die überprüft werden sollen?
    60. While ind < dest
    61. 'Falls die beiden Buchstaben nicht gleich sind, wird der Index nicht verändert, die Funktion gibt False zurück
    62. If input(index + ind) <> comp(ind) Then Return False
    63. ind += 1
    64. End While
    65. 'Index wird erhöht, wenn die beiden Strings gleich waren (der Teilstring und comp) und die Funktion gibt True zurück
    66. index = index + ind
    67. Return True
    68. End Function


    Sogar brav auf Iterator verzichtet, obwohl sich das eigentlich fast angeboten hätte. Der Code ist alles andere, als performant.

    Count habe ich jetzt zu implementieren vergessen. Das sollte aber nicht weiter schwer sein, einfach jedes mal inkrementieren und abfragen, ob es -1 ist (und wenn, dann einfach den Reststring anhängen und Exit While aufrufen, ansonsten munter weiterarbeiten.

    IsStr ist zwar blöd benannt, aber es ist eine mehr oder minder elegante Methode, den Vergleich mit dem Inkrementieren des Indexes zu vereinen.

    Gruß
    ~blaze~

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

    @~blaze~

    Danke dir für deine Idee :thumbsup: , allerdings habe ich als ich das gesehen hatte schon selbst ein wenig rumexperimentiert:

    VB.NET-Quellcode

    1. Public Function NewSplit(ByVal term As String, ByVal splitter As String()) As List(Of String)
    2. Dim Buffer As String = ""
    3. Dim tokens As New List(Of String)
    4. Dim d As Double
    5. For index As Integer = 0 To term.Length - 1
    6. If splitter.Contains(Buffer) Then
    7. tokens.Add(Buffer)
    8. Buffer = ""
    9. End If
    10. Dim LoopEx As Boolean = False
    11. Dim Counter As Integer = index
    12. While Double.TryParse(Buffer & term(Counter).ToString, d)
    13. Buffer = Buffer + term(Counter).ToString
    14. LoopEx = True
    15. If Counter + 1 < term.Length Then
    16. Counter = Counter + 1
    17. Else
    18. Exit While
    19. End If
    20. End While
    21. If LoopEx Then
    22. tokens.Add(Buffer)
    23. Buffer = ""
    24. End If
    25. If (Double.TryParse(Buffer, d) AndAlso Not Double.TryParse(term(Counter).ToString, d)) Then
    26. Buffer = ""
    27. End If
    28. index = Counter
    29. Buffer = Buffer + term(index).ToString
    30. Next
    31. If splitter.Contains(Buffer) Then
    32. tokens.Add(Buffer)
    33. End If
    34. Return tokens
    35. End Function


    Es funktioniert zwar soweit ich das beurteilen kann, allerdings werde ich es erst nochmal ausgiebig testen bevor ich es einbaue. Sollte meine Lösung starke Peformance Probleme aufweisen komme ich auf dich oder @WhitePage zurück :)

    8-) faxe1008 8-)

    faxe1008 schrieb:

    VB.NET-Quellcode

    1. If Counter + 1 < term.Length Then
    Wenn Du die If-Logik invertierst, brauchst Du keine Else, das liest sich dann besser:

    VB.NET-Quellcode

    1. If Counter > term.Length Then
    2. Exit While
    3. End If
    4. Counter += 1
    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!