Расчет процентилей на лету - PullRequest
11 голосов
/ 19 октября 2010

Я программирую на Java.Каждые 100 мс моя программа получает новый номер.

Она имеет кэш с историей последних n = 180 номеров.Когда я получаю новое число x, я хочу вычислить, сколько чисел в кэше меньше x.После этого я хочу удалить самое старое число в кэше.

Каждые 100 мсек я хочу повторить процесс вычисления количества меньших чисел и удалить самое старое число.

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

Ответы [ 8 ]

10 голосов
/ 19 октября 2010

По практическим причинам и разумным значениям n лучше всего использовать кольцевой буфер примитива int s (для отслеживания самого старого входа) и линейныйСканирование для определения, сколько значений меньше, чем x.

Для того, чтобы это было в O(log n), вам придется использовать что-то вроде Guavas TreeMultiset .Вот схема того, как это будет выглядеть.

class Statistics {

    private final static int N = 180;
    Queue<Integer> queue = new LinkedList<Integer>();
    SortedMap<Integer, Integer> counts = new TreeMap<Integer, Integer>();

    public int insertAndGetSmallerCount(int x) {

        queue.add(x);                                // O(1)
        counts.put(x, getCount(x) + 1);              // O(log N)

        int lessCount = 0;                           // O(N), unfortunately
        for (int i : counts.headMap(x).values())     // use Guavas TreeMultiset
            lessCount += i;                          // for O(log n)

        if (queue.size() > N) {                      // O(1)
            int oldest = queue.remove();             // O(1)
            int newCount = getCount(oldest) - 1;     // O(log N)
            if (newCount == 0)
                counts.remove(oldest);               // O(log N)
            else
                counts.put(oldest, newCount);        // O(log N)
        }

        return lessCount;
    }

    private int getCount(int x) {
        return counts.containsKey(x) ? counts.get(x) : 0;
    }

}

На моем ноутбуке с частотой 1,8 ГГц это решение выполняет 1 000 000 итераций в течение примерно 13 секунд (то есть одна итерация занимает около 0,013 мс, что значительно меньше 100 мс).

6 голосов
/ 19 октября 2010

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

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


Редактировать: Мне забавно видеть, что "оптимизирована" версия работает в пять раз медленнее, чем эта тривиальная реализация (спасибо @ Eiko для анализа). Я думаю, это связано с тем, что при использовании деревьев и карт вы теряете локальность данных и получаете гораздо больше ошибок памяти (не говоря уже о выделении памяти и сборке мусора).

3 голосов
/ 19 октября 2010

Добавьте свои номера в список.Если размер> 180, удалите первое число.Подсчет просто повторяет 180 элементов, что, вероятно, достаточно быстро.Трудно превзойти производительность.

1 голос
/ 19 октября 2010

Вы можете попробовать настраиваемую структуру данных связанного списка, где каждый узел поддерживает следующий / предыдущий, а также отсортированные ссылки следующий / предыдущий.Затем вставка становится двухфазным процессом, сначала всегда вставляйте узел в хвост, и сортировка вставки, и сортировка вставки будет возвращать число чисел меньше x.Удаление - это просто удаление головы.

Вот пример, ПРИМЕЧАНИЕ: ЭТО ОЧЕНЬ НЕВЕРНО ЯВА, ЭТО ПРИМЕРНЫЙ КОД ДЛЯ ЧИСТОГО Демонстрации ИДЕИВы поняли идею!;) Кроме того, я только добавляю несколько элементов, но это должно дать вам представление о том, как это будет работать ... Худший случай для этого - полная итерация по отсортированному связанному списку - что не хуже, чем в примерах.Наверное, выше?

import java.util.*;

class SortedLinkedList {

  public static class SortedLL<T>
  {
    public class SortedNode<T>
    {
      public SortedNode(T value)
      {
        _value = value;
      }

      T _value;

      SortedNode<T> prev;
      SortedNode<T> next;

      SortedNode<T> sortedPrev;
      SortedNode<T> sortedNext;
    }

    public SortedLL(Comparator comp)
    {
      _comp = comp;
      _head = new SortedNode<T>(null);
      _tail = new SortedNode<T>(null);
      // Setup the pointers
      _head.next = _tail;
      _tail.prev = _head;
      _head.sortedNext = _tail;
      _tail.sortedPrev = _head;
      _sortedHead = _head;
      _sortedTail = _tail;      
    }

    int insert(T value)
    {
      SortedNode<T> nn = new SortedNode<T>(value);

      // always add node at end
      nn.prev = _tail.prev;
      nn.prev.next = nn;
      nn.next = _tail;
      _tail.prev = nn;

      // now second insert sort through..
      int count = 0;
      SortedNode<T> ptr = _sortedHead.sortedNext;
      while(ptr.sortedNext != null)
      {
        if (_comp.compare(ptr._value, nn._value) >= 0)
        {
          break;
        }
        ++count;
        ptr = ptr.sortedNext;
      }  

      // update the sorted pointers..
      nn.sortedNext = ptr;
      nn.sortedPrev = ptr.sortedPrev;
      if (nn.sortedPrev != null)
        nn.sortedPrev.sortedNext = nn;
      ptr.sortedPrev = nn;

      return count;            
    }

    void trim()
    {
      // Remove from the head...
      if (_head.next != _tail)
      {
        // trim.
        SortedNode<T> tmp = _head.next;
        _head.next = tmp.next;
        _head.next.prev = _head;

        // Now updated the sorted list
        if (tmp.sortedPrev != null)
        {
          tmp.sortedPrev.sortedNext = tmp.sortedNext;
        }
        if (tmp.sortedNext != null)
        {
          tmp.sortedNext.sortedPrev = tmp.sortedPrev;
        }
      }
    }

    void printList()
    {
      SortedNode<T> ptr = _head.next;
      while (ptr != _tail)
      {
        System.out.println("node: v: " + ptr._value);
        ptr = ptr.next;
      }      
    }

    void printSorted()
    {
      SortedNode<T> ptr = _sortedHead.sortedNext;
      while (ptr != _sortedTail)
      {
        System.out.println("sorted: v: " + ptr._value);
        ptr = ptr.sortedNext;
      }      
    }

    Comparator _comp;

    SortedNode<T> _head;
    SortedNode<T> _tail;    

    SortedNode<T> _sortedHead;
    SortedNode<T> _sortedTail;    

  }

  public static class IntComparator implements Comparator
  {
    public int compare(Object v1, Object v2){
      Integer iv1 = (Integer)v1;
      Integer iv2 = (Integer)v2;
      return iv1.compareTo(iv2);
    }
  }


  public static void main(String[] args){

    SortedLL<Integer> ll = new SortedLL<Integer>(new IntComparator());
    System.out.println("inserting: " + ll.insert(1));
    System.out.println("inserting: " + ll.insert(3));
    System.out.println("inserting: " + ll.insert(2));
    System.out.println("inserting: " + ll.insert(5));
    System.out.println("inserting: " + ll.insert(4));
    ll.printList();
    ll.printSorted();    

    System.out.println("inserting new value");
    System.out.println("inserting: " + ll.insert(3));
    ll.trim();
    ll.printList();
    ll.printSorted();    
  }
}
1 голос
/ 19 октября 2010

Вы можете использовать реализацию LinkedList.

С этой структурой вы можете легко манипулировать первым и последним элементами списка.(addFirst, removeFirst, ...) Для алгоритма (определите, сколько чисел меньше / больше), достаточно простого цикла в списке, который даст вам результат менее чем за 100 мс в списке элементов 180-х.

0 голосов
/ 20 октября 2010

180 значений - это не много, и простой массив, в котором поиск методом грубой силы и System.arraycopy () должен быть быстрее 1 микросекунды (1/1000 миллисекунды) и не требует GC. Это может быть быстрее, чем играть с более сложными коллекциями.

Я предлагаю вам сохранить простоту и измерить, сколько времени занимает время, прежде чем предположить, что вам нужно его оптимизировать.

0 голосов
/ 20 октября 2010

Взгляните на реализацию commons-math класса DescriptiveStatistics ( Percentile.java )

0 голосов
/ 19 октября 2010

Пусть кэш будет списком, поэтому вы можете вставить в начало, а самый старый будет в конце и будет удален.

Затем после каждой вставки просто отсканируйте весь список и рассчитайте нужное вам число.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...