Basically you will have to combine all boxes with all possible colors. In each new row a box gets the next color assigned to that it had in the previous row. It becomes a bit clearer if you write all the possible box/color combinations and write all the indices. PointA is a perfect example:
For the input
{box1=[blue, red, orange]}
{box2=[blue, red, orange]}
{box3=[blue, red, orange]}
All combination for the above input are (with boxIndex , colorIndex in front):
0,0 {box1=blue}
0,1 {box1=red}
0,2 {box1=orange}
1,0 {box2=blue}
1,1 {box2=red}
1,2 {box2=orange}
2,0 {box3=blue}
2,1 {box3=red}
2,2 {box3=orange}
You are looking for the following output:
{box1=blue, box2=red, box3=orange}
{box1=red, box2=orange, box3=blue}
{box1=orange, box2=blue, box3=red}
Thus the indices you are looking for are the following :
row1 0,0 1,1 2,2
row2 0,1 1,2 2,0
row3 0,2 1,0 2,1
Now when you know what you are looking for, it becomes easy to write some loops (disclaimer: As far as I have correctly understood your question / Not fully tested!!!):
public List<Map<String, String>> create(Map<String, List<String>> input) {
List<Map<String, String>> output = new ArrayList<>();
// find all boxes
List<String> boxes = new ArrayList<>(input.keySet());
// find all colors
Set<String> distinctColors = new LinkedHashSet<>();
for(List<String> e : input.values()) {
for(String color : e) {
if(! distinctColors.contains(color)) {
distinctColors.add(color);
}
}
}
List<String> colors = new ArrayList<String>(distinctColors);
int colorIndex = 0;
for(int i = 0; i < boxes.size(); i++) {
Map<String, String> row = new LinkedHashMap<>();
output.add(row);
colorIndex = i;
for(int j = 0; j < colors.size(); j++) {
int boxIndex = j;
if(boxIndex >= boxes.size()) {
boxIndex = 0;
}
String box = boxes.get(boxIndex);
List<String> boxColors = input.get(box);
if(colorIndex >= colors.size()) {
colorIndex = 0;
}
String color = colors.get(colorIndex++);
// a combination is generated only if the actual
// colors does exist in the actual box
if(boxColors.contains(color)) {
row.put(box, color);
}
}
}
return output;
}
Here is some testings using some of the inputs you have provided:
PointA
@Test
public void createFromPointA() {
// {box1=[blue, red, orange]}
// {box2=[blue, red, orange]}
// {box3=[blue, red, orange]}
// [{box1=blue, box2=red, box3=orange},
// {box1=red, box2=orange, box3=blue},
// {box1=orange, box2=blue, box3=red}]
// 0,0 {box1=blue}
// 0,1 {box1=red}
// 0,2 {box1=orange}
// 1,0 {box2=blue}
// 1,1 {box2=red}
// 1,2 {box2=orange}
// 2,0 {box3=blue}
// 2,1 {box3=red}
// 2,2 {box3=orange}
// 0,0 1,1 2,2
// 0,1 1,2 2,0
// 0,2 1,0 2,1
Map<String, List<String>> input = new LinkedHashMap<>();
input.put("box1", Arrays.asList("blue", "red", "orange"));
input.put("box2", Arrays.asList("blue", "red", "orange"));
input.put("box3", Arrays.asList("blue", "red", "orange"));
List<Map<String, String>> output = create(input);
for(Map<String, String> e : output) {
System.out.println(e);
}
}
PointB
@Test
public void createFromPointB() {
// {box1=[blue, red, orange]}
// {box2=[blue, red, orange]}
// {box3=[]}
// [{box1=blue, box2=red},
// {box1=red, box2=orange},
// {box1=orange, box2=blue}]
// 0,0 {box1=blue}
// 0,1 {box1=red}
// 0,2 {box1=orange}
// 1,0 {box2=blue}
// 1,1 {box2=red}
// 1,2 {box2=orange}
// 2,x {box3=blue}
// 2,x {box3=red}
// 2,X {box3=orange}
// 0,0 1,1 2,x
// 0,1 1,1 2,x
// 0,2 1,0 2,x
Map<String, List<String>> input = new LinkedHashMap<>();
input.put("box1", Arrays.asList("blue", "red", "orange"));
input.put("box2", Arrays.asList("blue", "red", "orange"));
input.put("box3", Collections.<String>emptyList());
List<Map<String, String>> output = create(input);
for(Map<String, String> e : output) {
System.out.println(e);
}
}
PointC
@Test
public void createFromPointC() {
// {box1=[blue, red, orange]}
// {box2=[blue, red]}
// {box3=[blue, red, orange]}
// [{box1=blue, box2=red, box3=orange},
// {box1=red, box3=blue},
// {box1=orange, box2=blue, box3=red}]
// 0,0 {box1=blue}
// 0,1 {box1=red}
// 0,2 {box1=orange}
// 1,0 {box2=blue}
// 1,1 {box2=red}
// 1,x {box2=orange}
// 2,0 {box3=blue}
// 2,1 {box3=red}
// 2,2 {box3=orange}
// 0,0 1,1 2,2
// 0,1 1,x 2,0
// 0,2 1,0 2,1
Map<String, List<String>> input = new LinkedHashMap<>();
input.put("box1", Arrays.asList("blue", "red", "orange"));
input.put("box2", Arrays.asList("blue", "red"));
input.put("box3", Arrays.asList("blue", "red", "orange"));
List<Map<String, String>> output = create(input);
for(Map<String, String> e : output) {
System.out.println(e);
}
}
OutputA
{box1=blue, box2=red, box3=orange}
{box1=red, box2=orange, box3=blue}
{box1=orange, box2=blue, box3=red}
OutputB
{box1=blue, box2=red}
{box1=red, box2=orange}
{box1=orange, box2=blue}
OutputC
{box1=blue, box2=red, box3=orange}
{box1=red, box3=blue}
{box1=orange, box2=blue, box3=red}
Hope this helps or at least give you some hints in your way finding a solution.
EDIT
You could replace the outer for loop
for(int i = 0; i < boxes.size(); i++) {
with
for(int i = 0; i < colors.size(); i++) {
This way the generation is oriented after the number of colors not that of the boxes. If this doesn't help with other combinations then you might want to add a check before adding a combination to a row:
if(boxColors.contains(color) && notYetGenerated()) {
row.put(box, color);
}
EDIT 2
Here is a sample implementation of isNotYetGenerated
private boolean isNotYetGenerated(String box, String color,
Set<String> generationHistory) {
String key = box + "=" + color;
boolean notYetGenerated = ! generationHistory.contains(key);
if(notYetGenerated) {
generationHistory.add(key);
}
return notYetGenerated;
}
Create the set in the create
method an pass it to that method.
Set<String> generationHistory = new LinkedHashSet<>();
int colorIndex = 0;
int index = boxes.size() > colors.size() ? boxes.size() : colors.size();
for(int i = 0; i < index; i++) {
Map<String, String> row = new LinkedHashMap<>();
output.add(row);
colorIndex = i;
for(int j = 0; j < index; j++) {
int boxIndex = j;
if(boxIndex >= boxes.size()) {
boxIndex = 0;
}
String box = boxes.get(boxIndex);
List<String> boxColors = input.get(box);
if(colorIndex >= colors.size()) {
colorIndex = 0;
}
String color = colors.get(colorIndex++);
// a combination is generated only if the actual
// colors does exist in the actual box
// and it has not already been generated i all previous rows
if(boxColors.contains(color) && isNotYetGenerated(box, color, generationHistory)) {
row.put(box, color);
}
}
}
Test for PonitF
@Test
public void createFromPointF() {
// {box1=red, box2=blue, box3=orange}
// {box1=blue, box2=orange, box3=purple}
// {box1=red, box3=pink}
// {box3=red, box1=orange}
// {box3=blue}
// 0,0 {box1=red}
// 0,1 {box1=blue}
// 0,2 {box1=orange}
// 0,x {box1=purple}
// 0,x {box1=pink}
//
// 1,0 {box2=red}
// 1,1 {box2=blue}
// 1,2 {box2=orange}
// 1,x {box2=purple}
// 1,x {box2=pink}
//
// 2,0 {box3=red}
// 2,1 {box3=blue}
// 2,2 {box3=orange}
// 2,3 {box3=purple}
// 2,4 {box3=pink}
// 0,0 1,1 2,2
// 0,1 1,2 2,3
// 0,x 1,x 2,0
// 0,x 1,0 2,1
Map<String, List<String>> input = new LinkedHashMap<>();
input.put("box1", Arrays.asList("red", "blue", "orange"));
input.put("box2", Arrays.asList("red", "blue", "orange"));
input.put("box3", Arrays.asList("red", "blue", "orange", "purple", "pink"));
List<Map<String, String>> output = create(input);
Assert.assertEquals(
"{box1=red, box2=blue, box3=orange}
" +
"{box1=blue, box2=orange, box3=purple}
" +
"{box1=orange, box3=pink}
" +
"{box3=red}
" +
"{box2=red, box3=blue}
", toString(output));
}
private String toString(List<Map<String, String>> output) {
StringWriter sw = new StringWriter();
for(Map<String, String> e : output) {
sw.write(e.toString());
sw.write("
");
}
return sw.toString();
}
OuputF
{box1=red, box2=blue, box3=orange}
{box1=blue, box2=orange, box3=purple}
{box1=orange, box3=pink}
{box3=red}
{box2=red, box3=blue}