/* * Apache License * Version 2.0, January 2004 * http://www.apache.org/licenses/ * * Copyright 2013 Aurelian Tutuianu * Copyright 2014 Aurelian Tutuianu * Copyright 2015 Aurelian Tutuianu * Copyright 2016 Aurelian Tutuianu * * 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 rapaio.data; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; /** * A bound variable is a variable which is obtained by binding observations * from multiple variables of the same type. * <p> * Created by <a href="mailto:padreati@yahoo.com">Aurelian Tutuianu</a>. */ public class BoundVar extends AbstractVar { public static BoundVar from(List<Integer> counts, List<Var> vars) { return new BoundVar(counts, vars); } public static BoundVar from(List<Var> vars) { return new BoundVar(vars.stream().map(Var::rowCount).collect(Collectors.toList()), vars); } // private constructor public static BoundVar from(Var... vars) { return new BoundVar( Arrays.stream(vars).map(Var::rowCount).collect(Collectors.toList()), Arrays.stream(vars).collect(Collectors.toList()) ); } private static final long serialVersionUID = 5449912906816640189L; private final int rowCount; private final VarType varType; private final List<Integer> counts; private final List<Var> vars; // static builders private BoundVar(List<Integer> counts, List<Var> vars) { if (vars.isEmpty()) throw new IllegalArgumentException("List of vars is empty"); if (counts.isEmpty()) throw new IllegalArgumentException("List of counts is empty"); if (vars.size() != counts.size()) throw new IllegalArgumentException("List of counts is not equal with list of variables"); if (vars.stream().map(Var::type).distinct().count() != 1) throw new IllegalArgumentException("It is not allowed to bind variables of different types"); this.rowCount = counts.stream().mapToInt(i -> i).sum(); this.varType = vars.get(0).type(); this.counts = new ArrayList<>(); this.vars = new ArrayList<>(); int last = 0; for (int i = 0; i < counts.size(); i++) { if (vars.get(i) instanceof BoundVar) { BoundVar boundVar = (BoundVar) vars.get(i); for (int j = 0; j < boundVar.counts.size(); j++) { this.counts.add(boundVar.counts.get(j) + last); this.vars.add(boundVar.vars.get(j)); } last += boundVar.rowCount; } else { this.counts.add(counts.get(i) + last); this.vars.add(vars.get(i)); last += vars.get(i).rowCount(); } } this.withName(vars.get(0).name()); } private int findIndex(int row) { if (row >= rowCount || row < 0) throw new IllegalArgumentException("Row index is not valid: " + row); int pos = Collections.binarySearch(counts, row); return pos >= 0 ? (counts.get(pos) == row ? pos + 1 : pos) : -pos - 1; } private int localRow(int pos, int row) { return pos > 0 ? row - counts.get(pos - 1) : row; } @Override public VarType type() { return varType; } @Override public int rowCount() { return rowCount; } @Override public Var bindRows(Var var) { if (var instanceof BoundVar) { List<Integer> newCounts = new ArrayList<>(); List<Var> newVars = new ArrayList<>(); int last = 0; for (int i = 0; i < counts.size(); i++) { newCounts.add(counts.get(i) - last); newVars.add(vars.get(i)); last = counts.get(i); } BoundVar boundVar = (BoundVar) var; last = 0; for (int i = 0; i < boundVar.counts.size(); i++) { newCounts.add(boundVar.counts.get(i) - last); newVars.add(boundVar.vars.get(i)); last = boundVar.counts.get(i); } return BoundVar.from(newCounts, newVars); } else { return BoundVar.from(this, var); } } @Override public void addRows(int rowCount) { bindRows(newInstance(rowCount)); } @Override public double value(int row) { int pos = findIndex(row); return vars.get(pos).value(localRow(pos, row)); } @Override public void setValue(int row, double value) { int pos = findIndex(row); vars.get(pos).setValue(localRow(pos, row), value); } @Override public void addValue(double value) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public int index(int row) { int pos = findIndex(row); return vars.get(pos).index(localRow(pos, row)); } @Override public void setIndex(int row, int value) { int pos = findIndex(row); vars.get(pos).setIndex(localRow(pos, row), value); } @Override public void addIndex(int value) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public String label(int row) { int pos = findIndex(row); return vars.get(pos).label(localRow(pos, row)); } @Override public void setLabel(int row, String value) { int pos = findIndex(row); vars.get(pos).setLabel(localRow(pos, row), value); } @Override public void addLabel(String value) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public String[] levels() { return vars.get(0).levels(); } @Override public void setLevels(String[] dict) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public boolean binary(int row) { int pos = findIndex(row); return vars.get(pos).binary(localRow(pos, row)); } @Override public void setBinary(int row, boolean value) { int pos = findIndex(row); vars.get(pos).setBinary(localRow(pos, row), value); } @Override public void addBinary(boolean value) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public long stamp(int row) { int pos = findIndex(row); return vars.get(pos).stamp(localRow(pos, row)); } @Override public void setStamp(int row, long value) { int pos = findIndex(row); vars.get(pos).setStamp(localRow(pos, row), value); } @Override public void addStamp(long value) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public boolean missing(int row) { int pos = findIndex(row); int localRow = localRow(pos, row); return vars.get(pos).missing(localRow); } @Override public void setMissing(int row) { int pos = findIndex(row); vars.get(pos).setMissing(localRow(pos, row)); } @Override public void addMissing() { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public void remove(int row) { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public void clear() { throw new IllegalArgumentException("This operation is not available for bound variable"); } @Override public Var newInstance(int rows) { if (vars.isEmpty()) throw new IllegalArgumentException("this operation is not available for a bounded var with no rows"); return vars.get(0).newInstance(rows); } }