Best way to create a tree from rowset in Java - java

Let's assume I have a list of lists in Java as follows:
[
[A, AA, 10],
[A, AB, 11],
[B, BA, 20],
[A, AA, 12],
]
I would like to process each row to create maps of maps, so that I can address the last value in each row as follows:
{
A: {
AA: [10, 12]
AB: [11]
},
B: {
BA: [20]
}
}
In this way I can do calls like:
for (int i : map.get("A").get("AA")) { ... }
Of course, I can iterate over the list and create the maps manually. However that is quite ugly piece of code and it is hard to generalize it for 3,4,5,...,n columns.
Is there some clever way of working with such lists? Some kind of library or something else I haven't thought about?

Yet another quite ugly piece of code :-)
class CustomTree {
private final Map store;
private final int length;
public CustomTree(List<List<String>> source, int length) {
if (length < 2)
throw new IllegalArgumentException("Length must be greater than 2");
this.length = length;
this.store = new HashMap();
for (int i = 0; i < source.size(); i++) {
List<String> line = source.get(i);
if (line.size() != length)
throw new IllegalArgumentException(String.format("Line %d has wrong length", i));
}
for (List<String> line : source) {
if (line.size() != length)
throw new IllegalArgumentException("Not all lines have right length");
accumulate(store, line);
}
}
public void accumulate(Map parent, List<String> keys) {
String key = keys.get(0);
Object value = parent.get(key);
if (keys.size() == 2) {
parent.put(key, value != null
? addToList((List) value, keys.get(1))
: addToList(new ArrayList(), keys.get(1)));
} else {
Map child;
if (value != null) {
child = (Map) value;
} else {
child = new HashMap();
parent.put(key, child);
}
accumulate(child, keys.subList(1, keys.size()));
}
}
private List addToList(List list, String key) {
Integer intValue = Integer.valueOf(key);
if (!list.contains(intValue))
list.add(intValue);
return list;
}
public List<Integer> get(List<String> keys) {
if (keys.size() != (length - 1))
throw new IllegalArgumentException("Bad keys length");
return get(keys, store);
}
private List<Integer> get(List<String> keys, Map tree) {
Object object = tree.get(keys.get(0));
if (object == null)
return new ArrayList<Integer>(0);
return keys.size() == 1
? ((List<Integer>) object)
: get(keys.subList(1, keys.size()), (Map) object);
}
}
Usage
public class Main {
public static void main(String[] args) {
List<List<String>> source = new ArrayList<List<String>>();
List<String> first = Arrays.asList("A", "AA", "CB", "10");
List<String> second = Arrays.asList("A", "AB", "CB", "11");
List<String> third = Arrays.asList("BA", "BA", "CB", "20");
List<String> fourth = Arrays.asList("A", "AA", "CB", "12");
List<String> fifth = Arrays.asList("BA", "BA", "CB", "21");
source.add(first);
source.add(second);
source.add(third);
source.add(fourth);
source.add(fifth);
CustomTree tree = new CustomTree(source, 4);
System.out.println(tree.get(Arrays.asList("BA", "BA", "CB")));
System.out.println(tree.get(Arrays.asList("BA", "B", "sf")));
}
}
May be too ugly. It works only if need a final element of tree without any intermediate Map.

Related

Property based testing for a custom ordered list in Java

Given the following ordering requirement:
All strings starting with "foo" should be first.
All string starting with "bar" should be last.
Strings that do not start with "foo" or "bar" can also be present in the list.
How can one use Property-Based Testing to test an implementation of the above requirements without getting a headache?
Is there some thing more elegant then the following:
List<String> strings = Arrays.asList("foo", "bar", "bar1", "jar");
Collections.shuffle(strings);
assertListStartWith(strings, "foo");
assertListEndsWith(strings, "bar", "bar1");
assertThat(strings, hasItem( "jar"));
I assume that you have some sorter function with signature
List<String> sortFooBar(List<String> list)
I see at least five properties that sortFooBar(list) should fulfill:
Keep all items - and only those - in the list
No item before first "foo"
No other items between first and last "foo"
No item after last "bar"
No other item between first and last "bar"
In a real functional language those properties are all rather easy to formulate in Java it requires a bit of code. So here's my take on the problem using jqwik as PBT framework and AssertJ for assertions:
import java.util.*;
import java.util.function.*;
import org.assertj.core.api.*;
import net.jqwik.api.*;
class MySorterProperties {
#Property
void allItemsAreKept(#ForAll List<#From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
Assertions.assertThat(sorted).containsExactlyInAnyOrderElementsOf(list);
}
#Property
void noItemBeforeFoo(#ForAll List<#From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
if (firstFoo < 0) return;
Assertions.assertThat(sorted.stream().limit(firstFoo)).isEmpty();
}
#Property
void noItemBetweenFoos(#ForAll List<#From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstFoo = findFirst(sorted, item -> item.startsWith("foo"));
int lastFoo = findLast(sorted, item -> item.startsWith("foo"));
if (firstFoo < 0 && lastFoo < 0) return;
List<String> allFoos = sorted.subList(
Math.max(firstFoo, 0),
lastFoo >= 0 ? lastFoo + 1 : sorted.size()
);
Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("foo"));
}
#Property
void noItemAfterBar(#ForAll List<#From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int lastBar = findLast(sorted, item -> item.startsWith("bar"));
if (lastBar < 0) return;
Assertions.assertThat(sorted.stream().skip(lastBar + 1)).isEmpty();
}
#Property
void noItemBetweenBars(#ForAll List<#From("withFooBars") String> list) {
List<String> sorted = MySorter.sortFooBar(list);
int firstBar = findFirst(sorted, item -> item.startsWith("bar"));
int lastBar = findLast(sorted, item -> item.startsWith("bar"));
if (firstBar < 0 && lastBar < 0) return;
List<String> allFoos = sorted.subList(
Math.max(firstBar, 0),
lastBar >= 0 ? lastBar + 1 : sorted.size()
);
Assertions.assertThat(allFoos).allMatch(item -> item.startsWith("bar"));
}
#Provide
Arbitrary<String> withFooBars() {
Arbitrary<String> postFix = Arbitraries.strings().alpha().ofMaxLength(10);
return Arbitraries.oneOf(
postFix, postFix.map(post -> "foo" + post), postFix.map(post -> "bar" + post)
);
}
int findFirst(List<String> list, Predicate<String> condition) {
for (int i = 0; i < list.size(); i++) {
String item = list.get(i);
if (condition.test(item)) {
return i;
}
}
return -1;
}
int findLast(List<String> list, Predicate<String> condition) {
for (int i = list.size() - 1; i >= 0; i--) {
String item = list.get(i);
if (condition.test(item)) {
return i;
}
}
return -1;
}
}
And this is a naive implementation that is consistent with the spec:
class MySorter {
static List<String> sortFooBar(List<String> in) {
ArrayList<String> result = new ArrayList<>();
int countFoos = 0;
for (String item : in) {
if (item.startsWith("foo")) {
result.add(0, item);
countFoos++;
} else if (item.startsWith("bar")) {
result.add(result.size(), item);
} else {
result.add(countFoos, item);
}
}
return result;
}
}
In this example the code for the properties exceeds the amount of code for the implementation. This might be good or bad depending on how tricky the desired behaviour is.

Java 8 stream - merge collections of objects sharing the same Name and who follow each other

Suppose a class Person:
public class Person {
private String name;
private int amount;
}
and suppose a collection of Person:
persons = [{"Joe", 5}, {"Joe", 8}, {"Joe", 10}, {"Jack", 3}, {"Jack",6}, {"Joe" 4},
{"Joe", 7}, {"Jack", 12}, {"Jack", 15}, {"Luke", 10}, {"Luke", 12}]
What i want is to get list of Person with merged element who have same name and who follow each other and sum the amount (with java 8 Stream); a list like this:
Perons = [{"Joe", 23}, {"Jack", 9}, {"Joe", 11}, {"Jack", 27}, {"Luke", 22}]
You should create your own custom Collector for this.
class AdjacentNames {
List<Person> result = new ArrayList<>();
Person last = null;
void accumulate(Person person) {
if (last != null && last.getName().equals(person.getName())) {
last.setAmount(last.getAmount() + person.getAmount())
} else {
last = new Person(person.getName(), person.getAmount()); // Clone
result.add(last);
}
}
AdjacentNames merge(AdjacentNames other) {
List<Person> other_list = other.finisher();
if (other_list.size() > 0) {
accumulate(other_list.remove(0));
result.addAll(other_list);
last = result.get(result.size()-1);
}
return this;
}
List<Person> finisher() {
return result;
}
public static Collector<Person, AdjacentNames, List<Person>> collector() {
return Collector.of(AdjacentNames::new,
AdjacentNames::accumulate,
AdjacentNames::merge,
AdjacentNames::finisher);
}
}
And use like:
List<Person> result = persons.stream().collect(AdjacentNames.collector());
The solution you are looking for is bit complicated with stream approach, I would suggest to go with basic for loop and use Map with a duplicate logic for each sequence
List<Person> persons = List.of(new Person("Jeo", 5), new Person("Jeo", 5), new Person("Jack", 8),
new Person("Luke", 5), new Person("Jeo", 5), new Person("Jeo", 5));
List<Person> result = new ArrayList<>();
Map<String, Integer> check = new HashMap<String, Integer>();
for (Person per : persons) {
if (check.containsKey(per.getName())) {
check.compute(per.getName(), (k, v) -> v + per.getAmount());
} else if (check.size() == 0) {
check.put(per.getName(), per.getAmount());
}else {
result.add(check.entrySet().stream().map(e->new Person(e.getKey(), e.getValue())).findFirst().get());
check.clear();
check.put(per.getName(), per.getAmount());
}
}
result.add(check.entrySet().stream().map(e->new Person(e.getKey(), e.getValue())).findFirst().get());
I would also suggest to go with stream approach to just get each Person sum based on name by using Collectors.groupingBy and summingInt and save them to LinkedHashMap by maintaining the order, and then convert each entry back to Person object.
person.stream()
.collect(Collectors.groupingBy(Person::getName, LinkedHashMap::new,Collectors.summingInt(Person::getAmount)))
.entrySet()
.stream()
.map(entry->new Person(entry.getKey(),entry.getValue()))
.collect(Collectors.toList());
If you don't want to group all person by name, and keep the ones that are together in the original list, I don't think a stream solution is a suitable solution, something with a for loop seems better and easier :
List<Person> output = new ArrayList<>();
Person toAdd = null;
for (Person current : persons) {
if (toAdd == null) {
toAdd = current;
} else if (toAdd.getName().equals(current.getName())) {
toAdd = new Person(toAdd.getName(), toAdd.getAmount() + current.getAmount());
} else {
output.add(toAdd);
toAdd = current;
}
}
output.add(toAdd);
You can do this :
List<Person> result = persons.stream()
.reduce(new ArrayList<>(), (list, currentPerson) -> {
// get the last person of the list being computed
Person lastListPerson = list.isEmpty() ? null : list.get(list.size() - 1);
if (lastListPerson != null && currentPerson.name.equals(lastListPerson.name)) {
// if the previous persone had the same name, just add the amount
lastListPerson.amount += currentPerson.amount;
} else {
// if the previous person had a different name, clone the person and add it in the result list
list.add(new Person(currentPerson));
}
return list;
}, (a, b) -> a);

Sort ArrayList items by name

I am trying to rearrange an ArrayList based on the name of the items to be on specific index.
My list currently is this:
"SL"
"TA"
"VP"
"SP"
"PR"
and i want to rearrange them to:
"SL"
"SP"
"TA"
"PR"
"VP"
but based on the name and not in the index.
I have tried this:
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("SL")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("SP")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("TA")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("PR")){
orderedDummyJSONModelList.add(list.get(i));
}
}
for (int i=0; i< list.size(); i++){
if (list.get(i).getCategoryName().equals("VP")){
orderedDummyJSONModelList.add(list.get(i));
}
}
and it works fine, but i want to know if there is a more efficient way to do in 1 for loop or maybe a function. I do not wish to do it like this:
orderedDummyJSONModelList.add(list.get(0));
orderedDummyJSONModelList.add(list.get(3));
orderedDummyJSONModelList.add(list.get(1));
orderedDummyJSONModelList.add(list.get(4));
orderedDummyJSONModelList.add(list.get(2));
Which also works. Any ideas?
You can use Collection.Sort method as Collection.Sort(list) since list is a List<String> you will be fine. But if you want to implement a new comparator:
Collections.sort(list, new NameComparator());
class NameComparator implements Comparator<String> { //You can use classes
#Override
public int compare(String a, String b) { //You can use classes
return a.compareTo(b);
}
}
EDIT:
You can define a class comparator for your needs:
class ClassComparator implements Comparator<YourClass> { //You can use classes
#Override
public int compare(YourClass a, YourClass b) { //You can use classes
return a.name.compareTo(b.name);
}
}
The key thing here is: you need to get clear on your requirements.
In other words: of course one can shuffle around objects stored within a list. But: probably you want to do that programmatically.
In other words: the correct approach is to use the built-in Collection sorting mechanisms, but with providing a custom Comparator.
Meaning: you better find an algorithm that defines how to come from
"SL"
"TA"
"VP"
"SP"
"PR"
to
"SL"
"SP"
"TA"
"PR"
"VP"
That algorithm should go into your comparator implementation!
The point is: you have some List<X> in the first place. And X objects provide some sort of method to retrieve those strings you are showing here. Thus you have to create a Comparator<X> that works on X values; and uses some mean to get to those string values; and based on that you decide if X1 is <, = or > than some X2 object!
hereĀ“s an answer just specific for your problem working just for the given output. If the List contains anything else this might break your ordering, as there is no rule given on how to order it and the PR just randomly appears in the end.
public static void main(String[] args) {
List<String> justSomeNoRuleOrderingWithARandomPRInside = new ArrayList<String>();
justSomeNoRuleOrderingWithARandomPRInside.add("SL");
justSomeNoRuleOrderingWithARandomPRInside.add("TA");
justSomeNoRuleOrderingWithARandomPRInside.add("VP");
justSomeNoRuleOrderingWithARandomPRInside.add("SP");
justSomeNoRuleOrderingWithARandomPRInside.add("PR");
java.util.Collections.sort(justSomeNoRuleOrderingWithARandomPRInside, new NameComparator());
for(String s : justSomeNoRuleOrderingWithARandomPRInside) {
System.out.println(s);
}
}
static class NameComparator implements Comparator<String> { //You can use classes
#Override
public int compare(String a, String b) { //You can use classes
// Lets just add a T in front to make the VP appear at the end
// after TA, because why not
if (a.equals("PR")) {
a = "T"+a;
} else if(b.equals("PR")) {
b = "T"+b;
}
return a.compareTo(b);
}
}
O/P
SL
SP
TA
PR
VP
But honestly, this solution is crap, and without any clear rule on how to order these this will be doomed to fail as soon as you change anything as #GhostCat tried to explain.
How about this
// define the order
List<String> ORDER = Arrays.asList("SL", "SP", "TA", "PR", "VP");
List<MyObject> list = ...
list.sort((a, b) -> {
// lamba syntax for a Comparator<MyObject>
return Integer.compare(ORDER.indexOf(a.getString()), ORDER.indexOf(b.getString());
});
Note that this will put any strings that aren't defined in the ORDER list at the start of the sorted list. This may or may not be acceptable - it may be worth checking that only valid strings (i.e. members of ORDER) appear as the result of MyObject.getString().
Use a hashmap to store the weight of all strings (Higher the value of the hashmap means the later this string should come in the final list).
Using a Hashmap, so you can expand it later for other strings as well. It'll be easier to enhance in future.
Finally, Use a custom Comparator to do it.
Required Setup:
List<String> listOfStrings = Arrays.asList("SL", "TA", "VP", "SP", "PR");
HashMap<String, Integer> sortOrder = new HashMap<>();
sortOrder.put("SL", 0);
sortOrder.put("TA", 1);
sortOrder.put("VP", 2);
sortOrder.put("SP", 3);
sortOrder.put("PR", 4);
Streams:
List<String> sortedList = listOfStrings.stream().sorted((a, b) -> {
return Integer.compare(sortOrder.get(a), sortOrder.get(b));
}).collect(Collectors.toList());
System.out.println(sortedList);
Non-Stream:
Collections.sort(listOfStrings, (a, b) -> {
return Integer.compare(sortOrder.get(a), sortOrder.get(b));
});
OR
listOfStrings.sort((a, b) -> {
return Integer.compare(sortOrder.get(a), sortOrder.get(b));
});
System.out.println(listOfStrings);
Output:
[SL, TA, VP, SP, PR]
You can build an index map using a LinkedHashMap. This will be used to lookup the order which to sort using the category names of your items.
ItemSorting
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ItemSorting {
public static void main(String[] args) {
List<Item> list = new ArrayList<Item>();
IndexMap indexMap = new IndexMap("SL", "SP", "TA", "PR", "VP");
ItemComparator itemComparator = new ItemComparator(indexMap);
list.add(new Item("SL"));
list.add(new Item("TA"));
list.add(new Item("VP"));
list.add(new Item("SP"));
list.add(new Item("PR"));
Collections.sort(list, itemComparator);
for (Item item : list) {
System.out.println(item);
}
}
}
ItemComparator
import java.util.Comparator;
public class ItemComparator implements Comparator<Item> {
private IndexMap indexMap;
public IndexMap getIndexMap() {
return indexMap;
}
public void setIndexMap(IndexMap indexMap) {
this.indexMap = indexMap;
}
public ItemComparator(IndexMap indexMap) {
this.indexMap = indexMap;
}
#Override
public int compare(Item itemA, Item itemB) {
if (itemB == null) return -1;
if (itemA == null) return 1;
if (itemA.equals(itemB)) return 0;
Integer valA = indexMap.get(itemA.getCategoryName());
Integer valB = indexMap.get(itemB.getCategoryName());
if (valB == null) return -1;
if (valA == null) return 1;
return valA.compareTo(valB);
}
}
IndexMap
import java.util.LinkedHashMap;
public class IndexMap extends LinkedHashMap<String, Integer> {
private static final long serialVersionUID = 7891095847767899453L;
public IndexMap(String... indicies) {
super();
if (indicies != null) {
for (int i = 0; i < indicies.length; i++) {
this.put(indicies[i], new Integer(i));
}
}
}
}
Item
public class Item {
private String categoryName;
public Item(String categoryName) {
super();
this.categoryName = categoryName;
}
public String getCategoryName() {
return categoryName;
}
public void setCategoryName(String categoryName) {
this.categoryName = categoryName;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((categoryName == null) ? 0 : categoryName.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Item other = (Item) obj;
if (categoryName == null) {
if (other.categoryName != null) return false;
} else if (!categoryName.equals(other.categoryName)) return false;
return true;
}
#Override
public String toString() {
return String.format("Item { \"categoryName\" : \"%s\" }", categoryName);
}
}
Result
Item { "categoryName" : "SL" }
Item { "categoryName" : "SP" }
Item { "categoryName" : "TA" }
Item { "categoryName" : "PR" }
Item { "categoryName" : "VP" }
You coud define a helper method like this one:
public static int get(String name) {
switch (name) {
case "SL":
return 1;
case "SP":
return 2;
case "TA":
return 3;
case "PR":
return 4;
case "VP":
return 5;
default:
return 6;
}
}
and write in your main method something like:
ArrayList<String> al = new ArrayList<>();
al.add("SL");
al.add("TA");
al.add("VP");
al.add("SP");
al.add("PR");
Collections.sort(al, (o1, o2) -> return get(o1) - get(o2); );
al.forEach((s) -> System.out.println(s));
You can create a Map that maintains the position. When you iterate through the unordered list just get the position of that string value and insert into new array(not arraylist), then later if required you can convert that array to ArrayList.
Example code:
Map<String,Integer> map = new HashMap<>(); //you can may be loop through and make this map
map.put("SL", 0);
map.put("SP", 1);
map.put("TA",2);
map.put("PR",3);
map.put("VP",3);
List<String> list1 // your unordered list with values in random order
String[] newArr = new String[list1.size()];
for(String strName: list1){
int position = map.get(strName);
arr[position] = strName;
}
//newArr has ordered result.

Join two arrays in Java? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
How to concatenate two arrays in Java?
I have two objects
HealthMessage[] healthMessages1;
HealthMessage[] healthMessages2;
HealthMessage[] healthMessagesAll;
healthMessages1 = x.getHealth( );
healthMessages2 = y.getHealth( );
How should I join the two objects, so I can return only one:
return healthMessagesAll;
What's the recommended way?
Using Apache Commons Collections API is a good way:
healthMessagesAll = ArrayUtils.addAll(healthMessages1,healthMessages2);
I'd allocate an array with the total length of healthMessages1 and healthMessages2 and use System.arraycopy or two for loops to copy their contents. Here is a sample with System.arraycopy:
public class HelloWorld {
public static void main(String []args) {
int[] a = new int[] { 1, 2, 3};
int[] b = new int[] { 3, 4, 5};
int[] r = new int[a.length + b.length];
System.arraycopy(a, 0, r, 0, a.length);
System.arraycopy(b, 0, r, a.length, b.length);
// prints 1, 2, 3, 4, 5 on sep. lines
for(int x : r) {
System.out.println(x);
}
}
}
This is more intuitive to write and you don't have to deal with array indexes:
Collection<HealthMessage> collection = new ArrayList<HealthMessage>();
collection.addAll(Arrays.asList(healthMessages1));
collection.addAll(Arrays.asList(healthMessages2));
HealthMessage[] healthMessagesAll = collection.toArray(new HealthMessage[] {});
.. but don't ask me about it's performance in contrast to System.arraycopy.
I would go with System.arraycopy
private static HealthMessage[] join(HealthMessage[] healthMessages1, HealthMessage[] healthMessages2)
{
HealthMessage[] healthMessagesAll = new HealthMessage[healthMessages1.length + healthMessages2.length];
System.arraycopy(healthMessages1, 0, healthMessagesAll, 0, healthMessages1.length);
System.arraycopy(healthMessages2, 0, healthMessagesAll, healthMessages1.length, healthMessages2.length);
return healthMessagesAll;
}
Arrays are fixed length, so you have various alternatives. Here are a couple:
a) Create a new array with the size of the others and copy all the elements manually.
healthMessagesAll = new HealthMessage[healthMessages1.length + healthMessages2.length];
int i = 0;
for (HealthMessage msg : healthMessases1)
{
healthMessagesAll[i] = msg;
i++;
}
for (HealthMessage msg : healthMessages2)
{
healthMessagesAll[i] = msg;
i++;
}
b) Use the methods provided by the Arrays class. You can convert the array to a List, or copy elements around in bulk. Have a look at the functions it provides and choose the one that suits you.
UPDATE
Seeing your comment about duplicates. You might want to put everything in a Set which guarantees uniqueness. If you add the same element twice, it won't be added the second time.
You can then convert the Set back to an array if you explicitly require an array with its own toArray() method.
As suggested by other respondents, System.arraycopy() helps you copy the contents of the elements too, so its a shorter version of my alternative (a) above.
And for the most complex but least memory-hungry solution you can wrap them in an object. This one provides an Iterator<T> across all of the items and a copyTo method to copy to a new array. It could be easily enhanced to provide getters and setters.
public class JoinedArray<T> implements Iterable<T> {
final List<T[]> joined;
// Pass all arrays to be joined as constructor parameters.
public JoinedArray(T[]... arrays) {
joined = Arrays.asList(arrays);
}
// Iterate across all entries in all arrays (in sequence).
public Iterator<T> iterator() {
return new JoinedIterator<T>(joined);
}
private class JoinedIterator<T> implements Iterator<T> {
// The iterator across the arrays.
Iterator<T[]> i;
// The array I am working on. Equivalent to i.next without the hassle.
T[] a;
// Where we are in it.
int ai;
// The next T to return.
T next = null;
private JoinedIterator(List<T[]> joined) {
i = joined.iterator();
a = nextArray();
}
private T[] nextArray () {
ai = 0;
return i.hasNext() ? i.next() : null;
}
public boolean hasNext() {
if (next == null) {
// a goes to null at the end of i.
if (a != null) {
// End of a?
if (ai >= a.length) {
// Yes! Next i.
a = nextArray();
}
if (a != null) {
next = a[ai++];
}
}
}
return next != null;
}
public T next() {
T n = null;
if (hasNext()) {
// Give it to them.
n = next;
next = null;
} else {
// Not there!!
throw new NoSuchElementException();
}
return n;
}
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
}
public int copyTo(T[] to, int offset, int length) {
int copied = 0;
// Walk each of my arrays.
for (T[] a : joined) {
// All done if nothing left to copy.
if (length <= 0) {
break;
}
if (offset < a.length) {
// Copy up to the end or to the limit, whichever is the first.
int n = Math.min(a.length - offset, length);
System.arraycopy(a, offset, to, copied, n);
offset = 0;
copied += n;
length -= n;
} else {
// Skip this array completely.
offset -= a.length;
}
}
return copied;
}
public int copyTo(T[] to, int offset) {
return copyTo(to, offset, to.length);
}
public int copyTo(T[] to) {
return copyTo(to, 0);
}
#Override
public String toString() {
StringBuilder s = new StringBuilder();
Separator comma = new Separator(",");
for (T[] a : joined) {
s.append(comma.sep()).append(Arrays.toString(a));
}
return s.toString();
}
public static void main(String[] args) {
JoinedArray<String> a = new JoinedArray<String>(
new String[]{
"One"
},
new String[]{
"Two",
"Three",
"Four",
"Five"
},
new String[]{
"Six",
"Seven",
"Eight",
"Nine"
});
for (String s : a) {
System.out.println(s);
}
String[] four = new String[4];
int copied = a.copyTo(four, 3, 4);
System.out.println("Copied " + copied + " = " + Arrays.toString(four));
}
}
what about something along this way:
List<String> l1 = Arrays.asList(healthMessages1);
l1.addAll(Arrays.asList(healthMessages2));
HealthMessage[] result = l1.toArray();
(needs a bit of generification... :)

recursion instead of multi-loops

I want this method to work for any given number of arguments, i can do that with code generation(with a lot of ugly code), can it be done with recursion? if so how? I understand recursion, but i dont know how to write this.
private static void allCombinations(List<String>... lists) {
if (lists.length == 3) {
for (String s3 : lists[0]) {
for (String s1 : lists[1]) {
for (String s2 : lists[2]) {
System.out.println(s1 + "-" + s2 + "-" + s3);
}
}
}
}
if (lists.length == 2) {
for (String s3 : lists[0]) {
for (String s1 : lists[1]) {
System.out.println(s1 + "-" + s3);
}
}
}
}
Here is a simple recursive implementation:
private static void allCombinations(List<String>... lists) {
allCombinations(lists, 0, "");
}
private static void allCombinations(List<String>[] lists, int index, String pre) {
for (String s : lists[index]) {
if (index < lists.length - 1) {
allCombinations(lists, index + 1, pre + s + "-");
}else{
System.out.println(pre + s);
}
}
}
Do you particularly need it to be recursive? I'd make it non-recursive but still not special case things:
public static void allCombinations(List<String>... lists) {
int[] indexes = new int[lists.length];
while (incrementIndexes(lists, indexes)) {
StringBuilder builder = new StringBuilder();
for (int i=0; i < indexes.length; i++) {
if (i != 0) {
builder.append("-");
}
builder.append(lists[i].get(indexes[i]));
}
System.out.println(builder);
}
}
private static boolean incrementIndexes(List<String>[] lists, int[] indexes) {
for (int depth = indexes.length-1; depth >= 0; depth--) {
indexes[depth]++;
if (indexes[depth] != lists[depth].size()) {
return true;
}
// Overflowed this index. Reset to 0 and backtrack
indexes[depth] = 0;
}
// Everything is back to 0. Finished!
return false;
}
Here's a generalised recursive version. It complains about unchecked generic array creation in the test code, but the permute code itself is okay:
import java.util.*;
public class Test
{
public interface Action<T> {
void execute(Iterable<T> values);
}
public static void main(String[] args) {
List<String> first = Arrays.asList(new String[]{"1", "2", "3"});
List<String> second = Arrays.asList(new String[]{"a", "b", "c"});
List<String> third = Arrays.asList(new String[]{"x", "y"});
Action<String> action = new Action<String>() {
#Override public void execute(Iterable<String> values) {
StringBuilder builder = new StringBuilder();
for (String value : values) {
if (builder.length() != 0) {
builder.append("-");
}
builder.append(value);
}
System.out.println(builder);
}
};
permute(action, first, second, third);
}
public static <T> void permute(Action<T> action, Iterable<T>... lists) {
Stack<T> current = new Stack<T>();
permute(action, lists, 0, current);
}
public static <T> void permute(Action<T> action, Iterable<T>[] lists,
int index, Stack<T> current) {
for (T element : lists[index]) {
current.push(element);
if (index == lists.length-1) {
action.execute(current);
} else {
permute(action, lists, index+1, current);
}
current.pop();
}
}
}
here's my recursive solution with correct ordering, based on Rasmus' solution. it works only if all lists are of same size.
import java.util.Arrays;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<String> first = Arrays.asList(new String[]{"1", "2", "3"});
List<String> second = Arrays.asList(new String[]{"a", "b", "c"});
List<String> third = Arrays.asList(new String[]{"x", "y", "z"});
allCombinations (first, second, third);
}
private static void allCombinations(List<String>... lists) {
allCombinations(lists, 1, "");
}
private static void allCombinations(List<String>[] lists, int index, String pre) {
int nextHop = hop(index, lists.length-1);
for (String s : lists[index]) {
if (index != 0) {
allCombinations(lists, nextHop, pre + s + "-");
} else System.out.println(pre + s);
}
}
private static int hop(int prevIndex, int maxResult){
if (prevIndex%2 == 0){
return prevIndex-2;
} else {
if (prevIndex == maxResult)
return prevIndex-1;
int nextHop = prevIndex+2;
if (nextHop > maxResult){
return maxResult;
} else return nextHop;
}
}
}
a "correct ordering" solution that allows lists of different sizes will have to start from the last list and work it's way backwards to the first list (lists[0]), appending the element at either beginning or end of the "pre" string and passing it onward. again, the first list will print the result. I'd have coded that, but lunch is ready and girlfriend is beginning to dislike stackoverflow...

Categories

Resources