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;
}
Related
I'm creating a java scraping program using selenium and inserting the data into a database. I'm actively looking to improve my skillset but I don't find instructional videos too helpful since I lose interest, but I really enjoy learning through doing. This code below works as needed, but it looks really really ugly and I feel there must be a better/cleaner solution. For reference it builds a comma separated string with data such as "Value1", or "Value1, Value2", etc depending on the keyword count. My original logic was outputting ", Value1, Value2" which is why I added the "if (x ==0)" logic. I have a lot of methods that are just sloppy like this, so any pointers for improving my code is appreciated, thanks!
ArrayList<String> keywords = new ArrayList<String>();
keywords = keywordChecker(title);
for (int x = 0; x < keywords.size(); x++) {
String list = keywords.get(x);
if (x == 0) {
keywordListBuilder = list;
} else if (x > 0) {
keywordListBuilder = keywordListBuilder + ", " + list;
}
}
keywordValues.add(keywordListBuilder);
public ArrayList<String> keywordChecker(String title) {
ArrayList<String> keywordList = new ArrayList<String>();
String keyword1 = "";
String keyword2 = "";
String keyword3 = "";
String[] keywordTextCombinations = { "Value1", "Value2", "Value3", [imagine a list of 20 items]};
for (int i = 0; i < keywordTextCombinations.length; i++) {
if (title.toLowerCase().contains(keywordTextCombinations[i].toLowerCase())) {
keyword1 = keywordTextCombinations[i];
keywordList.add(keyword1);
break;
}
}
for (int i = 0; i < keywordTextCombinations.length; i++) {
if (title.toLowerCase().contains(keywordTextCombinations[i].toLowerCase())
&& !keywordTextCombinations[i].toLowerCase().equals(keyword1.toLowerCase())
&& !keywordTextCombinations[i].toLowerCase().equals(keyword2.toLowerCase())) {
keyword2 = keywordTextCombinations[i];
keywordList.add(keyword2);
break;
}
}
for (int i = 0; i < keywordTextCombinations.length; i++) {
if (title.toLowerCase().contains(keywordTextCombinations[i].toLowerCase())
&& !keywordTextCombinations[i].toLowerCase().equals(keyword1.toLowerCase())
&& !keywordTextCombinations[i].toLowerCase().equals(keyword2.toLowerCase())) {
keyword3 = keywordTextCombinations[i];
keywordList.add(keyword3);
break;
}
}
return keywordList;
}
ArrayList<String> keywords = new ArrayList<String>();
keywords = keywordChecker(title);
This will:
Create a new variable, named keywords, that can point at arraylists.
Makes a new arraylist object.
Assigns the reference to this newly created object to the keywords variable.
Then tosses that reference away and makes that created object instant garbage, as you then immediately assign some other reference to it.
In other words, that new ArrayList<String>(); does nothing whatsoever but waste time and space. Get rid of it. Let's also be like other java coders and use the most general type that we're interested in. For beginners, that basically means, 'the variable should be of type List, not ArrayList. It's good to write code in similar style to other java coders; makes it easier to read their code and it makes it easier for them to read your code.
List<String> keywords = keywordChecker(title);
for (int x = 0; x < keywords.size(); x++) {
String list = keywords.get(x);
if (x == 0) {
keywordListBuilder = list;
} else if (x > 0) {
keywordListBuilder = keywordListBuilder + ", " + list;
}
}
keywordValues.add(keywordListBuilder);
You're getting a single keyword and you call this list? Names are important. When they lie, your code becomes unreadable.
You're turning a list of strings into a single string with all the values, separated by a comma. That sounds like a common job. When something sounds common enough, search the web. You'll usually find that there's a one-liner. So it is here:
keywordValues.add(String.join(", ", keywords));
Oof, that's way less code.
The keywordChecker method
It helps to document code, especially when asking for help. Evidently, this method is to scan the provided title variable, and search for any of a list of keywords, then it is to return each matching keyword. However, you've limited to return at most 3. I assume you didn't want that. But if you do, I'll show you how, with a one-liner of course.
String keyword1 = "";
String keyword2 = "";
String keyword3 = "";
When you start naming variables like this, stop. There's no way that's correct. Think for a moment. You're already using them, you know how to do this properly: Lists. Once you use a list, this becomes near trivial. Also, method names should generally be a verb; common java style. Let's also make constants, well, constant. Let's also avoid arrays, they are unwieldy and annoying.
private static final List<String> KEYWORDS = List.of("Value1", "Value2", "Value3", [imagine a list of 20 items]);
public List<String> findMatchingKeywords(String title) {
var out = new ArrayList<String>();
String lowercased = title.toLowerCase();
for (String keyword : KEYWORDS) {
if (lowercased.contains(keyword.toLowerCase()) out.add(keyword);
}
return out;
}
That eliminated a ton of lines, that's nice. If you want to return no more than 3 keywords at most... all you need to do is abort looping when you're 'full'. As last line within the for loop:
if (out.length() == 3) break;
Putting it all together:
keywordValues.add(String.join(", ", findMatchingKeywords(title)));
...
private static final List<String> KEYWORDS = List.of("Value1", "Value2", "Value3", [imagine a list of 20 items]);
public List<String> findMatchingKeywords(String title) {
var out = new ArrayList<String>();
String lowercased = title.toLowerCase();
for (String keyword : KEYWORDS) {
if (lowercased.contains(keyword.toLowerCase()) {
out.add(keyword);
if (out.length() == 3) break;
}
}
return out;
}
You can try to do everything in one for loop. Also, I recommend that you use a HashSet since you are comparing elements. A HashSet cannot contain duplicate elements, so if you try to add an element that already exists it doesn't do it and it returns false (Yes, the add function in HashSet returns a boolean).
I have an loop inside loop to remove the elements from the seconde loop, and what is present in the list has to perform certain actions. I wish to optimize the iteration and comparisons, can it be done?
How do I optimize my logic shown below to avoid so many lines of code
public MEntity getBuffer(String entityName, String Buffer, String... ignoreFields) {
McgEntity entity = getElementsByEntityFromXml(entityName);
int minLenghtOfEntities = 0;
List<McgField> fieldsToRemove = new ArrayList<>();
if (ignoreFields != null && ignoreFields.length > 0) {
for (int i = 0; i < ignoreFields.length; i++) {
for (McgField field : entity.getFieldList()) {
if (field.getFieldName().contains(ignoreFields[i])) {
minLenghtOfEntities += field.getFieldLength();
fieldsToRemove.add(field);
}
}
}
entity.setLengthBuffer(entity.getLengthBuffer() - minLenghtOfEntities);
entity.getFieldList().removeAll(fieldsToRemove);
}
....
}
After minLenghtOfEntities += field.getFieldLength(); a break is missing.
With streams the code becomes a bit more structured, first collecting the fields to remove and then the entity lengths to correct.
public MEntity getBuffer(String entityName, String buffer, String... ignoreFields) {
Objects.requireNonNull(ignoreFields);
McgEntity entity = getElementsByEntityFromXml(entityName);
List<McgField> fieldsToRemove = entity.getFieldList().stream()
.filter(f -> Arrays.stream(ignoreFields)
.anyMatch(f.getFieldName()::contains))
.collect(Collectors.toList());
int minLenghtOfEntities = fieldsToRemove.stream()
.mapToInt(McgField::getFieldLength).sum();
entity.setLengthBuffer(entity.getLengthBuffer() - minLenghtOfEntities);
entity.getFieldList().removeAll(fieldsToRemove);
...
}
Unfortunately because of contains a Set<String> ignoreFields is not better.
I read an Excel table containing four columns and create a List. Now, I'd like to use the first three columns as key and use the last column as value. I've seen similar questions asked, but in all those questions, either String or Integer is used as a key.
public class initial {
private int from;
private int to;
private int via;
private int cost;
//constructor
//set and get methods
//hashCode and equals methods
}
public class myTuple {
private int from;
private int to;
private int via;
}
//main function
//get the Excel Table as a list
ArrayList<initial> myList = new ArrayList<initial>();
for(int i= mySheet.getFirstRowNum()+1 ; i<= mySheet.getLastRowNum(); i++) {
initial e = new initial();
Row ro = mySheet.getRow(i);
for(int j = ro.getFirstCellNum(); j <= ro.getLastCellNum(); j++) {
Cell ce = ro.getCell(j);
switch(j) {
case 0:
e.setFrom((int) ce.getNumericCellValue());
break;
.....
case 3:
e.setCost((int) ce.getNumericCellValue());
break;
}
}
myList.add(e);
}
//Create map
Map<myTuple, Integer> myMap = new HashMap<>();
I do not know how to proceed after this point. I believe I should use something like;
Map<myTuple, Integer> myMap= myList.stream().collectC(ollectors.toMap(myList:: , myList::));
If someone could assist me, I'd really appreciate.
Also, if you believe that there is a more efficient way to perform this (e.g., the way I read my data and parse into a list, the way I convert the list into a map), please let me know. Even though it is not in the content of this question, if there is a better way to read a multi dimensional table and parse into a List as I do, I 'd love to hear that too. In the future, I will have a bigger tables with more columns. Hence, I'm not quite sure if going through every column with a switch statement is the way to go.
You can just create the map while looping.
Tuple key = new Tuple(row.getNum(0), row.getNum(1), row.getNum(2));
List<Integer> value = new ArrayList<>();
for (int cell = 3; cell < row.getCount(); cell++) {
value.add(row.getNum(cell));
}
Map.put(key,value);
The toMap collector needs 2 functions (1 to create a key & 1 to create a value). You can use lambdas (to extract the relevant fields from your source type):
Map<MyTuple, Integer> myMap = myList
.stream()
.collect(Collectors.toMap(
i -> new myTuple(i.from, i.to, i.via),
i -> i.cost
));
Your destination type "MyTuple" needs a constructor, equals, and hashcode.
Here is an example:
class Tuple implements Comparable<Tuple> {
Object one;
Object two;
Object three;
public Tuple(final Object one, final Object two, final Object three) {
this.one = one;
this.two = two;
this.three = three;
}
#Override
public int compareTo(final Tuple that) {
// TODO: Do your comparison here for the fields one, two and three
return 0;
}
}
Map<Tuple, Object> mapKeyedByCompositeTuple = new HashMap<>();
// TODO: Inside your loop
for (int i = 10; i > 0; i--) {
Tuple key = new Tuple("cell-one-value-" + i, "cell-two-value-" + i, "cell-three-value-" + i);
mapKeyedByCompositeTuple.put(key, "cell-four-value-" + i);
}
System.out.println(mapKeyedByCompositeTuple);
Hope that helps,
Cheers,
Michael
Let's say I've made the following.
ArrayList<Object> happylist = new ArrayList<Object>();
happylist.add("cat");
happylist.add(98);
...
And so on, adding different types of elements. What sort of method would help me count how many times there was a reference to certain type in my ArrayList?
You can use the getClass() method to determine some object's class.
Take a look at the documentation for Object.
It could easily be done counting the number of different types of reference in the list using reflections. I have coded the following method:
public Map<String, Integer> countReferences(List<Object> happyList) {
Map<String, Integer> referenceCounter = new HashMap<>();
for (Object object : happyList) {
String className = object.getClass().getName();
referenceCounter.put(className, referenceCounter.getOrDefault(className, 0) + 1);
}
return referenceCounter;
}
Basically, each class with difference name gives difference reference. By counting reference to each type, and storing them in map gives you what you are looking for.
But I am not quite sure the usefulness of such particular problems.
static long countByTypeJava8(Collection col, Class clazz) {
return col.stream()
.filter(clazz::isInstance)
.count();
}
static long countByType(Collection col, Class clazz) {
long count = 0;
for (Object o : col) {
if (clazz.isInstance(o)) {
count++;
}
}
return count;
}
In Java 8 or other high version you can use Stream Group API to do, a simple code like this:
ArrayList<Object> happylist = new ArrayList<Object>();
happylist.add("cat");
happylist.add(98);
happylist.add(198);
happylist.add(1L);
Map<Object,IntSummaryStatistics> result = happylist.stream()
.collect(Collectors.groupingBy(o -> o.getClass(),Collectors.summarizingInt(value -> 1)));
// output result
result.entrySet().stream()
.forEach(entry -> System.out.println(String.format("class name = %s\t sum count = %d",entry.getKey(),entry.getValue().getSum())));
IntSummaryStatistics is state object for collecting statistics such as count, min, max, sum, and average.
Thank you for your answers, they helped a lot. For my particular problem in real life, I was able to ease my problem by knowing beforehand what type I was looking for. In my case, I did the following method to call in main method.
int counter = 0;
for (int i = 0; i < happylist.size(); i++) {
if (happylist.get(i) instanceof WantedType) {
counter++;
}
} return counter;
Situation
Well, this method is managing a conversion which accepts a list as a parameter, but definately doesn't look scalable.
List<Long> array_list_data= new ArrayList<>();
public void get_Data() {
long0 = array_list_data.get(0);
long1= array_list_data.get(1);
long2= array_list_data.get(2);
}
Afterwards, it will create a different class with the long fields.
Problem
However, what if we have to expand this data to a 100 parameters on this list?
What I have done so far is:
List<Long> array_list_data= new ArrayList<>();
public void get_Data() {
int k = 0;
long0= array_list_data.get(k);
long1= array_list_data.get(k++);
long2= array_list_data.get(k++);
}
Why incrementing k is not the right way to do it?
k++ performs a post-increment. In other words, the value of the expression is the original value of k, and then k is incremented. It's still incremented before the method is called, but the value passed as the argument is the value before the increment takes place. In other words, a call of:
x = list.get(k++);
is equivalent to:
int tmp = k;
k = k + 1;
x = list.get(tmp);
So if you actually had:
memory_version = array_list_data.get(k++); // Calls with 0, then k=1
mains_voltage_min = array_list_data.get(k++); // Calls with 1, then k=2
mains_voltage_max = array_list_data.get(k++); // Calls with 2, then k=3
then it would be fine, and equivalent to your first code. Your current problem is that you've actually got:
memory_version = array_list_data.get(k); // Calls with 0, then k=0
mains_voltage_min = array_list_data.get(k++); // Calls with 0, then k=1
mains_voltage_max = array_list_data.get(k++); // Calls with 1, then k=2
However, I'd suggest that if you're modelling the data in a class using a collection as a field, you may well be better off with a separate field for each value. (You may create an instance of the class by extracting the data from a list, of course, but that's a different matter.)
k++ will return the value of k then increment it.
++k will increment k then return the incremented value.
You should use ++k in your situation.
It works fine, just k++ does first return k and then increment it by one, so you get k, k, k+1, k+2, etc. Use ++k instead. Or use k++ in the first call, too, your choice.
Although your approach works fine with some tweaking of ++ position, with 100 fields you may be better off with reflection. Put field names into an array or a list, then go through them one by one in a loop, and set values based on a loop variable:
String[] fieldNames = new String[] {"memory_version", " mains_voltage_min", ...};
...
Class<MyClass> c = MyClass.class;
for (int i = 0 ; i != fieldNames.length ; i++) {
Field f = c.getDeclaredField(fieldNames[i]);
f.setLong(this, array_list_data.get(i));
}
This reduces your list processing code to a few simple lines, and lets you change the order of fields in array_list_data simply by arranging your fieldNames array in proper order.
You don't need to maintain the index variable at all; this is why we have iterators:
final Iterator<Integer> iterator = array_list_data.iterator();
memory_version = iterator.next();
mains_voltage_min = iterator.next();
mains_voltage_max = iterator.next();
To manage scalability, I'd use an enum and a Map:
enum Var {
MEMORY_VERSION(0),
MAINS_VOLTAGE_MIN(1),
MAINS_VOLTAGE_MAX(2);
private Integer value;
Var(Integer value) {
this.value = value;
}
public Integer value() { return value; }
}
List<Long> array_list_data= new ArrayList<>();
Map<Integer, Long> variables = new HashMap<>();
public void getData() {
for (int j=0; j<array_list_data.size(); j++) {
variables.put(j, array_list_data.get(j));
}
}
public void test() {
System.out.println("Memory version: " + variables.get(Var.MEMORY_VERSION.value()));
}
so that you can add as many vars as you need, and you can retrieve with a meaningful name, like in the test() method.
Just about the incrementing part, try this:
public void getData() {
int i = 0;
// get element 0, then increment i
memory_version = array_list_data.get(i++);
// get element 1, then increment i
mains_voltage_min = array_list_data.get(i++);
// get element 2, then increment i
mains_voltage_max = array_list_data.get(i++);
}
That's how I do it for example when working with JDBC ResultSet.