001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030package org.openimaj.ml.timeseries.series; 031 032import java.lang.reflect.Array; 033import java.util.Collection; 034import java.util.Iterator; 035import java.util.LinkedList; 036import java.util.Map.Entry; 037import java.util.Set; 038import java.util.TreeMap; 039 040import org.joda.time.Interval; 041import org.joda.time.Period; 042import org.joda.time.PeriodType; 043import org.openimaj.ml.timeseries.TimeSeries; 044import org.openimaj.ml.timeseries.collection.TimeSeriesCollectionAssignable; 045import org.openimaj.util.pair.IndependentPair; 046import org.openimaj.util.reflection.ReflectionUtils; 047 048/** 049 * A generic though inefficient time series which can be used by any data type. This implementation 050 * is backed by a treemap. Array construction is handled using {@link ReflectionUtils#getTypeArguments(Class, Class)}. To 051 * use this class simple define a new class 052 * @author Sina Samangooei (ss@ecs.soton.ac.uk) 053 * 054 * @param <DATA> 055 * @param <TS> 056 */ 057public abstract class ConcreteTimeSeries<DATA,TS extends ConcreteTimeSeries<DATA,TS>> 058 extends TimeSeries<DATA[],DATA,TS> 059 implements TimeSeriesCollectionAssignable<DATA, TS> 060{ 061 private TreeMap<Long, DATA> timeSeries; 062 063 /** 064 * Initialise the backing treemap 065 */ 066 public ConcreteTimeSeries() { 067 this.timeSeries = new TreeMap<Long, DATA>(); 068 } 069 070 @Override 071 public TS get(long time, int nbefore, int nafter) { 072 if(nbefore < 0 || nafter < 0){ 073 return newInstance(); 074 } 075 LinkedList<DATA> dataList = new LinkedList<DATA>(); 076 LinkedList<Long> timeList = new LinkedList<Long>(); 077 addBefore(timeList,dataList, time, nbefore); 078 addCurrent(timeList,dataList, time); 079 addAfter(timeList,dataList, time, nafter); 080 return this.newInstance(timeList,dataList); 081 } 082 083 private void set(Collection<Long> timeList, Collection<DATA> dataList) { 084 this.timeSeries.clear(); 085 Iterator<DATA> dataIter = dataList.iterator(); 086 Iterator<Long> timeIter = timeList.iterator(); 087 for (; dataIter.hasNext();) { 088 this.timeSeries.put(timeIter.next(), dataIter.next()); 089 } 090 } 091 092 @Override 093 public TS newInstance(Collection<Long> timeList,Collection<DATA> dataList) { 094 TS instance = newInstance(); 095 Iterator<DATA> dataIter = dataList.iterator(); 096 Iterator<Long> timeIter = timeList.iterator(); 097 for (; dataIter.hasNext();) { 098 ((ConcreteTimeSeries<DATA,TS>)instance).timeSeries.put(timeIter.next(), dataIter.next()); 099 } 100 return instance; 101 } 102 103 @Override 104 public TS get(long time, int nbefore, int nafter, TS output) { 105 if(nbefore < 0 || nafter < 0){ 106 return newInstance(); 107 } 108 LinkedList<DATA> dataList = new LinkedList<DATA>(); 109 LinkedList<Long> timeList = new LinkedList<Long>(); 110 addBefore(timeList,dataList , time, nbefore); 111 addCurrent(timeList,dataList , time); 112 addAfter(timeList,dataList , time, nafter); 113 ((ConcreteTimeSeries<DATA,TS>)output).set(timeList, dataList); 114 return output; 115 } 116 117 @Override 118 public TS get(long time, long threshbefore, long threshafter) { 119 if(threshbefore < 0 || threshafter< 0){ 120 return newInstance(); 121 } 122 LinkedList<DATA> dataList = new LinkedList<DATA>(); 123 LinkedList<Long> timeList = new LinkedList<Long>(); 124 addBefore(timeList,dataList,time,threshbefore); 125 addCurrent(timeList,dataList,time); 126 addAfter(timeList,dataList,time,threshafter); 127 return newInstance(timeList,dataList); 128 } 129 130 @Override 131 public TS get(long start, long end) { 132 return get(start,0,end-start); 133 } 134 135 private void addAfter(LinkedList<Long> timeList,LinkedList<DATA> dataList, long time, long threshafter) { 136 Entry<Long, DATA> ceilEntry = null; 137 long maxTime = time + threshafter; 138 long currentTime = time + 1; // Is this ok? The ceil should be either 139 // time + 1 140 while ((ceilEntry = this.timeSeries.ceilingEntry(currentTime)) != null) { 141 currentTime = ceilEntry.getKey(); 142 if (currentTime <= maxTime) { 143 dataList.addLast(ceilEntry.getValue()); 144 timeList.addLast(currentTime); 145 currentTime++; // we have to nudge it forward, ceil doesn't find 146 // the next one otherwise 147 } else { 148 break; 149 } 150 }// The entry was either null (didn't exist) or was beyond the threshold 151 return; 152 } 153 154 private void addCurrent(LinkedList<Long> timeList, LinkedList<DATA> dataList, long time) { 155 DATA entry = this.timeSeries.get(time); 156 if (entry != null) 157 { 158 dataList.addLast(entry); 159 timeList.addLast(time); 160 } 161 } 162 163 private void addBefore(LinkedList<Long> timeList,LinkedList<DATA> dataList, long time, long threshbefore) { 164 Entry<Long, DATA> floorEntry = null; 165 long minTime = time - threshbefore; 166 long currentTime = time; 167 while ((floorEntry = this.timeSeries.lowerEntry(currentTime)) != null) { 168 currentTime = floorEntry.getKey(); 169 if (currentTime >= minTime) { 170 dataList.addFirst(floorEntry.getValue()); 171 timeList.addFirst(currentTime); 172 } else { 173 break; 174 } 175 }// The entry was either null (didn't exist) or was beyond the threshold 176 return; 177 } 178 179 private void addAfter(LinkedList<Long> timeList, LinkedList<DATA> dataList, long time, int nafter) { 180 Entry<Long, DATA> ceilEntry = null; 181 int seen = 0; 182 long currentTime = time; 183 while ((ceilEntry = this.timeSeries.higherEntry(currentTime)) != null) { 184 currentTime = ceilEntry.getKey(); 185 if (seen < nafter) { 186 dataList.addLast(ceilEntry.getValue()); 187 timeList.addLast(currentTime); 188 seen++; 189 } else { 190 break; 191 } 192 }// The entry was either null (didn't exist) or was beyond the threshold 193 return; 194 } 195 196 private void addBefore(LinkedList<Long> timeList, LinkedList<DATA> dataList, long time, int nbefore) { 197 Entry<Long, DATA> floorEntry = null; 198 int seen = 0; 199 long currentTime = time; 200 while ((floorEntry = this.timeSeries.lowerEntry(currentTime)) != null) { 201 currentTime = floorEntry.getKey(); 202 if (seen < nbefore) { 203 dataList.addFirst(floorEntry.getValue()); 204 timeList.addFirst(currentTime); 205 seen++; 206 } else { 207 break; 208 } 209 }// The entry was either null (didn't exist) or was beyond the threshold 210 return; 211 } 212 213 /** 214 * Add an element to this time series 215 * @param time 216 * @param value 217 */ 218 public void add(long time, DATA value) { 219 this.timeSeries.put(time, value); 220 } 221 222 @Override 223 public void set(long[] time, DATA[] data) { 224 for (int i = 0; i < time.length; i++) { 225 this.timeSeries.put(time[i], data[i]); 226 } 227 }; 228 229 @Override 230 public long[] getTimes() { 231 Set<Long> timeSet = this.timeSeries.keySet(); 232 long[] times = new long[timeSet.size()]; 233 int i = 0; 234 for (long time : timeSet) { 235 times[i++] = time; 236 } 237 return times; 238 } 239 240 @Override 241 public DATA[] getData() { 242 Collection<DATA> dataSet = this.timeSeries.values(); 243 DATA[] toret = dataSet.toArray(this.constructData(dataSet.size())); 244 return toret; 245 } 246 247 @SuppressWarnings("unchecked") 248 private DATA[] constructData(int size) { 249 Class<?> a = ReflectionUtils.getTypeArguments(ConcreteTimeSeries.class, this.getClass()).get(0); 250 return (DATA[]) Array.newInstance(a, size); 251 } 252 253 @Override 254 public int size() { 255 return this.timeSeries.size(); 256 } 257 258 @Override 259 public void internalAssign(TS assign) { 260 this.set(assign.getTimes(),assign.getData()); 261 } 262 263 @Override 264 public void internalAssign(long[] times, DATA[] data) { 265 this.set(times, data); 266 }; 267 268 @Override 269 public void internalAssign(Collection<Long> times, Collection<DATA> data) { 270 this.set(times, data); 271 } 272 273 @Override 274 public String toString() { 275 StringBuffer sb = new StringBuffer(); 276 String lf = "+%s => %s\n"; 277 Iterator<Entry<Long, DATA>> entryItr = this.timeSeries.entrySet().iterator(); 278 long last = 0; 279 for (int i = 0; i < this.size(); i++) { 280 Entry<Long, DATA> entry = entryItr.next(); 281 long time = entry.getKey(); 282 DATA data = entry.getValue(); 283 Interval inter = new Interval(last,time); 284 Period p = inter.toPeriod(PeriodType.yearDayTime()); 285 sb.append(String.format(lf, p,data)); 286 last =time; 287 } 288 return sb.toString(); 289 } 290 291 @Override 292 public Iterator<IndependentPair<Long, DATA>> iterator() { 293 return new Iterator<IndependentPair<Long,DATA>>(){ 294 Iterator<Entry<Long, DATA>> internal = ConcreteTimeSeries.this.timeSeries.entrySet().iterator(); 295 @Override 296 public boolean hasNext() { 297 return internal.hasNext(); 298 } 299 300 @Override 301 public IndependentPair<Long, DATA> next() { 302 Entry<Long, DATA> next = internal.next(); 303 return IndependentPair.pair(next.getKey(), next.getValue()); 304 } 305 306 @Override 307 public void remove() { 308 internal.remove(); 309 } 310 311 }; 312 } 313 314 315} 316 317