Skip to content

Commit ee8ad40

Browse files
committed
Finished Intersection
1 parent da8936c commit ee8ad40

7 files changed

Lines changed: 612 additions & 76 deletions

File tree

Source/Core/src/ca/uqac/lif/spreadsheet/Spreadsheet.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,16 @@ public int compareTo(Spreadsheet s)
658658
return 0;
659659
}
660660

661-
protected static int compareRows(Object[] r1, Object[] r2)
661+
/**
662+
* Compares two arrays of objects representing the contents of two rows in
663+
* a spreadsheet.
664+
* @param r1 The values of the first row
665+
* @param r2 The values of the second row
666+
* @return A negative value if r1 goes before r2, 0 if the two rows are
667+
* identical, and a positive value otherwise
668+
* @see #equalRows(Object[], Object[])
669+
*/
670+
public static int compareRows(Object[] r1, Object[] r2)
662671
{
663672
int l1 = r1.length, l2 = r2.length;
664673
for (int col = 0; col < Math.max(l1, l2); col++)
@@ -673,6 +682,29 @@ protected static int compareRows(Object[] r1, Object[] r2)
673682
}
674683
return 0;
675684
}
685+
686+
/**
687+
* Determines if two arrays of objects representing the contents of two rows
688+
* in a spreadsheet are equal.
689+
* @param r1 The values of the first row
690+
* @param r2 The values of the second row
691+
* @return <tt>true</tt> if they are equal, <tt>false</tt> otherwise
692+
* @see #compareRows(Object[], Object[])
693+
*/
694+
public static boolean equalRows(Object[] r1, Object[] r2)
695+
{
696+
int l1 = r1.length, l2 = r2.length;
697+
for (int col = 0; col < Math.max(l1, l2); col++)
698+
{
699+
Object o1 = col < l1 ? r1[col] : null;
700+
Object o2 = col < l2 ? r2[col] : null;
701+
if (!same(o1, o2))
702+
{
703+
return false;
704+
}
705+
}
706+
return true;
707+
}
676708

677709
/**
678710
* Retrieves the value of a cell and converts it to a string.
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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.List;
22+
23+
import ca.uqac.lif.dag.LabelledNode;
24+
import ca.uqac.lif.petitpoucet.NodeFactory;
25+
import ca.uqac.lif.petitpoucet.function.InvalidArgumentTypeException;
26+
import ca.uqac.lif.petitpoucet.function.InvalidNumberOfArgumentsException;
27+
import ca.uqac.lif.spreadsheet.Spreadsheet;
28+
29+
/**
30+
* Calculates the intersection of multiple relations. For example, given these
31+
* two spreadsheets:
32+
* <p>
33+
* <div style="display:flex">
34+
* <table border="1" style="margin-right:12pt">
35+
* <thead>
36+
* <tr><th>A</th><th>B</th><th>C</th></tr>
37+
* </thead>
38+
* <tbody>
39+
* <tr><td>3</td><td>f</td><td>true</td></tr>
40+
* <tr style="background:cyan"><td></td><td>o</td><td>true</td></tr>
41+
* <tr style="background:yellow"><td>1</td><td>o</td><td></td></tr>
42+
* </tbody>
43+
* </table>
44+
* <table border="1">
45+
* <thead>
46+
* <tr><th>A</th><th>B</th><th>C</th></tr>
47+
* </thead>
48+
* <tbody>
49+
* <tr style="background:yellow"><td>1</td><td>o</td><td></td></tr>
50+
* <tr><td>5</td><td>f</td><td>true</td></tr>
51+
* <tr style="background:cyan"><td></td><td>o</td><td>true</td></tr>
52+
* </tbody>
53+
* </table>
54+
* </div>
55+
* <p>
56+
* their union is the spreadsheet:
57+
* <p>
58+
* <table border="1">
59+
* <thead>
60+
* <tr><th>A</th><th>B</th><th>C</th></tr>
61+
* </thead>
62+
* <tbody>
63+
* <tr style="background:cyan"><td></td><td>o</td><td>true</td></tr>
64+
* <tr style="background:yellow"><td>1</td><td>o</td><td></td></tr>
65+
* </tbody>
66+
* </table>
67+
* <p>
68+
* In line with the definition of a relation, rows with identical values
69+
* appearing in more than one spreadsheet occur only once in the result.
70+
* However, contrary to the definition of a relation, the rows of the result
71+
* are listed in their order of occurrence in the first input spreadsheet.
72+
* <p>
73+
* The operator also tracks the provenance of each cell of the output. For
74+
* example:
75+
* <ul>
76+
* <li>in the output spreadsheet, cells in the second row are associated
77+
* to corresponding cells in both the second row of the first input
78+
* spreadsheet, and the fourth row of the second input spreadsheet (highlighted
79+
* in cyan)</li>
80+
* <li>in the output spreadsheet, cells in the third row are associated
81+
* to corresponding cells in both the fourth row of the first input
82+
* spreadsheet, and the second row of the yellow spreadsheet (highlighted in
83+
* yellow)</li>
84+
* </ul>
85+
*
86+
* @author Sylvain Hallé
87+
*
88+
*/
89+
public class Intersection extends RelationalOperator
90+
{
91+
/**
92+
* Creates a new instance of the Union function, assuming that the rows
93+
* of its output will be unsorted.
94+
* @param in_arity The input arity of the function
95+
*/
96+
public Intersection(int in_arity)
97+
{
98+
super(in_arity);
99+
}
100+
101+
/**
102+
* Creates a new instance of the Intersection function.
103+
* @param in_arity The input arity of the function
104+
* @param sort_output Set to <tt>true</tt> to sort rows of the output,
105+
* <tt>false</tt> otherwise
106+
*/
107+
public Intersection(int in_arity, boolean sort_output)
108+
{
109+
super(in_arity, sort_output);
110+
}
111+
112+
@Override
113+
/*@ non_null @*/ public Intersection sortOutput(boolean b)
114+
{
115+
super.sortOutput(b);
116+
return this;
117+
}
118+
119+
@Override
120+
protected Object[] getValue(Object... inputs) throws InvalidNumberOfArgumentsException
121+
{
122+
m_mapping.clear();
123+
Spreadsheet[] s_inputs = new Spreadsheet[inputs.length];
124+
for (int i = 0; i < getInputArity(); i++)
125+
{
126+
if (!(inputs[i] instanceof Spreadsheet))
127+
{
128+
throw new InvalidArgumentTypeException("Argument is not a spreadsheet");
129+
}
130+
s_inputs[i] = (Spreadsheet) inputs[i];
131+
if (i > 0 && !sameSignature(s_inputs[i - 1], s_inputs[i]))
132+
{
133+
throw new InvalidArgumentTypeException("Arguments have incompatible signatures");
134+
}
135+
}
136+
List<Row> row_list = new ArrayList<Row>();
137+
for (int row = 1; row < s_inputs[0].getHeight(); row++)
138+
{
139+
Row r = new Row(s_inputs[0].getRow(row));
140+
List<Integer[]> tuples = new ArrayList<Integer[]>(s_inputs.length);
141+
tuples.add(new Integer[] {0, row});
142+
boolean all_present = true;
143+
for (int s_index = 1; s_index < s_inputs.length && all_present; s_index++)
144+
{
145+
int row_index = r.rowOf(s_inputs[s_index]);
146+
if (row_index < 0)
147+
{
148+
all_present = false;
149+
continue;
150+
}
151+
tuples.add(new Integer[] {s_index, row_index});
152+
}
153+
if (all_present)
154+
{
155+
row_list.add(r);
156+
m_mapping.add(tuples);
157+
}
158+
}
159+
return new Object[] {createOutput(s_inputs[0].getRow(0), row_list)};
160+
}
161+
162+
@Override
163+
protected LabelledNode getConnectorNode(NodeFactory f)
164+
{
165+
return f.getAndNode();
166+
}
167+
168+
@Override
169+
public String toString()
170+
{
171+
return "\u2229";
172+
}
173+
}

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

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,25 +17,172 @@
1717
*/
1818
package ca.uqac.lif.spreadsheet.relation;
1919

20+
import java.util.ArrayList;
21+
import java.util.Collections;
22+
import java.util.List;
23+
24+
import ca.uqac.lif.dag.LabelledNode;
25+
import ca.uqac.lif.petitpoucet.NodeFactory;
26+
import ca.uqac.lif.petitpoucet.OrNode;
27+
import ca.uqac.lif.petitpoucet.Part;
28+
import ca.uqac.lif.petitpoucet.PartNode;
2029
import ca.uqac.lif.petitpoucet.function.AtomicFunction;
30+
import ca.uqac.lif.petitpoucet.function.NthOutput;
31+
import ca.uqac.lif.spreadsheet.Cell;
2132
import ca.uqac.lif.spreadsheet.Spreadsheet;
2233

2334
/**
2435
* Common ancestor to functions specific to relational algebra. These functions
2536
* may have a varying input arity, but their output arity is always 1.
37+
* <p>
38+
* A relational operator <strong>assumes</strong> that its input arguments are
39+
* spreadsheets with no duplicate rows. In turn, it <strong>guarantees</strong>
40+
* that the spreadsheet it produces as its output contains no duplicate rows.
2641
* @author Sylvain Hallé
2742
*/
2843
public abstract class RelationalOperator extends AtomicFunction
2944
{
3045
/**
31-
* Creates a new instance of the relational operator.
46+
* A mapping associating each row of the output spreadsheet with the row(s)
47+
* of the input spreadsheet(s) where it is found.
48+
*/
49+
/*@ non_null @*/ protected final List<List<Integer[]>> m_mapping;
50+
51+
/**
52+
* A flag indicating whether the rows of the output spreadsheet should be
53+
* sorted.
54+
*/
55+
protected boolean m_sortOutput = false;
56+
57+
/**
58+
* Creates a new instance of the relational operator, assuming that the rows
59+
* of its output will be unsorted.
3260
* @param in_arity The input arity of the function
3361
*/
3462
public RelationalOperator(int in_arity)
63+
{
64+
this(in_arity, false);
65+
}
66+
67+
/**
68+
* Creates a new instance of the relational operator.
69+
* @param in_arity The input arity of the function
70+
* @param sort_output Set to <tt>true</tt> to sort rows of the output,
71+
* <tt>false</tt> otherwise
72+
*/
73+
public RelationalOperator(int in_arity, boolean sort_output)
3574
{
3675
super(in_arity, 1);
76+
m_mapping = new ArrayList<List<Integer[]>>();
77+
m_sortOutput = sort_output;
78+
}
79+
80+
/**
81+
* Sets whether the the rows of the output spreadsheet should be sorted.
82+
* @param b Set to <tt>true</tt> to sort rows, <tt>false</tt> otherwise
83+
* @return This relational operator
84+
*/
85+
/*@ non_null @*/ public RelationalOperator sortOutput(boolean b)
86+
{
87+
m_sortOutput = b;
88+
return this;
3789
}
3890

91+
@Override
92+
public PartNode getExplanation(Part d, NodeFactory f)
93+
{
94+
Cell c = Cell.mentionedCell(d);
95+
if (c == null)
96+
{
97+
return super.getExplanation(d, f);
98+
}
99+
int c_row = c.getRow();
100+
int c_col = c.getColumn();
101+
PartNode root = f.getPartNode(d, this);
102+
if (c_row == 0)
103+
{
104+
// First row is made of labels
105+
LabelledNode to_add = root;
106+
int in_arity = getInputArity();
107+
if (in_arity > 1)
108+
{
109+
OrNode or = f.getOrNode();
110+
to_add.addChild(or);
111+
to_add = or;
112+
}
113+
for (int s_index = 0; s_index < in_arity; s_index++)
114+
{
115+
Part p = NthOutput.replaceOutByIn(d, s_index);
116+
to_add.addChild(f.getPartNode(p, this));
117+
}
118+
return root;
119+
}
120+
if (c_row < 1 || c_row > m_mapping.size())
121+
{
122+
root.addChild(f.getUnknownNode());
123+
return root;
124+
}
125+
List<Integer[]> positions = m_mapping.get(c_row - 1);
126+
LabelledNode to_add = root;
127+
if (positions.size() > 1)
128+
{
129+
LabelledNode conn = getConnectorNode(f);
130+
to_add.addChild(conn);
131+
to_add = conn;
132+
}
133+
for (Integer[] pos : positions)
134+
{
135+
Part p = NthOutput.replaceOutByIn(d, pos[0]);
136+
p = Cell.replaceCellBy(p, Cell.get(c_col, pos[1]));
137+
to_add.addChild(f.getPartNode(p, this));
138+
}
139+
return root;
140+
}
141+
142+
/**
143+
* Creates the output spreadsheet out of a list of rows gathered from the
144+
* input spreadsheets. This method is called by {@link #getValue(Object...)}
145+
* in each of the descendant classes.
146+
* @param top_row The values of the top row, containing the names of each
147+
* column of the output spreadsheet. The size of this array determines the
148+
* width of the output spreadsheet.
149+
* @param row_list The list of rows to be appended to the output spreadsheet.
150+
* The size of this list determines the height of the output spreadsheet
151+
* (which is size of list + 1).
152+
* @return The output spreadsheet
153+
*/
154+
protected Spreadsheet createOutput(Object[] top_row, List<Row> row_list)
155+
{
156+
Spreadsheet out = new Spreadsheet(top_row.length, row_list.size() + 1);
157+
for (int col = 0; col < top_row.length; col++)
158+
{
159+
// First row
160+
out.set(col, 0, top_row[col]);
161+
}
162+
if (m_sortOutput)
163+
{
164+
Collections.sort(row_list);
165+
}
166+
for (int i = 0; i < row_list.size(); i++)
167+
{
168+
Row r = row_list.get(i);
169+
r.set(out, i + 1);
170+
}
171+
return out;
172+
}
173+
174+
/**
175+
* Creates a labelled node for an explanation involving more than one
176+
* spreadsheet. What type of labelled node this corresponds to depends on the
177+
* actual operator providing the explanation. For example, {@link Union}
178+
* produces an {@link OrNode}, while {@link Intersection} produces an
179+
* {@link AndNode}. Classes where this method is not used can simply return
180+
* <tt>null</tt>.
181+
* @param f The node factory used to obtain a node instance
182+
* @return The node (or null)
183+
*/
184+
/*@ null @*/ protected abstract LabelledNode getConnectorNode(NodeFactory f);
185+
39186
/**
40187
* Checks if two spreadsheets, when interpreted as relations, have the same
41188
* signature. For two spreadsheets to have the same signature, they must have

0 commit comments

Comments
 (0)