Алгоритм Python для области, содержащейся в массиве (график)

Настройка:

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

Пример: [5, 4, 3, 7, 2, 3, 1, 12]

       █
       █
       █
       █
       █
   █   █
   █   █
█  █   █
██ █   █
████ █ █
██████ █
████████

Анализировать:

Это гистограмма предыдущих чисел. Мне нужно найти область, содержащуюся на графике в количестве открытых (или незаполненных) единиц.

Решение (Pt.1):

Чтобы обойти это, я сделал алгоритм для вычисления всех пиков в массиве.

Это возвращает: [5, 7, 3, 12], а также другой список с индексами каждой записи, [0,3,5,7]

Для нас есть только три важных пика. 5, 7 и 12. Затем мы можем сломать это.

Количество открытой площади между 5 и 7 (общее правило):

(([Index Of Larger] - [Index Of Smaller] - [1])*[SmallerValue]) - [Values Of All In B/W]

Таким образом, площадь первого раздела будет (2*5) - (4+3) или 10-7 или 3. Это имеет смысл, потому что, если вы посмотрите на график, который вы видите, есть пустая секция L-образной формы, в которую вы могли бы поместить 3 единицы, например, воду без переполнения. Если вы повторите это со второй секцией, вы также получите ее правильную область.

Моя проблема заключается в разработке алгоритма перехода от ALL PEAKS к IMPORTANT PEAKS.

Ложная:

В этом случае очень легко увидеть, как это можно сделать. Вы просто пишете алгоритм, чтобы узнать, что 3 меньше, чем 7 и 12, поэтому избавитесь от него и верните уточненную версию пиков.

Однако это не всегда так просто.

Более сложный пример:

У меня есть массив:

[5, 4, 3, 7, 2, 3, 1, 12, 9, 10, 5, 3, 6, 8, 5, 6, 4, 7, 6, 9, 4, 11, 11, 4, 1, 2, 1]

Запуск через базовый алгоритм поиска пиков O(N) Он возвращает:

[5, 7, 3, 12, 10, 8, 6, 7, 9, 11, 11, 4, 2]

В этом примере мы видим ту же проблему в первой части этого вопроса, однако, после 12 в этом списке пиков, человек может легко увидеть, что следующий наиболее важный пик, на который нужно обратить внимание, - это два 11s, 4 и 2. Поэтому мне нужно пройти путь:

[5, 7, 3, 12, 10, 8, 6, 7, 9, 11, 11, 4, 2]

To:

[5, 7, 12, 11, 11, 4, 2]

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

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

Спасибо.

Ответ 1

Вы можете решить эту проблему, рассмотрев два значения:

  • Максимальный пик до сих пор, начиная с левого
  • Максимальный пик до сих пор, начиная с правого

И не принимайте пик, если он уступает обеим, потому что он будет под водой.

Ответ 2

Я думаю, что это обрабатывает все условия, но все максимальные вычисления замедлят его для больших наборов данных. Я использовал IPython Notebook для его построения. Это в основном идея @Rémi:

Для любой точки данных:

  • Возьмите максимальную точку влево и максимальную точку вправо. а. Если на концах принять нуль.
  • Возьмите минимум две точки максимума.
  • Если datapoint находится ниже этого минимума, он находится под водой и возвращает разницу в ноль.

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

Алгоритм, который занимает около 4,1 секунды, чтобы сделать 10 000 точек данных в моей системе.

Незаполненная область (желтый) будет sum(C):

%matplotlib inline
import matplotlib.pyplot as plt
import random

def contribution(L,i):
    max_left = 0 if i==0 else max(L[:i])
    max_right = 0 if i==len(L)-1 else max(L[i+1:])
    lower = min(max_left,max_right)
    return 0 if lower < L[i] else lower - L[i]

N = [random.randint(0,12) for i in range(50)]
C = [contribution(N,i) for i in range(len(N))]

ind = list(range(len(N))) # the x locations for the groups
width = 1                 # the width of the bars: can also be len(x) sequence

p1 = plt.bar(ind, N, width, color='r')
p2 = plt.bar(ind, C, width, color='y',bottom=N)

enter image description here

Изменить

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

%matplotlib inline
import matplotlib.pyplot as plt
import random

def right_maximums(L):
    '''Given list L, compute [max(L[i+1:] for i in range(len(L)-1)]+[0] more efficiently.

    This gives the maximum cell to the right of the current cell.
    Example: [1,2,3,4,5,4,3,2,1] -> [5,5,5,5,4,3,2,1,0]
    '''
    N = [0]
    for i,v in enumerate(L[:0:-1]):
        N.append(max(N[i],v))
    return N[::-1]

def contribution(N):
    '''In a bar graph of data N, compute how much "water" a data valley, assuming water
    spills off the sides of the bar graph.
    '''
    rmaxs = right_maximums(N) # compute maximums to the right of a data point in advance.
    lmax = 0 # compute maximums to the left as we go.
    C = []
    for i,v in enumerate(N):
         # find the lower of the left and right maximum.
        lower = min(lmax,rmaxs[i])
        # if the data point is higher than the maximums, it won't hold water,
        # else it holds the difference between the lower maximum and its value.
        C.append(0 if lower < v else lower - v)
        lmax = max(lmax,v)
    return C

N = [random.randrange(0,50) for i in range(50)]
C = contribution(N)

ind = list(range(len(N))) # the x locations for the groups
width = 1                 # the width of the bars: can also be len(x) sequence

p1 = plt.bar(ind, N, width, color='r')
p2 = plt.bar(ind, C, width, color='y',bottom=N)

Ответ 3

Это можно сделать за 3 прохода:

  public static int areaContained(int[] arr) {
    int[] maxL = new int[arr.length];
    int[] maxR = new int[arr.length];

    int max = 0;
    for (int i = 0; i < arr.length; i++) {
      max = Math.max(arr[i], max);
      maxL[i] = max;
    }

    max = 0;
    for (int i = arr.length - 1; i >= 0; i--) {
      max = Math.max(arr[i], max);
      maxR[i] = max;
    }

    int total = 0;
    for (int i = 0; i < arr.length; i++) {
      int areaI = Math.min(maxL[i], maxR[i]) - arr[i];
      if (areaI > 0)
        total += areaI;
    }

    return total;
  }

Основная идея заключается в том, что вклад bar i определяется комбинацией arr[i], max значения после i и максимального значения до i.