/** * Copyright (C) 2001-2017 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.datatable; import java.util.Iterator; import java.util.Random; import java.util.Vector; /** * This class is a view on a DataTable which hides all examples not listed in an index list. * * Set the list of selected examples via {@link #setSelectedIndices(Vector)}. * * @author Marius Helf * */ public class DataTableView extends AbstractDataTable implements DataTableListener { private final DataTable parentTable; private Vector<Integer> selectedIndices = null; private int numberOfSelectedRows; public DataTableView(DataTable parentDataTable) { super(parentDataTable.getName()); this.parentTable = parentDataTable; // building initial selected indices: All numberOfSelectedRows = parentTable.getNumberOfRows(); parentTable.addDataTableListener(this, true); } public DataTable getParentTable() { return parentTable; } public void setSelectedIndices(Vector<Integer> selectedIndices) { this.selectedIndices = selectedIndices; if (selectedIndices != null) { numberOfSelectedRows = selectedIndices.size(); } else { numberOfSelectedRows = parentTable.getRowNumber(); } fireEvent(); } @Override public Iterator<DataTableRow> iterator() { return new Iterator<DataTableRow>() { int nextRow = 0; @Override public boolean hasNext() { return nextRow < numberOfSelectedRows; } @Override public DataTableRow next() { DataTableRow row = getRow(nextRow); nextRow++; return row; } @Override public void remove() { throw new UnsupportedOperationException("remove() not suppported by FilterDataTable"); } }; } @Override public int getNumberOfRows() { return numberOfSelectedRows; } @Override public DataTableRow getRow(int index) { if (index < numberOfSelectedRows) { if (selectedIndices == null) { return parentTable.getRow(index); } else { return parentTable.getRow(selectedIndices.get(index)); } } else { throw new ArrayIndexOutOfBoundsException("Index exceeds filtered range: " + index); } } /* * Delegating methods */ @Override public void add(DataTableRow row) { parentTable.add(row); } @Override public int getColumnIndex(String name) { return parentTable.getColumnIndex(name); } @Override public String getColumnName(int i) { return parentTable.getColumnName(i); } @Override public double getColumnWeight(int i) { return parentTable.getColumnWeight(i); } @Override public int getNumberOfColumns() { return parentTable.getNumberOfColumns(); } @Override public int getNumberOfSpecialColumns() { return parentTable.getNumberOfSpecialColumns(); } @Override public int getNumberOfValues(int column) { return parentTable.getNumberOfValues(column); } @Override public boolean isDate(int index) { return parentTable.isDate(index); } @Override public boolean isDateTime(int index) { return parentTable.isDateTime(index); } @Override public boolean isNominal(int index) { return parentTable.isNominal(index); } @Override public boolean isNumerical(int index) { return parentTable.isNumerical(index); } @Override public boolean isSpecial(int column) { return parentTable.isSpecial(column); } @Override public boolean isSupportingColumnWeights() { return parentTable.isSupportingColumnWeights(); } @Override public boolean isTime(int index) { return parentTable.isTime(index); } @Override public String mapIndex(int column, int index) { return parentTable.mapIndex(column, index); } @Override public int mapString(int column, String value) { return parentTable.mapString(column, value); } /** * Performs a simple sampling without replacement. If newSize is greater than the size of this * DataTableView, this DataTableView is returned. * * Creates a view onto this DataTableView (i.e. the this DataTableView won't get garbage * collected as long as the sampled DataTableView is around.) */ @Override public DataTable sample(int newSize) { int rowCount = getRowNumber(); if (rowCount <= newSize) { return this; } // initialize sampled indices int[] sampledSelectedIndices = new int[rowCount]; for (int i = 0; i < rowCount; ++i) { sampledSelectedIndices[i] = i; } // shuffle sampled indices Random rng = new Random(0); int swapIdx; int tmpValue; for (int i = 0; i < rowCount; ++i) { swapIdx = rng.nextInt(rowCount); tmpValue = sampledSelectedIndices[swapIdx]; sampledSelectedIndices[swapIdx] = sampledSelectedIndices[i]; sampledSelectedIndices[i] = tmpValue; } // convert prefix of sampled indices to vector and set as selected indices for sampled data // table view DataTableView sampledDataTable = new DataTableView(this); Vector<Integer> sampledSelectedIndicesVector = new Vector<Integer>(newSize); for (int i = 0; i < newSize; ++i) { sampledSelectedIndicesVector.add(sampledSelectedIndices[i]); } sampledDataTable.setSelectedIndices(sampledSelectedIndicesVector); return sampledDataTable; } /** * Resets the selected indices and then contains all rows. Anything else is not possible since * there does not exist any rule how to update the view. * * Subclasses like {@link FilteredDataTable} should implement a smarter version of this * function. */ @Override public void dataTableUpdated(DataTable source) { selectedIndices = null; numberOfSelectedRows = source.getRowNumber(); fireEvent(); } }