How do I be inclusive of Letters into? - java

String
input="4,8,222cd,77,77A";
I sorted the values.for example, I sorted 3,4,5,6 consecutive values in the range [3-6].I have done consecutive numbers "[]" in this format but I couldn't make the letters. Sorted list should not be disrupted.

Here is an example method to merge the letters at the end:
static List<String> mergeLetters(List<String> items){
Map<String, List<String>> mergingCandidates = new HashMap<>();
for (String s : items) {
int length = s.length();
if (length > 1 && Character.isLetter(s.charAt(length - 1))) {
String key = s.substring(0, length - 1);
String value = s.substring(length - 1);
List<String> tmp = mergingCandidates.computeIfAbsent(key, k -> new LinkedList<String>());
tmp.add(value);
}
}
for (Map.Entry<String, List<String>> entry : mergingCandidates.entrySet()) {
String key = entry.getKey();
List<String> characterList = entry.getValue();
if (characterList.size() > 1) {
boolean consecutive = true;
Character[] characters = characterList.stream().map(s -> s.charAt(0)).toArray(Character[]::new);
for (int i = 0; i < characters.length - 1; i++) {
if (characters[i + 1] - characters[i] != 1) {
consecutive = false;
break;
}
}
if (consecutive) {
int indexToBeReplaced = items.indexOf(key + characterList.get(0));
String newValue = key + "[" + characterList.get(0)
+ "-" + characterList.get(characterList.size() - 1) + "]";
characterList.forEach(letter -> items.remove(key + letter));
items.add(indexToBeReplaced, newValue);
}
}
}
return items;
}
Explanation:
You find all the sequences that differ only on the last letter and make a Map out of them.
You pick the Map entries that have 2 or more ending letters.
You check if the letters are consecutive.
If yes, remove them and add a new merged entry instead. Be careful to add the new entry at the same index.
The main method should sort the items, first merge the digits as you correctly do now, and then replace the letters that can be merged:
String input = "605,2A,401-2A,32C,21F,201A,605A,401-1A,200-2E,462A,462,583-58D,200,462-1A,583/58E,583-57D, 542,2B, 1, 542/2E, 801, 802, 803, 605B, 32D, 3, 603, 4, 6, 5, 60, 201C, 542/2D,40,20,2C,800,800-1,50,200-2C,21C,800A,200A,571-573L,51/2,470/1,51/1,571-573K,454-1,444-446,571-573M";
List<String> items = Arrays.asList(input.split("\\s*,\\s*"));
items.sort(Comparator.naturalOrder());
items.sort(Comparator.comparingInt(o -> Integer.parseInt(o.split("\\D")[0])));
List<String> merged = mergeRanges(items);
List<String> mergeLetters = mergeLetters(merged);
System.out.println(("Result: " + mergeLetters));
The output for your input is:
Result: [1, 2[A-C], [3-6], 20, 21C, 21F, 32[C-D], 40, 50, 51/1, 51/2, 60, 200, 200-2C, 200-2E, 200A, 201A, 201C, 401-1A, 401-2A, 444-446, 454-1, 462, 462-1A, 462A, 470/1, 542, 542/2[D-E], 571-573[K-M], 583-57D, 583-58D, 583/58E, 603, 605, 605[A-B], 800, 800-1, 800A, [801-803]]

The general idea is first to expand all the declarations in the input, then sort them, then to emit them again, while compacting ranges when you encounter successive items,.
Expand all declarations: 571-573K (if I understand correctly) would expand to the three items 571K, 572K, 573K. Most simple declarations just expand to themselves.
Sort: You need a sort function that is sensitive to both numbers and letters. It will probably have to break up the input string and compre the number part numerically, but the letter part alphabetically.
Emit: Walk the sorted list and emit each item; however, if the next item is adjacent to the current item, merge it with the current item instead. This will probably require that you create a temporary list of outputs, and delay the actual output until you've gone through the original list.

Related

How in Java do I create an associative array structure and sorted, while keeping duplicate keys with different values pairs

Two comma separated strings exist.
The first string is essentially the keys and the second is the linked values,
The first string needs to be in ascending order while retaining any duplicate values, and the second string needs to follow in suit to maintain the sequence as such.
Looked at hashmaps and tuples without success so far.
System is Java 6
String A = "3, 4, 1, 2, 3"
String B = "19, 24, 32, 68, 50"
Result Output needed
String A = "1, 2, 3, 3, 4"
String B = "32, 68, 19, 50, 24"
You have a multitude of possibilities to realize your requirements. Here are two examples (existing in parallel within the example code):
You can use a Map<Integer, List<Integer>> that holds a key and all values it has in a List
you can create a POJO class that holds exactly one key and one value, but you need to make it sortable/comparable and use a suitable data structure
See this example and pay attention to the code comments:
public class StackoverflowMain {
public static void main(String[] args) {
String a = "3, 4, 1, 2, 3";
String b = "19, 24, 32, 68, 50";
// Map-approach: use a map that maps a key to a list of values
Map<Integer, List<Integer>> ab = new TreeMap<>();
// Pair-approach: make a sortable POJO that holds a key and a value only
// and create a data structure that holds them sorted
SortedSet<Pair> pairList = new TreeSet<Pair>();
// split both Strings by comma
String[] aSplit = a.split(",");
String[] bSplit = b.split(",");
// check if the length of the resulting arrays is the same
if (aSplit.length == bSplit.length) {
// if yes, go through the arrays of numbers
for (int i = 0; i < aSplit.length; i++) {
int key = Integer.parseInt(aSplit[i].trim());
int value = Integer.parseInt(bSplit[i].trim());
// in the Pair-approach, you just have to create a Pair with the value found
Pair pair = new Pair(key, value);
// and add it to the set of pairs
pairList.add(pair);
// the following check is only needed for the Map-solution
if (ab.containsKey(key)) {
// if the key is already present,
// just add the new value to its value list
ab.get(key).add(value);
// sort the value list each time a new value has been added
ab.get(key).sort(Comparator.naturalOrder());
} else {
// if the key is not present in the Map so far,
// create a new List for the value
List<Integer> valueList = new ArrayList<>();
// add the value to that list
valueList.add(value);
// and put both into the Map
ab.put(key, valueList);
}
}
} else {
System.err.println("The Strings have different amounts of elements!");
}
// print what's in the Map
System.out.println("Map-approach:");
for (int key : ab.keySet()) {
List<Integer> value = ab.get(key);
for (int val : value) {
System.out.println(key + " : " + val);
}
}
System.out.println("————————————————");
System.out.println("Pairs-approach:");
for (Pair pair : pairList) {
System.out.println(pair.key + " : " + pair.val);
}
}
/**
* This class is needed for the Pair-approach.
* It is comparable (and by that, sortable) and will be sorted by key
* and if the keys are equal, it will sort by value.
*/
static class Pair implements Comparable<Pair> {
int key;
int val;
Pair(int key, int value) {
this.key = key;
this.val = value;
}
#Override
public int compareTo(Pair otherPair) {
if (key == otherPair.key) {
if (val == otherPair.val) {
return 0;
} else if (val < otherPair.key) {
return -1;
} else {
return 1;
}
} else if (key < otherPair.key) {
return -1;
} else {
return 1;
}
}
}
}
This code produces the following output:
Map-approach:
1 : [32]
2 : [68]
3 : [19, 50]
4 : [24]
————————————————
Pairs-approach:
1 : 32
2 : 68
3 : 19
3 : 50
4 : 24
EDIT
Since the Pair-approach does not sort correctly, I came up with this Map-approach:
public class StackoverflowMain {
public static void main(String[] args) {
String a = "3, 4, 1, 3, 3, 2, 3";
String b = "5, 24, 35, 99, 32, 68, 19";
// Map-approach: use a map that maps a key to a list of values
Map<Integer, List<Integer>> ab = new TreeMap<>();
// split both Strings by comma
String[] aSplit = a.split(",");
String[] bSplit = b.split(",");
// check if the length of the resulting arrays is the same
if (aSplit.length == bSplit.length) {
// if yes, go through the arrays of numbers
for (int i = 0; i < aSplit.length; i++) {
int key = Integer.parseInt(aSplit[i].trim());
int value = Integer.parseInt(bSplit[i].trim());
// the following check is only needed for the Map-solution
if (ab.containsKey(key)) {
// if the key is already present, just add the new value to its value list
ab.get(key).add(value);
// sort the value list each time a new value has been added
ab.get(key).sort(Comparator.naturalOrder());
} else {
// if the key is not present in the Map so far, create a new List for the value
List<Integer> valueList = new ArrayList<>();
// add the value to that list
valueList.add(value);
// and put both into the Map
ab.put(key, valueList);
}
}
} else {
System.err.println("The Strings have different amounts of elements!");
}
// print what's in the Map
System.out.println("Map-approach:");
for (int key : ab.keySet()) {
List<Integer> value = ab.get(key);
for (int val : value) {
System.out.println(key + " : " + val);
}
}
}
}
It is shorter and uses a Map<Integer, List<Integer>> and sorts the List<Integer> every time a new value gets added (apart from the first value, which doesn't need a sort). That needed another loop in the output code, but you don't have to create a new class.
It produces the following output:
Map-approach:
1 : 35
2 : 68
3 : 5
3 : 19
3 : 32
3 : 99
4 : 24
First: java 6 is too old; at least java 8 (generic types, expressive Streams). Then use variable & method names starting with a small letter, as this is a really hard community convention.
In java 6 a poor solution would be an array of packed longs:
Sorting must be done on pairs of A and B values, so here the values are packied.
Wanting things ordered implies a logarithmic access time for a get,
hence a binary search on a sorted array is feasible.
The consequence of using arrays is the fixed array size: insert/delete being cumbersome;
a List would be better, a Map then best.
Turn the strings into data of single elements.
public class MapIntToInts {
long[] ab;
public MapIntToInts (String A, String B) {
String[] a = A.split(", ");
String[] b = B.split(", ");
ab = new int[a.length];
for (int i = 0; i < ab.length; ++i) {
long key = Integer.parseInt(a[i]) << 32L;
long value = Integer.parseInt(b[i]) && 0xFFFF_FFFFL;
ab[i] = key | value;
}
Arrays.sort(ab);
}
Getting the values of one A key can be done by a binary search in O(log N) time:
public int[] get(int key) {
long abKey <<= 32L;
int firstI = Arrays.binSearch(ab, key);
if (firstI < 0) { // Not found
firstI = ~firstI ; // Insert position
}
int i = firstI;
while (i < ab.length && ab[i] >> 32 == key) {
++i;
}
int n = i - firstI;
int[] result = new int[n];
for (int i = 0; i < n; ++i) {
result[i] = (int)ab[firstI + i];
}
Arrays.sort(result); // For mixed negative positive values
return result();
}
}
Usage:
String A = "3, 4, 1, 2, 3";
String B = "19, 24, 32, 68, 50";
MapIntToInts map = MapIntToInts(A, B);
int[] results = map.get(3);
Improvements:
Replace long[] ab; by int[] a; int[b]; or better:
Replace long[] ab; by int[] uniqueKeys; int[][] values;.
You should invest more into decomposing a problem into smaller chunks. For example, converting a string into array of integers is a far more simple operation than the one declared in the title. Let's assume it could be dealt with separately.
Integer[] A = {3, 4, 1, 2, 3};
Integer[] B = {19, 24, 32, 68, 50};
So, when you have two integer arrays at your disposal, you can benefit from natural ordering—it will allow you to skip Comparator implementation should you choose a SortedMap solution.
SortedMap<Integer, List<Integer>> map = new TreeMap<Integer, List<Integer>>();
for (int i = 0; i < A.length; i++) {
if (map.get(A[i]) == null) {
List<Integer> list = new ArrayList<Integer>();
list.add(B[i]);
map.put(A[i], list);
} else {
map.get(A[i]).add(B[i]);
}
}
With the above mentioned the most complex thing to do will be getting rid of the trailing comma when creating an output. But with a small trick it shouldn't be a problem.
StringBuilder sb = new StringBuilder();
for (List<Integer> list : map.values()) {
for (Integer integer : list) {
sb.append(", ").append(integer);
}
}
String output = sb.toString().substring(2);
If you want to verify the answer, you can enable assertions by passing -ea argument to JVM on application start (or just write a unit test).
assert output.equals("32, 68, 19, 50, 24");

Java count string index 0 in array

Im trying to keep track of occurrences of strings that start with certain letters in an array. Given the following array, count the number of occurrences of strings that start with "T", "B, and "A" for example..
String[] month_codes = {"July 2007", "TRUCKS", "APPLES", "BEANS", "COATS", "BANANA", "CODES"}
Output would look like this for each month:
T A B
July 2 1 2
Augu ....
Sept ....
This is the code I used but it only captures one occurrence
public static int extract_counts(String[] month_array){
for(int i=0; i < month_array.length; i++){
System.out.println(month_array[i]);
if(month_array[i].startsWith("T")){
july_ab++;
}
}
return july_ab;
}
What I would do is iterate through all of the items once, counting the number of occurrences. Then I'd print the results.
The easiest way to do this, in my opinion, would be to use an array of integers representing the 26 letters in the English alphabet:
int[] frequency = new int[26];
// frequency[0] is the number of words that start with A
Here, we can take advantage of the fact that Java initializes an integer arrays with all zeroes.
Then we'd iterate through the month_array and count how many words start with each letter.
for( String word : month_array ) {
char firstLetter = word.toLowerCase().charAt(0);
int index = firstLetter - 'a'; // a = 0, b = 1, etc.
frequency[index]++;
}
At the end of this for-loop, the frequency array now contains how many words start with each letter. We could print them like this:
for(int i = 0; i < 26; i++) {
System.out.print( frequency[i] );
}
Of course, this would be in order from A to Z. You'd also probably want to print the headers with the letters A-Z first before iterating through your months.
In your example, your first word is "July 2007" -- of course you'd need to omit this first item before counting since you'd otherwise be artificially incrementing (for example) the 'J' counter.
public int[] extractCounts( String[] month_array ) {
int[] frequency = new int[26];
for(int i = 1; i < month_array.length; i++) {
// here, we start at 1 to omit the 'July 2007' record
String word = month_array[i];
char firstLetter = word.toLowerCase().charAt(0);
int index = firstLetter - 'a';
frequency[index]++;
}
return frequency;
}
public void printCounts(String month, int[] frequencies) {
System.out.print(month + "\t");
for(int frequency : frequencies) {
System.out.print(frequency);
System.out.print("\t");
}
}
String[] month_codes = ...;
Map<Character, Long> result = Arrays.stream(month_codes)
.collect(Collectors.groupingBy(
str -> Character.toLowerCase(str.charAt(0)),
Collectors.counting()));
So, Map<Character, Long> contains following data:
'a' -> 1
'b' -> 2
'c' -> 2
't' -> 1
'j' -> 1
You can do it in several ways.
I am providing you a way which can find the occurrences (Starting chars) using single loop and Map to store only occurrence of String start with "A", "B" and "T".
Here is the code:
public static Map<String, Integer> extract_counts(String[] month_array){
Map<String, Integer> map = new HashMap<>();
for(int i=0; i < month_array.length; i++){
//System.out.println(month_array[i]);
if(month_array[i].startsWith("T")){
if(map.containsKey("T")){
map.put("T", map.get("T") + 1);
}else {
map.put("T", 1);
}
}
if(month_array[i].startsWith("A")){
if(map.containsKey("A")){
map.put("A", map.get("A") + 1);
}else {
map.put("A", 1);
}
}
if(month_array[i].startsWith("B")){
if(map.containsKey("B")){
map.put("B", map.get("B") + 1);
}else {
map.put("B", 1);
}
}
}
return map;
Calling the method:
String[] month_codes = {"July 2007", "TRUCKS", "APPLES", "BEANS", "COATS", "BANANA", "CODES"};
System.out.println(extract_counts(month_codes));
Will get output like this:
{A=1, B=2, T=1}

Find the index value of a character stored in HashMap

I'm looking out to print the index value of a respective character as many times as it would appear using HashMap.
For example, let's say I have String str = "Hello World". The program as of now displays the occurence of characters via {d=1, W=1, e=1, r=1, o=2, l=3, H=1}.
What I intend to achieve as resultset is {d=[9], o=[4, 6], r=[7], W=[5], H=[0], l=[2, 3, 8], e=[1]} where *=[*] represents key=[indexValue].
(The whitespace character is not to be taken into consideration in the final resultSet.)
import java.util.HashMap;
import java.util.Map;
public class ConcordanceOfStrings {
public static void main(String[] args) {
String str = "Hello World";
//code to remove whitespaces
String newStr = str.replaceAll(" ", "");
Map<Character, Integer> numCount = new HashMap<Character, Integer>(Math.min(newStr.length(), 26));
System.out.println("The count is: ");
for(int i=0; i<newStr.length(); i++){
char charAt = newStr.charAt(i);
if(!numCount.containsKey(charAt)){
numCount.put(charAt, 1);
}
else{
numCount.put(charAt, numCount.get(charAt)+1);
}
}
System.out.println(numCount);
}
}
You're very close. Right now, you are storing the result in a Map<Character, Integer>, so a map from each character to the number of times it appears in the String.
To store all the indexes where the character appears, you need to have a Map<Character, List<Integer>>: each character will be mapped to a list of integers, which will be the list of the indexes where this character appears.
In your current code, you just need to adapt the logic that populates the map:
if(!numCount.containsKey(charAt)){
numCount.put(charAt, new ArrayList<>(Arrays.asList(i))); // <-- we store a list containing the first index i
// numCount.put(charAt, 1);
} else{
numCount.get(charAt).add(i); // <-- we add to the existing list the index i
// numCount.put(charAt, numCount.get(charAt)+1);
}
In the case where the map doesn't contain the character, we initialize the mapping with a list containing the first index i. Arrays.asList(i) returns a fixed-size list so I wrapped it in another ArrayList.
In the case where the map already contains the character, we just need to get the current list of indexes and add the one we just found.
If you are using Java 8, your whole code could be written more simply with Streams:
Map<Character, List<Integer>> numCount =
IntStream.range(0, str.length())
.filter(i -> str.charAt(i) != ' ')
.boxed()
.collect(Collectors.groupingBy(
i -> str.charAt(i),
Collectors.mapping(v -> v - 1, Collectors.toList())
));
You need something more than just Map you need something like Map>. Here is a example of a modification to your algorithm:
Map<Character, List<Integer>> positions = new HashMap<>();
Map<Character, Integer> numCount = new HashMap<Character, Integer>(Math.min(newStr.length(), 26));
for (int i = 0; i < newStr.length(); i++) {
char charAt = newStr.charAt(i);
if (!numCount.containsKey(charAt)) {
numCount.put(charAt, 1);
}
else {
numCount.put(charAt, numCount.get(charAt) + 1);
}
if(!positions.containsKey(charAt)){
List<Integer> cPosition = new LinkedList<>();
cPosition.add(i);
positions.put(charAt, cPosition);
}
else{
List<Integer> cPosition = positions.get(charAt);
cPosition.add(i);
//positions.put(charAt, cPosition); because of references there is no need to do this
}
}

Creating permutations by selecting from arrays

I have a 2D array that stores different letters that correspond to what you would see on a phone keypad.
char [][] convert =
{ {},
{'A','B','C'},
{'D','E','F'},
{'G','H','I'},
{'J','K','L'},
{'M','N','O'},
{'P','R','S'},
{'T','U','V'},
{'W','X','Y'}
};
If I want to find all the possible permutations of a 5 letter word that takes 1 letter each from the first 5 rows of the 2D array, how would I do that? I am thinking about recursion but it's just confusing me.
To make this problem easier to understand, here's an example:
A 3 letter word takes its first letter from row 1, {'A','B','C'}, its second letter from row 3, {'G','H','I'}, and its third letter from row 6, {'P','R','S'}. There would be in total 27 possible outcomes: AGP AGR AGS AHP AHR AHS AIP AIR AIS BGP BGR BGS BHP BHR BHS BIP BIR BIS CGP CGR CGS CHP CHR CHS CIP CIR CIS.
The first thing to observe is that if you are making words by selecting one of 3 characters from each of 5 rows, you will end with 35 = 243 words in total. Regardless of how you implement the program, it must end up building each of those 243 words.
Recursion is a good implementation strategy because it makes it clear that you're selecting one of the three characters in the first row, and for each of those choices you go on to select one of the three characters in the second row, and so on.
In the Java program below, the first version of makeWord is a recursive function that selects a character in the row indexed by currentRowIndex and appends that character to wordBuffer. If this is the last row, the word is complete and it gets appended to a list of words. Otherwise, the function calls itself to work on row currentRowIndex + 1.
Notice that the current state of wordBuffer carries through to the recursive call. Only after returning from the recursive call do we delete the last character from wordBuffer.
The second version of makeWord lets you pass an array of row indices that specify which rows you want to select characters from. For example, to select characters from rows 1, 3, and 6, you would call:
permuter.makeWord(new int[]{ 1, 3, 6 }, 0);
You can substitute that call in the main method instead of the current line, which causes a word to be built with characters from rows 1 through 5:
permuter.makeWord(1, 5);
If you take a close look at the makeWord methods, you'll see that the first one doesn't recurse when the string is complete, while the second one recurses once and then returns early because position == indices.length. The latter approach is slightly less efficient because it costs one more recursive call, but you may find that it expresses the concept of recursion more clearly. It's a matter of taste.
import java.util.*;
public class PermuteCharacters {
char[][] rows = {
{},
{'A','B','C'},
{'D','E','F'},
{'G','H','I'},
{'J','K','L'},
{'M','N','O'},
{'P','R','S'},
{'T','U','V'},
{'W','X','Y'}
};
StringBuffer wordBuffer = new StringBuffer();
ArrayList<String> words = new ArrayList<String>();
void makeWord(int currentRowIndex, int endRowIndex) {
char[] row = rows[currentRowIndex];
for (int i = 0; i < row.length; ++i) {
wordBuffer.append(row[i]);
if (currentRowIndex == endRowIndex) {
words.add(wordBuffer.toString());
} else {
makeWord(currentRowIndex + 1, endRowIndex);
}
wordBuffer.deleteCharAt(wordBuffer.length() - 1);
}
}
void makeWord(int[] indices, int position) {
if (position == indices.length) {
words.add(wordBuffer.toString());
return;
}
char[] row = rows[indices[position]];
for (int i = 0; i < row.length; ++i) {
wordBuffer.append(row[i]);
makeWord(indices, position + 1);
wordBuffer.deleteCharAt(wordBuffer.length() - 1);
}
}
void displayWords() {
if (words.size() != 0) {
System.out.print(words.get(0));
for (int i = 1; i < words.size(); ++i) {
System.out.print(" " + words.get(i));
}
System.out.println();
}
System.out.println(words.size() + " words");
}
public static void main(String[] args) {
PermuteCharacters permuter = new PermuteCharacters();
permuter.makeWord(1, 5);
permuter.displayWords();
}
}
Try this, if you can use Java8
static Stream<String> stream(char[] chars) {
return new String(chars).chars().mapToObj(c -> Character.toString((char)c));
}
public static void main(String[] args) {
char [][] convert =
{ {},
{'A','B','C'},
{'D','E','F'},
{'G','H','I'},
{'J','K','L'},
{'M','N','O'},
{'P','R','S'},
{'T','U','V'},
{'W','X','Y'}
};
stream(convert[1])
.flatMap(s -> stream(convert[2]).map(t -> s + t))
.flatMap(s -> stream(convert[3]).map(t -> s + t))
.flatMap(s -> stream(convert[4]).map(t -> s + t))
.flatMap(s -> stream(convert[5]).map(t -> s + t))
.forEach(x -> System.out.println(x));
}
You can also write recursive version.
static Stream<String> permutation(char[][] convert, int row) {
return row == 1
? stream(convert[1])
: permutation(convert, row - 1)
.flatMap(s -> stream(convert[row]).map(t -> s + t));
}
And call this.
permutation(convert, 5)
.forEach(x -> System.out.println(x));
This can be solved using dynamic programming approach.
Take the first two rows and combine all the strings in the arrays. That will give you a resultant array of size m*n. Where m is the size of the first array and n is the size of the second array. In your case it is 9. Then assign the resultant array to the first array and assign the third array to the second array. Repeat it till the fifth array. That will give you all possible strings from the first five arrays.
public static String[] getAllCombinations(String array[][], int l) {
String a[] = array[0];
String temp[]=null;
for(int i=1;i<l;i++) {
int z=0;
String b[] = array[i];
temp = new String[a.length*b.length];
for(String x:a) {
for(String y:b) {
temp[z] = ""+x+y;
z++;
}
}
a = temp;
}
System.out.println(temp.length);
return temp;
}
This function should do it.
Here is one possible solution.
You first define the sequence of keys to be pressed. For example, if you want to take one letter each from the first five rows of the array, the sequence would be (1, 2, 3, 4, 5) (since the first row is empty). If you want to spell "stack", the sequence would be (6, 7, 1, 1, 4).
Then you loop through the sequence. In each step, you get the array of chars that correspond to this position of the sequence. Then you generate all the words that result from all the combinations so far, which are all the words from the previous step combined with all the chars of the current step.
Finally, the result of the last step is the final result that contains all the possible words.
char [][] convert = {
{}, // 0
{'A','B','C'}, // 1
{'D','E','F'}, // 2
{'G','H','I'}, // 3
{'J','K','L'}, // 4
{'M','N','O'}, // 5
{'P','R','S'}, // 6
{'T','U','V'}, // 7
{'W','X','Y'} // 8
};
// Sequence of keys to be pressed. In this case the pressed keys are
// [ABC], [DEF], [GHI]
int[] sequence = new int[] {1, 2, 3};
// List for storing the results of each level.
List<List<String>> results = new ArrayList<>();
for(int i=0; i<sequence.length; i++){
// Results of this level
List<String> words = new ArrayList<>();
results.add(words);
List<String> prevLevelWords;
if(i==0){
prevLevelWords = Collections.singletonList("");
} else {
prevLevelWords = results.get(i-1);
}
char[] thisLevelChars = convert[sequence[i]];
if(thisLevelChars.length == 0){
words.addAll(prevLevelWords);
} else {
for(String word : prevLevelWords){
for(char ch : convert[sequence[i]]){
words.add(word + ch);
}
}
}
}
List<String> finalResult = results.get(sequence.length-1);
for(String word : finalResult) {
System.out.println(word);
}
Run it
Here's an alternate solution using Java 8 streams for your interest.
public class Combiner {
private List<String> combos = new ArrayList<>();
public Stream<String> stream() {
return combos.stream();
}
public void accept(char[] values) {
if (combos.isEmpty()) {
for (char value : values) {
combos.add(String.valueOf(value));
}
} else {
Combiner other = new Combiner();
other.accept(values);
combine(other);
}
}
public Combiner combine(Combiner other) {
combos = stream()
.flatMap(v1 -> other.stream().map(v2 -> v1 + v2))
.collect(Collectors.toList());
return this;
}
}
Essentially this is a collector that takes each element in the stream and adds a new combination for every concatenation of the items in the element and the existing combinations.
And here's some sample code showing how to use it:
public static void main(String[] args) {
char[][] vals = {{'a', 'b'}, {'c'}, {'d', 'e'}, {'f', 'g', 'h'}};
Arrays.stream(vals).parallel()
.collect(Combiner::new, Combiner::accept, Combiner::combine)
.stream().forEach(System.out::println);
}
The parallel isn't strictly necessary: it's just to show that for massive numbers of combinations the stream can be split across processors and then combined.
The code is a lot simpler for other types of data that can be naturally streamed. Unfortunately there's no Arrays.stream(char[]) so the traditional iteration makes the code a bit clearer. You can use something ugly like converting to string then an IntStream and then to Stream<Character> but frankly that's a lot of work to avoid iterating over the array :-)

Smartly building pair elements in different lists

I am building a mind game, it's hard to explain so I put an example.
I have a list of words (it can be infinite):
String myList[] = {"chair", "house", "ocean", "plane", "dog", "TV", "grass", "money" etc....}
Now the tricky part, I need to build 4 lists of pair index/word (every list has the same size) randomly but that fit these rule:
if I chose a number, the word that match this number only appears in 2 lists.
for example this would be correct:
List1:
1/chair
2/house
3/plane
4/grass
List2
1/chair
2/dog
3/plane
4/TV
List3:
1/ocean
2/house
3/money
4/TV
List4
1/ocean
2/dog
3/money
4/grass
For example:
If I pick number 3, then list 3 and list 4 match the word 'money', list 1 and 2 match the word 'plane'. There always must be 2 matching lists (never less, never more). They should be build from a huge array of words randomly, so you can't guess which list will be matching when you pick a number.
I tried to do it with a nice simple recursive algorithm. But I badly failed.
My initial approach to this problem would be to
Select a random word in the universe
Assign the selected word to two lists that
are different and
are not full
Store the random word in a set of closed words in case it is selected again
Rinse and repeat until all lists are full
Something like this should do the trick (you can change the provided words and the respective listSize accordingly). The number of words should be dividable by the listSize in order to fill up all lists.
public static void main(String[] args) {
String[] words = new String[] { "chair", "house", "ocean", "plane",
"dog", "TV", "grass", "money" };
// valid list sizes for 8 words: 1, 2, 4, 8
int listSize = 4;
List<String[]> result = distributeRandomly(words, listSize);
for (String[] resultList : result) {
for (int index = 0; index < listSize; index++) {
System.out.println((index + 1) + "/" + resultList[index]);
}
System.out.println();
}
}
private static List<String[]> distributeRandomly(String[] words, int listSize) {
// each word goes into 2 lists, so how many lists do we need?
int listCount = words.length * 2 / listSize;
if (listCount * listSize != words.length * 2) {
throw new IllegalArgumentException("Number of words"
+ " must be a multiple of the size of the individual lists!");
}
// initialize result lists (here arrays) in fitting size
List<String[]> listsToFill = new ArrayList<String[]>(listCount);
for (int index = 0; index < listCount; index++) {
listsToFill.add(new String[listSize]);
}
// be sure to randomly pick the given words by shuffling them
List<String> shuffledWords = new ArrayList<String>(Arrays.asList(words));
Collections.shuffle(shuffledWords);
List<String[]> result = new ArrayList<String[]>(listCount);
int maxWordPosition = listSize - 1;
// distribute words
for (String word : shuffledWords) {
// word is supposed to be inserted in two lists at the same index
int wordPosition = -1;
// iterate result lists
Iterator<String[]> listIterator = listsToFill.iterator();
while (listIterator.hasNext()) {
String[] list = listIterator.next();
if (wordPosition == -1) {
// look out for the first list with an empty slot
for (int index = 0; index < listSize; index++) {
if (list[index] == null) {
// found empty slot at this index
wordPosition = index;
// insert word here (first list)
list[wordPosition] = word;
if (wordPosition == maxWordPosition) {
// the list is full, no more empty slots here
listIterator.remove();
result.add(list);
}
break;
}
}
} else if (list[wordPosition] == null) {
// found second list with an empty slot at the same index
list[wordPosition] = word;
if (wordPosition == maxWordPosition) {
// the list is full, no more empty slots here
listIterator.remove();
result.add(list);
}
// we are done with this word
break;
}
}
// shuffle result lists again, to ensure randomness
Collections.shuffle(listsToFill);
}
return result;
}
This produces (for example) the following output:
1/grass
2/TV
3/plane
4/ocean
1/grass
2/dog
3/money
4/chair
1/house
2/dog
3/money
4/ocean
1/house
2/TV
3/plane
4/chair

Categories

Resources