Uwe: Punkt gegen Komma tauschen mit Nebenbedingungen

Hallo zusammen

Ich versuche, Punkte gegen Kommata auszutauschen. Dies soll aber nur passieren, wenn es sich um eine normale Zahl handelt. Der zu verarbeitende String sieht z.B. wie folgt aus
my $test="192.168.34.2 <td>-15.34 %</td> +5.644 3.2<p> 13.12.2005</p>";
Der String macht jetzt keinen Sinn, aber demonstriert das Problem. Ich möchte nämlich nur den Punkt in -15.34 und 5.644 und 3.2 gegen ein Komma austauschen. Meine bisher beste Idee war $test =~ s/(\d+?).(\d+?)(?=[^.,\d])/$1,$2/g; welche aber auch nicht ganz das gewünschte Ergebnis liefert.

Hat jemand eine Idee?

Viele Grüße
Uwe

  1. Tag Uwe.

    my $test="192.168.34.2 <td>-15.34 %</td> +5.644 3.2<p> 13.12.2005</p>";
    [...]
    Hat jemand eine Idee?

    Wenn du die Datenstruktur abstrakt beschreiben könntest, wäre das sicher hilfreicher als oben stehender Teststring.

    Siechfred

  2. gudn tach!

    verarbeitende String sieht z.B. wie folgt aus

    my $test="192.168.34.2 <td>-15.34 %</td> +5.644 3.2<p> 13.12.2005</p>";

    <scnr>
    lade die entsprechende datei in vim und tippe
    :%s/./,/gc
    nnnyyynn
    </scnr>

    wie Siechfred schon sagte, muesstest du zuerst genau festlegen, was (in deinem fall) (k)eine zahl ist.

    ungetestetes beispiel:
    $test=~s/([ +-]\d+).(\d+(\D{2}|\D$|$))/$1,$2/g;

    vor der zahl darf hier nur ein [ +-] stehen.
    nach der zahl muss entweder
    a) die zeile zu ende sein oder
    b) es duerfen eine nicht-ziffer und dann EOL stehen oder
    c) es duerfen zwei nicht-ziffern stehen.

    prost
    seth

    1. Hallo

      ungetestetes beispiel:
      $test=~s/([ +-]\d+).(\d+(\D{2}|\D$|$))/$1,$2/g;

      Dies funktioniert leider nicht so wie ich mir das vorstelle. Im Beispielstring bleibt z.B. der Punkt bei +5.644 stehen.

      Das Beispiel (siehe erstes Posting) ist anscheinend doch nicht so anschaulich, wie ich gehofft hatte. Hier also die abstrakte Beschreibung:
      Gegeben sei ein Teil eines Strings, bestehend aus Ziffern, Punkten und Kommata, welcher mit einer Ziffer anfängt und aufhört (Die Zeichen vorher und nachher sind beliebig). Enthält der gegebene Teilstring genau einen Punkt und sonst nur Ziffern, soll dieser gegen ein Komma ausgetauscht werden.

      Ich hoffe, jetzt wird klar, was ich meine.

      Vielen Dank für die Antworten
      Uwe

      1. gudn tach!

        ungetestetes beispiel:
        $test=~s/([ +-]\d+).(\d+(\D{2}|\D$|$))/$1,$2/g;
        Dies funktioniert leider nicht so wie ich mir das vorstelle. Im Beispielstring bleibt z.B. der Punkt bei +5.644 stehen.

        so, jetzt sitze ich wieder an einem rechner, bei dem ich besser denken, schreiben und auch testen kann. ja, das, was ich mailte, war nicht gut.
        fuer dein _spezielles_ _beispiel_ (und noch einige faelle mehr) waere aber geeignet:
        $test2=~s/((?:^|\G|[ +-])\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;

        hier wird von links eine zahl begrenzt von /^|\G|[ +-]/
        und direkt nach einer zahl darf kein zeichen stehen, welches eine ziffer ist. ferner darf einer zahl nicht die zeichenfolge "[punkt][ziffer]" folgen.

        was die dinger bedeuten, die du evtl. noch nicht kennst (z.b. "\G"), steht imho sehr gut erklaert auf "perldoc perlre"

        Das Beispiel (siehe erstes Posting) ist anscheinend doch nicht so anschaulich, wie ich gehofft hatte.

        jein. es umfasst schon ein menge faelle, jedoch wissen wir damit ja noch nicht, ob damit schon alle faelle abgedeckt sind, die bei dir auftreten koennen. allgemein ist dieses problem nicht zu loesen, denn woher will man wissen, ob in dem string "12.2004-10.2005" die subtraktion der zahlen 12,2004 und 10,2005 gemeint ist oder der zeitraum von dez. 2004 bis okt. 2005?
        deswegen bat dich Siechfred gleich als erstes nach mehr informationen.

        Hier also die abstrakte Beschreibung:

        rischdisch. mit sowas sollte man bei solchen problemen anfangen.
        und jene beschreibung sollte zwar abstrakt, aber genau und eindeutig sein, damit man's dem doofen computer auch gescheit beibringen kann.

        Gegeben sei ein Teil eines Strings, bestehend aus Ziffern, Punkten und Kommata, welcher mit einer Ziffer anfängt und aufhört (Die Zeichen vorher und nachher sind beliebig).

        wirklich beliebig? duerfen es auch ziffern sein? afais nicht.
        also: welche zeichen oder strings begrenzen eine zahl von links und welche von rechts?

        prost
        seth

        1. gudn tach!

          $test2=~s/((?:^|\G|[ +-])\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;

          hier wird von links eine zahl begrenzt von /^|\G|[ +-]/
          und direkt nach einer zahl darf kein zeichen stehen, welches eine ziffer ist. ferner darf einer zahl nicht die zeichenfolge "[punkt][ziffer]" folgen.

          mir ist noch was eingefallen: "\G" wird hier gar nicht mehr benoetigt, weil vermoege "?!" ja mit _zero_-_length_ gematcht wird.

          also:
          $test=~s/((?:^|[ +-])\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;
          oder
          $test=~s/(?<![^ +-])(\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;

          prost
          seth

          1. Hallo seth

            Vielen Dank für die Antworten.

            mir ist noch was eingefallen: "\G" wird hier gar nicht mehr benoetigt, weil vermoege "?!" ja mit _zero_-_length_ gematcht wird.

            Aha. Ich habe mir die Funktion von "\G" angeschaut und mich gefragt, wofür das in diesem konkreten Fall notwendig ist. Aber jetzt weiß ich, wie ich die Suche an einer Stelle fortsetzen kann.

            also:
            $test=~s/((?:^|[ +-])\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;
            oder
            $test=~s/(?<![^ +-])(\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;

            Ich habe mir meine Datenstruktur noch einmal genau angeschaut und festgestellt, dass vor und nach der Zahlen/Punk(e)-Folgen niemals ein Punkt oder Komma steht. Daher habe ich deine Lösung etwas abgewandelt und benutze jetzt folgendes:
            $test=~ s/((?:[^\d,.])\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;
            Funktioniert einwandfrei und macht genau dass, was es soll. Mit anderen Worten: Problem gelöst :-)

            Viele Grüße

            Uwe

            1. gudn tach Uwe!

              mir ist noch was eingefallen: "\G" wird hier gar nicht mehr benoetigt, weil vermoege "?!" ja mit _zero_-_length_ gematcht wird.
              Aha. Ich habe mir die Funktion von "\G" angeschaut und mich gefragt, wofür das in diesem konkreten Fall notwendig ist.

              das "\G" waere (u.u.) notwendig gewesen, wenn man den hinteren teil nicht mit zero-length (sondern z.b. mit "?:") matcht, denn bei "1.0 2.0" wuerde dann zunaechst "1.0 " (mit dem abschliessenden leerzeichen!) gematcht und anschliessend aber nicht "2.0", weil "2" weder am zeilenanfang noch ein zeichen davor steht.

              Aber jetzt weiß ich, wie ich die Suche an einer Stelle fortsetzen kann.

              ;-)

              Ich habe mir meine Datenstruktur noch einmal genau angeschaut und festgestellt, dass vor und nach der Zahlen/Punk(e)-Folgen niemals ein Punkt oder Komma steht.

              ok, dann kommt also sowas wie "das erste ergebnis ist 1.0, das zweite 2.0." nie vor. dann wird's (wegen dem punkt ".") noch ein wenig leichter, weil man dann nicht pruefen muss, ob danach eine ziffer kommt.

              Daher habe ich deine Lösung etwas abgewandelt und benutze jetzt folgendes:
              $test=~ s/((?:[^\d,.])\d+)\.(\d+)(?!\d|\.\d)/$1,$2/g;
              Funktioniert einwandfrei und macht genau dass, was es soll.

              vorsicht! wegen dem (?:[^\d,.]) _muss_ nun vor jeder zahl ein zeichen stehen. wenn also ein string mit einer zahl beginnt, z.b. "1.0 ...", dann wird ihr punkt nicht ersetzt.
              geschickter ist also evtl.
              $test=~s/(?<![\d,.])(\d+)\.(\d+)(?![\d,.])/$1,$2/g;

              prost
              seth