Подсчет и суммирование последовательностей положительных и отрицательных чисел

31

Я хочу написать код для подсчета и суммирования любых положительных и отрицательных серий чисел.
Числа либо положительные, либо отрицательные (без нуля).
Я написал коды с forпетлями. Есть ли творческая альтернатива?

Данные

р

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

питон

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]

петли

р

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])])
}

питон

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)]))

результат

        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
Иман
источник

Ответы:

17

Другие решения выглядят хорошо, но вам не нужно использовать сложные языковые функции или библиотечные функции для этой простой проблемы.

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

Как видите, вам не нужен sign_indicatorсписок, два rangeцикла for или функция, как во фрагменте в разделе вопросов.

Если вы хотите, чтобы индекс начинался с 1, используйте enumerate(x, 1)вместоenumerate(x)

Чтобы увидеть результат, вы можете запустить следующий код

for idx, num, length, summation in result:
     print(f"{idx: >2d} {num: .3f} {length: >2d} {summation: .3f}")
бомбы
источник
14

В R вы можете использовать data.tables rleidдля создания групп с положительными и отрицательными рядами чисел, а затем создать последовательность строк в каждой группе и сделать кумулятивную сумму xзначений.

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

Мы можем использовать rleidв dplyrкачестве хорошо для создания групп и сделать то же самое.

library(dplyr)
df %>%
  group_by(gr = data.table::rleid(sign(x))) %>%
  mutate(n_of_sequence = row_number(), sum = cumsum(x))
Ронак Шах
источник
2
n_of_sequenceне совпадает с желаемым
Иман
@Iman Извините, я неправильно прочитал вывод. Я исправил это сейчас.
Ронак Шах
10

Вы можете рассчитать длину пробега каждого знака, используя rleот baseи сделать что-то вроде этого.

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

Получить 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

Наконец, чтобы получить суммы последовательностей,

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
эмир
источник
6

Вот простая нецикличная функция в 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))
}

Так что вы можете сделать:

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

Создано 2020-02-16 пакетом представлением (v0.3.0)

Аллан Кэмерон
источник
5

Вот простое tidyverseрешение ...

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 
Эндрю Густар
источник
5

Что касается Python, кто-то придумает решение с использованием библиотеки панд. А пока вот простое предложение:

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}")

Вывод:

  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

Если вам нужны отдельные списки, вы можете сделать

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

или, если итераторы в порядке, просто

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

(объяснение здесь )

Уолтер Тросс
источник
5

Два разных ленивых решения в Python, использующих модуль itertools .

Используя itertools.groupby (и накапливать)

from itertools import accumulate, groupby

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

Использование itertools.accumulate с пользовательской функцией накопления

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

initialКлючевое слово аргумент был добавлен в Python 3.8. В более ранних версиях вы можете использовать itertools.chainдля добавления (0,0) -тупы:

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

Результат такой, как и ожидалось:

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
источник
5

Я рекомендую R пакет бегун для этого вида операций. streak_run вычисляет последовательное вхождение одного и того же значения, а sum_run вычисляет сумму в окне, длина которой определяется kаргументом.

Вот решение:

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

Ниже ориентир для сравнения актуальных решений

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
источник
1
измерение в микросекундах не имеет особого смысла. Некоторые функции имеют начальные издержки в микросекундах, но они масштабируются для больших наборов данных намного лучше, чем другие. Также df <- data.table(x)полная копия данных. Кроме того, вы печатаете данные в некоторых примерах (это еще одна полная копия), а в других - нет.
Дэвид Аренбург
Вы правы, исправлены.
GoGonzo
некоторые функции возвращают разные объекты - некоторые векторы и некоторые кадры данных - так что это все еще не совсем справедливый тест. Также некоторые дают разные результаты. Попробуй r = runner_streak(x); d = dt_streak(dt) ; all.equal(r, d$sum). Только проверил несколько bbut tv_streakдает так же, как dt_streak; count_and_sumдает то же, runner_streakчто отличается от предыдущих двух.
user2957945
3

В R вы также можете сделать:

# 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
источник
3

Киньте мой [r] ответ в шапку, оптимизированный для скорости и работающий с любой длиной x (в отличие от ответа автора, который был жестко запрограммирован для длины 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)
  }

И, чтобы сравнить время выполнения на моем текущем (очень медленном) рабочем компьютере, вот вывод моего микробенчмарка, использующего все решения R в этом потоке. Неудивительно, что решения, делающие наибольшее количество копий и конверсий, были медленнее.

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

-------------- РЕДАКТИРОВАТЬ -------------- @nicola указала, что мое решение не самое быстрое для длинных длин x, что должно быть довольно очевидно, так как я постоянно делаю копии векторов, используя вызовы типа x <- c (x, y). Я только создал самое быстрое решение для длин и 20 и просто использовал микробенчмарки настолько низко, насколько мог.

Чтобы сделать более справедливое сравнение, я отредактировал все версии, чтобы сгенерировать оригинальный код так, как я считаю, будет самым быстрым, но я приветствую отзывы об этом. Вот мой полный код тестирования и результаты для моей очень медленной системы. Я приветствую любые отзывы.

# 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)

}

Как показывают эти результаты, для других длин, чем я оптимизировал, моя версия медленная. Чем длиннее «х», тем медленнее он становится смехотворно медленным на всем, что выше 1000. Моя любимая версия - «Ронак», которая только вторая по скорости в моей системе. GoGonzo является самым быстрым на моей машине на этих длинных длинах.

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
Неблагоприятное событие
источник
Также другие ответы работают для любой длины, и ваш тест должен иметь некоторые проблемы. Что касается data.tableрешения @ Ronak, ваша длина на порядок медленнее для длины ~ 100000.
Никола
Спасибо @nicola, я только сказал, что решение asker работает только для 20 пунктов, не то, что любое другое решение не работает - они на самом деле делают. Я также оптимизировал скорость для длины 20 предметов, так что мое требование быть самым быстрым заканчивается там. Что бы это ни стоило, мне больше всего понравилось решение Ronaks, но автор явно попросил более разные способы решения проблемы. Ronak's уже быстрее для длины 1000, а также.
Adverse_Event
И расширить на микробенчмарки. Я перекодировал свой тест, чтобы каждое решение создавало (x) в формате, который они используют, чтобы те, которые делают тиблы, генерировали x в вызове tibble, то же самое для data.table и т. Д. Я перекодировал исходное решение asker, поэтому работаю для любой длины (просто сохраняя длину x в переменной и заменяя ее на 20. Затем я запустил ее для длины 100.000 для 10 итераций. Обратите внимание, что мой компьютер работает слишком медленно, он работает на межпроцессорном процессоре 5-го поколения с ddr3 в 1600 МГц. Я редактирую свой пост с этими результатами
Adverse_Event
2

В Python, помимо определения класса для хранения переменных памяти, вы можете использовать замыкание для достижения того же.

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]

Обратите внимание, что это работает только для Python 3 (в Python 2 я думаю, что вы не можете изменить переменную замыкания следующим образом). Аналогичная вещь для суммирования.

Продипта Гош
источник
2

Я думаю, что цикл будет легче читать, но просто для удовольствия, вот решение на Python с использованием рекурсии:

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
источник
2

Вот еще один базовый подход R:

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
источник
Просто чтобы придираться, Reduceскрывает петлю, так что это не зацикленное решение.
Никола
2

Простой ответ Python, игнорирует регистр 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

Немного более сложное решение, также заботится о 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
Синан Курмус
источник