简体   繁体   中英

Replacing nested FOR loops using recursion or any other solution

<?xml version="1.0"?>
<Event>
 <Country>England</Country>
 <City>London</City>
 <City>Liverpool</City>  
 <Code>DWW</Code>  
 <ID>DG300</ID>
 <ID>SS500</ID>
 <Division>Europe</Division>
</Event>

Let say I have an XML like above. I have a method which takes different number of parameters:

myMethod(String... params){}

A parameter is an element which I want to read from XML. As an example lets take 3 elements form XML

myMethod(Country, City, ID){}

At the beginning, I count how many parameters is passed to this method:

int count=  params.size(); // let say for this example count=3

Here I create an array with lists as an elements:

List<String>[] tab= new ArrayList[count];

Here I iterate as many times as count parameter is equal to and put a list in every array element:

for(int i=0; i<count; i++){
    tab[i] = new ArrayList<>();
}

In the middle my method I have some loops which read elements from XML and put them to the arrays (add them to the lists). I use JAVAX

At the end my array looks like this

tab=[(England),(London,Liverpool),(DG300,SS500)]

Now I have a problem. I need Cartesian of every list which mean that i need lines:

England London DG300
England London SS500
England Liverpool DG300
England Liverpool SS500

I can do this with nested loops like this

for(int i=0; i< tab[0].size();i++){
    for(int j=0; i< tab[1].size();j++){
        for(int k=0; i< tab[2].size();k++){

        System.out.println(tab[0].get(i)+ " " + tab[1].get(j)+" "+tab[2].get(k))
}}}}}

But this is not a good idea as I mentioned at the beginning, I can have a different number of parameters so I can have different number of nested loops. It can be two of them but can be ten as well.

How can replace this nested loops using RECURSION? Or maybe I can do this any other way using something else than recursion?

Here is a solution. It's a modification of my answer to your other post: how to use recursion for nested 'for' loops .

public static void iterate(Object[] previousValues, List<Object>[] tabs) {
    if (tabs.length == 0) {
        System.out.println(Arrays.toString(previousValues));
    }
    else {
        final Object[] values = new Object[previousValues.length + 1];
        for (int i = 0; i < previousValues.length; i++) {
            values[i] = previousValues[i];
        }
        final List<Object>[] nextTabs = new List[tabs.length - 1];
        for (int i = 0; i < nextTabs.length; i++) {
            nextTabs[i] = tabs[i + 1];
        }
        for (Object o : tabs[0]) {
            values[values.length - 1] = o;
            iterate(values, nextTabs);
        }
    }
}
public static void iterate(List<Object>[] tabs) {
    iterate(new Object[0], tabs);
}

public static void main(String[] args) {
    final List<String> firstTab = new ArrayList<>();
    firstTab.add("England");
    final List<String> secondTab = new ArrayList<>();
    secondTab.add("London");
    secondTab.add("Liverpool");
    final List<String> thirdTab = new ArrayList<>();
    thirdTab.add("DG300");
    thirdTab.add("SS500");
    iterate(new List[]{firstTab, secondTab, thirdTab});
}

Suggest you use the Guava's Lists.cartesianProduct method:

import java.util.Arrays;
import java.util.List;

import com.google.common.collect.Lists;

public class CartesianParams
{
    public static void main(String args[])
    {
        List<String> countries = Arrays.asList("England", "Bhutan", "Peru");
        List<String> cities = Arrays.asList("London", "Thimphu", "Lima");
        List<String> codes = Arrays.asList("DG300", "SS500");
        printCartesian(countries, cities, codes);
    }

    private static void printCartesian(List<String>... params)
    {
        //Exactly one line of code
        List<List<String>> results = Lists.cartesianProduct(params); 

        System.out.println("results: " + results);
    }
}

Output:

results: [[England, London, DG300], [England, London, SS500], [England, Thimphu, DG300], [England, Thimphu, SS500], [England, Lima, DG300], [England, Lima, SS500], [Bhutan, London, DG300], [Bhutan, London, SS500], [Bhutan, Thimphu, DG300], [Bhutan, Thimphu, SS500], [Bhutan, Lima, DG300], [Bhutan, Lima, SS500], [Peru, London, DG300], [Peru, London, SS500], [Peru, Thimphu, DG300], [Peru, Thimphu, SS500], [Peru, Lima, DG300], [Peru, Lima, SS500]]

It should be noted that this solution is exactly one line of code:

  • Much easier to maintain and for the developers who inherit your code to understand.
  • Much less likely to be broken by future developers.
  • Hardly merits a unit test, whereas the recursion solutions provided certainly do.
  • You did ask if you could "do this any other way using something else than recursion".

Assuming you have your list set up so that tab[0] is the list of the first strings to print, tab[1] the following strings, etc., you can use recursion as follows:

void print_rec(List<String>[] tab, int depth, String str) {
    int maxdepth = tab.length - 1;
    for (int i = 0; i < tab[depth].size(); i++) { // number of strings at this depth
        if (depth == maxdepth) {
            System.out.println(str + tab[depth].get(i)); // print line
            // no more lists to go through, print the line
        } else {
            print_rec(tab, depth + 1, str + tab[depth].get(i) + " ");
            // add next entry at this level to str and recurse
        }
    }
    // done with this depth, go back up one
}

void printAll(List<Strings>[] tab) {
    print_rec(tab, 0, "");
}

This covers all the entries exactly like nested loops.

When it comes to writing recursive code, I prefer to write the function in Haskell first, and then translate it back to Java. Java is too verbose for me to think about the big picture.

Here's my Haskell implementation:

combinations :: [[String]] -> [[String]]
combinations []     = [[]]
combinations (x:xs) = do
    ys <- combinations xs
    y <- x
    return (y:ys)

And a quick test to show that it works :

λ> mapM_ print $ combinations [["England"],["London","Liverpool"],["DG300","SS500"]]
["England","London","DG300"]
["England","London","SS500"]
["England","Liverpool","DG300"]
["England","Liverpool","SS500"]

If you don't know Haskell, that's fine. Here's the Java implementation for combinations :

// combinations :: [[String]] -> [[String]]
public static List<List<String>> combinations(List<List<String>> values) {

    // combinations [] = [[]]
    if (values.isEmpty()) {
        return Arrays.asList(Arrays.asList());
    }

    // combinations (x:xs) =
    List<String>       x  = values.get(0);
    List<List<String>> xs = values.subList(1, values.size());

    // do
    List<List<String>> outputs = new LinkedList<>();

    // ys <- combinations xs
    for (List<String> ys : combinations(xs)) {

        // y <- x
        for (String y : x) {

            // (y:ys)
            List<String> output = new LinkedList<>();
            output.add(y);
            output.addAll(ys);

            // return
            outputs.add(output);
        }
    }

    return outputs;
}

Ideone Demo

Note : this Java code is very inefficient, because Haskell has optimizations which make recursion and linked-lists much more efficient. Optimizing this code will be my challenge to the reader. I hope this will be educational (and motivate some to learn Haskell ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM