As a note beforehand: I think about this as opportunity to simplify tests, so it doesn't have to be cutting-edge efficient.
Lets say I have this unit test:
#Test
public void testVoiceOutput() {
Stone testStone = new Stone();
testStone.talkTo("Hey, how are you doing?");
assertNull("Stone's answer", testStone.listenForReply());
}
When I now add possibilities of conditions, the stone can be in, this gets more and more unreadable:
#Test
public void testVoiceOutput() {
Stone testStone = new Stone();
for (String currentEnvironment : new String[]{"FLOOR", "MEADOW", "STREET"}) {
testStone.setEnvironment(currentEnvironment);
testStone.talkTo("Hey, how are you doing?");
assertNull("Stone's answer, environment=" + currentEnvironment, testStone.listenForReply());
}
}
and
#Test
public void testVoiceOutput() {
Stone testStone = new Stone();
for (String timeOfDay : new String[]{"NIGHT", "NOON", "TEATIME", "DINNERTIME"}) {
for (String currentWeather : new String[]{"RAINY", "FOGGY", "SUNNY", "SNOWY"}) {
for (String currentEnvironment : new String[]{"FLOOR", "MEADOW", "STREET"}) {
testStone.setTimeOfDay(timeOfDay);
testStone.setWeather(currentWeather);
testStone.setEnvironment(currentEnvironment);
testStone.talkTo("Hey, how are you doing?");
assertNull("Stone's answer, environment=" + currentEnvironment + ", weather=" + currentWeather + ", time=" + timeOfDay, testStone.listenForReply());
}
}
}
}
Im am currently collecting ideas on how to simplify that, maybe with a functional interface that creates a Runnable? I am fairly new to the lambda functionalities of newer Java, so maybe you could give me a hint into a promising direction. Of course, in reality it wouldn't all be Strings as arguments.
I already know the #Parameterized functionalities of JUnit, but that's very unhandy if I only have one test that needs all possibilities or if I want to reuse the same Stone object over and over.
If I understand you correctly, you basically want to have a generic method that builds the permutations based on a list of parameters which includes types and values and a class that represents a permutation. In that case you could do something like this:
//P is the type of the Permutation object, T is the type of the parameter
class Parameter<P, T> {
private final List<T> values;
private final BiConsumer<P, T> setter;
private Parameter<P, ?> next;
private int valueIndex = 0;
private boolean indexReset = false;
//first in line
//pass the setter first and the values as varargs for convenience
public Parameter(BiConsumer<P, T> setter, T... vals) {
this(null, setter, vals);
}
//next chain elements
//pass the previous element and the setter first and the values as varargs for convenience
public Parameter(Parameter<P, ?> prev, BiConsumer<P, T> setter, T... vals) {
this.values = Arrays.stream(vals).collect(Collectors.toList());
this.setter = setter;
if( prev != null ) {
prev.next = this;
}
}
//add the next parameter in the chain
public void setNext(Parameter<P, ?> next) {
this.next = next;
}
//creates the permutations and uses the supplier to create a new instance at the end of the chain
public List<P> createPermutations(Supplier<P> instanceSupplier) {
//we don't know the number of permutations so use a linked list here
List<P> permutations = new LinkedList<>();
//run as long as the index didn't get reset to 0 (reached the end of 1 complete iteration)
while( !indexReset ) {
permutations.add(build(instanceSupplier));
}
return permutations;
}
//builds a single permutation by passing the command along the chain
private P build(Supplier<P> instanceSupplier) {
P permutation;
//at the end of the chain build the permutation first, otherwise follow the chain
if( next == null ) {
permutation = instanceSupplier.get();
} else {
permutation = next.build(instanceSupplier);
}
//at this point we're unwinding, i.e. follow the chain tail to head
//set the value, the index is basically the permutation index so we need to get it into range
setter.accept(permutation, values.get(valueIndex));
//assume the index has not reset, we'll check this next
indexReset = false;
//increment the index if there is no next parameter or it has reset in this chain of calls
if( next == null || next.indexReset ) {
//increment
valueIndex++;
//reset if needed
if( valueIndex >= values.size() ) {
valueIndex = 0;
indexReset = true;
}
}
return permutation;
}
}
So what does this class do? It basically builds a linked list of parameters used to build the permutation. The order normally shouldn't matter, just pick what makes sense.
When you build the permutations the class will create call chains for each permutation, creates the instance at the end of the chain and then unwinds, sets the values to the new instance and increments indices as needed. So this is a depth-first approach.
Now let's build the permutations using your Stone class:
//time of day
Parameter<Stone, String> todParam = new Parameter<>(Stone::setTimeOfDay, "NIGHT", "NOON", "TEATIME", "DINNERTIME");
//current weather
Parameter<Stone, String> wheaterParam = new Parameter<>(todParam, Stone::setCurrentWeather, "RAINY", "FOGGY", "SUNNY", "SNOWY");
//current environment
Parameter<Stone, String> envParam = new Parameter<>(wheaterParam, Stone::setCurrentEnvironment, "FLOOR", "MEADOW", "STREET");
List<Stone> permutations = todParam.createPermutations(Stone::new);
This would create permutions like
NIGHT, RAINY, FLOOR
NIGHT, RAINY, MEADOW
...
NIGHT, FOGGY, FLOOR
...
DINNERTIME, SNOWY, STREET
Note that there's still a lot room for improvement, especially on the API, but it should get you started.
You could as well build a kinf of iterator that returns just one Stone permutation on each call and even reuse the same Stone in that case. As mentioned, this is meant to get you started. Now it's time to play around with the code and fit it to your needs :)
JUnit 5 has #ParameterizedTest. This parameterizes the test case. Each test case is created by a static method that returns Stream<Arguments>. You can test efficiently if you create a combinations method that returns all the combinations of the arrays specified by the arguments as Stream<Arguments>.
(I'm adding the number parameter to indicate that there may be parameters other than strings.)
static Stream<Arguments> combinations(Object[]... values) {
Stream.Builder<Arguments> builder = Stream.builder();
int length = values.length;
Object[] arguments = new Object[length];
new Object() {
void perm(int index) {
if (index >= length)
builder.add(Arguments.of(arguments.clone()));
else
for (int i = 0, max = values[index].length; i < max; ++i) {
arguments[index] = values[index][i];
perm(index + 1);
}
}
}.perm(0);
return builder.build();
}
static Stream<Arguments> stonesCombinations() {
return combinations(
new Integer[] {1, 2},
new String[] {"NIGHT", "NOON", "TEATIME", "DINNERTIME"},
new String[] {"RAINY", "FOGGY", "SUNNY", "SNOWY"},
new String[] {"FLOOR", "MEADOW", "STREET"});
}
#ParameterizedTest
#MethodSource("stonesCombinations") // Specify name of the static method that generates test cases
void testVoiceOutput(int number, String timeOfDay, String currentWeather, String currentEnvironment) {
System.out.println(
"number=" + number
+ ", timeOfDay=" + timeOfDay
+ ", currentWeather=" + currentWeather
+ ", currentEnvironment=" + currentEnvironment);
}
output:
number=1, timeOfDay=NIGHT, currentWeather=RAINY, currentEnvironment=FLOOR
number=1, timeOfDay=NIGHT, currentWeather=RAINY, currentEnvironment=MEADOW
number=1, timeOfDay=NIGHT, currentWeather=RAINY, currentEnvironment=STREET
number=1, timeOfDay=NIGHT, currentWeather=FOGGY, currentEnvironment=FLOOR
number=1, timeOfDay=NIGHT, currentWeather=FOGGY, currentEnvironment=MEADOW
number=1, timeOfDay=NIGHT, currentWeather=FOGGY, currentEnvironment=STREET
number=1, timeOfDay=NIGHT, currentWeather=SUNNY, currentEnvironment=FLOOR
.....
number=2, timeOfDay=DINNERTIME, currentWeather=SNOWY, currentEnvironment=FLOOR
number=2, timeOfDay=DINNERTIME, currentWeather=SNOWY, currentEnvironment=MEADOW
number=2, timeOfDay=DINNERTIME, currentWeather=SNOWY, currentEnvironment=STREET
First keep in mind that tests need to justify their costs in terms of added running time, complexity and maintenance. You must ask yourself what is the extra benefit of an exhaustive test like the one you are describing over a sampling one that checks 2 or three different permutations? Is there anything in the code that would make it fail only for a particular permutation of time/whether/environment out of 1000?
Now let us suppose that for some reason the exhaustive test is a must.
Sometimes code with lambdas is harder to read than traditional code.
( at least for me )
I think you answered your own question. A small and simple step for reducing and simplifying the test code, would be to create a simple test helper, as helper functions, or preferably as a helper class.
#Test
public void testVoiceOutput() {
Stone testStone = new Stone();
for (String timeOfDay : new String[]{"NIGHT", "NOON", "TEATIME", "DINNERTIME"}) {
for (String currentWeather : new String[]{"RAINY", "FOGGY", "SUNNY", "SNOWY"}) {
for (String currentEnvironment : new String[]{"FLOOR", "MEADOW", "STREET"}) {
prepareTestStone(testStone,timeOfDay,currentWeather,currentEnvironment);
testStone.talkTo("Hey, how are you doing?");
String expected = getStoneResponse(testStone,timeOfDay,currentWeather,currentEnvironment);
assertEquals(expected, testStone.listenForReply());
}
}
}
}
There's a lot of room to play with here. If you really want to abstract the test further ( now this may be hiding too much of the test for my taste, you can move both the loading of the inputs and the testing of the results into the helper )
#Test
public void testVoiceOutput() {
Stone testStone = new Stone();
StoneTestHelper stoneTestHelper = new StoneTestHelper(testStone);
for (String timeOfDay : new String[]{"NIGHT", "NOON", "TEATIME", "DINNERTIME"}) {
for (String currentWeather : new String[]{"RAINY", "FOGGY", "SUNNY", "SNOWY"}) {
for (String currentEnvironment : new String[]{"FLOOR", "MEADOW", "STREET"}) {
stoneTestHelper.checkStone(testStone,timeOfDay,currentWeather,currentEnvironment);
}
}
}
}
class StoneTestHelper {
private Stone testStone;
public void StoneTestHelper(Stone stone) { this.testStone=stone; }
public void CheckStone(String timeOfDay,String currentWeather,String currentEnvironment) {
{
testStone.setTimeOfDay(timeOfDay);
testStone.setWeather(currentWeather);
testStone.setEnvironment(currentEnvironment);
testStone.talkTo("Hey, how are you doing?");
assertEquals("Stone's answer, environment=" + currentEnvironment + ", weather=" + currentWeather + ", time=" + timeOfDay, testStone.listenForReply());
}
}
}
Related
I was looking for a right answer, but found nothing that fill my purpose.
I have a simple for loop like this:
String test = "hi";
for(Something something : somethingList) {
if(something.getSomething() != null) {
test = cleaner.clean(test, something.getSomething());
} else if(something.getOther() != null) {
test = StaticClass.clean(test, something.getOther());
}
}
and I never understood if the same result can be achieved using java stream. With reduce maybe? I need to pass the response of the previuos loop (saved in the "test" variable) to the next loop (see clean method, where I pass test). How can I do that?
If you want to do something for each element in a list (with a for each loop) I would suggest using the forEach or forEachOrdered functions. These should represent your for(Object o : objects). You can easily define your own Consumer class, which handles all the stuff for you:
class CustomConsumer implements Consumer<Integer> {
private Integer previous;
public CustomConsumer(Integer initialValue) {
previous = initialValue;
}
#Override
public void accept(Integer current) {
// do stuff with your current / previous object :)
System.out.println("previous: " + previous);
previous = current;
}
}
List<Integer> values = getValues();
values.stream()
.forEachOrdered(new CustomConsumer(-1));
This example uses Integer as a provided class, if you want to use your own just replace Integer. You can even use generics:
class CustomConsumer<T> implements Consumer<T> {
private T previous;
public CustomConsumer(T initialValue) {
previous = initialValue;
}
#Override
public void accept(T current) {
// do stuff with your current / previous object :)
System.out.println("previous: " + previous);
previous = current;
}
}
List<Integer> values = new ArrayList<>();
for(int i = 0; i < 6; i++)
values.add(i);
values.stream()
.forEachOrdered(new CustomConsumer<>("hello"));
Output:
previous: hello
previous: 0
previous: 1
previous: 2
previous: 3
previous: 4
If you want to learn more about streams the oracle docs provide some good stuff.
To expand on my comment, with streams you basically could use reduction, e.g. by using the reduce() method.
Example:
//some list simulating your somethingList
List<Integer> list = List.of(2,4,6,1,3,5);
String result = list.stream()
//make sure the stream is sequential to keep processing order
.sequential()
//start reduction with an initial value
.reduce("initial",
//in the accumulator you get the previous reduction result and the current element
(test, element) -> {
//simulates your conditions, just adding the new element for demonstration purposes
// test could also be replaced
if( element % 2 == 0 ) {
test += ", even:" + element;
} else {
test += ", odd: " + element;
}
//return the new reduction result
return test;
},
//combiner is not used in sequential streams so just one of the elements
(l, r) -> l);
This would result in:
initial, even:2, even:4, even:6, odd: 1, odd: 3, odd: 5
Note, however, that streams are not a silver bullet and sometimes a simple loop like your initial code is just fine or even better. This seems to be such a case.
In a way, it is the reverse of the problem of generating subsets of size k from an array containing k+1 elements.
For example, if somebody gives me the pairs {a,b} , {a,c} , {b,c} , {a,e} , {b,e}, {a,f}, I need an algorithm that will tell me the triplets {a,b,c} and (a,b,e} are completely covered for their pairwise combinations in the pairs given to me. I need to generalize from pair/triplet in my example to the case k/k+1
My hunch was that there would be a well documented and efficient algorithm that solves my problem. Sadly, searching the internet did not help obtaining it. Questions already posted in stackoverflow do not cover this problem. I am thereby compelled to post this question to find my solution.
I'm not familiar with an established algorithm for this and you didn't ask for a specific language so I've written up a C# algorithm that accomplishes what you've asked and matches the test values provided. It doesn't have much real-world error checking. I've got a .Net fiddle you can run to see the results within a web browser. https://dotnetfiddle.net/ErwTeg
It works by converting your array of arrays (or similar container) to a dictionary with every unique value as a key and the value for each key being every value that is found within any list with the key. From your sample, a gets {b,c,e,f} (We'll call them friends, and this is what the GetFriends function does)
The AreFriendsWithEachother function indicates whether or not all passed values are friends with all other values.
The results of the friends list are then fed to the MakeTeam function which makes teams of a given size by enumerating every friend that a key has and trying every size length permutation of these. For instance, in the original example a has friend permutations of {{a,b,c},{a,b,e},{a,b,f},{a,c,b},{a,c,e},{a,c,f},{a,e,b},{a,e,c},{a,e,f},{a,f,b},{a,f,c},{a,f,e}}. Of these we make sure that all three values are friends by checking the friends list we created earlier. If all values within a permutation are friends then we add it to our results cache. The results would then be culled for all duplicate sets. This is handled in C# by using HashSet which only adds items that aren't already on the list.
The MakeTeam function is terrible looking because it contains a runtime variable number of loops (normally visualized by foreach). I am rolling up and down through enumerators and emulating the foreach loops myself.
I've included versions for MakeTeamOf3 and MakeTeamOf4 which show static loop structures, which are very easily adapted when you know your k value ahead of time.
The same code is provided here
using System;
using System.Collections.Generic;
using System.Linq;
namespace kfromkm1 // k elements from k minus 1
{
public class Program
{
static readonly string[][] pairs =
{
new string[] { "a", "b" },
new string[] { "a", "c" },
new string[] { "b", "c" },
new string[] { "a", "e" },
new string[] { "b", "e" },
new string[] { "a", "f" }
};
static readonly string[][] pairsExpectedResult =
{
new string[] { "a", "b", "c" },
new string[] { "a", "b", "e" }
};
static readonly string[][] triplets =
{
new string[] { "a", "b", "c" },
new string[] { "a", "b", "d" },
new string[] { "a", "c", "d" },
new string[] { "b", "c", "d" },
new string[] { "b", "c", "e" }
};
static readonly string[][] tripletsExpectedResults =
{
new string[] { "a", "b", "c", "d" }
};
public static void Main(string[] args)
{
Dictionary<string, HashSet<string>> friendsList = GetFriends(pairs);
Dump(nameof(pairs), pairs);
Console.WriteLine();
Dump(nameof(pairsExpectedResult), pairsExpectedResult);
Console.WriteLine();
HashSet<HashSet<string>> teams = MakeTeams(friendsList, 3);
Dump(nameof(teams), teams);
Console.WriteLine();
friendsList = GetFriends(triplets);
Dump(nameof(triplets), triplets);
Console.WriteLine();
Dump(nameof(tripletsExpectedResults), tripletsExpectedResults);
Console.WriteLine();
teams = MakeTeams(friendsList, 4);
Dump(nameof(teams), teams);
Console.ReadLine();
}
// helper function to display results
static void Dump<T>(string name, IEnumerable<IEnumerable<T>> values)
{
Console.WriteLine($"{name} =");
int line = 0;
bool notfirst;
foreach (IEnumerable<T> layer in values)
{
Console.Write($"{line}: {{");
notfirst = false;
foreach (T value in layer)
{
if (notfirst)
Console.Write($", {value}");
else
{
Console.Write(value);
notfirst = true;
}
}
Console.WriteLine("}");
line++;
}
}
// items are friends if they show up in a set (pair in the example) together
// list can be a list of lists, array of arrays, list of arrays, etc
// {a, b} means a and b are friends
// {a, b, c} means a is friends with b and c, b is friends with a and c, c is friends with a and b
static Dictionary<T, HashSet<T>> GetFriends<T>(IEnumerable<IEnumerable<T>> list) where T : IEquatable<T>
{
Dictionary<T, HashSet<T>> result = new Dictionary<T, HashSet<T>>();
foreach (IEnumerable<T> set in list) // one set at a time
{
foreach (T current in set) // enumerate the set from front to back
{
foreach (T other in set) // enumerate the set with a second pointer to compare every item
{
if (!current.Equals(other)) // ignore self
{
if (!result.ContainsKey(current)) // initialize this item's result hashset
result[current] = new HashSet<T>();
result[current].Add(other); // add friend (hashset will ignore duplicates)
}
}
}
}
return result;
}
// indicates whether or not all items are friends
static bool AreFriendsWithEachother<T>(Dictionary<T, HashSet<T>> friendsList, IEnumerable<T> values)
{
if (friendsList == null) // no list = no results
throw new ArgumentNullException(nameof(friendsList));
foreach (T first in values)
{
if (!friendsList.ContainsKey(first)) // not on list, has no friends
return false;
foreach (T other in values)
{
if (!friendsList[first].Contains(other) && !first.Equals(other)) // false if even one doesn't match, don't count self as non-friend for computational ease
return false;
}
}
return true; // all matched so true
}
// size represents how many items should be in each team
static HashSet<HashSet<T>> MakeTeams<T>(Dictionary<T, HashSet<T>> friendsList, int size) where T : IEquatable<T>
{
if (friendsList == null) // no list = no results
throw new ArgumentNullException(nameof(friendsList));
if (size < 2)
throw new ArgumentOutOfRangeException(nameof(size), size, "Size should be greater than 2");
HashSet<HashSet<T>> result = new HashSet<HashSet<T>>(HashSet<T>.CreateSetComparer());
T[] values = new T[size];
IEnumerator<T>[] enumerators = new IEnumerator<T>[size - 1]; // gotta cache our own enumerators with a variable number of "foreach" layers
int layer;
bool moveNext;
foreach (T key in friendsList.Keys) // this is a mess because it's a runtime variable number of copies of enumerators running over the same list
{
values[0] = key;
for (int index = 0; index < size - 1; index++)
enumerators[index] = friendsList[key].GetEnumerator();
moveNext = true;
layer = 0;
while (moveNext)
{
while (layer < size - 1 && moveNext)
{
if (enumerators[layer].MoveNext())
layer++;
else
{
if (layer == 0)
moveNext = false;
else
{
enumerators[layer].Reset();
layer--;
}
}
}
for (int index = 1; index < size; index++)
values[index] = enumerators[index - 1].Current;
if (values.Distinct().Count() == size && AreFriendsWithEachother(friendsList, values))
result.Add(new HashSet<T>(values));
layer--;
}
}
return result;
}
// provided as an example
static HashSet<HashSet<T>> MakeTeamsOf3<T>(Dictionary<T, HashSet<T>> friendsList) where T : IEquatable<T>
{
if (friendsList == null) // no list = no results
throw new ArgumentNullException(nameof(friendsList));
HashSet<HashSet<T>> result = new HashSet<HashSet<T>>(HashSet<T>.CreateSetComparer());
T[] values;
foreach (T key in friendsList.Keys) // start with every key
{
foreach (T first in friendsList[key])
{
foreach (T second in friendsList[key])
{
values = new T[] { key, first, second };
if (values.Distinct().Count() == 3 && AreFriendsWithEachother(friendsList, values)) // there's no duplicates and they are friends
result.Add(new HashSet<T>(values));
}
}
}
return result;
}
// provided as an example
static HashSet<HashSet<T>> MakeTeamsOf4<T>(Dictionary<T, HashSet<T>> friendsList) where T : IEquatable<T>
{
if (friendsList == null) // no list = no results
throw new ArgumentNullException(nameof(friendsList));
HashSet<HashSet<T>> result = new HashSet<HashSet<T>>(HashSet<T>.CreateSetComparer());
T[] values;
foreach (T key in friendsList.Keys) // start with every key
{
foreach (T first in friendsList[key])
{
foreach (T second in friendsList[key])
{
foreach (T third in friendsList[key])
{
values = new T[] { key, first, second, third };
if (values.Distinct().Count() == 4 && AreFriendsWithEachother(friendsList, values)) // there's no duplicates and they are friends
result.Add(new HashSet<T>(values));
}
}
}
}
return result;
}
}
}
Function to generate SetOfkNbrdElementCombinations
//to generate outputs with k values greater than two (pairwise)
Take SetOfkNbrdElementCombinations as an input
//Example - {{a,b},{b,c},...} : here k is 2 (though variable name will retain the letter k); elements are a,b,c,..; sets {a,b}, {b,c} are 2-numbered combinations of elements
Take nextSize as an input
//nextSize should be bigger than the k in the input SetOfkNbrdElementCombinations by 1.
//For example above where k is 2, nextSize would be 3
//Logic:
Comb(SetOfkNbrdElementCombinations={S1,S2,...Sn},nextSize) = {S1,Comb({SetOfkNbrdElementCombinations-a1},nextSize-l)}
//The recursive algorithm specified in the line above generates sets containing unique nextSize numbered combinations of the combinations in SetOfkNbrdElementCombinations
//Code that implements the algorithm is available at Rosetta code
//In our example it would generate {{{a,b},{b,c},{b,e}},{{a,b},{b,c},{a,c}},...} (triplets of pairs)
//My logic to generate nextSize numbered combinations of elements is below
// Example of my output, based on the example input above, would be {{a,b,c},{a,c,e},...}
Intitialize oputSetOfkNbrdElementCombinations to empty
For each nextSize sized combination of combinations generated above
Join the contained combinations in a union set
If the nbr of elements in the union is nextSize, add the union set to oputSetOfkNbrdElementCombinations
Output oputSetOfkNbrdElementCombinations
Here is the Java implementation of the algorithm. You can copy, paste and run on https://ide.geeksforgeeks.org/
/* This program takes k sized element combinations and generates the k+1 sized element combinations
that are possible.
For example if the program is given {a,b,c}, {a,b,d}, {a,c,d}, {b,c,d}, {b,c,e}
which are 3 sized combinations, it will identify {a,b,c,d} the
4 sized combination that has all the 3 sized combinations of its elements covered
in what were provided to the program
The program can scale to higher values of k.
The program uses only the hashset data structure
*/
//AUTHOR: Suri Chitti
import java.util.*;
public class uppOrdCombsFromCombs {
//sample CSV strings...let us pretend they came from a file
//This is a sample of input to the program
static String[] csvStrings = new String[] {
"a,b,c",
"a,b,d",
"a,c,d",
"b,c,d",
"b,c,e"
};
/* //Another sample CSV strings...let us pretend they came from a file
//This is another sample of input to the program
static String[] csvStrings = new String[] {
"a,b",
"b,c",
"a,c",
"c,e",
"a,e"
};
*/ /////USE ONLY ONE SAMPLE
//Before we can generate a k+1 sized combination of elements from a bunch
//of k sized combinations we need to obtain groups containing k+1 number of
// k sized combinations
//The method below, called SetOfNxtSizeNbrdkElementCombinationsets will do it for us
//It takes a bunch of k sized combinations called the parameter hsSetOfkNbrdCombinationsetsPrm
//which is a hashset.
//It also takes k+1 as input called the parameter nextSize which is an integer
//Outputs k+1 sized groups of k sized element combinations as a variable called hsSetOfNxtSizeNbrdCombinationsets
//which is hashset
static HashSet SetOfNxtSizeNbrdCombinationsets(HashSet hsSetOfkNbrdCombinationsetsPrm, Integer nextSize){
HashSet hsSetOfNxtSizeNbrdCombinationsets = new HashSet<>();//is it better to have nested <HashSet> tokens in this declaration?
HashSet hsRecursor = new HashSet<>(hsSetOfkNbrdCombinationsetsPrm);
Iterator <HashSet> loopIterator1 = hsSetOfkNbrdCombinationsetsPrm.iterator();
while (loopIterator1.hasNext()) {
HashSet hsName = loopIterator1.next();
if(nextSize == 1){
hsSetOfNxtSizeNbrdCombinationsets.add(hsName);
}
else {
HashSet hsConc1 = new HashSet<>();
hsRecursor.remove(hsName);
hsConc1 = SetOfNxtSizeNbrdCombinationsets(hsRecursor,nextSize-1);
Iterator <HashSet> loopIterator2 = hsConc1.iterator();
while (loopIterator2.hasNext()) {
HashSet hsConc2 = new HashSet<>();
HashSet hsConc3 = new HashSet<>();
hsConc2 = loopIterator2.next();
Iterator <HashSet> loopIterator3 = hsConc2.iterator();
Object obj = loopIterator3.next();
if (String.class.isInstance(obj)) {
hsConc3.add(hsName);
hsConc3.add(hsConc2);
}
else {
loopIterator3 = hsConc2.iterator();
hsConc3.add(hsName);
while (loopIterator3.hasNext()) {
hsConc3.add(loopIterator3.next());
}
}
hsSetOfNxtSizeNbrdCombinationsets.add(hsConc3);
}
}
}
return hsSetOfNxtSizeNbrdCombinationsets;
}
//The method below takes the k+1 sized groupings of k sized element combinations
//generated by the method above and generates all possible K+1 sized combinations of
//elements contained in them
//Name of the method is SetOfkNbrdCombinationsets
//It takes the k+1 sized groupings in a parameter called hsSetOfNxtSizeNbrdCombinationsetsPrm which is a HashSet
//It takes the value k+1 as a parameter called nextSize which is an Integer
//It returns k+1 sized combinations as a variable called hsSetOfkNbrdCombinationsets which is a HashSet
//This is the intended output of the whole program
static HashSet SetOfkNbrdCombinationsets(HashSet hsSetOfNxtSizeNbrdCombinationsetsPrm, Integer nextSize){
HashSet hsSetOfkNbrdCombinationsets = new HashSet<>();
HashSet hsMember = new HashSet<>();
Iterator <HashSet> loopIteratorOverParam = hsSetOfNxtSizeNbrdCombinationsetsPrm.iterator();
while (loopIteratorOverParam.hasNext()) {
hsMember = loopIteratorOverParam.next();
HashSet hsInnerUnion = new HashSet<>();
Iterator <HashSet> loopIteratorOverMember = hsMember.iterator();
while (loopIteratorOverMember.hasNext()) {
HashSet hsInnerMemb = new HashSet<>(loopIteratorOverMember.next());
hsInnerUnion.addAll(hsInnerMemb);
}
if (hsInnerUnion.size()==nextSize) {
HashSet hsTemp = new HashSet<>(hsInnerUnion);
hsSetOfkNbrdCombinationsets.add(hsTemp);
}
hsInnerUnion.clear();
}
return hsSetOfkNbrdCombinationsets;
}
public static void main(String args[]) {
HashSet hsSetOfkNbrdCombinationsets = new HashSet<>();//should this have nested <HashSet> tokens?
HashSet hsSetOfNxtSizeNbrdCombinationsets = new HashSet<>();//should this have nested <HashSet> tokens?
Integer innerSize=0,nextSize = 0;
System.out.println("Ahoy");
//pretend we are looping through lines in a file here
for(String line : csvStrings)
{
String[] linePieces = line.split(",");
List<String> csvPieces = new ArrayList<String>(linePieces.length);
for(String piece : linePieces)
{
//System.out.println(piece); will print each piece in separate lines
csvPieces.add(piece);
}
innerSize = csvPieces.size();
Set<String> hsInner = new HashSet<String>(csvPieces);
hsSetOfkNbrdCombinationsets.add(hsInner);
}
nextSize = innerSize+1; //programmatically obtain nextSize
hsSetOfNxtSizeNbrdCombinationsets = SetOfNxtSizeNbrdCombinationsets(hsSetOfkNbrdCombinationsets,nextSize);
hsSetOfkNbrdCombinationsets = SetOfkNbrdCombinationsets(hsSetOfNxtSizeNbrdCombinationsets, nextSize);
System.out.println("The " + nextSize + " sized combinations from elements present in the input combinations are: " + hsSetOfkNbrdCombinationsets);
} //end of main
} //end of class
I'm trying to replace the below code using stream API, Optional API. I'm unable to think of a solution. Kindly help me on this.
Note: Please don't bother with the FUNCTIONALITY. This is not the exact client code and hence some of the operations doesn't make sense from the outside perspective.
public class Person {
private String fName;
private String lName;
private String empId;
// constructors, setters, getters
}
.. MAIN CLASS..
private boolean indexExists(final List <Person> list, final int index) {
return index >= 0 && index < list.size();
}
public void mainFunction() {
Person per1 = new Person("fname1", "lname1", "101");
Person per2 = new Person("fname2", "lname2", "102");
List<Person> allPersons = new ArrayList<>();
allPersons.add(per1);
allPersons.add(per2);
System.out.println(allPersons);
List<String> lNamesAppend = Arrays.asList("123","456","789");
// CAN THE BELOW BE REPLACED IN JAVA8 ?
int index = 0;
Person person = null;
for(String str : lNamesAppend) {
if(indexExists(allPersons, index)) {
person = allPersons.get(index++);
} else {
person = new Person("fname" + index++ , "lname" + index++, "10" + index++);
allPersons.add(person);
}
person.setlName(str + index);
}
System.out.println(allPersons);
}
You can create code using the Stream API following the same logic, but there is no sense in doing that without revising the logic. After all, the Stream API allows you to express the intent instead of an iteration logic, at least when you have a suitable task. If not suitable, there is no advantage in changing the code.
In your case, the logic is flawed right from the start, as you are polling indices for validity, despite you know in advance that the valid indices of a list form a range from zero to the list’s size, just to do two entirely different operations, updating old entries or creating new entries, within the same loop.
Compare with a straight-forward approach not doing two things in one:
int existing = Math.min(allPersons.size(), lNamesAppend.size());
for(int index = 0; index < existing; index++)
allPersons.get(index).setlName(lNamesAppend.get(index)+index);
for(int index = existing, end = lNamesAppend.size(); index < end; index++)
allPersons.add(new Person("fname"+index, lNamesAppend.get(index)+index, "10"+index));
I assumed doing index++ three times for a new Person was a bug.
You can do the same using the Stream API:
int existing = Math.min(allPersons.size(), lNamesAppend.size());
IntStream.range(0, existing)
.forEach(index -> allPersons.get(index).setlName(lNamesAppend.get(index)+index));
allPersons.addAll(IntStream.range(existing, lNamesAppend.size())
.mapToObj(index -> new Person("fname"+index,lNamesAppend.get(index)+index,"10"+index))
.collect(Collectors.toList()));
Following is one of the options. Note that the code is not CLEAN, given that the functionality isn't clear, but you can get an idea on how to achieve it
//mainMethod
{
....
AtomicInteger index = new AtomicInteger();
lNamesAppend.stream()
.map(str-> indexExists(allPersons, index.get()) ?
new ImmutablePair<>(str, allPersons.get(index.getAndIncrement())) :
new ImmutablePair<>(str, getPerson(allPersons, index)))
.forEach(p->p.getRight().setlName(p.getLeft()+index));
}
private Person getPerson(List<Person> allPersons, AtomicInteger index) {
Person person = new Person("fname" + index.getAndIncrement(), "lname" + index.getAndIncrement(), "10" + index.getAndIncrement());
allPersons.add(person);
return person;
}
I have a string arraylist under that i need to pass 22184 elements from ["AA00001", "AA00005" ,"AA00003" ----- "ZZ00678"] and i need to generate the sequence elements which are not present in the list. I have written code for that and for less inputs it is generating the required output. But when i am adding 22184 elements and want to generate 200 unique ids which are not present in the arraylist i am getting error as
The code of method main(String[]) is exceeding the 65535 bytes limit
Can someone please help ?
import java.util.ArrayList;
public class GenerateIds
{
private static ArrayList<String> ids = new ArrayList<>();
static int n=50; //no of Ids u want to generate
static int completed =0;
static char ID[] = new char[7];
public static void main(String[] args)
{
ids.add("AA00001");
ids.add("AA00004");
ids.add("AA00007");
generateIds(0);
for(String id : ids)
{
System.out.println(id);
}
}
private static void generateIds(int i)
{
if(n!=completed)
{
if(i<2)
{
for(char c ='A';c<'Z';c++)
{
ID[i]=c;
generateIds(i+1);
}
}
else if(i>=2 && i<7)
{
for(char c ='0';c<='9';c++)
{
ID[i]=c;
generateIds(i+1);
}
}else if(i==7)
{
String id = String.valueOf(ID);
if(!ids.contains(id))
{
ids.add(id);
completed++;
}
}
}
}
}
You can put your id's in a text file. Then use something like.
List<String> ids = Files.readAllLines(Paths.get("ids.txt"));
In java a methods can't have more than 65535 bytes.
The main method is becoming too large since you are doing all the adds inline:
ids.add("AA00001");
ids.add("AA00004");
ids.add("AA00007");
...
This will make the main method too long. What you can do to solve this (and to find the missing elements) is putting all the String values in a List and loop over it to find the missing elements:
public void findMissingElements() {
List<String> missingIds = allPossibleIds.stream()
.filter(isMissingIn(existingIds))
.collect(toList());
//do something with the missingIds...
}
As other readers such as matt suggested, you can e.g. put all the Strings in a file and read the file.
I wrote a small example to show how it would all work together. I rewrote your generateIds method with jOOλ to generate all the possible ids and renamed it to allPossibleIds (however your recursive method would work too). I limited the ids to a 3 size digit number to limit the search time as an example.
public class FindMissingIdsTest {
private List<String> allPossibleIds;
private List<String> existingIds;
#Before
public void setup() throws IOException {
allPossibleIds = allPossibleIds();
existingIds = retrieveIdsFromSubSystem();
}
#Test
public void findMissingElements() {
List<String> missingIds = allPossibleIds.stream()
.filter(isMissingIn(existingIds))
.collect(toList());
}
private Predicate<String> isMissingIn(List<String> existingIds) {
return possibleId -> !existingIds.contains(possibleId);
}
public List<String> allPossibleIds(){
List<String> alphabet = Seq.rangeClosed('A', 'Z').map(Object::toString).toList();
List<String> letterCombinations = Seq.seq(alphabet).crossJoin(Seq.seq(alphabet)).map(t -> t.v1 + t.v2).toList();
List<String> numbericParts = IntStream.range(0, 1000)
.mapToObj(i -> String.format("%03d", i))
.collect(toList());
return Seq.seq(letterCombinations).crossJoin(Seq.seq(numbericParts)).map(t -> t.v1 + t.v2).toList();
}
public List<String> retrieveIdsFromSubSystem() throws IOException {
return Files.readAllLines(Paths.get("ids.txt"));
}
}
To change to 5 digits again you can just change the 1000 to 100000 and the %03d to %05d.
If you can order the list, you could probably find a faster and better algorithm. It all depends on the situation. e.g. if you have an ordered list, you could build up the stream of all the ids, iterate over it and follow in the existing list with a pointer instead of always doing a resource consuming contains().
I'm developing a Java Application that reads a lot of strings data likes this:
1 cat (first read)
2 dog
3 fish
4 dog
5 fish
6 dog
7 dog
8 cat
9 horse
...(last read)
I need a way to keep all couple [string, occurrences] in order from last read to first read.
string occurrences
horse 1 (first print)
cat 2
dog 4
fish 2 (last print)
Actually i use two list:
1) List<string> input; where i add all data
In my example:
input.add("cat");
input.add("dog");
input.add("fish");
...
2)List<string> possibilities; where I insert the strings once in this way:
if(possibilities.contains("cat")){
possibilities.remove("cat");
}
possibilities.add("cat");
In this way I've got a sorted list where all possibilities.
I use it like that:
int occurrence;
for(String possible:possibilities){
occurrence = Collections.frequency(input, possible);
System.out.println(possible + " " + occurrence);
}
That trick works good but it's too slow(i've got millions of input)... any help?
(English isn’t my first language, so please excuse any mistakes.)
Use a Map<String, Integer>, as #radoslaw pointed, to keep the insertion sorting use LinkedHashMap and not a TreeMap as described here:
LinkedHashMap keeps the keys in the order they were inserted, while a TreeMap is kept sorted via a Comparator or the natural Comparable ordering of the elements.
Imagine you have all the strings in some array, call it listOfAllStrings, iterate over this array and use the string as key in your map, if it does not exists, put in the map, if it exists, sum 1 to actual result...
Map<String, Integer> results = new LinkedHashMap<String, Integer>();
for (String s : listOfAllStrings) {
if (results.get(s) != null) {
results.put(s, results.get(s) + 1);
} else {
results.put(s, 1);
}
}
Make use of a TreeMap, which will keep ordering on the keys as specified by the compare of your MyStringComparator class handling MyString class which wraps String adding insertion indexes, like this:
// this better be immutable
class MyString {
private MyString() {}
public static MyString valueOf(String s, Long l) { ... }
private String string;
private Long index;
public hashcode(){ return string.hashcode(); }
public boolean equals() { // return rely on string.equals() }
}
class MyStringComparator implements Comparator<MyString> {
public int compare(MyString s1, MyString s2) {
return -s1.getIndex().compareTo(s2.gtIndex());
}
}
Pass the comparator while constructing the map:
Map<MyString,Integer> map = new TreeMap<>(new MyStringComparator());
Then, while parsing your input, do
Long counter = 0;
while (...) {
MyString item = MyString.valueOf(readString, counter++);
if (map.contains(item)) {
map.put(map.get(item)+1);
} else {
map.put(item,1);
}
}
There will be a lot of instantiation because of the immutable class, and the comparator will not be consistent with equals, but it should work.
Disclaimer: this is untested code just to show what I'd do, I'll come back and recheck it when I get my hands on a compiler.
Here is the complete solution for your problem,
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class DataDto implements Comparable<DataDto>{
public int count = 0;
public String string;
public long lastSeenTime;
public DataDto(String string) {
this.string = string;
this.lastSeenTime = System.currentTimeMillis();
}
public boolean equals(Object object) {
if(object != null && object instanceof DataDto) {
DataDto temp = (DataDto) object;
if(temp.string != null && temp.string.equals(this.string)) {
return true;
}
}
return false;
}
public int hashcode() {
return string.hashCode();
}
public int compareTo(DataDto o) {
if(o != null) {
return o.lastSeenTime < this.lastSeenTime ? -1 : 1;
}
return 0;
}
public String toString() {
return this.string + " : " + this.count;
}
public static final void main(String[] args) {
String[] listOfAllStrings = {"horse", "cat", "dog", "fish", "cat", "fish", "dog", "cat", "horse", "fish"};
Map<String, DataDto> results = new HashMap<String, DataDto>();
for (String s : listOfAllStrings) {
DataDto dataDto = results.get(s);
if(dataDto != null) {
dataDto.count = dataDto.count + 1;
dataDto.lastSeenTime = System.nanoTime();
} else {
dataDto = new DataDto(s);
results.put(s, dataDto);
}
}
List<DataDto> finalResults = new ArrayList<DataDto>(results.values());
System.out.println(finalResults);
Collections.sort(finalResults);
System.out.println(finalResults);
}
}
Ans
[horse : 1, cat : 2, fish : 2, dog : 1]
[fish : 2, horse : 1, cat : 2, dog : 1]
I think this solution will be suitable for your requirement.
If you know that your data is not going to exceed your memory capacity when you read it all into memory, then the solution is simple - using a LinkedList or a and a LinkedHashMap.
For example, if you use a Linked list:
LinkedList<String> input = new LinkedList();
You then proceed to use input.add() as you did originally. But when the input list is full, you basically use Jordi Castilla's solution - but put the entries in the linked list in reverse order. To do that, you do:
Iterator<String> iter = list.descendingIterator();
LinkedHashMap<String,Integer> map = new LinkedHashMap<>();
while (iter.hasNext()) {
String s = iter.next();
if ( map.containsKey(s)) {
map.put( s, map.get(s) + 1);
} else {
map.put(s, 1);
}
}
Now, the only real difference between his solution and mine is that I'm using list.descendingIterator() which is a method in LinkedList that gives you the entries in backwards order, from "horse" to "cat".
The LinkedHashMap will keep the proper order - whatever was entered first will be printed first, and because we entered things in reverse order, then whatever was read last will be printed first. So if you print your map the result will be:
{horse=1, cat=2, dog=4, fish=2}
If you have a very long file, and you can't load the entire list of strings into memory, you had better keep just the map of frequencies. In this case, in order to keep the order of entry, we'll use an object such as this:
private static class Entry implements Comparable<Entry> {
private static long nextOrder = Long.MIN_VALUE;
private String str;
private int frequency = 1;
private long order = nextOrder++;
public Entry(String str) {
this.str = str;
}
public String getString() {
return str;
}
public int getFrequency() {
return frequency;
}
public void updateEntry() {
frequency++;
order = nextOrder++;
}
#Override
public int compareTo(Entry e) {
if ( order > e.order )
return -1;
if ( order < e.order )
return 1;
return 0;
}
#Override
public String toString() {
return String.format( "%s: %d", str, frequency );
}
}
The trick here is that every time you update the entry (add one to the frequency), it also updates the order. But the compareTo() method orders Entry objects from high order (updated/inserted later) to low order (updated/inserted earlier).
Now you can use a simple HashMap<String,Entry> to store the information as you read it (I'm assuming you are reading from some sort of scanner):
Map<String,Entry> m = new HashMap<>();
while ( scanner.hasNextLine() ) {
String str = scanner.nextLine();
Entry entry = m.get(str);
if ( entry == null ) {
entry = new Entry(str);
m.put(str, entry);
} else {
entry.updateEntry();
}
}
Scanner.close();
Now you can sort the values of the entries:
List<Entry> orderedList = new ArrayList<Entry>(m.values());
m = null;
Collections.sort(orderedList);
Running System.out.println(orderedList) will give you:
[horse: 1, cat: 2, dog: 4, fish: 2]
In principle, you could use a TreeMap whose keys contained the "order" stuff, rather than a plain HashMap like this followed by sorting, but I prefer not having either mutable keys in a map, nor changing the keys constantly. Here we are only changing the values as we fill the map, and each key is inserted into the map only once.
What you could do:
Reverse the order of the list using
Collections.reverse(input). This runs in linear time - O(n);
Create a Set from the input list. A Set garantees uniqueness.
To preserve insertion order, you'll need a LinkedHashSet;
Iterate over this set, just as you did above.
Code:
/* I don't know what logic you use to create the input list,
* so I'm using your input example. */
List<String> input = Arrays.asList("cat", "dog", "fish", "dog",
"fish", "dog", "dog", "cat", "horse");
/* by the way, this changes the input list!
* Copy it in case you need to preserve the original input. */
Collections.reverse(input);
Set<String> possibilities = new LinkedHashSet<String>(strings);
for (String s : possibilities) {
System.out.println(s + " " + Collections.frequency(strings, s));
}
Output:
horse 1
cat 2
dog 4
fish 2