Aceste limbi se acumulează | R-bloggeri

URMĂREȘTE-NE
16,065FaniÎmi place
1,142CititoriConectați-vă

Tot spun că, cu cât cunoști mai multe limbaje de programare, cu atât le vei înțelege mai mult pe toate celelalte pe care le cunoști – acum sunt în punctul în care vreau să rezolv fiecare problemă pe care o văd într-o mână de limbi diferite. Toate oferă funcționalități diferite, iar unele sunt cu siguranță mai potrivite pentru anumite probleme decât altele, dar există o lume de diferență între două caractere și importul din două biblioteci.

Un buletin informativ pe care îl urmăresc (și nu găsesc copii online) care demonstrează lucruri bune în python (trebuie să-l învăț, deși nu-l iubesc) a fost acoperit recent
accumulatearătând că sum şi accumulate erau oarecum înrudite

>>> my_list = (42, 73, 0, 16, 10)
>>> sum(my_list)
141
>>> from itertools import accumulate
>>> list(accumulate(my_list))
(42, 115, 115, 131, 141)

sum adună toate elementele listei, în timp ce accumulate face același lucru, dar păstrează fiecare sumă parțială succesivă.

Acesta completează demonstrația cu o funcție alternativă utilizată în accumulate

>>> from itertools import accumulate
>>> from operator import mul  # mul(2, 3) == 6
>>> initial_investment = 1000
>>> rates = (1.01, 1.01, 1.02, 1.025, 1.035, 1.035, 1.06)
>>> list(
...     accumulate(rates, mul, initial=initial_investment)
... )
(1000, 1010.0, 1020.1, 1040.502, 1066.515, 1103.843, 1142.477, 1211.026)

Acum, în primul rând… from operator import mul??? Se pare că nu ai cum să treci * ca argument pentru o funcție. eu putea definiți o funcție care efectuează același lucru pe argumente cunoscute, de ex lambda x, y: x * y

>>> list(accumulate(rates, lambda x, y: x*y, initial=initial_investment))
(1000, 1010.0, 1020.1, 1040.502, 1066.5145499999999, 1103.8425592499998, 1142.4770488237498, 1211.0256717531747)

dar… ew.

Este posibil să existe o modalitate diferită de a aborda asta. Îmi vine în minte o listă de înțelegere, de exemplu ceva de genul

>>> (sum(my_list(0:i)) for i in range(1, len(my_list)+1))
(42, 115, 115, 131, 141)

dar asta necesită efectuarea unei sume pentru fiecare sub-interval, astfel încât performanța nu s-ar scala bine (desigur, asta nu a fost deloc luat în considerare aici). Nici eu nu cred că există un încorporat prod deci trebuie import math pentru a face similar

>>> import math
>>> x = (initial_investment) + rates
>>> (math.prod(x(0:i)) for i in range(1, len(x)+1))
(1000, 1010.0, 1020.1, 1040.502, 1066.5145499999999, 1103.8425592499998, 1142.4770488237498, 1211.0256717531747)

În R care ar putea folosi încorporat cumprod pentru produsul cumulat

initial_investment <- 1000
rates = c(1.01, 1.01, 1.02, 1.025, 1.035, 1.035, 1.06)

cumprod(c(initial_investment, rates))
## (1) 1000.000 1010.000 1020.100 1040.502 1066.515 1103.843 1142.477 1211.026

dar care are operația de „multiplicare” codificată. cumsum utilizări + ca funcția… hmmm. Poate că R nu are un generalizat accumulate?

M-am jucat cu Haskell în ultima vreme, așa că funcțiile recursive la salvare! O caracteristică a funcțiilor recursive din R care îmi place foarte mult este Recall
care apelează funcția în care este definit cu un nou set de argumente – perfect pentru recursivitate!

accumulate_recall <- function(x, f, i=x(1)) {
  if (!length(x)) return(NULL)
  c(i, Recall(tail(x, -1), f, f(i, x(2))))
}

Este, de asemenea, robust împotriva redenumirea funcției; corpul de fapt nu sună
accumulate_recall pe nume deloc.

Acest lucru ar putea fi ineficient, totuși – nu este neobișnuit să aruncați în aer stiva, deci o nouă Tailcall funcția (care nu are aceeași eleganță de a fi robustă împotriva redenumiri) ajută la semnalarea acestui lucru ca ceva care poate fi optimizat

accumulate <- function(x, f, i=x(1)) {
  if (!length(x)) return(NULL)
  c(i, Tailcall(accumulate, tail(x, -1), f, f(i, x(2))))
}

Cu aceasta, pot emula cumsum() şi cumprod() funcții

cumprod(1:6)
## (1)   1   2   6  24 120 720
accumulate(1:6, `*`)
## (1)   1   2   6  24 120 720
cumsum(2:6)
## (1)  2  5  9 14 20
accumulate(2:6, `+`)
## (1)  2  5  9 14 20

dacă nu încerc să calculez ceva prea mare…

cumprod(5:15)
##  (1)           5          30         210        1680       15120      151200
##  (7)     1663200    19958400   259459200  3632428800 54486432000
accumulate(5:15, `*`)
## Warning in f(i, x(2)): NAs produced by integer overflow
##  (1)         5        30       210      1680     15120    151200   1663200
##  (8)  19958400 259459200        NA        NA

Se pare că funcțiile încorporate se convertesc în numere. Acest lucru se rezolvă ușor la intrare

accumulate(as.numeric(5:15), `*`)
##  (1)           5          30         210        1680       15120      151200
##  (7)     1663200    19958400   259459200  3632428800 54486432000

În orice caz, există o generalizare accumulate care ia funcțiile goale drept argumente.

Dar poate fi mult mai curat decât asta!

În APL nu veți găsi nicio funcție numită „sumă” deoarece este doar o reducere (Reduce în R) cu funcţia +

      sum←+/
      
      sum ⍳6 ⍝ sum the values 1:6
21

      sum 1↓⍳6 ⍝ sum the values 2:6
20

care în R este

sum(1:6)
## (1) 21
sum(2:6)
## (1) 20

De ce ai scrie sum doar dacă poți folosi +/? Sunt mai puține caractere pentru a scrie implementarea decât numele!

Pentru accumulate terminologia din APL este scan care folosește un glif foarte asemănător deoarece operația în sine este foarte asemănătoare; o reduce (/) este doar ultima valoare a lui a scan () care păstrează valorile progresive. În ambele cazuri, operatorul (fie bară oblică) ia o funcție binară ca argument din stânga și produce o funcție modificată – în aceste exemple, efectiv sum şi prod – care se aplică apoi valorilor din dreapta. The scan versiunea face la fel

      +⍳6
1 3 6 10 15 21

      ×⍳6
1 2 6 24 120 720
accumulate(1:6, `+`)
## (1)  1  3  6 10 15 21
accumulate(1:6, `*`)
## (1)   1   2   6  24 120 720

În ceea ce privește exemplul de rate de mai sus, concatenăm valoarea inițială cu catenate
(,) la fel ca exemplul R, dar altfel funcționează bine

      rates ← 1.01 1.01 1.02 1.025 1.035 1.035 1.06
      inv ← 1000
      
      ×/inv, rates
1211.025672

      ×inv, rates
1000 1010 1020.1 1040.502 1066.51455 1103.842559 1142.477049 1211.025672

Deci tot acel cod R recursiv făcut pentru a generaliza aplicarea cumulativă a unei funcții furnizate ca argument se rezumă doar la un singur glif . Remarcabil!

Mai mult, există multe de funcții binare cu care s-ar folosi acest lucru, toate având nume scrise în alte limbi

      +/ ⍝ sum (add)
      ×/ ⍝ prod (multiply)
      ∧/ ⍝ all (and)
      ∨/ ⍝ any (or)
      ⌈/ ⍝ maximum (max)
      ⌊/ ⍝ minimum (min)

În rezumat, se pare că, privind în aceste limbi, opțiunile disponibile variază de la un singur glif pentru scan împreună cu operatorul binar simplu, de ex
×/; o cumprod() funcție care nu este bine generalizată, dar funcționează imediat; și apoi există orice mizerie ar fi aceasta (odată ce ai făcut instalat aceste)

>>> from itertools import accumulate
>>> from operator import mul
>>> list(accumulate(rates, mul, initial=initial_investment))

Unde am greșit așa?

Pentru cât valorează, Julia are un reduce si un accumulate care se comportă foarte frumos; generalizat pentru funcţia binară ca argument

julia> reduce(+, 1:6)
21

julia> reduce(*, 1:6)
720

julia> accumulate(+, 1:6)
6-element Vector{Int64}:
  1
  3
  6
 10
 15
 21

julia> accumulate(*, 1:6)
6-element Vector{Int64}:
   1
   2
   6
  24
 120
 720

Acest lucru este extrem de aproape de abordarea APL, dar cu nume mai lungi pentru reduce şi scan operatori. De asemenea, definește cel mai convenabil sum,
prod, cumsumși cumprod; nu lipsesc modalitățile de a face asta în Julia!

În Haskell, foldl şi scanl sunt versiunea (stânga-asociativă) a reduce
şi accumulateiar trecerea unui infix ca argument necesită împachetarea lui între paranteze

ghci> foldl (+) 0 (1..6)
21

ghci> scanl (+) 0 (1..6)
(0,1,3,6,10,15,21)

ghci> foldl (*) 1 (1..6)
720

ghci> scanl (*) 1 (1..6)
(1,1,2,6,24,120,720)

Aceasta necesită o valoare de pornire explicită, cu excepția cazului în care se utilizează versiunile specializate care folosesc prima valoare ca valoare inițială

ghci> foldl1 (+) (1..6)
21

ghci> scanl1 (+) (1..6)
(1,3,6,10,15,21)

ghci> foldl1 (*) (1..6)
720

ghci> scanl1 (*) (1..6)
(1,2,6,24,120,720)

Am început această postare sperând să demonstrez cât de plăcută a fost sintaxa APL pentru asta, dar ocolul prin generalizarea funcției R a fost și o distracție neașteptată.

Comentariile, îmbunătățirile sau propriile soluții sunt binevenite. Pot fi găsit pe Mastodon sau pot folosi comentariile de mai jos.

Anexe

Probabil trebuie remarcat faptul că R face au o funcție scan dar este pentru citirea datelor într-un vector – dacă observați vreodată pe cineva care îl folosește pentru asta… rulați. Am povești de război despre această funcție.

Mi-ar plăcea să aud cum se realizează acest lucru și în alte limbi – are încorporat accumulate care necesită o funcție binară?

devtools::session_info()
## ─ Session info ───────────────────────────────────────────────────────────────
##  setting  value
##  version  R version 4.4.1 (2024-06-14)
##  os       macOS Sonoma 14.6
##  system   aarch64, darwin20
##  ui       X11
##  language (EN)
##  collate  en_US.UTF-8
##  ctype    en_US.UTF-8
##  tz       Australia/Adelaide
##  date     2024-11-28
##  pandoc   3.5 @ /opt/homebrew/bin/ (via rmarkdown)
## 
## ─ Packages ───────────────────────────────────────────────────────────────────
##  package     * version    date (UTC) lib source
##  blogdown      1.19       2024-02-01 (1) CRAN (R 4.4.0)
##  bookdown      0.41       2024-10-16 (1) CRAN (R 4.4.1)
##  bslib         0.8.0      2024-07-29 (1) CRAN (R 4.4.0)
##  cachem        1.1.0      2024-05-16 (1) CRAN (R 4.4.0)
##  cli           3.6.3      2024-06-21 (1) CRAN (R 4.4.0)
##  devtools      2.4.5      2022-10-11 (1) CRAN (R 4.4.0)
##  digest        0.6.37     2024-08-19 (1) CRAN (R 4.4.1)
##  ellipsis      0.3.2      2021-04-29 (1) CRAN (R 4.4.0)
##  evaluate      1.0.1      2024-10-10 (1) CRAN (R 4.4.1)
##  fastmap       1.2.0      2024-05-15 (1) CRAN (R 4.4.0)
##  fs            1.6.5      2024-10-30 (1) CRAN (R 4.4.1)
##  glue          1.8.0      2024-09-30 (1) CRAN (R 4.4.1)
##  htmltools     0.5.8.1    2024-04-04 (1) CRAN (R 4.4.0)
##  htmlwidgets   1.6.4      2023-12-06 (1) CRAN (R 4.4.0)
##  httpuv        1.6.15     2024-03-26 (1) CRAN (R 4.4.0)
##  jquerylib     0.1.4      2021-04-26 (1) CRAN (R 4.4.0)
##  jsonlite      1.8.9      2024-09-20 (1) CRAN (R 4.4.1)
##  knitr         1.48       2024-07-07 (1) CRAN (R 4.4.0)
##  later         1.3.2      2023-12-06 (1) CRAN (R 4.4.0)
##  lifecycle     1.0.4      2023-11-07 (1) CRAN (R 4.4.0)
##  magrittr      2.0.3      2022-03-30 (1) CRAN (R 4.4.0)
##  memoise       2.0.1      2021-11-26 (1) CRAN (R 4.4.0)
##  mime          0.12       2021-09-28 (1) CRAN (R 4.4.0)
##  miniUI        0.1.1.1    2018-05-18 (1) CRAN (R 4.4.0)
##  pkgbuild      1.4.5      2024-10-28 (1) CRAN (R 4.4.1)
##  pkgload       1.4.0      2024-06-28 (1) CRAN (R 4.4.0)
##  profvis       0.4.0      2024-09-20 (1) CRAN (R 4.4.1)
##  promises      1.3.0      2024-04-05 (1) CRAN (R 4.4.0)
##  purrr         1.0.2      2023-08-10 (1) CRAN (R 4.4.0)
##  R6            2.5.1      2021-08-19 (1) CRAN (R 4.4.0)
##  Rcpp          1.0.13-1   2024-11-02 (1) CRAN (R 4.4.1)
##  remotes       2.5.0.9000 2024-11-03 (1) Github (r-lib/remotes@5b7eb08)
##  rlang         1.1.4      2024-06-04 (1) CRAN (R 4.4.0)
##  rmarkdown     2.28       2024-08-17 (1) CRAN (R 4.4.0)
##  rstudioapi    0.17.1     2024-10-22 (1) CRAN (R 4.4.1)
##  sass          0.4.9      2024-03-15 (1) CRAN (R 4.4.0)
##  sessioninfo   1.2.2      2021-12-06 (1) CRAN (R 4.4.0)
##  shiny         1.9.1      2024-08-01 (1) CRAN (R 4.4.0)
##  urlchecker    1.0.1      2021-11-30 (1) CRAN (R 4.4.0)
##  usethis       3.0.0      2024-07-29 (1) CRAN (R 4.4.0)
##  vctrs         0.6.5      2023-12-01 (1) CRAN (R 4.4.0)
##  xfun          0.49       2024-10-31 (1) CRAN (R 4.4.1)
##  xtable        1.8-4      2019-04-21 (1) CRAN (R 4.4.0)
##  yaml          2.3.10     2024-07-26 (1) CRAN (R 4.4.0)
## 
##  (1) /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
## 
## ──────────────────────────────────────────────────────────────────────────────

Dominic Botezariu
Dominic Botezariuhttps://www.noobz.ro/
Creator de site și redactor-șef.

Cele mai noi știri

Pe același subiect

LĂSAȚI UN MESAJ

Vă rugăm să introduceți comentariul dvs.!
Introduceți aici numele dvs.