Oceń wyrażenie podane jako ciąg

283

Jestem ciekawy, czy R może użyć swojej eval()funkcji do wykonania obliczeń dostarczonych np. Przez ciąg.

Jest to częsty przypadek:

eval("5+5")

Jednak zamiast 10 otrzymuję:

[1] "5+5"

Jakieś rozwiązanie?

Federico Giorgi
źródło
6
Pomimo wszystkich odpowiedzi pokazujących, jak rozwiązać to za pomocą analizy składniowej ... Dlaczego musisz przechowywać typy języka w postaci string? Odpowiedź Martina Mächlera powinna zasługiwać na znacznie większe poparcie.
Petr Matousu
7
Dziękuję @PetrMatousu. Tak, jestem zszokowany, widząc, jak błędne informacje rozprzestrzeniają się teraz na SO .. przez ludzi popierających eval(parse(text = *)) fałszywe rozwiązania.
Martin Mächler,
2
Chcę uruchamiać skrypty w postaci:, QQ = c('11','12','13','21','22','23')tj .: QQ = c (..., 'ij', ..) z i, j zmieniającymi się w zakresie, który może być różny w różnych biegach. Dla tego i podobnych przykładów mogę napisać skrypt jako paste( "QQ = c('", paste(rep(1:2,each=3),1:3, sep="", collapse="','"), "')",sep=""), a opcja eval(parse(text=...))tworzy wektor QQ w środowisku roboczym zgodnie ze skryptem. Jaki byłby to odpowiedni sposób kodera R, gdyby nie „text = ...”?
VictorZurkowski

Odpowiedzi:

418

eval()Funkcja ocenia wyrażenie, ale "5+5"jest ciągiem znaków, a nie wyrazem. Użyj za parse()pomocą, text=<string>aby zmienić ciąg znaków na wyrażenie:

> eval(parse(text="5+5"))
[1] 10
> class("5+5")
[1] "character"
> class(parse(text="5+5"))
[1] "expression"

Wywołanie eval()wywołuje wiele zachowań, niektóre nie są od razu oczywiste:

> class(eval(parse(text="5+5")))
[1] "numeric"
> class(eval(parse(text="gray")))
[1] "function"
> class(eval(parse(text="blue")))
Error in eval(expr, envir, enclos) : object 'blue' not found

Zobacz także tryCatch .

Harlan
źródło
27
Jak zauważa poniżej Shane, „Musisz określić, że dane wejściowe to tekst, ponieważ parsowanie domyślnie oczekuje pliku”
PatrickT
1
należy określić skutki uboczne używania eval (parsowania). Na przykład, jeśli masz predefiniowaną nazwę zmiennej równą „David” i ponownie przypisujesz za pomocą eval (parsuj (tekst = „nazwa”) == „Aleksander”), pojawi się błąd, ponieważ eval i parsowanie nie zwracają Wyrażenie R., które można ocenić
Crt
1
@NelsonGon: Nieocenione wyrażenia zbudowane przy użyciu quote(), bquote()lub bardziej wyrafinowanych narzędzi dostarczonych przez rlangpakiet.
Artem Sokolov,
@ArtemSokolov Dzięki, jakoś wracam do tego pytania, szukając alternatywy. Patrzyłem, rlangale najbliższe, jakie znalazłem, to parse_exprktóre połączenia są parse_exprsz kolei takie same, jak używanie parsei zawijanie, evalco wydaje się być tym samym, co tutaj zrobione. Nie jestem pewien, jaka byłaby korzyść z użycia rlang.
NelsonGon,
1
@NelsonGon: z rlang, będziesz pracować bezpośrednio z wyrażeniami, a nie łańcuchami. Nie jest wymagany żaden etap analizy. Ma dwie zalety. 1. Manipulowanie wyrażeniami zawsze spowoduje powstanie poprawnych wyrażeń. Manipulacje ciągami będą produkować tylko prawidłowe ciągi. Nie będziesz wiedział, czy są one prawidłowymi wyrażeniami, dopóki ich nie przeanalizujesz. 2. W substitute()świecie łańcuchów nie ma odpowiednika klasy funkcji, co poważnie ogranicza twoją zdolność do manipulowania wywołaniami funkcji. Rozważ to opakowanie glm . Jak wyglądałby ekwiwalent ciągu?
Artem Sokolov
100

Możesz użyć tej parse()funkcji do konwersji znaków na wyrażenie. Musisz określić, że dane wejściowe to tekst, ponieważ parsowanie domyślnie oczekuje pliku:

eval(parse(text="5+5"))
Shane
źródło
7
> fortuny :: fortune („odpowiedź jest parsowana”) Jeśli odpowiedź jest parsowana (), zwykle powinieneś przemyśleć pytanie. - Thomas Lumley R-help (luty 2005)>
Martin Mächler,
13
@ MartinMächler To ironia, ponieważ podstawowe pakiety R używają parsecały czas! github.com/wch/r-source/…
genorama
49

Przepraszam, ale nie rozumiem, dlaczego zbyt wiele osób uważa, że ​​łańcuch był czymś, co można ocenić. Naprawdę musisz zmienić swój sposób myślenia. Zapomnij o wszystkich połączeniach między łańcuchami po jednej stronie i wyrażeniami, wywołaniami, oceną po drugiej stronie.

Połączenie (możliwe) tylko przez parse(text = ....)i wszyscy dobrzy programiści R powinni wiedzieć, że rzadko jest to skuteczny lub bezpieczny sposób konstruowania wyrażeń (lub wywołań). Raczej dowiedzieć się więcej na temat substitute(), quote()i ewentualnie moc użyciu do.call(substitute, ......).

fortunes::fortune("answer is parse")
# If the answer is parse() you should usually rethink the question.
#    -- Thomas Lumley
#       R-help (February 2005)

Dec.2017: Ok, oto przykład (w komentarzach nie ma ładnego formatowania):

q5 <- quote(5+5)
str(q5)
# language 5 + 5

e5 <- expression(5+5)
str(e5)
# expression(5 + 5)

a jeśli zdobędziesz więcej doświadczenia, nauczysz się, że q5jest to "call"a e5jest "expression", a nawet to e5[[1]]jest identyczne z q5:

identical(q5, e5[[1]])
# [1] TRUE
Martin Mächler
źródło
4
czy możesz podać przykład? może mógłbyś nam pokazać, jak „trzymać” do 5 + 5 w obiekcie r, a następnie ocenić go później, używając cytowania i podstawiania zamiast znaku i eval (parsowanie (tekst =)?
Richard DiSalvo
3
Mogę być trochę zagubiony. W którym momencie dostajesz 10? Czy nie o to chodzi?
Nick S
@RichardDiSalvo: tak, q5 <- quote(5+5)powyżej znajduje się wyrażenie (właściwie „wywołanie”) 5+5i jest to obiekt R, ale nie ciąg znaków. Możesz to ocenić w dowolnym momencie. Ponownie: użycie, quote (), substitute (), ... zamiast tego parsowanie tworzy wywołania lub wyrażenia bezpośrednio i wydajniej niż przez parsowanie (tekst =.). Używanie eval()jest w porządku, używanie parse(text=*)jest podatne na błędy, a czasem dość nieefektywne w porównaniu do wywołań konstrukcyjnych i manipulowania nimi. @Nick S: To jest eval(q5) lub eval(e5) w naszym bieżącym przykładzie
Martin Mächler
@NickS: Aby uzyskać 10, oceniasz połączenie / wyrażenie, tzn. Wywołujesz eval(.)je. Chodziło mi o to, że ludzie nie powinni używać, parse(text=.)ale quote(.)itp., Do konstruowania połączenia, które później będzie eval()edytowane.
Martin Mächler,
2
eval(quote())działa w kilku przypadkach, ale zawiedzie w niektórych przypadkach, w których eval(parse())działałoby dobrze.
NelsonGon,
18

Alternatywnie możesz użyć evalsz mojego panderpakietu do przechwytywania danych wyjściowych i wszystkich ostrzeżeń, błędów i innych komunikatów wraz z nieprzetworzonymi wynikami:

> pander::evals("5+5")
[[1]]
$src
[1] "5 + 5"

$result
[1] 10

$output
[1] "[1] 10"

$type
[1] "numeric"

$msg
$msg$messages
NULL

$msg$warnings
NULL

$msg$errors
NULL


$stdout
NULL

attr(,"class")
[1] "evals"
daroczig
źródło
2
Niezła funkcja; wypełnia pozostawiony otwór evaluate::evaluate, faktycznie zwracając wynikowy obiekt; dzięki czemu twoja funkcja nadaje się do wywoływania przez mclapply. Mam nadzieję, że ta funkcja pozostanie!
russellpierce
Dziękuję, @rpierce. Ta funkcja została pierwotnie napisana w 2011 roku jako część naszego rapportpakietu i od tego czasu jest aktywnie utrzymywana jako intensywnie używana w naszym rapporter.net oprócz kilku innych projektów - więc jestem pewien, że pozostanie ona utrzymana przez pewien czas podczas :) Cieszę się, że okażą się przydatne, dziękuję za miłe opinie.
daroczig
14

Obecnie możesz także korzystać lazy_evalz funkcji z lazyevalpakietu.

> lazyeval::lazy_eval("5+5")
[1] 10
Paweł Kozielski-Romaneczko
źródło
2

Podobnie za pomocą rlang:

eval(parse_expr("5+5"))
c1au61o_HH
źródło
3
Przybyłeś tutaj, szukając rlangodpowiedzi, ale co, jeśli taka jest, ma tę przewagę nad alternatywnymi rozwiązaniami? Właściwie dokładne zbadanie użytego kodu pokazuje, że w rzeczywistości używa on eval(parse(....))tego, czego chciałem uniknąć.
NelsonGon,
4
Nie tylko te negatywy, ale także ich nazwa wprowadza w błąd. NIE ocenia wyrażenia. Powinien się nazywać parse_to_expr lub coś innego, aby wskazać, że użytkownik będzie wiedział, że jest przeznaczony do argumentów znakowych.
IRTFM,