[C++] Da $ a \(

marco2132k
Ciao. Volevo scrivere una funzione [inline]latex2e_fy[/inline] che data una stringa come
Sia $ G $ un gruppo.
ritornasse la stringa
Sia \( G \) un gruppo.


Non so bene come fare. Ho scritto un prototipo che produce
Sia ( G ) un gruppo.
perché non so bene come trattare i due caratteri di [inline]\([/inline].

Inoltre conosco pochissimo il linguaggio e non ho idea se quello che ho scritto è buon codice (in particolare se avete voglia di spiegarmi come si potrebbe fare una cosa del genere in C++ moderno ciò è molto ben accetto).

#include <iostream>
#include <string>

std::string latex2e_fy(std::string s) {
    bool d = false;
    for(std::string::iterator c = s.begin(); c != s.end(); ++c) {
        if(*c == '$' && d == false) {
            d = true;
            *c = '(';
        }
        if(*c == '$' && d == true) {
            *c = ')';
            d = false;
        }
    }
    
    return s;
}

int main() {
    std::cout << latex2e_fy("Sia $ G $ un gruppo.");
    return 0;
}


Come bonus: come si potrebbe fare una cosa del genere in (buon) Python (3)?

Risposte
megas_archon
Anche tu lo odi quando devi scrivere qui, vero?

Se usi vim fai come me: installa Ultisnips https://github.com/SirVer/ultisnips e poi definisci uno snippet così:
snippet '\$(.+?)\$' "mentify text" wrA
\(`!p snip.rv = match.group(1)`\)
endsnippet
(questo è anche la risposta alla domanda bonus, perché stai usando una regex e del pyhton basilare per trasformare ogni gruppo [inline]$culo$[/inline] in [inline]\(culo\)[/inline] mentre scrivi).


PS: C++? A meno che tu non debba programmare un videogioco o un semaforo, eviterei.

utente__medio11
Ciao.

- se ho ben capito vuoi che le sottostringhe
$ "something" $

siano sostituite con
\( "something" \)

, giusto?

- il carattere $ può comparire nella stringa solo con l'accezione sopra evidenziata?

- la stringa in input può essere ritenuta sintatticamente corretta oppure andrebbe comunque controllata?

marco2132k
"megas_archon":
Anche tu lo odi quando devi scrivere qui, vero?
Ahah sì, ma almeno i [inline]\([/inline], [inline]\)[/inline] sono LaTeX standard.

Comunque volevo solo vedere se mi ricordavo come si scrivevano due righe di codice. Credo che si potesse fare anche con una cosa come https://www.gnu.org/software/emacs/manu ... place.html, ma la voglia di imparare le regex oggi non c'è.

@utente_medio Rispondo "Sì" a tutte le tue domande, anche se ovviamente non ho un'idea ben precisa di come dovrebbe essere fatta una stringa ben formata.

"megas_archon":
PS: C++? A meno che tu non debba programmare un videogioco o un semaforo, eviterei.
Oddio, assieme alla STL non è meno scriptabile di Python. Credo che in un linguaggio tipo Haskell invece la soluzione sia uno one-liner...

megas_archon
Ma anche in bash è uno oneliner, eh...

megas_archon
Adesso non ho tempo di scrivere tutto per bene, ma quello che vuoi è parsare il tuo file cercando cose del tipo [inline]\$[^\$\n]{1,}\n[/inline] e alzando un errore se ce ne sono: se ce ne sono significa che c'è una cosa del genere
$matematica bellissima ma
dollari che non matchano$

e una regex "stupida" non può gestire queste situazioni.
Quindi, come si fa questo in python? Non lo so, ma non penso sia molto diverso da un try-catch con un messaggio di errore personalizzato...

Quindi è meglio vietarle all'origine e chiedere all'user (tu o altri) di presentare solo un file dove il dollaro "che apre" e quello "che chiude" sono sulla stessa riga.

Fatto questo, la regex è la stessa, cerchi [inline]\$(.+?)\$[/inline] e vuoi sostituirlo con [inline]\($1\)[/inline]. A seconda del linguaggio in cui scrivi cambiano le wildcards delle espressioni regolari e anche il modo specifico di implementarli (e.g. dove stanno di preciso i gruppi che la regex catcha) quindi non provo nemmeno a farlo oggi, tanto piu che sta saltando la luce ogni dieci minuti...

utente__medio11
"marco2132k":
@utente_medio Rispondo "Sì" a tutte le tue domande

In tal caso penso che qualcosa del genere potrebbe andare:
#include <iostream>
#include <string>

using namespace std;

string fun(const string &s_in)
{
    string s_out;
    for(unsigned int flag = false, i = 0; i < s_in.size(); ++i)
    {
        if(s_in[i] == '$')
        {
            s_out.push_back('\\');
            s_out.push_back((flag = !flag) ? '(' : ')');
        }
        else
        {
            s_out.push_back(s_in[i]);
        }
    }
    return s_out;
}

int main()
{
    cout << fun("Sia $ G $ un gruppo.") << endl;
}

megas_archon
import re

pattern = re.compile("\$[^\$\n]{1,}\n")

for i, line in enumerate(open('cose.txt')):
    for match in re.finditer(pattern, line):
        print( 'Unmatched dollar before newline on line %s: %s' % (i+1, match.group()))

Questo serve a trovare i dollari solitari. Ovviamente si può fare di meglio:

- dando contesto all'utente (printa la riga del match, la precedente, e la successiva)
- colorando il match di rosso

ma adesso ho voglia zero. Il resto è banale, e in effetti è uno script che già ho: adattalo da qui

#!/usr/bin/env python3
import re, os, sys

good_delims = lambda x: re.sub(r"\$(.*?)\$", r"\\(\1\\)", x, flags = re.M)

with open (sys.argv[1], 'r+' ) as f:
    content = f.read()
    content = good_delims(content)
    f.seek(0)
    f.write(content)
    f.truncate()

os.system("latexindent -w " + sys.argv[1])

Fammi sapere se funonzia.

marco2132k
"megas_archon":
[È] meglio vietarle all'origine e chiedere all'user (tu o altri) di presentare solo un file dove il dollaro "che apre" e quello "che chiude" sono sulla stessa riga.
In teoria se la stringa è "ben formata" (cioè è del LaTeX scritto nella vita reale) una cosa del genere può capitare solo se l'editor fa hard wrapping, no?

Poi provo se quello che hai scritto tu funzia ma faccio un po' fatica a capire cosa fa (perché non conosco bene né Python né appunto le regex).

@utente_medio Leggendo il tuo snippet mi è venuto in mente che la mia [inline]latex2e_fy[/inline] ha side effects. Ci riprovo.

#include <fstream>
#include <iostream>
#include <string>

std::string latex2e_fy(const std::string& s_in) {
  bool d = false;

  std::string s_out;
  s_out.reserve(s_in.length());

  for(auto c : s_in) {
    if(c == '$' && d == false) {
      s_out.append("\\(");
      d = !d;
    }
    else if (c == '$' && d == true) {
      s_out.append("\\)");
      d = !d;
    }
    else {
      s_out.push_back(c);
    }
  }
  return s_out;
}

int main(int argc, char** argv) {
  if(argc < 3) {
    std::cout << "Utilizzo: latex2e_fy input file output file.";
    return 1;
  }

  std::ifstream f_in(argv[1]);
  std::ofstream f_out(argv[2]);

  if(f_in.is_open() && f_out.is_open()) {
    std::string current_line;
    while(std::getline(f_in,current_line)) {
      f_out << latex2e_fy(current_line) << std::endl;
    }
  }

  f_in.close();
  f_out.close();

  return 0;
}


Questo apparentemente funzia.

Domanda: ho pensato di usare [inline]reserve[/inline] per migliorare le prestazioni. E invece le peggiora. Come mai?

kaspar1
[ot]Tipicamente, questa roba è da Perl. Ad esempio:
#!/usr/bin/env perl -w

# usage: $ ./this-script.pl < input.tex > output.tex

my $delim = "\\("; my $line;

while ($line = <STDIN>) {
  while ($line =~ /[^\\]\$/) {
    $line =~ s/([^\\])\$/$1$delim/;
    $delim = $delim eq "\\(" ? "\\)" : "\\(";
  }
  print $line;
}

In Haskell comunque non è complicato: con [tt]interact[/tt] si può fare qualcosa di decente...[/ot]

apatriarca
Alcuni commenti:

    [*:1jugthex] Non è possibile modificare una stringa con un'altra più lunga "in-place". È quindi sempre necessario avere un input e output separati in operazioni come queste.[/*:m:1jugthex]
    [*:1jugthex] Le classi stringa sono raramente ottimizzate per operazioni come questa. In C++ sarebbe meglio fare uso di stringstream. In Python esistono classi simili come StringIO.[/*:m:1jugthex]
    [*:1jugthex] Se si desidera che questo strumento funzioni correttamente è necessario considerare alcune situazioni particolari. Prima di tutto se il dollaro è preceduto da '\' allora va ignorato. TeX supporta poi anche '\$\$' per indicare una formula in una riga indipendente. Scopro ora che in realtà non è ufficialmente supportato in LaTeX ma ho l'impressione abbia sempre funzionato lo stesso. Almeno in teoria è poi necessario assicurarsi che il paragrafo non finisca all'interno dell'ambiente matematico perché indicherebbe un file LaTeX non corretto. Ovviamente non ha importanza se si tratta di un esercizio.[/*:m:1jugthex][/list:u:1jugthex]

    In Python puoi scrivere qualcosa del genere (non l'ho testato):
    from io import StringIO
    
    def latex2e_fy(s):
        output = StringIO()
        met_dollar = False
    
        # Iterate over the string an write the desired output to the StringIO object
        for c in s:
            if c != '$':
                output.write(c)
            else:
                output.write(r'\)' if met_dollar else r'\(')
                met_dollar = not met_dollar
    
        if met_dollar:
            # There is an odd number of dollars
            # You may want to handle an invalid input differently but I am lazy here
            return None
    
        result = output.getvalue()
        output.close()
        return result
    

    Puoi scrivere anche meno codice di così (usando [tt]accumulate/reduce[/tt] e [tt]join[/tt] per esempio) ma ho cercato di renderlo leggibile. In C++ il codice sarebbe abbastanza simile.

kaspar1
[ot]Era stato menzionato Haskell. La cosa bella è che non hai bisogno di espressioni regolari o librerie assurde; ad esempio:
-- L'uso è lo stesso dello script Perl sopra...

main :: IO ()
main = interact change_delims

change_delims :: String -> String
change_delims = helper '('
  where
    helper :: Char -> String -> String
    helper c ('\\':'$':str) = '\\':'$':helper c str
    helper c ('$':str) =
      (\d -> '\\':c:helper d str)
        (if c == '(' then ')' else '(')
    helper c (s:ss) = s:helper c ss
    helper _ "" = ""

Probabilmente la cosa si può ottimizzare considerando [inline]ByteString[/inline]... ma già va bene.

Come lo script in Perl sopra, pensa a non trattare il dollaro escappato, cioè [inline]\$[/inline].[/ot]

kaspar1
Non so se la cosa interessa ancora, o se tu usi *nix.
$ perl -i.bak -lpe 's/\$([^\$]+)\$/\\($1\\)/g' hello.tex

È una soluzione molto breve ma funzionante se non hai dollari pendenti sul tuo sorgente TeX né dollari escappati. (La soluzione in Perl che ho dato prima non si scrive in una riga, ma almeno si cura di questi due aspetti.) Per sicurezza viene creata una copia di backup con estensione ".bak"

Esempio:
$ cat <<"EOF" > hello.tex
A formula: $E = mc^2$. Some text. Another formula $a^2 + b^2 = c^2$.
Yet another formula $\lambda x . x$. Blah blah blah...
EOF

$ perl -i.bak -lpe 's/\$([^\$]+)\$/\\($1\\)/g' hello.tex

$ cat hello.tex
A formula: \(E = mc^2\). Some text. Another formula \(a^2 + b^2 = c^2\).
Yet another formula \(\lambda x . x\). Blah blah blah...


Vedi questa documentazione di Perl.

Rispondi
Per rispondere a questa discussione devi prima effettuare il login.