/******************************************************************************* * Copyright (c) 2007, 2011 Association for Decentralized Information Management in * Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.history.util; import org.simantics.databoard.annotations.Optional; import org.simantics.databoard.annotations.Referable; /** * * @author toni.kalajainen */ public class WeightedMedian { public int maxItems; public int itemCount; @Optional public Item median; // Relative pos = pos - median.accumulated weight public double rpos; public WeightedMedian() { maxItems = Integer.MAX_VALUE; } public WeightedMedian(int maxItems) { this.maxItems = maxItems; } public void clear() { itemCount = 0; median = null; rpos = 0; } public void add(double weight, double value) { if (weight<=0) return; if (median == null) { median = new Item( weight, value ); itemCount++; rpos = weight / 2; return; } Item i = median; while (true) { if (i.value==value) { i.weight += weight; break; } if (value < i.value) { // i = 1st, add new item as first if (i.prev==null) { i.prev = new Item( weight, value, null, i ); itemCount++; break; } // value is between two items, add new item if (i.prev.value < value) { Item n = new Item( weight, value, i.prev, i ); itemCount++; i.prev.next = n; i.prev = n; break; } i = i.prev; continue; } if (value > i.value) { // i = last, add new item as last if (i.next==null) { i.next = new Item( weight, value, i, null ); itemCount++; break; } // value is between two items, add new item if (i.next.value > value) { Item n = new Item( weight, value, i, i.next ); itemCount++; i.next.prev = n; i.next = n; break; } i = i.next; continue; } } rpos += (value=median.weight) { rpos -= median.weight; median = median.next; } if (itemCount>maxItems) coalesce(); } /** */ public void coalesce() { coalesceVerySimple(); } /** * Simple coalesce, combine two samples */ public void coalesceVerySimple() { Item i = median.next; while (i!=null) { Item n = i.next; if (n==null) break; i.next = n.next; if (n.next!=null) n.next.prev = i; Item itemToMerge = n.weight>i.weight ? n : i; Item mergeToItem = n.weight>i.weight ? i : n; double totWeight = mergeToItem.weight+itemToMerge.weight; //mergeToItem.value = ( mergeToItem.value * mergeToItem.weight + itemToMerge.value * itemToMerge.weight ) / totWeight; mergeToItem.weight = totWeight; i = i.next; } i = median.prev; while (i!=null) { Item p = i.prev; if (p==null) break; i.prev = p.prev; if (p.prev!=null) p.prev.next = i; Item itemToMerge = p.weight>i.weight ? p : i; Item mergeToItem = p.weight>i.weight ? p : i; double totWeight = mergeToItem.weight+itemToMerge.weight; //mergeToItem.value = ( mergeToItem.value * mergeToItem.weight + itemToMerge.value * itemToMerge.weight ) / totWeight; mergeToItem.weight = totWeight; i.weight = totWeight; i = i.prev; } } public double getMedian() { if (median==null) return Double.NaN; return median.value; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append( "Median="+getMedian()+" "); Item i = head(); while (i!=null) { if (i==median) { sb.append("(value="+i.value+", w="+i.weight+", pos="+rpos+")"); } else { sb.append("(value="+i.value+", w="+i.weight+")"); } i = i.next; } return sb.toString(); } Item head() { if (median==null) return null; Item i = median; while (i.prev!=null) i = i.prev; return i; } @Referable static public class Item { public double weight; public double value; public Item(double weight, double value) { this.weight = weight; this.value = value; } public Item(double weight, double value, Item prev, Item next) { this.weight = weight; this.value = value; this.prev = prev; this.next = next; } @Optional public Item prev; @Optional public Item next; @Override public String toString() { return "(value="+value+", w="+weight+")"; } } }