Package ch.agent.t2.timeseries

Source Code of ch.agent.t2.timeseries.RegularTimeSeries$TimeSeriesIterator

/*
*   Copyright 2011-2013 Hauser Olsson GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package ch.agent.t2.timeseries;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import ch.agent.t2.T2Exception;
import ch.agent.t2.T2Msg;
import ch.agent.t2.T2Msg.K;
import ch.agent.t2.time.Range;
import ch.agent.t2.time.TimeDomain;

/**
* RegularTimeSeries implements {@link TimeIndexable}.
* <p>
* <b>Nota bene.</b>
* The implementation is not thread-safe.
*
* @author Jean-Paul Vetterli
* @param <T> the value type
*/
public class RegularTimeSeries<T> extends AbstractTimeSeries<T> implements TimeIndexable<T> {

  /**
   * TimeSeriesIterator is an {@link Iterator} returning {@link Observation} objects.
   */
  public class TimeSeriesIterator implements Iterator<Observation<T>> {

    private Iterator<T> dataIt;
    private long index;
    private TimeDomain domain;
   
    /**
     * Construct an iterator using the index start and the data.
     * @param start the numerical time index of the first element of data
     * @param data the non-null list of values
     */
    public TimeSeriesIterator(long start, List<T> data) {
      dataIt = data.iterator();
      domain = getTimeDomain();
      index = start;
    }

    @Override
    public boolean hasNext() {
      return dataIt.hasNext();
    }

    @Override
    public Observation<T> next() {
      Observation<T> obs = new Observation<T>(domain, index++, dataIt.next());
      return obs;
    }

    @Override
    public void remove() {
      throw new UnsupportedOperationException();
    }

  }

  private List<T> data;
  private T[] template;
  private long start; // negative when no data
  private T[] empty;
  private int maxGap;
 
  /**
   * The parameterless constructor is never used.
   */
  @SuppressWarnings("unused")
  private RegularTimeSeries() {
  }
 
  /**
   * Construct a regular time series.
   * <p>
   * The template argument is required for turning the generic collection into an array.
   *
   * @param domain a non-null time domain
   * @param template a non-null array, typically empty
   * @param missingValue an object representing missing values for this value type
   * @param maxGap the largest run of missing values allowed
   */
  protected RegularTimeSeries(TimeDomain domain, T[] template, T missingValue, int maxGap) {
    super(domain, missingValue);
    this.data = new ArrayList<T>();
    this.start = -1;
    this.template = template;
    this.empty = new ArrayList<T>(0).toArray(template);
    this.maxGap = maxGap;
  }
 
  /**
   * Construct a shorter view of a time series.
   *
   * @param ts the non-null time series to copy
   * @param fromOffset start offset of the view (inclusive)
   * @param toOffset  end offset of the view (exclusive)
   * @throws T2Exception
   */
  private RegularTimeSeries(RegularTimeSeries<T> ts, int fromOffset, int toOffset) {
    // no need to clone domain, template, empty, or missingValue
    super(ts.getTimeDomain(), ts.getMissingValue());
    this.template = ts.template;
    this.empty = ts.empty;
    this.maxGap = ts.maxGap;
    if (toOffset > fromOffset) {
      this.data = new ArrayList<T>(ts.data.subList(fromOffset, toOffset));
      this.start = ts.start + fromOffset;
    } else {
      this.data = new ArrayList<T>();
      this.start = -1;
    }
  }

  @Override
  public Iterator<Observation<T>> iterator() {
    return new TimeSeriesIterator(start, data);
  }
 
  @Override
  public TimeAddressable<T> get(Range range) throws T2Exception {
    getTimeDomain().requireEquality(range.getTimeDomain());
    if (range.isEmpty())
      return new RegularTimeSeries<T>(this, 0, -1);
    else
      return get(range.getFirstIndex(), range.getLastIndex());
  }

  @Override
  public TimeAddressable<T> get(long first, long last) throws T2Exception {
    if (first > last) {
      if (first == 0 && last == -1)
        return new RegularTimeSeries<T>(this, 0, -1);
      throw T2Msg.exception(K.T5016, getTimeDomain().time(first).toString(),
          getTimeDomain().time(last).toString());
    }
   
    if (start < 0)
      return new RegularTimeSeries<T>(this, 0, -1);
   
    int fromOffset = offset(first, start);
    if (fromOffset < 0)
      fromOffset = 0;
   
    if (fromOffset >= data.size())
      return new RegularTimeSeries<T>(this, 0, -1);

    /* toOffset is the index of the element after the last one requested;
     * by construction, it can be cast to an int.
     */
    long toOffset = fromOffset + last - first + 1; // can be < 0 due to overflow
    if (toOffset > data.size() || toOffset < 0)
      toOffset = data.size();

    // exclude missing values at the beginning of the series
    while (data.get(fromOffset) == getMissingValue()) {
      if (++fromOffset == toOffset)
        break;
    }
    if (fromOffset < toOffset) {
      // exclude missing values at the end of the series
      while (data.get((int) toOffset - 1) == getMissingValue() && fromOffset < toOffset) {
        toOffset--;
      }
    }
   
    return new RegularTimeSeries<T>(this, fromOffset, (int) toOffset);
  }
 
  @Override
  protected Observation<T> internalGetLast(long index) throws T2Exception {
    T value = get(index);
    if (isMissing(value)) {
      long last = internalGetLastIndex();
      if (last >= 0) {
        if (index > last) {
          value = get(last);
          index = last;
        } else {
          long first = internalGetFirstIndex();
          while (index > first && isMissing(value)) {
            value = get(--index);
          }
          // else a missing value is returned
        }
      }
    }
    if (isMissing(value))
      return null;
    else
      return new Observation<T>(getTimeDomain(), index, value);
  }
 
  @Override
  protected Observation<T> internalGetFirst(long index) throws T2Exception {
    T value = get(index);
    if (isMissing(value)) {
      long first = internalGetFirstIndex();
      if (first >= 0) {
        if (index < first) {
          value = get(first);
          index = first;
        } else {
          long last = internalGetLastIndex();
          while (index < last && isMissing(value)) {
            value = get(++index);
          }
          // else a missing value is returned
        }
      }
    }
    if (isMissing(value))
      return null;
    else
      return new Observation<T>(getTimeDomain(), index, value);
  }

  @Override
  public int getValueCount() {
    T[] values = getArray();
    int valueCount = 0;
    for (T d: values)
      if (!isMissing(d))
        valueCount++;
    return valueCount;
  }

  @Override
  public boolean isIndexable() {
    return true;
  }

  @Override
  public int getMaxGap() {
    return maxGap;
  }

  @Override
  public TimeIndexable<T> makeEmptyCopy() {
    return new RegularTimeSeries<T>(getTimeDomain(), template, getMissingValue(), maxGap);
  }
 
  @Override
  public TimeIndexable<T> asIndexable() throws T2Exception {
    return this;
  }

  @Override
  public TimeIndexable<T> copy() throws T2Exception {
    TimeIndexable<T> ts = makeEmptyCopy();
    ts.put(this, null);
    return ts;
  }

  @Override
  public T[] getArray() {
    if (start < 0)
      return empty;
    return data.toArray(template);
  }
 
  @Override
  public T[] getArray(Range range) throws T2Exception {
    long first = range.getFirstIndex();
    long last = range.getLastIndex();
    if (start < 0)
      return empty;
    int firstOffset = offset(first, start);
    int lastOffset = offset(last, start);
    if (firstOffset >= data.size())
      return empty;
    if (lastOffset >= data.size())
      lastOffset = data.size() - 1;
    // subList() is a view but toArray() makes a copy
    return data.subList(firstOffset, lastOffset + 1).toArray(template);
  }
 
  @Override
  public void put(long index, T[] values) throws T2Exception {
    if (getSize() != 0)
      super.put(index, values);
    else {
      // optimized version
      // ensure all missing values are the same object and that there are no MVs at the boundaries
      int firstNonMissing = 0;
      int lastNonMissing = -1;
      for (int i = 0; i < values.length; i++) {
        values[i] = normalizeMissingValue(values[i]);
        if (isMissing(values[i])) {
          if (firstNonMissing == i)
            firstNonMissing = i + 1;
        } else
          lastNonMissing = i;
      }
      if (lastNonMissing < 0) {
        ; // nothing to add
      } else if (firstNonMissing == 0 && lastNonMissing == values.length - 1) {
        data.addAll(0, Arrays.asList(values));
        start = index;
      } else {
        data.addAll(0, Arrays.asList(values).subList(firstNonMissing, lastNonMissing + 1));
        start = index + firstNonMissing;
      }
    }
  }

  @Override
  public void put(TimeAddressable<T> values, UpdateReviewer<T> reviewer) throws T2Exception {
    if (reviewer != null || getSize() != 0 || !values.isIndexable())
      super.put(values, reviewer);
    else {
      // optimized version
      data.addAll(0, ((AbstractTimeSeries<T>) values).internalGetData());
      start = ((AbstractTimeSeries<T>) values).internalGetFirstIndex()// -1 when no data
    }
  }

  /**
   * Append a value many times.
   *
   * @param value a value
   * @param repetitions a number
   */
  private void append(T value, long repetitions) {
    if ((data.size() + repetitions) > Integer.MAX_VALUE)
      throw new RuntimeException("too many repetitions: " + repetitions);
    if (repetitions > 0) {
      while (repetitions > 0) {
        data.add(value);
        repetitions--;
      }
    }
  }
 
  @Override
  public int fill(T replacement, long tailLength) throws T2Exception {
    T mv = getMissingValue();
   
    if (replacement == null && mv != null)
      throw T2Msg.exception(K.T5015);
   
    if (replacement.equals(mv) && tailLength > 0)
      throw T2Msg.exception(K.T5020);
    else
      replacement = mv;
     
    int count = 0;
    T[] val = getArray();
    if (val == null)
      return count;
   
    for (int i = 0; i < val.length; i++) {
      if (isMissing(val[i])) {
        val[i] = replacement;
        count++;
      }
    }
    if (count > 0) {
      data.clear();
      data.addAll(Arrays.asList(val));
    }
    if (tailLength > 0) {
      append(replacement, tailLength);
      count += tailLength;
    }
    return count;
  }
 
  @Override
  public int fill(long tailLength) {
    int count = 0;
    T[] val = getArray();
    if (val == null)
      return count;
   
    for (int i = 1; i < val.length; i++) {
      if (isMissing(val[i]) && !isMissing(val[i-1])) {
        val[i] = val[i-1];
        count++;
      }
    }
    if (count > 0) {
      data.clear();
      data.addAll(Arrays.asList(val));
    }
    if (tailLength > 0 && val.length > 0) {
      T replacement = val[val.length - 1];
      append(replacement, tailLength);
      count += tailLength;
    }
    return count;
  }
 
  @Override
  public int fill(Filler<T> interpolator) throws T2Exception {
    int count = 0;
    T[] val = getArray();
    if (val == null)
      return count;

    int mvStart = -1;
    for (int i = 0; i < val.length; i++) {
      if (isMissing(val[i])) {
        count++;
        if (mvStart == -1)
          mvStart = i;
      } else {
        if (mvStart > 0) {
          // i.e. don't interpolate when first element is a missing value
          try {
            interpolator.fillHole(val, mvStart - 1, i);
          } catch (Exception e) {
            Range range = new Range(getTimeDomain(), getFirstIndex() + mvStart,  getFirstIndex() + i - 1);
            throw T2Msg.exception(e, K.T5017, range.toString());
          }
          for (int j = mvStart; j < i; j++) {
            val[j] = normalizeMissingValue(val[j]);
          }
        }
        mvStart = -1;
      }
    }

    if (count > 0) {
      data.clear();
      data.addAll(Arrays.asList(val));
    }
    return count;
  }

  @Override
  protected void internalClear() {
    data.clear();
    start = -1;
  }

  @Override
  protected T internalGet(long index) throws T2Exception {
    if (start < 0)
      return getMissingValue();
    int offset = offset(index, start);
    if (offset < 0 || offset >= data.size())
      return getMissingValue();
    return data.get(offset);
  }

  @Override
  protected Collection<T> internalGetData() {
    return data;
  }

  @Override
  protected long internalGetFirstIndex() {
    return start;
  }

  @Override
  protected long internalGetLastIndex() {
    if (start < 0)
      return start;
    else
      return start + internalGetSize() - 1;
  }

  @Override
  protected int internalGetSize() {
    return data.size();
  }

  @Override
  protected void internalPut(long index, T value) throws T2Exception {
    if (index < 0)
      throw new IllegalArgumentException("index < 0");
   
    value = normalizeMissingValue(value);
   
    if (start < 0) {
      if (!isMissing(value)) {
        // new series
        start = index;
        data.add(value);
      }
      return;
    }

    // series has data
    int offset = offset(index, start);
   
    if (offset >= 0 && offset < data.size()) {
      // update existing element
      // adding a missing value at the boundary reduces the range
      if (isMissing(value)) {
        if (offset == 0) {
          start++;
          data.remove(0);
          removeBeginningMissingValues();
        }
        if (offset == data.size() - 1) {
          data.remove(offset);
          removeEndingMissingValues();
        }
      } else {
        data.set(offset, value);
      }
    } else {
      // do not add missing values out of range
      if (isMissing(value)) {
        return;
      }
      // append new element to the ...
      if (offset < 0) {
        // ... left ...
        int padSize = -offset - 1;
        if (padSize > 0) {
          if (padSize > maxGap)
            throw T2Msg.exception(K.T5018, padSize, maxGap, getTimeDomain().time(index).toString());
          List<T> pad = new ArrayList<T>(padSize);
          for (int i = 0; i < padSize; i++)
            pad.add(getMissingValue());
          data.addAll(0, pad);
        }
        data.add(0, value);
        start = index;
      } else {
        // ... or to the right
        int padSize = offset - data.size();
        if (padSize > 0) {
          if (padSize > maxGap) // versions 1.26 to 1.49 had "padSize > 10* MAX_GAP" ???
            throw T2Msg.exception(K.T5019, padSize, maxGap, getTimeDomain().time(index).toString());
          List<T> pad = new ArrayList<T>(padSize);
          for (int i = 0; i < padSize; i++)
            pad.add(getMissingValue());
          data.addAll(pad);
        }
        data.add(value);
      }
    }
  }

  @Override
  protected void internalRemove(long index) throws T2Exception {
    internalPut(index, getMissingValue());
  }

  @Override
  protected void internalSetBounds(long first, long last) throws T2Exception {
    // - the range becomes smaller, so the long-to-int casts are okay by definition
    // - here it is okay to keep the subList because the original won't be used by anyone
    data = data.subList((int)(first - start), (int) (last - start + 1));
    start = first;
    removeBeginningMissingValues();
    removeEndingMissingValues();
  }
 
  /**
   * Remove all missing values at the start of the series.
   */
  private void removeBeginningMissingValues() {
    while(data.size() > 0 && isMissing(data.get(0))) {
      data.remove(0);
      start++;
    }
  }
 
  /**
   * Remove all missing values at the end of the series.
   */
  private void removeEndingMissingValues() {
    while(data.size() > 0 && isMissing(data.get(data.size() - 1))) {
      data.remove(data.size() - 1);
    }
  }
 
  /**
   * Return a numerical time index as an offset from the start of the time series and
   * ensure it fits in a 32 bit integer.
   *
   * @param index a numerical time index
   * @param start the numerical time index of the start of the series
   * @return the difference between index and start
   * @throws T2Exception
   */
  private int offset(long index, long start) throws T2Exception {
    long offset = index - start;
    if (offset < Integer.MIN_VALUE || offset > Integer.MAX_VALUE)
      throw T2Msg.exception(K.T1058, index, start);
    return (int) offset;
  }
 
}
TOP

Related Classes of ch.agent.t2.timeseries.RegularTimeSeries$TimeSeriesIterator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.