Selective Combinations

Consider this scenario:

You have a list of strings with which you have generate ordered selective combinations of strings starting with the first string in the list. Let us say the list of strings is abc, def and ghi. I have to generate ordered combinations of the above list restricted to the ones starting with abc.

So that would be as follows:

abc def ghi

abc def

abc ghi

abc

If the problem sounds abstract, here is the real situation. I have a list of parameters with which I have to select the best matching and available resource (a file in my application config folder). Let us say, the parameter values are

config, type, and Locale (say en_US for this discussion). So I have to go find the best matching file for the given parameters but in the following order:

config\_type\_en_US.json
  
config_type.json
  
config\_en\_US.json
  
config.json

Not a difficult one, eh? Only that, at the time of this writing, the number of parameters was not fixed but could be more (definitely not hundreds or thousands) but more than three.

The non-solution

Alright, let us not talk about generating all possible combinations of the given list of strings, and pluck the ones starting with the first string in the list.

Solution #1

The conventional technique or the don’t overengineer advice is to write a helper method with a bunch of nested ifs; something I will not allow myself to succumb to. This helper method, although appears to be insignificant in the might of other things in the application, is going to be source of several problems later; at least the one related to resource selection.

Solution #3

A slightly better way is to have a predefined ordered set of strings that represent the filename pattern such as:

  
private static final String[] resourceSelectionPatterns = { 
	"{p1}\_{p2}\_{loc}",
	"{p1}_{p2}",
	"{p1}_{loc}",
	"{p1}"
};

public static String findResource(
    String param1,
    String param2,
    Locale locale
) throws IOException {
    
	for (String pattern : patterns) {
		final String resName = 
			pattern
				.replaceFirst("{p1}", param1)    
				.replaceFirst("{p2}", param2)
				.replaceFirst("{loc}", locale.toString());

		final String fileName = resName + ".json";

		if (new File(fileName).exists()) {
				return resName;
		}
	}

	throw new IOException("No matching resource found");
}

This is better than nested ifs which I would not bother to show the code. But even this is not really better. First, when there are more parameters in the equation, I have to go update resourceSelectionPatterns with the corresponding patterns and in the right order, and findResource method too. Also, the replaceFirst calls could create many throw-away Strings. Of course, you can use a StringBuilder but that does not make it all better.

Take a step back

What I want is to generate the filename segments (or the equivalent patterns) dynamically based on the given set of input strings so that the solution is write once and for all (any count of parameters). We can assume that the order of the generated patterns is dictated by the order of the input strings.

Finale

If we can represent the input parameters in a more succinct format like a number and if we can identify a pattern among the numerical representation for each parameter, then we should be able to generate the filename segments dynamically.

If we assume each parameter to be a bit of an integer, first parameter being the most significant bit, then the bit representations for the input – config, type, en_US would be as follows:

0000 0111 // 7 => config\_type\_en_US
  
0000 0110 // 6 => config_type
  
0000 0101 // 5 => config\_en\_US.json
  
0000 0100 // 4 => config.json

Well, I am sure you see the pattern. The integer representation of each filename segment decrements by 1 from the most specialized filename and stops at the least specialized one. This is true of any set of parameters and the selective combinations are generated dynamically.

Writing the code now for this solution should be a pie, the pleasure of which I will leave it to you.

Thanks to Sammy in helping me discover the solution!