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