My concern is performance of the code. It takes way longer than I would like. I would be willing to give up some memory for higher performance.
I have JTable with serial numbers in column1 and options in column2. With every option the person has chosen on a new row.
Each serial number can only have 1 of each options. Each serial number usually has around 60 options.
My dataset is very large (100k-1m rows).
I have a total of 350 options but I realize that it is impossible to get combinations of all of them as that would be 7084700 combinations.
My combinations are not position specific.
ex:
option1 option2 option3
is the same as
option2 option1 option3
Here is an Example of how my data looks:
1 a
1 b
1 c
2 a
2 b
2 c
3 a
3 b
4 b
4 c
4 d
5 a
5 b
5 c
5 d
6 c
6 d
6 e
8 d
8 e
And here is the output after calculation:
a b 57%
a c 42%
a d 14%
a e 0%
b c 42%
b d 14%
b e 0%
c d 14%
c e 0%
d e 0%
a b c 42%
a b d 14%
a c d 14%
b c d 14%
a b e 0%
a c e 0%
a d e 0%
b c e 0%
b d e 0%
c d e 0%
Here is my code to get the frequency of combinations of 3 options.
//used to get number of combinations (n!/(r!(n-r)!)
static BigInteger binomial(final int N, final int K) {
BigInteger ret = BigInteger.ONE;
for (int k = 0; k < K; k++) {
ret = ret.multiply(BigInteger.valueOf(N - k)).divide(BigInteger.valueOf(k + 1));
}
return ret;
}
// method for calculalating combinations of 3
public void calc3(JTable table) {
currentSerialNums2 = new ArrayList<Object>(1500);
options = new ArrayList<Object>(table.getRowCount());
serialNum = new ArrayList<Object>(table.getRowCount());
currentSerialNums = new ArrayList<Object>(1000);
DistinctOptions = new ArrayList<Object>();
// Loop through all rows and extract options and serial numbers to lists
for (int i = 0; i < table.getRowCount(); i++) {
// add all objects from 2nd column in table to options list
options.add(table.getModel().getValueAt(table.convertRowIndexToModel(i), 1));
// add all objects from first column in table to serialNum list
serialNum.add(table.getModel().getValueAt(table.convertRowIndexToModel(i), 0));
}
// Extract unique options and save to List<Object> DistinctOptions. This is a github library called jstreams but I could use hashset aswell
DistinctOptions = Stream.create(options).distinct().toList();
// declaring these ArrayLists with custom capacity to avoid constant resizing
previousCombos = new ArrayList<List<Object>>(binomial(DistinctOptions.size(), 3).intValue());
SerialNumsPerOption = new ArrayList<List<Object>>(DistinctOptions.size());
// Adding all serial numbers for each option in a List<List<Object>>
for (int i = 0; i < DistinctOptions.size(); i++) {
for (int y = 0; y < table.getRowCount(); y++) {
if (DistinctOptions.get(i).equals(options.get(y))) {
currentSerialNums.add(serialNum.get(y));
}
}
if (!currentSerialNums.isEmpty()) {
if (i == 0) {
}
currentSerialNums2 = new ArrayList<Object>(currentSerialNums);
SerialNumsPerOption.add(currentSerialNums2);
currentSerialNums.clear();
}
}
for (int i = 0; i < SerialNumsPerOption.size(); i++) {
compareSerialNums = new HashSet<>(SerialNumsPerOption.get(i));
System.out.println("next");
outerloop2: for (int j = 0; j < SerialNumsPerOption.size(); j++) {
if (DistinctOptions.get(i).equals(DistinctOptions.get(j))) {
continue;
}
currentCombo = new ArrayList<Object>();
currentCombo.add(DistinctOptions.get(i));
currentCombo.add(DistinctOptions.get(j));
currentCombo.add("");
for (int a = 0; a < previousCombos.size(); a++) {
if (previousCombos.get(a).containsAll(currentCombo)) {
continue outerloop2;
}
}
compare2 = new HashSet<>(SerialNumsPerOption.get(j));
compare2.retainAll(compareSerialNums);
currentCombo.add(compare2.size());
previousCombos.add(currentCombo);
innerloop: for (int j2 = 0; j2 < SerialNumsPerOption.size(); j2++) {
if (DistinctOptions.get(i).equals(DistinctOptions.get(j2))) {
continue;
}
if (DistinctOptions.get(j).equals(DistinctOptions.get(j2))) {
continue;
}
currentCombo = new ArrayList<Object>(4);
currentCombo.add(DistinctOptions.get(i));
currentCombo.add(DistinctOptions.get(j));
currentCombo.add(DistinctOptions.get(j2));
for (int a = 0; a < previousCombos.size(); a++) {
if (previousCombos.get(a).containsAll(currentCombo)) {
continue innerloop;
}
}
SerialNumsPerOption.get(j2).retainAll(compare2);
currentCombo.add(SerialNumsPerOption.get(j2).size());
previousCombos.add(currentCombo);
}
}
}
System.out.println("Saving to list");
tabledata = new Object[previousCombos.size()][4];
for (int i = 0; i < previousCombos.size(); i++) {
for (int j = 0; j < 4; j++) {
if (previousCombos.get(i).get(j) != null) {
tabledata[i][j] = previousCombos.get(i).get(j);
}
}
}
System.out.println("Calculations completed");
}
What is taking all the time:
Comparing the two lists to see how many serial numbers the two/three options share using the retainAll. Is there an alternative here? Since there are all unique values, converting to a set works but it takes more time to convert the values than it takes to do retainAll on two Lists.
comparing current combination with all the previous ones to eliminate ones that I already have (order doesn't matter)
for (int a = 0; a < previousCombos.size(); a++) {
if (previousCombos.get(a).containsAll(currentCombo)) {
continue innerloop;
}
}
EDIT:
Stingy´s answer made my improvements obsolete