Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find number of elements across multiple lists and combine; remove if/else complex?

Tags:

java

list

I have a list of lists:

List<List<String>> someList = new List<List<>>();

The maximum size of a list is five strings. It's something like below:

someList.get(0).size(); // 4 elements
someList.get(1).size(); // 1 elements
someList.get(2).size(); // 3 elements
someList.get(3).size(); // 1 elements
...

I'm trying to devise a method to create a new list of a specific size (1-5 elements) by combining some of the above nested lists. I could do something like the below (in this example, three elements):

public List<String> getThree() {
    for (int j = 0; j < someList.size(); j++) {
        //look for nested lists of size 3
        if (someList.get(j).size() == 3) {
            return someList.get(j);
        }
    for (int j = 0; j < someList.size(); j++) {
        //if found nested list of size 2, find one of size 1 to combine
        if (someList.get(j).size() == 2) {
            for (int k = 0; k < someList.size(); k++) {
                if (someList.get(k).size() == 1) {
                    return someList.get(j).add(someList.get(k).get(0));
                }
            }
        }
    for (int j = 0; j < someList.size(); j++) {
        //if found nested list of size 1, find one of size 2 to combine
        if (someList.get(j).size() == 1) {
            for (int l = 0; l < someList.size(); l++) {
                if (someList.get(l).size() == 2) {
                    return someList.get(j).addAll(someList.get(l));
                }
            }
        }
    }
}

I haven't included the loop for if no sublists are of size 2, to find three of size 1, but you can imagine how long and how ugly it can get. The order is important, thus the for loops incrementing sequentially (ie. I'd rather combine subList 1 + 2 more than 2 + 3, 1 + 3 more than 2 + 3, etc).

I'm hoping to find a way to dynamically implement this. I can only fathom how unreadable and long the getFive method will be provided my current methodology. I have multiple methods (getOne through getFive), it doesn't need to be dynamic in this sense, I'd just like to get rid of a lot of the if/else and for loops to reduce complexity and improve readability.

I should mention this is homework related, so I don't quite want a specific answer, but a nudge in the right direction. Something modulo perhaps? To do with remainders?

edit; to clarify and give an example:

aList = new List<String>;
aList.add("a");
aList.add("b");
someList.add(aList);
bList = new List<String>;
bList.add("c");
someList.add(bList);
newList = someList.getThree();
//newList.size() == 3
//newList contains "a","b","c"

The getThree() method is creating a new list comprised of elements from the sublists of someList. It cannot split a sublist (ie. it can't take 1 element from a sublist of 2 elements), it's combining whole sublists.

like image 700
gator Avatar asked May 28 '15 23:05

gator


2 Answers

If your intention is to keep collecting from successive lists until you get 5 elements, keep adding then break out when your list is full:

public static List<String> fill(List<List<String>> sources, int size) {
    List<String> list = new ArrayList<>();
    for (List<String> source : sources) 
        if (source.size() <= size - list.size()) 
            list.addAll(source);
    return list;
}

If you want to consume the largest lists first, add this line as the first line of the method:

Collections.sort(sources, (a, b) -> b.size() - a.size());

In java 8, quite succinct:

public static List<String> fill(List<List<String>> sources, int size) {
    return sources.stream().reduce(new ArrayList<>(), 
      (a, b) -> {if (b.size() <= a.size() - size) a.addAll(b); return a;});
}

and with the largest-first mod:

public static List<String> fill(List<List<String>> sources, int size) {
    return sources.stream()
        .sorted((a,b) -> b.size() - a.size())
        .reduce(new ArrayList<>(), (a, b) -> 
            {if (b.size() <= a.size() - size) a.addAll(b); return a;});
}
like image 162
Bohemian Avatar answered Nov 16 '22 21:11

Bohemian


Since you state that the priority of combining Lists is from left to right. An O(N^2) loop is sufficient to handle combining sublists to be less than or equal to your desired amount.

public static void main(String[] args) throws Exception {
    List<List<String>> someList = new ArrayList() {{
       add(new ArrayList() {{
           add("a1");
           add("a2");
       }});
       add(new ArrayList() {{
           add("b1");
       }});
       add(new ArrayList() {{
           add("c1");
           add("c2");
           add("c3");
       }});
       add(new ArrayList() {{
           add("d1");
       }});
    }};

    combine(someList, 4);

    for(List<String> subList : someList) {
        System.out.println(subList);
    }
}

private static void combine(List<List<String>> someList, int combineAmount) {
    for (int i = 0; i < someList.size(); i++) {
        // Check if the current list already equals or exceeds the combineAmount
        if (someList.get(i).size() >= combineAmount) {
            continue;
        }

        // Add sublists to the current sublists until the size of the current
        // sublist equals or exceeds the combineAmount
        for (int j = i + 1; j < someList.size(); j++) {
            if (someList.get(i).size() + someList.get(j).size() > combineAmount) {
                continue;
            }
            someList.get(i).addAll(someList.get(j));
            someList.remove(j);
            j--;

            // Don't bother checking other sublists if the newly 
            // combined sublists equals or exceeds the combineAmount
            if (someList.get(i).size() >= combineAmount) {
                break;
            }
        }
    }
}

Results (combineAmount = 4):

[a1, a2, b1, d1]
[c1, c2, c3]

Results (combineAmount = 2):

[a1, a2]
[b1, d1]
[c1, c2, c3]

Results (combineAmount = 6):

[a1, a2, b1, c1, c2, c3]
[d1]
like image 1
Shar1er80 Avatar answered Nov 16 '22 21:11

Shar1er80