Contagem e somatório de seqüências numéricas positivas e negativas

31

Eu quero escrever um código para contar e somar qualquer série de números positivos e negativos.
Os números são positivos ou negativos (sem zero).
Eu escrevi códigos com forloops. Existe alguma alternativa criativa?

Dados

R

set.seed(100)
x <- round(rnorm(20, sd = 0.02), 3)

Pitão

x = [-0.01, 0.003, -0.002, 0.018, 0.002, 0.006, -0.012, 0.014, -0.017, -0.007,

     0.002, 0.002, -0.004, 0.015, 0.002, -0.001, -0.008, 0.01, -0.018, 0.046]

rotações

R

sign_indicator <- ifelse(x > 0, 1,-1)
number_of_sequence <- rep(NA, 20)
n <- 1
for (i in 2:20) {
  if (sign_indicator[i] == sign_indicator[i - 1]) {
    n <- n + 1
  } else{
    n <- 1
  }
  number_of_sequence[i] <- n

}
number_of_sequence[1] <- 1

#############################

summation <- rep(NA, 20)

for (i in 1:20) {
  summation[i] <- sum(x[i:(i + 1 - number_of_sequence[i])])
}

Pitão

sign_indicator = [1 if i > 0 else -1 for i in X]

number_of_sequence = [1]
N = 1
for i in range(1, len(sign_indicator)):
    if sign_indicator[i] == sign_indicator[i - 1]:
        N += 1
    else:
        N = 1
    number_of_sequence.append(N)

#############################
summation = []

for i in range(len(X)):
    if number_of_sequence[i] == 1:          
          summation.append(X[i])

    else:
        summation.append(sum(X[(i + 1 - number_of_sequence[i]):(i + 1)]))

resultado

        x n_of_sequence    sum
1  -0.010             1 -0.010
2   0.003             1  0.003
3  -0.002             1 -0.002
4   0.018             1  0.018
5   0.002             2  0.020
6   0.006             3  0.026
7  -0.012             1 -0.012
8   0.014             1  0.014
9  -0.017             1 -0.017
10 -0.007             2 -0.024
11  0.002             1  0.002
12  0.002             2  0.004
13 -0.004             1 -0.004
14  0.015             1  0.015
15  0.002             2  0.017
16 -0.001             1 -0.001
17 -0.008             2 -0.009
18  0.010             1  0.010
19 -0.018             1 -0.018
20  0.046             1  0.046
Iman
fonte

Respostas:

17

As outras soluções parecem boas, mas você realmente não precisa usar recursos sofisticados de idioma ou funções de biblioteca para esse problema simples.

result, prev = [], None

for idx, cur in enumerate(x):
    if not prev or (prev > 0) != (cur > 0):
        n, summation = 1, cur
    else:
        n, summation = n + 1, summation + cur
    result.append((idx, cur, n, summation))
    prev = cur

Como você pode ver, você realmente não precisa de sign_indicatorlista, dois for-loops ou rangefunção, como no snippet na seção de perguntas.

Se você deseja que o índice inicie em 1, use em enumerate(x, 1)vez deenumerate(x)

Para ver o resultado, você pode executar o seguinte código

for idx, num, length, summation in result:
     print(f"{idx: >2d} {num: .3f} {length: >2d} {summation: .3f}")
bombas
fonte
14

Em R, você pode usar data.tables rleidpara criar grupos com séries de números positivos e negativos e, em seguida, criar uma sequência de linhas em cada grupo e fazer uma soma cumulativa dos xvalores.

library(data.table)
df <- data.table(x)
df[, c("n_of_sequence", "sum") := list(seq_len(.N), cumsum(x)), by = rleid(sign(x))]
df

#         x n_of_sequence    sum
# 1: -0.010             1 -0.010
# 2:  0.003             1  0.003
# 3: -0.002             1 -0.002
# 4:  0.018             1  0.018
# 5:  0.002             2  0.020
# 6:  0.006             3  0.026
# 7: -0.012             1 -0.012
# 8:  0.014             1  0.014
# 9: -0.017             1 -0.017
#10: -0.007             2 -0.024
#11:  0.002             1  0.002
#12:  0.002             2  0.004
#13: -0.004             1 -0.004
#14:  0.015             1  0.015
#15:  0.002             2  0.017
#16: -0.001             1 -0.001
#17: -0.008             2 -0.009
#18:  0.010             1  0.010
#19: -0.018             1 -0.018
#20:  0.046             1  0.046

Podemos usar rleidem dplyrbem para criar grupos e fazer o mesmo.

library(dplyr)
df %>%
  group_by(gr = data.table::rleid(sign(x))) %>%
  mutate(n_of_sequence = row_number(), sum = cumsum(x))
Ronak Shah
fonte
2
n_of_sequencenão é idêntico ao desejado
Iman
@ Iman Desculpe, eu li mal a saída mais cedo. Eu o corrigi agora.
Ronak Shah 19/02
10

Você pode calcular os comprimentos de execução de cada sinal usando rlede basepara e fazer algo assim.

set.seed(0)
z <- round(rnorm(20, sd = 0.02), 3)
run_lengths <- rle(sign(z))$lengths
run_lengths
# [1] 1 1 1 3 1 1 2 2 1 2 2 1 1 1

Para obter n_of_sequence

n_of_sequence <- run_lengths %>% map(seq) %>% unlist
n_of_sequence
# [1] 1 1 1 1 2 3 1 1 1 2 1 2 1 1 2 1 2 1 1 1

Finalmente, para obter os somatórios das seqüências,

start <- cumsum(c(1,run_lengths))
start <- start[-length(start)] # start points of each series 
map2(start,run_lengths,~cumsum(z[.x:(.x+.y-1)])) %>% unlist()
# [1] -0.010  0.003 -0.002  0.018  0.020  0.026 -0.012  0.014 -0.017 -0.024
# [11]  0.002  0.004 -0.004  0.015  0.017 -0.001 -0.009  0.010 -0.018  0.046
Ameer
fonte
6

Aqui está uma função simples sem loop no R:

count_and_sum <- function(x)
{
  runs   <- rle((x > 0) * 1)$lengths
  groups <- split(x, rep(1:length(runs), runs))
  output <- function(group) data.frame(x = group, n = seq_along(group), sum = cumsum(group))
  result <- as.data.frame(do.call(rbind, lapply(groups, output)))
  `rownames<-`(result, 1:nrow(result))
}

Então você pode fazer:

set.seed(100)
x <- round(rnorm(20, sd = 0.02), 3)
count_and_sum(x)
#>         x n    sum
#> 1  -0.010 1 -0.010
#> 2   0.003 1  0.003
#> 3  -0.002 1 -0.002
#> 4   0.018 1  0.018
#> 5   0.002 2  0.020
#> 6   0.006 3  0.026
#> 7  -0.012 1 -0.012
#> 8   0.014 1  0.014
#> 9  -0.017 1 -0.017
#> 10 -0.007 2 -0.024
#> 11  0.002 1  0.002
#> 12  0.002 2  0.004
#> 13 -0.004 1 -0.004
#> 14  0.015 1  0.015
#> 15  0.002 2  0.017
#> 16 -0.001 1 -0.001
#> 17 -0.008 2 -0.009
#> 18  0.010 1  0.010
#> 19 -0.018 1 -0.018
#> 20  0.046 1  0.046

Criado em 2020-02-16 pelo pacote reprex (v0.3.0)

Allan Cameron
fonte
5

Aqui está uma tidyversesolução simples ...

library(tidyverse) #or just dplyr and tidyr

set.seed(100)
x <- round(rnorm(20, sd = 0.02), 3)

df <- tibble(x = x) %>% 
  mutate(seqno = cumsum(c(1, diff(sign(x)) != 0))) %>% #identify sequence ids
  group_by(seqno) %>%                                  #group by sequences
  mutate(n_of_sequence = row_number(),                 #count row numbers for each group
         sum = cumsum(x)) %>%                          #cumulative sum for each group
  ungroup() %>% 
  select(-seqno)                                       #remove sequence id

df
# A tibble: 20 x 3
        x n_of_sequence     sum
    <dbl>         <int>   <dbl>
 1 -0.01              1 -0.01  
 2  0.003             1  0.003 
 3 -0.002             1 -0.002 
 4  0.018             1  0.018 
 5  0.002             2  0.0200
 6  0.006             3  0.026 
 7 -0.012             1 -0.012 
 8  0.014             1  0.014 
 9 -0.017             1 -0.017 
10 -0.007             2 -0.024 
11  0.002             1  0.002 
12  0.002             2  0.004 
13 -0.004             1 -0.004 
14  0.015             1  0.015 
15  0.002             2  0.017 
16 -0.001             1 -0.001 
17 -0.008             2 -0.009 
18  0.01              1  0.01  
19 -0.018             1 -0.018 
20  0.046             1  0.046 
Andrew Gustar
fonte
5

Quanto ao Python, alguém terá uma solução usando a biblioteca pandas. Enquanto isso, aqui está uma proposta simples:

class Combiner:
    def __init__(self):
        self.index = self.seq_index = self.summation = 0

    def combine(self, value):
        self.index += 1
        if value * self.summation <= 0:
            self.seq_index = 1
            self.summation = value
        else:
            self.seq_index += 1
            self.summation += value
        return self.index, value, self.seq_index, self.summation

c = Combiner()
lst = [c.combine(v) for v in x]

for t in lst:
    print(f"{t[0]:3} {t[1]:7.3f} {t[2]:3} {t[3]:7.3f}")

Resultado:

  1  -0.010   1  -0.010
  2   0.003   1   0.003
  3  -0.002   1  -0.002
  4   0.018   1   0.018
  5   0.002   2   0.020
  6   0.006   3   0.026
  7  -0.012   1  -0.012
  8   0.014   1   0.014
  9  -0.017   1  -0.017
 10  -0.007   2  -0.024
 11   0.002   1   0.002
 12   0.002   2   0.004
 13  -0.004   1  -0.004
 14   0.015   1   0.015
 15   0.002   2   0.017
 16  -0.001   1  -0.001
 17  -0.008   2  -0.009
 18   0.010   1   0.010
 19  -0.018   1  -0.018
 20   0.046   1   0.046

Se você precisar de listas separadas, poderá fazer

idxs, vals, seqs, sums = (list(tpl) for tpl in zip(*lst))

ou, se os iteradores estiverem OK, basta

idxs, vals, seqs, sums = zip(*lst)

(explicação aqui )

Walter Tross
fonte
5

Duas soluções preguiçosas diferentes em Python, usando o módulo itertools .

Usando itertools.groupby (e acumule)

from itertools import accumulate, groupby

result = (
    item
    for _, group in groupby(x, key=lambda n: n < 0)
    for item in enumerate(accumulate(group), 1)
)

Usando itertools.acumule com uma função de acumulação personalizada

from itertools import accumulate

def sign_count_sum(count_sum, value):
    count, prev_sum = count_sum
    same_sign = (prev_sum < 0) is (value < 0)
    if same_sign:
        return count + 1, prev_sum + value
    else:
        return 1, value

result = accumulate(x, sign_count_sum, initial=(0, 0))
next(result)  # needed to skip the initial (0, 0) item

O initialargumento da palavra-chave foi adicionado no Python 3.8. Nas versões anteriores, você pode usar itertools.chainpara acrescentar o princípio (0,0):

result = accumulate(chain([(0, 0)], x), sign_count_sum)

A saída é como o esperado:

for (i, v), (c, s) in zip(enumerate(x), result):
    print(f"{i:3} {v:7.3f} {c:3} {s:7.3f}")
  0  -0.010   1  -0.010
  1   0.003   1   0.003
  2  -0.002   1  -0.002
  3   0.018   1   0.018
  4   0.002   2   0.020
  5   0.006   3   0.026
  6  -0.012   1  -0.012
  7   0.014   1   0.014
  8  -0.017   1  -0.017
  9  -0.007   2  -0.024
 10   0.002   1   0.002
 11   0.002   2   0.004
 12  -0.004   1  -0.004
 13   0.015   1   0.015
 14   0.002   2   0.017
 15  -0.001   1  -0.001
 16  -0.008   2  -0.009
 17   0.010   1   0.010
 18  -0.018   1  -0.018
 19   0.046   1   0.046
schot
fonte
5

Eu recomendo o corredor de pacotes R para esse tipo de operações. streak_run calcula ocorrência consecutiva do mesmo valor e sum_run calcula soma na janela cujo comprimento é definido pelo kargumento.

Aqui está a solução:

set.seed(100)
x <- round(rnorm(20, sd = 0.02), 3)

n_of_sequence <- runner::streak_run(x > 0)
sum <- runner::sum_run(x, k = n_of_sequence)

data.frame(x, n_of_sequence, sum)

#         x n_of_sequence    sum
# 1  -0.010             1 -0.010
# 2   0.003             1  0.003
# 3  -0.002             1 -0.002
# 4   0.018             1  0.018
# 5   0.002             2  0.020
# 6   0.006             3  0.026
# 7  -0.012             1 -0.012
# 8   0.014             1  0.014
# 9  -0.017             1 -0.017
# 10 -0.007             2 -0.024
# 11  0.002             1  0.002
# 12  0.002             2  0.004
# 13 -0.004             1 -0.004
# 14  0.015             1  0.015
# 15  0.002             2  0.017
# 16 -0.001             1 -0.001
# 17 -0.008             2 -0.009
# 18  0.010             1  0.010
# 19 -0.018             1 -0.018
# 20  0.046             1  0.046

Abaixo do benchmark para comparar as soluções reais

set.seed(0)
x <- round(rnorm(10000, sd = 0.02), 3)

library(runner)
runner_streak <- function(x) {
  n_of_sequence <- streak_run(x > 0)
  sum <- sum_run(x, k = n_of_sequence)
}

library(data.table)
dt <- data.table(x)
dt_streak <- function(dt) {
  dt[, c("n_of_sequence", "sum") := list(seq_len(.N), cumsum(x)),rleid(sign(x))]
}

rle_streak <- function(x) {
  run_lengths <- rle(sign(x))$lengths
  run_lengths

  n_of_sequence <- run_lengths %>% map(seq) %>% unlist

  start <- cumsum(c(1,run_lengths))
  start <- start[-length(start)]
  sum <- map2(start,run_lengths,~cumsum(x[.x:(.x+.y-1)])) %>% unlist()
}

library(tidyverse)
df <- tibble(x = x)
tv_streak <- function(x) {
  res <- df %>%
    mutate(seqno = cumsum(c(1, diff(sign(x)) != 0))) %>%
    group_by(seqno) %>%
    mutate(n_of_sequence = row_number(),
           sum = cumsum(x)) %>%
    ungroup() %>% 
    select(-seqno)  
}

count_and_sum <- function(x) {
  runs   <- rle((x > 0) * 1)$lengths
  groups <- split(x, rep(1:length(runs), runs))
  output <- function(group) 
    data.frame(x = group, n = seq_along(group), sum = cumsum(group))
  result <- as.data.frame(do.call(rbind, lapply(groups, output)))
  `rownames<-`(result, 1:nrow(result))
}
microbenchmark::microbenchmark(
  runner_streak(x),
  dt_streak(dt),
  rle_streak(x),
  tv_streak(df),
  count_and_sum(x),
  times = 100L
)


# Unit: milliseconds
#             expr         min          lq        mean      median          uq        max neval
# runner_streak(x)    4.240192    4.833563    6.321697    5.300817    6.543926   14.80221   100
#    dt_streak(dt)    7.648100    8.587887   10.862806    9.650483   11.295488   34.66027   100
#    rle_streak(x)   42.321506   55.397586   64.195692   63.404403   67.813738  167.71444   100
#    tv_streak(df)   31.398885   36.333751   45.141452   40.800077   45.756279  163.19535   100
# count_and_sum(x) 1691.438977 1919.518282 2306.036783 2149.543281 2499.951020 6158.43384   100
GoGonzo
fonte
11
medir em microssegundos não faz muito sentido. Algumas funções têm uma sobrecarga inicial em microssegundos, mas elas serão dimensionadas para conjuntos de big data muito melhores do que outras. Também df <- data.table(x)é uma cópia de dados completa. Além disso, você está imprimindo os dados em alguns exemplos (que é outra cópia completa) enquanto não em outros.
David Arenburg
Você está certo, fixo.
GoGonzo 20/02
algumas das funções retornam objetos diferentes - alguns vetores e alguns quadros de dados -, portanto, ainda não é uma referência bastante justa. Também alguns dão resultados diferentes. Tente r = runner_streak(x); d = dt_streak(dt) ; all.equal(r, d$sum). Apenas verificado alguns bbut tv_streakdá o mesmo que dt_streak; count_and_sumdá o mesmo runner_streakque são diferentes dos dois anteriores.
user2957945 23/02
3

Em R, você também pode fazer:

# DATA
set.seed(100)
x <- round(rnorm(20, sd = 0.02), 3)

library(data.table)
dt <- data.table(x = x)

# Create Positive or Negative variable
dt$x_logical <- ifelse(dt$x > 0, "P", "N")

# Create a reference data.frame/table to keep continuous counts
seq_dt <- data.frame(val = rle(x = dt$x_logical)$lengths)
seq_dt$id <- 1:nrow(seq_dt)

# Map id in the main data.table and get cumulative sum
dt$id <- rep(seq_dt$id, seq_dt$val)
dt[, csum := cumsum(x), by = "id"]


        x x_logical id   csum
 1: -0.010         N  1 -0.010
 2:  0.003         P  2  0.003
 3: -0.002         N  3 -0.002
 4:  0.018         P  4  0.018
 5:  0.002         P  4  0.020
 6:  0.006         P  4  0.026
 7: -0.012         N  5 -0.012
 8:  0.014         P  6  0.014
 9: -0.017         N  7 -0.017
10: -0.007         N  7 -0.024
11:  0.002         P  8  0.002
12:  0.002         P  8  0.004
13: -0.004         N  9 -0.004
14:  0.015         P 10  0.015
15:  0.002         P 10  0.017
16: -0.001         N 11 -0.001
17: -0.008         N 11 -0.009
18:  0.010         P 12  0.010
19: -0.018         N 13 -0.018
20:  0.046         P 14  0.046
MKa
fonte
3

Jogando minha [r] resposta no chapéu, otimizada para velocidade e funciona com qualquer comprimento de x (diferente das perguntas que foram codificadas no comprimento 20):

### data 
set.seed(100)
x <- round(rnorm(20, sd = 0.02), 3)

### solution
summation <- c(x[1])
enn <- 1
n_of_seq <- c(enn)
for(i in 2:length(x)){
  first <- x[i]
  second <- summation[i - 1]

  if(sign(first) == sign(second)){
    summation <- c(summation, first + second)
    enn <- enn + 1
  }else{
    summation <- c(summation, first)
    enn <- 1

  }
  n_of_seq <- c(n_of_seq, enn)
  }

E, para comparar os tempos de execução no meu computador de trabalho atual (muito lento), aqui está a saída do meu microbenchmark usando todas as soluções R neste segmento. Sem surpresa, as soluções que tiram o máximo de cópias e conversões tendem a ser mais lentas.

Unit: microseconds
         expr      min       lq       mean    median       uq      max neval
     my_way()   13.301   19.200   23.38352   21.4010   23.401  20604.0 1e+05
 author_way()   19.702   31.701   40.12371   36.0015   40.502  24393.9 1e+05
      ronak()  856.401 1113.601 1305.36419 1236.8010 1377.501 453191.4 1e+05
      ameer()  388.501  452.002  553.08263  491.3000  548.701 456156.6 1e+05
     andrew() 2007.801 2336.801 2748.57713 2518.1510 2760.302 463175.8 1e+05
      gonzo()   21.901   35.502   48.84946   43.9010   51.001  29519.5 1e+05

-------------- EDIT -------------- Foi apontado pelo @nicola que minha solução não é a mais rápida para comprimentos maiores de x - o que deve ser bastante óbvio, pois estou continuamente fazendo cópias de vetores usando chamadas como x <- c (x, y). Eu só criei a solução mais rápida para comprimentos = 20 e apenas marquei a marca do microsseguro o mais baixo possível.

Para fazer uma comparação mais justa, editei todas as versões para gerar o código original da maneira que acredito ser mais rápida, mas agradeço comentários sobre isso. Aqui está o meu código de benchmarking completo e os resultados para o meu sistema muito lento. Congratulo-me com qualquer feedback.

# originally benchmarked a few different lengths
for(pie in c(100000)){


my_way<- function(){
  set.seed(100)
  x <- round(rnorm(pie, sd = 0.02), 3)
summation <- c(x[1])
enn <- 1
n_of_seq <- c(enn)
for(i in 2:length(x)){
  first <- x[i]
  second <- summation[i - 1]

  if(sign(first) == sign(second)){
    summation <- c(summation, first + second)
    enn <- enn + 1
  }else{
    summation <- c(summation, first)
    enn <- 1

  }
  n_of_seq <- c(n_of_seq, enn)
  }

# print(summation)
}




author_way <- function(){
  set.seed(100)
  x <- round(rnorm(pie, sd = 0.02), 3)

  sign_indicator <- ifelse(x > 0, 1,-1)
  sky <- length(x)
  number_of_sequence <- rep(NA, sky)
  n <- 1
  for (i in 2:sky) {
    if (sign_indicator[i] == sign_indicator[i - 1]) {
      n <- n + 1
    } else{
      n <- 1
    }
    number_of_sequence[i] <- n

  }
  number_of_sequence[1] <- 1

  #############################

  summation <- rep(NA, sky)

  for (i in 1:sky) {
    summation[i] <- sum(x[i:(i + 1 - number_of_sequence[i])])
  }
}


# other ppls solutions:




ronak <- function(){
df <- data.table('x' = round(rnorm(pie, sd = 0.02), 3))
df[, c("n_of_sequence", "sum") := list(seq_len(.N), cumsum(x)),rleid(sign(x))]
}



ameer <- function(){
  set.seed(100)
  x <- round(rnorm(pie, sd = 0.02), 3)
  run_lengths <- rle(sign(x))$lengths
  n_of_sequence <- run_lengths %>% map(seq) %>% unlist
  start <- cumsum(c(1,run_lengths))
  start <- start[-length(start)] # start points of each series 
  map2(start,run_lengths,~cumsum(x[.x:(.x+.y-1)])) %>% unlist()

}


count_and_sum <- function(x){
  set.seed(100)
  x <- round(rnorm(pie, sd = 0.02), 3)
  runs   <- rle((x > 0) * 1)$lengths
  groups <- split(x, rep(1:length(runs), runs))
  output <- function(group) data.frame(x = group, n = seq_along(group), sum = cumsum(group))
  result <- as.data.frame(do.call(rbind, lapply(groups, output)))
  `rownames<-`(result, 1:nrow(result))
}



andrew <- function(){
  set.seed(100)
  df <- tibble(x = round(rnorm(pie, sd = 0.02), 3)) %>% 
    mutate(seqno = cumsum(c(1, diff(sign(x)) != 0))) %>% #identify sequence ids
    group_by(seqno) %>%                                  #group by sequences
    mutate(n_of_sequence = row_number(),                 #count row numbers for each group
           sum = cumsum(x)) %>%                          #cumulative sum for each group
    ungroup() %>% 
    select(-seqno) 
}

gonzo <- function(){
  set.seed(100)
  x <- round(rnorm(pie, sd = 0.02), 3)
  n_of_sequence <- runner::streak_run(x > 0)
  sum <- runner::sum_run(x, k = n_of_sequence)
}



mi1 <- microbenchmark(my_way(), author_way(), ronak(), ameer(), andrew(), gonzo(), times = 10)
print(mi1)

}

Como esses resultados mostram, para outros comprimentos além dos otimizados, minha versão é lenta. Quanto maior o x, mais lento fica ridiculamente lento em tudo acima de 1000. Minha versão favorita é a da Ronak, que é apenas a segunda mais rápida no meu sistema. O GoGonzo é, de longe, o mais rápido da minha máquina nesses comprimentos mais longos.

Unit: milliseconds
         expr        min         lq        mean      median         uq        max neval
     my_way() 21276.9027 21428.2694 21604.30191 21581.97970 21806.9543 21896.7105    10
 author_way()    82.2465    83.0873    89.42343    84.78315    85.3638   115.4550    10
      ronak()    68.3922    69.3067    70.41924    69.84625    71.3509    74.7070    10
      ameer()   481.4566   509.7552   521.19034   514.77000   530.1121   579.4707    10
     andrew()   200.9654   202.1898   210.84914   206.20465   211.2006   233.7618    10
      gonzo()    27.3317    28.2550    28.66679    28.50535    28.9104    29.9549    10
Evento adverso
fonte
Além disso, as outras respostas funcionam de qualquer tamanho e sua referência deve ter algum problema. Com relação à data.tablesolução da @ Ronak, a sua é uma ordem de magnitudes mais lenta para um comprimento de ~ 100000.
nicola 21/02
Obrigado @nicola, eu apenas disse que a solução do solicitante funcionava para apenas 20 itens, não que nenhuma outra solução funcionasse - eles de fato funcionam. Também otimizei a velocidade para 20 itens, de modo que minha reivindicação por ser a mais rápida termina aí. Pelo que vale, também gostei da solução Ronaks, mas o autor pediu explicitamente maneiras mais diferentes de resolver o problema. O Ronak's já é mais rápido para um comprimento de 1000 também.
Adverse_Event 21/02
E para expandir a marca de microbench. Recodifiquei meu benchmark para que todas as soluções criassem (x) no formato que estão usando, para que as que produzem patches gerem x na chamada de petisco, o mesmo para data.table etc. Recodifiquei a solução original do solicitante para trabalhar de qualquer tamanho (apenas salvando o comprimento de x em uma variável e substituindo o 20 por ele. Eu o executei por um comprimento de 100.000 por 10 iterações. Observe que meu computador está suuuuper lento, está rodando em um processador de 5ª geração com ddr3 em 1600 MHz. Estou editando meu post com esses resultados
Adverse_Event 21/02
2

No Python, além de definir uma classe para armazenar as variáveis ​​de memória, você pode usar um fechamento para obter o mesmo.

def run():
    count = 0
    last_sign = 0

    def sign(i):
        return 1 if i > 0 else -1

    def f(i):
        nonlocal count
        nonlocal last_sign
        if sign(i) == last_sign:
            count = count+1
        else:
            last_sign = sign(i)
            count = 1
        return count

    return f

f = run()
y = [f(i) for i in x]

Observe que isso funciona apenas para Python 3 (no Python 2, acho que você não pode modificar a variável de fechamento como esta). Coisa semelhante para somatório também.

Prodipta Ghosh
fonte
2

Eu acho que um loop seria mais fácil de ler, mas apenas por diversão, aqui está uma solução em Python usando recursão:

x = [-0.01, 0.003, -0.002, 0.018, 0.002, 0.006, -0.012, 0.014, -0.017, -0.007, 0.002, 0.002, -0.004, 0.015, 0.002,
     -0.001, -0.008, 0.01, -0.018, 0.046]


def sign(number):
    return 1 if number > 0 else -1


def sum_previous(pos, result=None):
    if not result:
        result = x[pos]
    else:
        result += x[pos]
    if pos == 0 or sign(x[pos]) != sign(x[pos-1]):
        return result
    else:
        return sum_previous(pos-1, result)


results = [sum_previous(i) for i in range(len(x))]
print(results)
RogB
fonte
2

Aqui está outra abordagem R básica:

data.frame(x,
           n = sequence(rle(sign(x))$lengths),
           sum = Reduce(function(x, y) if (sign(x) == sign(y)) x + y else y, x, accumulate = TRUE))

        x n    sum
1  -0.010 1 -0.010
2   0.003 1  0.003
3  -0.002 1 -0.002
4   0.018 1  0.018
5   0.002 2  0.020
6   0.006 3  0.026
7  -0.012 1 -0.012
8   0.014 1  0.014
9  -0.017 1 -0.017
10 -0.007 2 -0.024
11  0.002 1  0.002
12  0.002 2  0.004
13 -0.004 1 -0.004
14  0.015 1  0.015
15  0.002 2  0.017
16 -0.001 1 -0.001
17 -0.008 2 -0.009
18  0.010 1  0.010
19 -0.018 1 -0.018
20  0.046 1  0.046
H 1
fonte
Apenas para nitpick, Reduceoculta um loop, portanto, essa não é uma solução sem loop.
nicola
2

Uma resposta simples em python ignora o caso 0:

x = [-0.01, 0.003, -0.002, 0.018, 
     0.002, 0.006, -0.012, 0.014, 
     -0.017, -0.007, 0.002, 0.002, 
     -0.004, 0.015, 0.002, -0.001, 
     -0.008, 0.01, -0.018, 0.046]

count = 0
sign_positive = x[0] > 0
sign_count = []
for n in x:
    # the idea is to keep track of the sign and increment the 
    # count if it agrees with the current number we are looking at
    if (n > 0 and sign_positive) or (n < 0 and not sign_positive):
        count = count + 1
    # if it does not, the count goes back to 1
    else:
        count = 1
    # Whether we increased the count or not, we update whether the
    # sign was positive or negative
    sign_positive = n > 0
    sign_count.append(count)

# This is just to reproduce the output 
# (although I find the last repetition of the number unnecessary)    
results = list(zip(x, sign_count))
for i, result in enumerate(results):
    print(f"{i: >2d} {result[0]: .3f} {result[1]: >2d} {result[0]: .3f}")

 0 -0.010  1 -0.010
 1  0.003  1  0.003
 2 -0.002  1 -0.002
 3  0.018  1  0.018
 4  0.002  2  0.002
 5  0.006  3  0.006
 6 -0.012  1 -0.012
 7  0.014  1  0.014
 8 -0.017  1 -0.017
 9 -0.007  2 -0.007
10  0.002  1  0.002
11  0.002  2  0.002
12 -0.004  1 -0.004
13  0.015  1  0.015
14  0.002  2  0.002
15 -0.001  1 -0.001
16 -0.008  2 -0.008
17  0.010  1  0.010
18 -0.018  1 -0.018
19  0.046  1  0.046

Uma solução um pouco mais sofisticada também cuida do caso 0:

# To test the 0 case I am changing two numbers to 0
x = [-0.01, 0.003, -0.002, 0.018, 
     0.002, 0.006, -0.012, 0.014, 
    -0.017, -0.007, 0, 0, 
    -0.004, 0.015, 0.002, -0.001, 
    -0.008, 0.01, -0.018, 0.046]

# The rest is similar
count = 0
# This time we are using a nested ternary assignment 
# to account for the case of 0
# This would be more readable as a function, 
# but what it does is simple
# It returns None if n is 0, 
# True if it is larger than 0 
# and False if it less than 0
sign_positive = None if n == 0 else False if n < 0 else True
sign_count = []
for n in x:
    # We add the case of 0 by adding a third condition where
    # sign_positive was None (meaning the previous
    # number was 0) and the current number is 0.
    if (n > 0 and sign_positive) or \
       (n < 0 and not sign_positive) or \
       (n == 0 and sign_positive == None):
        count = count + 1
    else:
        count = 1
    sign_positive = None if n == 0 else False if n < 0 else True
    sign_count.append(count)
results = list(zip(x, sign_count))
for i, result in enumerate(results):
    print(f"{i: >2d} {result[0]: .3f} {result[1]: >2d} {result[0]: .3f}")

 0 -0.010  1 -0.010
 1  0.003  1  0.003
 2 -0.002  1 -0.002
 3  0.018  1  0.018
 4  0.002  2  0.002
 5  0.006  3  0.006
 6 -0.012  1 -0.012
 7  0.014  1  0.014
 8 -0.017  1 -0.017
 9 -0.007  2 -0.007
10  0.000  1  0.000
11  0.000  2  0.000
12 -0.004  3 -0.004
13  0.015  1  0.015
14  0.002  2  0.002
15 -0.001  1 -0.001
16 -0.008  2 -0.008
17  0.010  1  0.010
18 -0.018  1 -0.018
19  0.046  1  0.046
Sinan Kurmus
fonte