Skip to content

Commit 40fa438

Browse files
committed
Added Projection
1 parent ee8ad40 commit 40fa438

8 files changed

Lines changed: 473 additions & 15 deletions

File tree

Source/Relations/src/ca/uqac/lif/spreadsheet/relation/Intersection.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,11 @@ public Intersection(int in_arity)
100100

101101
/**
102102
* Creates a new instance of the Intersection function.
103-
* @param in_arity The input arity of the function
104103
* @param sort_output Set to <tt>true</tt> to sort rows of the output,
105104
* <tt>false</tt> otherwise
105+
* @param in_arity The input arity of the function
106106
*/
107-
public Intersection(int in_arity, boolean sort_output)
107+
public Intersection(boolean sort_output, int in_arity)
108108
{
109109
super(in_arity, sort_output);
110110
}
@@ -130,7 +130,7 @@ protected Object[] getValue(Object... inputs) throws InvalidNumberOfArgumentsExc
130130
s_inputs[i] = (Spreadsheet) inputs[i];
131131
if (i > 0 && !sameSignature(s_inputs[i - 1], s_inputs[i]))
132132
{
133-
throw new InvalidArgumentTypeException("Arguments have incompatible signatures");
133+
throw new RelationalException("Arguments have incompatible signatures");
134134
}
135135
}
136136
List<Row> row_list = new ArrayList<Row>();
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
/*
2+
A provenance-aware spreadsheet library
3+
Copyright (C) 2021-2022 Sylvain Hallé
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package ca.uqac.lif.spreadsheet.relation;
19+
20+
import java.util.ArrayList;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Set;
24+
25+
import ca.uqac.lif.dag.LabelledNode;
26+
import ca.uqac.lif.petitpoucet.NodeFactory;
27+
import ca.uqac.lif.petitpoucet.function.InvalidArgumentTypeException;
28+
import ca.uqac.lif.spreadsheet.Spreadsheet;
29+
30+
/**
31+
* Calculates the projection of a relation, by retaining only specific
32+
* columns from the input. For example, given the spreadsheet:
33+
* <p>
34+
* <table border="1">
35+
* <thead>
36+
* <tr><th>A</th><th>B</th><th>C</th></tr>
37+
* </thead>
38+
* <tbody>
39+
* <tr><td style="background:yellow">3</td><td>f</td><td style="background:yellow">true</td></tr>
40+
* <tr><td>1</td><td>o</td><td></td></tr>
41+
* <tr><td style="background:yellow">3</td><td>o</td><td style="background:yellow">true</td></tr>
42+
* <tr><td style="background:cyan">2</td><td>f</td><td style="background:cyan">false</td></tr>
43+
* <tr><td style="background:yellow">3</td><td>g</td><td style="background:yellow">true</td></tr>
44+
* </tbody>
45+
* </table>
46+
* <p>
47+
* The projection on columns A and C results in the following output:
48+
* <p>
49+
* <table border="1">
50+
* <thead>
51+
* <tr><th>A</th><th>C</th></tr>
52+
* </thead>
53+
* <tbody>
54+
* <tr style="background:yellow"><td>3</td><td>true</td></tr>
55+
* <tr><td>1</td><td></td></tr>
56+
* <tr style="background:cyan"><td>2</td><td>false</td></tr>
57+
* </tbody>
58+
* </table>
59+
* <p>
60+
* As with other relational operators, projection removes duplicates rows from
61+
* the output, and also keeps track of the relationship between input and output
62+
* cells. In the example above:
63+
* <ul>
64+
* <li>cells of the third row of the output are mapped to the cells in the
65+
* corresponding row of the input (highlighted in cyan)</li>
66+
* <li>cells of the second row of the output are mapped to the corresponding
67+
* cells in <em>all</em> the rows that are projected into this row (highlighted
68+
* in yellow)</li>
69+
* </ul>
70+
* <p>
71+
* The projection can also be used to flip the ordering of columns; hence
72+
* projecting on C and A produces:
73+
* <p>
74+
* <table border="1">
75+
* <thead>
76+
* <tr><th>C</th><th>A</th></tr>
77+
* </thead>
78+
* <tbody>
79+
* <tr style="background:yellow"><td>true</td><td>3</td></tr>
80+
* <tr><td></td><td>1</td></tr>
81+
* <tr style="background:cyan"><td>false</td><td>2</td></tr>
82+
* </tbody>
83+
* </table>
84+
* <p>
85+
* In such a case, explanation follows each cell to its appropriate position in
86+
* the new column ordering.
87+
*
88+
* @author Sylvain Hallé
89+
*/
90+
public class Projection extends RelationalOperator
91+
{
92+
/**
93+
* The names of the columns to retain in the output spreadsheet.
94+
*/
95+
/*@ non_null @*/ protected final String[] m_columnNames;
96+
97+
/**
98+
* The index of each column with given name in the last processed input
99+
* spreadsheet.
100+
*/
101+
protected final int[] m_originalIndices;
102+
103+
/**
104+
* Creates a new instance of the function.
105+
* @param sort_output The names of the columns to retain in the output spreadsheet
106+
* @param col_names The names of the columns to retain in the output spreadsheet
107+
*/
108+
public Projection(boolean sort_output, String ... col_names)
109+
{
110+
super(1);
111+
m_sortOutput = sort_output;
112+
m_columnNames = col_names;
113+
m_originalIndices = new int[col_names.length];
114+
}
115+
116+
/**
117+
* Creates a new instance of the function, assuming unsorted rows in the
118+
* output.
119+
* @param col_names The names of the columns to retain in the output spreadsheet
120+
*/
121+
public Projection(String ... col_names)
122+
{
123+
this(false, col_names);
124+
}
125+
126+
@Override
127+
protected Object[] getValue(Object... inputs)
128+
{
129+
if (!(inputs[0] instanceof Spreadsheet))
130+
{
131+
throw new InvalidArgumentTypeException("Argument is not a spreadsheet");
132+
}
133+
Spreadsheet s = (Spreadsheet) inputs[0];
134+
for (int i = 0; i < m_columnNames.length; i++)
135+
{
136+
m_originalIndices[i] = s.getColumnIndex(m_columnNames[i]);
137+
if (m_originalIndices[i] < 0)
138+
{
139+
throw new RelationalException("Attribute " + m_columnNames[i] + " does not exist in input");
140+
}
141+
}
142+
// We use both a set and a list to store rows. The hashset is used to check
143+
// for the existence of a duplicate row; this avoids doing a linear search
144+
// in the list in case the row has never been seen before.
145+
Set<Row> row_set = new HashSet<Row>();
146+
List<Row> row_list = new ArrayList<Row>();
147+
Object[] headers = null;
148+
for (int s_row = 0; s_row < s.getHeight(); s_row++)
149+
{
150+
Object[] contents = new Object[m_columnNames.length];
151+
for (int i = 0; i < contents.length; i++)
152+
{
153+
contents[i] = s.get(m_originalIndices[i], s_row);
154+
}
155+
if (s_row == 0)
156+
{
157+
headers = contents;
158+
continue;
159+
}
160+
Row r = new Row(contents);
161+
if (row_set.contains(r))
162+
{
163+
// Duplicate of existing row in the output
164+
int index = row_list.indexOf(r);
165+
List<Integer[]> tuples = m_mapping.get(index);
166+
tuples.add(new Integer[] {0, s_row});
167+
}
168+
else
169+
{
170+
// New unique row
171+
row_set.add(r);
172+
List<Integer[]> tuples = new ArrayList<Integer[]>(1);
173+
tuples.add(new Integer[] {0, s_row});
174+
m_mapping.add(tuples);
175+
row_list.add(r);
176+
}
177+
}
178+
return new Object[] {createOutput(headers, row_list)};
179+
}
180+
181+
@Override
182+
public String toString()
183+
{
184+
StringBuilder out = new StringBuilder();
185+
out.append("\u03c0 ["); // 03c0 = pi
186+
for (int i = 0; i < m_columnNames.length; i++)
187+
{
188+
if (i > 0)
189+
{
190+
out.append(",");
191+
}
192+
out.append(m_columnNames[i]);
193+
}
194+
out.append("]");
195+
return out.toString();
196+
}
197+
198+
@Override
199+
protected LabelledNode getConnectorNode(NodeFactory f)
200+
{
201+
return f.getOrNode();
202+
}
203+
204+
@Override
205+
protected int getColumnOf(int col)
206+
{
207+
return m_originalIndices[col];
208+
}
209+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
A provenance-aware spreadsheet library
3+
Copyright (C) 2021-2022 Sylvain Hallé
4+
5+
This program is free software: you can redistribute it and/or modify
6+
it under the terms of the GNU Lesser General Public License as published by
7+
the Free Software Foundation, either version 3 of the License, or
8+
(at your option) any later version.
9+
10+
This program is distributed in the hope that it will be useful,
11+
but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
GNU General Public License for more details.
14+
15+
You should have received a copy of the GNU Lesser General Public License
16+
along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
package ca.uqac.lif.spreadsheet.relation;
19+
20+
import ca.uqac.lif.petitpoucet.function.FunctionException;
21+
22+
/**
23+
* Exception thrown by functions manipulating spreadsheets as relations.
24+
* @author Sylvain Hallé
25+
*/
26+
public class RelationalException extends FunctionException
27+
{
28+
/**
29+
* Dummy UID.
30+
*/
31+
private static final long serialVersionUID = 1L;
32+
33+
/**
34+
* Creates a new relational exception from a throwable instance.
35+
* @param t The throwable
36+
*/
37+
public RelationalException(Throwable t)
38+
{
39+
super(t);
40+
}
41+
42+
/**
43+
* Creates a new relational exception from a String.
44+
* @param s The string
45+
*/
46+
public RelationalException(String s)
47+
{
48+
super(s);
49+
}
50+
51+
}

Source/Relations/src/ca/uqac/lif/spreadsheet/relation/RelationalOperator.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,12 +133,23 @@ public PartNode getExplanation(Part d, NodeFactory f)
133133
for (Integer[] pos : positions)
134134
{
135135
Part p = NthOutput.replaceOutByIn(d, pos[0]);
136-
p = Cell.replaceCellBy(p, Cell.get(c_col, pos[1]));
136+
p = Cell.replaceCellBy(p, Cell.get(getColumnOf(c_col), pos[1]));
137137
to_add.addChild(f.getPartNode(p, this));
138138
}
139139
return root;
140140
}
141141

142+
/**
143+
* Gets the column index in the input spreadsheet corresponding to a column
144+
* index in the output spreadsheet.
145+
* @param col The index of the column
146+
* @return The column index in the input spreadsheet
147+
*/
148+
protected int getColumnOf(int col)
149+
{
150+
return col;
151+
}
152+
142153
/**
143154
* Creates the output spreadsheet out of a list of rows gathered from the
144155
* input spreadsheets. This method is called by {@link #getValue(Object...)}

Source/Relations/src/ca/uqac/lif/spreadsheet/relation/Union.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,11 @@ public Union(int in_arity)
104104

105105
/**
106106
* Creates a new instance of the Union function.
107-
* @param in_arity The input arity of the function
108107
* @param sort_output Set to <tt>true</tt> to sort rows of the output,
109108
* <tt>false</tt> otherwise
109+
* @param in_arity The input arity of the function
110110
*/
111-
public Union(int in_arity, boolean sort_output)
111+
public Union(boolean sort_output, int in_arity)
112112
{
113113
super(in_arity, sort_output);
114114
}
@@ -134,7 +134,7 @@ protected Object[] getValue(Object... inputs) throws InvalidNumberOfArgumentsExc
134134
s_inputs[i] = (Spreadsheet) inputs[i];
135135
if (i > 0 && !sameSignature(s_inputs[i - 1], s_inputs[i]))
136136
{
137-
throw new InvalidArgumentTypeException("Arguments have incompatible signatures");
137+
throw new RelationalException("Arguments have incompatible signatures");
138138
}
139139
}
140140
// We use both a set and a list to store rows. The hashset is used to check

Source/Relations/srctest/ca/uqac/lif/spreadsheet/relation/IntersectionTest.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import ca.uqac.lif.petitpoucet.ComposedPart;
2626
import ca.uqac.lif.petitpoucet.OrNode;
2727
import ca.uqac.lif.petitpoucet.PartNode;
28-
import ca.uqac.lif.petitpoucet.function.InvalidArgumentTypeException;
2928
import ca.uqac.lif.petitpoucet.function.NthInput;
3029
import ca.uqac.lif.petitpoucet.function.NthOutput;
3130
import ca.uqac.lif.spreadsheet.Cell;
@@ -58,7 +57,7 @@ public void test1()
5857
1, "o", null), out);
5958
}
6059

61-
@Test (expected = InvalidArgumentTypeException.class)
60+
@Test (expected = RelationalException.class)
6261
public void testInvalid1()
6362
{
6463
// Invalid: columns have different names
@@ -76,7 +75,7 @@ public void testInvalid1()
7675
f.evaluate(s1, s2);
7776
}
7877

79-
@Test (expected = InvalidArgumentTypeException.class)
78+
@Test (expected = RelationalException.class)
8079
public void testInvalid2()
8180
{
8281
// Invalid: different widths
@@ -94,7 +93,7 @@ public void testInvalid2()
9493
f.evaluate(s1, s2);
9594
}
9695

97-
@Test (expected = InvalidArgumentTypeException.class)
96+
@Test (expected = RelationalException.class)
9897
public void testInvalid3()
9998
{
10099
// Invalid: incompatible column types

0 commit comments

Comments
 (0)