Prüfen, ob String eine nichtnegative ganze Zahl enthält

  • VB.NET

Es gibt 54 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    @Peter329 Worum geht es denn?
    Als Anhaltspunkt:
    - Anzahl der Menschen in Deutschland / der Welt
    - Anzahl der Autos in Deutschland / der Welt
    - Anzahl der Hamdys in Deutschland / der Welt
    usw.
    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!

    VaporiZed schrieb:

    Das mit dem Double kapier ich immer noch nicht.


    Okay nach dem Testen hab ich verstanden, warum du auf dem Schlauch stehst. Ich hatte angenommen dass man "1,0" in Integer Parsen kann, aber das geht offensichtlich gar nicht. Dann kann man die y-Variable tatsächlich in die Tonne treten.


    Ein Computer wird das tun, was du programmierst - nicht das, was du willst.

    Yanbel schrieb:

    Ich hatte angenommen dass man "1,0" in Integer Parsen kann,
    Das geht mit amerikanischer Kultur, da ist das , der Tausender-Separator.
    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!
    @Yaribel hat doch vollkommen Recht ... genauso ist das ... Dezimale Nachkommastellen kann man nicht als Integer parsen, selbst wenn sie 0 sind. Aber das haben wir jetzt ja geklärt.

    Und deshalb sollten wir niemanden verwirren.

    Die Frage wie man einen String als "IsWholeNonNegative" abprüft ist doch beantwortet ... sogar unter Beachtung der Tausender Trennzeichen und der verschiedenen Cultures.

    Und deswegen sollten wir das Thema beenden ... es sei denn es gäbe wirklich noch eine wesentliche Anmerkung zu machen.

    LG
    Peter

    Peter329 schrieb:

    Die prinzipielle Auigabe, einen String auf die Eigenschaft "IsWholeNonNegative" zu prüfen, halte ich inzwischen für ausdiskutiert.
    Oh - meinen abschliessenden Senf habich noch garnet abgesondert:
    1) mit Double.TryParse die Zahl lesen, wenns denn eine ist
    2) mit Double.Round feststellen, obs einen Rest gibt
    3) mit CUInt() einen UInt von machen

    keine String-Operationen, und sollte in < 10 Zeilen abgefrühstückt sein.
    @EDR Na ja, deine Routine deckt aber nicht alle genannten Anforderungen ab, soweit ich das sehe.

    Weil meine obige Routine noch einige kleine Fehler enthält, stelle ich hier nochmal meine finale Version ein. Damit man im Netz eine vernünftige Routine findet. :) Ich habe den Ablauf noch ein bissl intuitiver gestaltet und insbesondere die culture (soweit sinnvoll) dynamisch gehalten.

    VB.NET-Quellcode

    1. ''' <summary>
    2. ''' Convert string to integer:
    3. ''' Input string must designate a whole, non-negative number in display format.
    4. ''' Leading and trailing blanks are accepted.
    5. ''' One + sign (floating or non-floating) is accepted.
    6. ''' Decimal numbers are accepted as long as all fractions are zero (e.g. 180,00).
    7. ''' Exponential notations (e.g. 2e5) are not accepted.
    8. ''' Expressions (e.g. 2^8) are not accepted.
    9. ''' </summary>
    10. ''' <param name="s">Input string</param>
    11. ''' <param name="x">Result number</param>
    12. ''' <returns>True=converted, False=failed</returns>
    13. '''
    14. Public Function IsWholeNonNegative(s As String, ByRef x As Integer) As Boolean
    15. 'Initialize return value
    16. x = 0
    17. 'Get separators according to current culture (decimal, grouping)
    18. Dim DSep =
    19. Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator
    20. Dim GSep =
    21. Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator
    22. 'Groups of thousand are assumed (this may not hold true for all cultures)
    23. Dim gl = 3 'Group length
    24. 'Check valid digits
    25. Dim str = s.Trim 'Remove leading and trailing spaces
    26. If str.StartsWith("+") Then _
    27. str = str.Substring(1).Trim 'Remove one + sign (non floating)
    28. Dim ValDigits = "0123456789" & DSep & GSep
    29. For i = 0 To str.Length - 1
    30. If Not ValDigits.Contains(str(i)) Then Return False
    31. Next
    32. 'Check grouping separarators
    33. Dim strw = str.Split(CChar(DSep)) 'Remove decimal fractions
    34. Dim blk = strw(0).Split(CChar(GSep)) 'Get blocks of thousand
    35. Dim cntblk = blk.Length 'Get number of blocks
    36. If cntblk > 1 Then 'Group separators specified
    37. Dim l = blk(0).Length 'Check length of first block
    38. If Not (1 <= l AndAlso l <= gl) Then Return False
    39. End If
    40. For i = 1 To cntblk - 1 'Check length of subsequent blocks
    41. If blk(i).Length <> gl Then Return False
    42. Next
    43. 'Check input is a whole number
    44. If Not Int32.TryParse(str,
    45. NumberStyles.Any,
    46. Thread.CurrentThread.CurrentCulture,
    47. x) Then Return False
    48. 'Check input is non negative
    49. If Not x >= 0 Then Return False
    50. 'Check completed
    51. Return True
    52. End Function


    Nochmals vielen Dank für eure unschätzbare Hilfe.

    LG
    Peter

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „Peter329“ ()

    Ein Wurf:

    VB.NET-Quellcode

    1. <Extension>
    2. Public Function TryParseAsPositive(ByRef x As Integer, s As String) As Boolean
    3. Dim d = 0.0
    4. If Not Double.TryParse(s.Trim.TrimStart("+"c, " "c), d) Then Return False
    5. If d - Math.Round(d) <> 0.0 Then Return False
    6. x = CInt(d) : Return True
    7. End Function
    Folgende Eingaben werden als Ganzzahl akzeptiert:

    VB.NET-Quellcode

    1. Dim x = 0
    2. Dim b = x.TryParseAsPositive("+300.214,0000")
    3. b = x.TryParseAsPositive(" + 300.214,0000")
    4. b = x.TryParseAsPositive(" +2e2")
    1. Geparst wird automatisch in der aktuellen Kultur.
    2. Dass die e-Schreibe akzeptiert wird finde ich vorteilhaft.
    3. Hab ich dich schoma auf die in vb.net vorgesehene Schreibweise für Char-Literale aufmerksam gemacht? Es gibt dafür eine vorgesehene Schreibweise, eine TypConvertierung mittels CChar() ist unangebracht.

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

    ErfinderDesRades schrieb:

    Hab ich dich schoma auf die in vb.net vorgesehene Schreibweise für Char-Literale aufmerksam gemacht? Es gibt dafür eine vorgesehene Schreibweise, eine TypConvertierung mittels CChar() ist unangebracht.


    Na, dann wäre ich dankbar, wenn du mir einfach sagst, wie ich das gemäß der reinen Lehre kodieren soll. Ich bin ja lernfähig.

    Deine Routine funktioniert recht gut. Die Abfrage auf "Zahl ist negativ" fehlt ... aber das ist ja nicht der Rede wert.

    Allerding nimmt deine Routine auch die folgenden Eingaben klaglos entgegen:

    1.8 (liefert 18 - das ist vor allem dann blöd, wenn die (ungültige) Eingabe 1,8 gemeint war!)

    1.2.3.4 (liefert 1224)

    1.........1 (liefert 11)

    Das dürften die meisten Anwender als Fehlfunktion einordnen. :)

    Und dass die Eingabe 2e5 angenommen wird und 200.000 liefert, dürfte "normalen" Anwendern auch nicht so ganz einfach zu vermitteln sein. :) In den meisten Fällen wird nämlich die beabsichtigte Eingabe 245 gewesen sein ... man hat sich eben mit der 4 vertan und "e" eingetippt ... und das dürfte dann schon zu einigen geharnischten Beschwerden führen !

    LG
    Peter

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Peter329“ ()

    Peter329 schrieb:

    1.2.3.4 (liefert 1224)
    1.........1 (liefert 11)
    Das haben wir doch alles schon diskutiert.
    Wenn Du das nicht haben willst, musst Du bei der TextBox einfach diese Tastendrücke verbieten und feddich.
    Der andere Weg, bei der Konvertierung die Separatoren zu unterdrücken ermöglicht Fehleingaben, die nicht konvertiert werden,
    da finde ich es besser, die Tasten rauszunehmen.
    Äquivalent dazu müsstest Du dann bei C&P den kompletten String ablehnen, wenn Trennzeichen enthalten sind.
    Probieren kannst Du das Soll-Verhalten mit nem NumericUpDown.
    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!

    RodFromGermany schrieb:

    Das haben wir doch alles schon diskutiert.


    Jau, das haben wir schon diskutiert. Aber anscheinend hast du meine Antwort darauf noch immer nicht gelesen.

    Ich nehme Eingaben von Anwendern so entgegen wie sie sind. Das mag nicht jedem gefallen, aber so handhabe ich das. Und zwar aus gutem Grund.

    Darüber möchte ich auch nicht weiter diskutieren. Weder ein NumericUpDown noch irgendwelche KeyPress Routinen stehen hier zur Debatte. Ich weiß, was ich will. Und ich weiß, was ich nicht will. Denn schließlich muss ich das Coding hinterher vertreten. Ich hoffe, dass wir diese Debatte damit einvernehmlich beenden können. :)

    LG
    Peter

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

    uih - danke fürs testen! Das hätt ich nicht gedacht, wie man muss schon sagen: fehlerhaft die Parse-methode mit Gruppen-Separatoren umgeht.

    Peter329 schrieb:

    Na, dann wäre ich dankbar, wenn du mir einfach sagst, wie ich das gemäß der reinen Lehre kodieren soll. Ich bin ja lernfähig.
    Na, in Zeile #4 in meim Snippet ists zu sehen einen char schreibt man wie einen String, aber c anhängen: "x"c, "4"c, ....
    Ich erzähle das immer wieder wenn ich cchar("x") sehe, aber neulich fand das jemand überflüssig zu wissen, daher meine vorsichtige Ausdrucksweise.

    Jo, dann bastel ich mal weiter. Wie gesagt: das mit den Gruppen-Separatoren ist ja schaurig, da muss man ja wirklich ein eigenes Parsen implementieren.

    Peter329 schrieb:

    Das dürften die meisten Anwender als Fehlfunktion einordnen.
    Naja - man könnte sich aber auch auf den Standpunkt stellen:
    "Lernt ihr mal, wie man Zahlen schreibt. 1....1 ist nunmal 11 in Deutschland. Und 2e2 ist numal 200 - sogar auf der ganzen Welt"
    Also von wegen "Fehlfunktion" können sie eiglich nicht kommen. (Aber natürlich dürfen sie sich ein anderes Verhalten wünschen)

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Guck - nun sowas:

    VB.NET-Quellcode

    1. <Extension>
    2. Public Function TryParseAsPositive(ByRef x As Integer, s As String) As Boolean
    3. Dim d = 0.0
    4. Dim DSep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator(0)
    5. Dim GSep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberGroupSeparator(0)
    6. If s.IndexOfAny("-e".ToCharArray) >= 0 Then Return False ' forbidden chars
    7. If s.Split("+"c).Length > 2 Then Return False ' maximal ein '+'
    8. Dim thousandGroups = s.Split(DSep)(0).Split(GSep).Skip(1) ' tausendergruppen, ggfs. vor dem ersten Komma
    9. If thousandGroups.Any(Function(g) g.Length <> 3) Then Return False
    10. If Not Double.TryParse(s.TrimStart(" "c, "+"c).TrimEnd(" "c), d) Then Return False
    11. If d - Math.Round(d) <> 0.0 Then Return False
    12. If d < 0.0 Then Return False
    13. x = CInt(d) : Return True
    14. End Function
    15. Private Sub TestTryParsePos(s As String, expectedResult As Integer?)
    16. Dim x = 0
    17. Dim b = x.TryParseAsPositive(s)
    18. ErgebnisBewerten(b, x, expectedResult)
    19. b = x.IsWholeNonNegative(s)
    20. ErgebnisBewerten(b, x, expectedResult)
    21. End Sub
    22. Private Sub ErgebnisBewerten(b As Boolean, x As Integer, expectedResult As Integer?)
    23. If expectedResult.HasValue Then
    24. If b Then
    25. If x <> expectedResult.Value Then Throw New Exception("falsches Ergebnis")
    26. Else
    27. Throw New Exception("es wurde ein Ergebnis erwartet, aber keines geliefert")
    28. End If
    29. Else
    30. If b Then Throw New Exception("es wurde kein Ergebnis erwartet, aber eines geliefert")
    31. End If
    32. End Sub
    33. Public Sub TryParseAsPositiveTests()
    34. 'positive Ganzzahl, ein '+' zulässig, tausenderGruppen zulässig, als Nachkommastellen nur '0' zulässig, e-Schreibweise unzulässig
    35. TestTryParsePos("0", 0)
    36. TestTryParsePos("4", 4)
    37. TestTryParsePos("4, ", 4)
    38. TestTryParsePos("4,0 ", 4)
    39. TestTryParsePos("4,0+", Nothing)
    40. TestTryParsePos("4+4", Nothing)
    41. TestTryParsePos("4,001", Nothing)
    42. TestTryParsePos("4,00.00", Nothing)
    43. TestTryParsePos("4,00,00", Nothing)
    44. TestTryParsePos("4,00 00", Nothing)
    45. TestTryParsePos("-4", Nothing)
    46. TestTryParsePos(" + 300.214,0000", 300214)
    47. TestTryParsePos(" + + 300.214,0000", Nothing)
    48. TestTryParsePos("300 214", Nothing)
    49. TestTryParsePos("2e2", Nothing)
    50. TestTryParsePos("1.8", Nothing)
    51. TestTryParsePos("1.8.4", Nothing)
    52. TestTryParsePos("1.....1", Nothing)
    53. End Sub

    Hier zeigt sich (malwieder), dass man Algorithmen kaum ohne komfortablen Testing-Code entwickeln kann.
    Die Tests-Sub ist nun auch die vollständige Definition der Anforderung (zumindest sollte man sie so gestalten).

    Aber ich muss zugeben: Es ist eindeutig viel viel ekliger als ich ursprünglich gedacht hab!
    Oh - ich würde sogar noch weiter gehen: Letztendlich finde ich mein Ergebnis kaum besser als deines (bisserl linq-hübscher aber auch nicht weniger umständlich - was ja eiglich mein Ziel war).

    Dieser Beitrag wurde bereits 13 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Na, nach bisher 13 Edits ist deine Routine ja doch ganz annehmbar geworden. :)

    Allerdings fehlt die Prüfung der Länge des ERSTEN Groupings ... Die Eingabe 123456.000 liefert die Zahl 123456000 ... und auch das würden normal veranlagte Menschen vielleicht nicht für ganz so gelungen halten. Vor allem dann, wenn sie sich vertippt haben sollten und eigentlich 123456,000 schreiben wollten.

    Die Prüfung kann man natürlich noch leicht einbauen. Nur klappt dann deine geniale LINQ Lösung mit .Skip(1) und Function(g) nicht mehr so ohne weiteres. Wie schade aber auch. :)

    Ich gebe dir Recht: deine Lösung ist dann tatsächlich nicht so revolutionär anders als mein bescheidener Lösungsvorschlag. Im Wesentlichen bleibt doch nur noch der Unterschied, dass ich mit Int2.TryParse auf GANZZAHLIGE Eingabe prüfe, während du mit Double.TryParse auf NUMERISCHE Eingabe prüfst und die Ganzzahligkeit mit der etwas sperrigen Anweisung

    VB.NET-Quellcode

    1. If d - Math.Round(d) <> 0.0 Then Return False


    abhandelst.

    Ganz offen gestanden, finde ich meine int32.TrypParse Lösung dann doch irgendwie eleganter. Aber das ist wieder eine Geschmacksfrage ... und über Geschmack lässt sich bekanntermaßen nun mal nicht streiten.

    Jetzt sollte die Sache aber wirklich final ausdiskutiert sein ... :)

    Aber vielleicht kannst du mir doch noch einen Gefallen tun:

    Die Anweisung

    VB.NET-Quellcode

    1. Dim DSep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator(0)


    liefert einen CHAR. Kannst du mir sagen, warum das so ist ? Was macht denn die (0) mit dem String ?

    Und noch etwas:

    <Extension>

    Was ist das für eine Geschichte ?

    LG
    Peter

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

    Peter329 schrieb:

    Ganz offen gestanden, finde ich meine int32.TrypParse Lösung dann doch irgendwie eleganter. Aber das ist wieder eine Geschmacksfrage ... und über Geschmack lässt sich bekanntermaßen nun mal nicht streiten.
    Ich sehe das anders:
    Es ist nicht "wieder" eine Geschmacksfrage, sondern ausnahmsweise mal.



    Zu Dim DSep = Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator(0): Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator ist ja ein String.
    Ja, und String ist indizierbar. Das heisst, es gibt eine parametrisierte DefaultProperty wo du für einen Index ein Element bekommst.
    Ebenso wie bei List(Of T). Da gibts die DefaultProperty Item(index As Integer) As T, und die gibt dir für den index ein T zurück. Voll ausgeschrieben heists also:

    VB.NET-Quellcode

    1. dim lst = {2,3,4}.ToList()
    2. dim element2 = lst.Item(1)
    Und weil Item die DefaultProperty ist, kann man den Property-Namen weglassen, und schreiben:

    VB.NET-Quellcode

    1. dim lst = {2,3,4}.ToList()
    2. dim element2 = lst(1)

    Bei String ists genauso, nur heisst da die DefaultProperty .Chars
    Kannste nachgucken im ObjectBrowser.
    Ach du sch.... Microsoft hat den OB degeneriert: Der zeigt jetzt nicht mehr an, wenn eine Property die DefaultProperty ist.
    Also früher konnteste das nachgucken im OB - heute siehste nur noch die String-Property Chars(index As Integer) as Char, aber er zeigt nicht mehr an, dass Chars die DefaultProperty ist, und somit ein Indexer :cursing:



    <Extension> - das ist eine lange Geschichte. Ein sehr nützliches Sprach-Feature, das globale Modul-Methoden so tun lässt, als seien sie Objekt-methoden.
    wie du siehst - post#53, zeile #18 - mein x.TryParseAsPositive(s) tut so, als wäre sie eine Objekt-Methode des Integer-Objekts. Isses aber nicht, weil ich kann ja den Code der Integer-Struktur des Frameworks nicht ändern.
    Aber ich kann eine Extension-Methode schreiben, die so tut als ob.
    Also ohne syntaktischen Zucker stünde da: TryParseAsPositive(x, s). Aber syntaktisch gezuckert ist besser (keine Geschmacksfrage!), weil Intellisense dir das vorschlägt, wenn du mit Integern herumhantierst.

    Besonders Linq macht exzessiven Gebrauch von Extensions.
    guck das dim lst = {2,3,4}.ToList() - das tut so, als ob .ToList() eine ObjektMethode der Array-Klasse wäre. Isses aber nicht, ist nur eine Extension.
    Ohne syntaktischen Zucker stünde da: dim lst = ToList({2,3,4}).