/* * 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.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.BitSet; import java.util.function.Function; /** * Numerical variable which store only 1,0 and missing values. * This is a storage-optimized version of a binary variable * * @author <a href="mailto:padreati@yahoo.com>Aurelian Tutuianu</a> */ public final class Binary extends AbstractVar { /** * Builds an empty binary var * * @return new instance of binary var */ public static Binary empty() { return new Binary(0, false, false); } /** * Builds a binary variable of given size with filled missing values * * @param rows size of variable * @return new instance of binary var */ public static Binary empty(int rows) { return new Binary(rows, true, false); } /** * Builds a new binary variable of given size filled with given value * * @param rows size of variable * @param fillValue fill value * @return new instance of binary var */ public static Binary fill(int rows, boolean fillValue) { return new Binary(rows, false, fillValue); } /** * Builds a new binary variable with values copied from given array of values * * @param values given array of values * @return new instance of binary var */ public static Binary copy(int... values) { final Binary b = new Binary(values.length, false, false); for (int i = 0; i < values.length; i++) { if (values[i] == 0) continue; if (values[i] == 1) { b.setBinary(i, true); continue; } b.setMissing(i); } return b; } /** * Builds a new binary variable with values copied from the given array of boolean values * * @param values source values * @return new instance of binary var */ public static Binary copy(boolean... values) { final Binary b = new Binary(values.length, false, false); for (int i = 0; i < values.length; i++) { if (values[i]) { b.setBinary(i, true); } } return b; } public static Binary fromIndex(int rows, Function<Integer, Integer> supplier) { int[] data = new int[rows]; for (int i = 0; i < data.length; i++) { data[i] = supplier.apply(i); } return Binary.copy(data); } public static Binary from(int rows, Function<Integer, Boolean> supplier) { boolean[] data = new boolean[rows]; for (int i = 0; i < data.length; i++) { data[i] = supplier.apply(i); } return Binary.copy(data); } private static final long serialVersionUID = -4977697633437126744L; private int rows; private BitSet missing; private BitSet values; /** * Private constructor to avoid instantiation from outside, other than statical builders. */ private Binary(final int rows, final boolean fillMissing, final boolean fillValue) { this.rows = rows; this.missing = new BitSet(rows); this.values = new BitSet(rows); if (fillMissing) this.missing.flip(0, rows); else if (fillValue) this.values.flip(0, rows); } @Override public VarType type() { return VarType.BINARY; } @Override public Binary withName(String name) { return (Binary) super.withName(name); } void increaseCapacity(int minCapacity) { if (minCapacity <= values.size()) { return; } if (minCapacity > rows) { minCapacity = Math.max(minCapacity, rows + (rows >> 1)); BitSet missingCopy = new BitSet(minCapacity); BitSet valuesCopy = new BitSet(minCapacity); missingCopy.or(missing); valuesCopy.or(values); missing = missingCopy; values = valuesCopy; } } @Override public int rowCount() { return rows; } @Override public void addRows(int rowCount) { increaseCapacity(rows + rowCount); for (int i = 0; i < rowCount; i++) { missing.set(i + rows); } rows += rowCount; } @Override public double value(int row) { if (missing(row)) return -1.0; return values.get(row) ? 1.0 : 0.0; } @Override public void setValue(int row, double value) { if (value == 1.0) { setBinary(row, true); return; } if (value == 0.0) { setBinary(row, false); return; } if (value == -1.0) { setMissing(row); return; } throw new IllegalArgumentException(String.format("Value %f is not a valid binary value", value)); } @Override public void addValue(double value) { if (Math.abs(value - 1.0) <= 10e-3) { addBinary(true); return; } if (Math.abs(value) <= 10e-3) { addBinary(false); return; } if (Math.abs(value + 1.0) <= 10e-3) { addMissing(); return; } throw new IllegalArgumentException(String.format("Value %f is not a valid binary value", value)); } @Override public int index(int row) { if (missing(row)) return -1; return binary(row) ? 1 : 0; } @Override public void setIndex(int row, int value) { if (value == 1) { setBinary(row, true); return; } if (value == 0) { setBinary(row, false); return; } if (value == -1) { setMissing(row); return; } throw new IllegalArgumentException(String.format("Value %d is not a valid binary value", value)); } @Override public void addIndex(int value) { if (value == 1) { addBinary(true); return; } if (value == 0) { addBinary(false); return; } if (value == -1) { addMissing(); return; } throw new IllegalArgumentException(String.format("Value %d is not a valid binary value", value)); } @Override public String label(int row) { return missing(row) ? "?" : (binary(row) ? "true" : "false"); } @Override public void setLabel(int row, String value) { if ("?".equals(value)) { setMissing(row); return; } if ("true".equalsIgnoreCase(value) || "1".equals(value)) { setBinary(row, true); return; } if ("false".equalsIgnoreCase(value) || "0".equals(value)) { setBinary(row, false); return; } throw new IllegalArgumentException( String.format("The value %s could not be converted to a binary value", value)); } @Override public void addLabel(String value) { if ("?".equals(value)) { addMissing(); return; } if ("true".equalsIgnoreCase(value) || "1".equals(value)) { addBinary(true); return; } if ("false".equalsIgnoreCase(value) || "0".equals(value)) { addBinary(false); return; } throw new IllegalArgumentException( String.format("The value %s could not be converted to a binary value", value)); } @Override public String[] levels() { return new String[]{"?", "true", "false"}; } @Override public void setLevels(String... dict) { throw new IllegalArgumentException("Operation not implemented on binary variables"); } @Override public boolean binary(int row) { return values.get(row); } @Override public void setBinary(int row, boolean value) { if (missing(row)) missing.set(row, false); values.set(row, value); } @Override public void addBinary(boolean value) { increaseCapacity(rows + 1); setBinary(rows, value); rows++; } @Override public long stamp(int row) { return binary(row) ? 1L : 0L; } @Override public void setStamp(int row, long value) { if (value == 1) { setBinary(row, true); return; } if (value == 0) { setBinary(row, false); return; } if (value == -1) { setMissing(row); return; } throw new IllegalArgumentException(String.format("This value %d is not a valid binary value", value)); } @Override public void addStamp(long value) { if (value == 1) { addBinary(true); return; } if (value == 0) { addBinary(false); return; } if (value == -1) { addMissing(); return; } throw new IllegalArgumentException(String.format("This value %d is not a valid binary value", value)); } @Override public boolean missing(int row) { return missing.get(row); } @Override public void setMissing(int row) { missing.set(row); } @Override public void addMissing() { increaseCapacity(rows + 1); missing.set(rows); rows++; } @Override public void remove(int row) { if (row < 0 || row >= rows) { throw new IllegalArgumentException(); } for (int i = row + 1; i < rows; i++) { values.set(i - 1, values.get(i)); missing.set(i - 1, missing.get(i)); } rows--; } @Override public void clear() { this.rows = 0; } @Override public Var newInstance(int rows) { return Binary.empty(rows).withName(name()); } @Override public Binary solidCopy() { return (Binary) super.solidCopy(); } private void writeObject(ObjectOutputStream out) throws IOException { out.writeInt(rowCount()); byte[] buff = values.toByteArray(); out.writeInt(buff.length); out.write(buff); buff = missing.toByteArray(); out.writeInt(buff.length); out.write(buff); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { rows = in.readInt(); byte[] buff = new byte[in.readInt()]; in.readFully(buff); values = BitSet.valueOf(buff); buff = new byte[in.readInt()]; in.readFully(buff); missing = BitSet.valueOf(buff); } }