Sorry about the non-descriptive title, I couldn't think of a way to explain it better short of 100 words. What I would like to be able to do is sort a list of strings into "boxes" based on a string associated with the main string and an array of strings "order".
For my current setup I am using a HashMap to store the string and it's associated "place-in-the-order" string.
I am aware that my explanation is truly crap so I have made an image which I hope will explain it better:
The variables are initialised as follows:
private final String[] order = new String[] {"Severe", "Warning", "Info"};
private final Box[] boxes = new Box[] {new Box(1), new Box(2), new Box(3), new Box(4)};
private final Map<String, String> texts = new HashMap<String, String>();
texts.put("Server on fire!", "Severe");
texts.put("All is good!", "Info");
texts.put("Monkeys detected!", "Warning");
texts.put("Nuke activated!", "Severe");
This shouldn't be too hard to implement but the only way I can think of doing it is by using 3 loops which seems a bit wasteful and would be slow if there was large numbers of any of the inputs.
Here is some example code which will hopefully show what I have come up with so far and perhaps explain the problem, I have not tested it and don't have an IDE handy so have probably overlooked something.
Set<Box> usedBoxes = new HashSet<Box>();
for(String curOrder : order) {
for (String text : texts) {
if (texts.get(text).equals(order)) {
for (Box box : boxes) {
if (!usedBoxes.contains(box)) {
box.setText(text);
usedBoxes.add(box);
break;
}
}
}
}
}
I'm not sure I fully understand what you want to achieve, but I feel that there are two things that would make your design much simpler:
Don't use Strings for your severity levels. Use enums instead. Enums have a name, may have other fields and methods, and are naturally ordered using their order of definition. And there is no way to make a typo and introduce an unknown severity: they're type-safe
enum Severity {
SEVERE, WARNING, INFO
}
Don't store things in parallel arrays or associate them with maps. Define a class containing the information of your objects:
public class Box {
private String text;
private Severity severity;
}
Now that you have these, you can simply create a List<Box>, and sort it using a Comparator<Box> which sorts them by severity, for example:
List<Box> boxes = Arrays.asList(new Box("Server is on fire", Severity.SEVERE),
new Box("All is good", Severity.INFO),
...);
Collections.sort(boxes, new Comparator<Box>() {
#Override
public int compare(Box b1, Box b2) {
return b1.getSeverity().compareTo(b2.getSeverity());
}
}
or even simpler, with Java 8:
boxes.sort(Comparator.comparing(Box::getSeverity));
You should make your "statuses" (Severe, Info etc) into an Enum.
public enum StatusLevel {
Severe,
Warning,
Info;
}
You can then sort by StatusLevel natively as long as you define the in a top to bottom order.
If you want to supply your Box object directly insead of pulling out the StatusLevel or have a secondary sort by another property like time or alphabetically you should implement your own Comparator
Also you may want to look into SortedMap or other Map that keeps its order so you don't have to resort a HashMap every time as it does not guarantee order.
Related
I need a solution to improve the performance of this method. I need to insert these inputs in order using LinkedHashMap and it works perfectly. However, I don't like this solution because I have a list and each time I go through it to retrieve the input that I want to insert into this map.
Here is my code
public void prepareData(final List<Category> categories, final Map<String, Boolean> preferencesMap) {
preferencesMap.put("ACCESSORIES",checkDataFromList(categories, "ACCESSORIES"));
preferencesMap.put("WATCHES",checkDataFromList(categories, "WATCHES"));
preferencesMap.put("PHONES",checkDataFromList(categories, "PHONES"));
}
private Boolean checkDataFromList(final List<Category> categories, final String val){
for (Category category: categories) {
if(val.equalsIgnoreCase(category.getCode()) && category.isActive() != null){
return !category.isActive();
}
}
return Boolean.FALSE;
}
I need a way to retrieve these objects without iterating through this list 3 times.
I would like to share my insights, maybe it is not an answer to your question. I try to list the possibilities that seem to be helpful.
I think you cannot do much with this data structure. You have collected your data into a list, and that's why you couldn't find a list member by its property in a simpler way. So that's why you need to iterate over this list as many times as you search for an item.
Basically, you have 2 choices, keeping the base data structure, List<Category>, or changing it to another one.
Keeping the list data structure. - The first thing I can think of, is that you can modify your method slightly, and gather all the Strings you need to test and test each item with them (if you found the item, you should remove the String from the list of Strings). That's how you need to iterate over the list only once.
private Map<String, Boolean> checkDataFromList(final List<Category> categories, final List<String> val){...}
I don't recommend this solution. I believe it makes the code harder to understand and you probably won't yield much performance.
Changing the data structure - The first thing that comes to my mind, is mapping. You could map these Categories by their codes. That way you don't need to iterate over the list. Using Map<String/*code*/, Category> can maybe yield performance, but it could make your code more difficult.
+1 You can often enhance performance by making your data structure items comparable and then using a sorted structure. Besides, you could try using caches if it makes any sense.
Solution 1
One loop by categories.
public void prepareData(final List<Category> categories, final Map<String, Boolean> preferencesMap) {
List<String> predefinedCategories = Stream.of("ACCESSORIES", "WATCHES", "PHONES").collect(Collectors.toList());
predefinedCategories.forEach(predefineCategory -> preferencesMap.put(predefineCategory, Boolean.FALSE));
for(Category category: categories) {
predefinedCategories.stream().filter(val -> val.equalsIgnoreCase(category.getCode()) && category.isActive() != null)
.findFirst()
.ifPresent(existingCategoryCode ->
{
preferencesMap.put(existingCategoryCode, !category.isActive());
predefinedCategories.remove(existingCategoryCode);
});
if (predefinedCategories.isEmpty()) {
break;
}
}
}
Solution 2
In case if you have ability to change categories List to Map<String, Category>
public void prepareData(final Map<String, Category> categoriesMap, final Map<String, Boolean> preferencesMap) {
Stream.of("ACCESSORIES", "WATCHES", "PHONES").forEach(predefinedCategory -> {
Category category = categoriesMap.get(predefinedCategory);
preferencesMap.put(predefinedCategory, category != null && category.isActive() != null && !category.isActive());
});
}
In my program I have a List of Plants, each plant has a measurement (String), day (int), camera (int), and replicate number(int). I obtain a List of all plants wanted by using filters:
List<Plant> selectPlants = allPlants.stream().filter(plant -> passesFilters(plant, filters)).collect(Collectors.toList());
What I would like to do now is take all Plants that have the same camera, measurement, and replicate values. And combine them in order of day. So if I have days 1,2,3,5 I would want to find all similar plants and append the values to one plant where the getValues (function).
I added a method to Plant that appends values by just using addAll( new plant values ).
Is there any way of doing this without iterating through the list over and over to find the similar plants, and then sorting each time by day then appending? I'm sorry for the horrible wording of this question.
While Vakh’s answer is correct, it is unnecessarily complex.
Often, the work of implementing your own key class does not pay off. You can use a List as a key which implies a slight overhead due to boxing primitive values but given the fact that we do operations like hashing here, it will be negligible.
And sorting doesn’t have to be done by using a for loop and, even worse, an anonymous inner class for the Comparator. Why do we have streams and lambdas? You can implement the Comparator using a lambda like (p1,p2) -> p1.getDay()-p2.getDay() or, even better, Comparator.comparing(Plant::getDay).
Further, you can do the entire operation in one step. The sort step will create an ordered stream and the collector will maintain the encounter order, so you can use one stream to sort and group:
Map<List<?>, List<Plant>> groupedPlants = allPlants.stream()
.filter(plant -> passesFilters(plant, filters))
.sorted(Comparator.comparing(Plant::getDay))
.collect(Collectors.groupingBy(p ->
Arrays.asList(p.getMeasurement(), p.getCamera(), p.getReplicateNumber())));
That’s all.
Using Collectors.groupBy:
private static class PlantKey {
private String measurement;
private int camera;
private int replicateNumber;
// + constructor, getters, setters and haschode equals
}
Map<PlantKey, List<Plant>> groupedPlants =
allPlants.stream().filter(plant -> passesFilters(plant, filters))
.collect(Collectors.groupBy(p ->
new PlantKey(p.getMeasurement(),
p.getCamera(),
p.getReplicateNumber())));
// order the list
for(List values : groupedPlants.values()) {
Collections.sort(values, new Comparator<Plant>(){
#Override
public int compare(Plant p1, Plant p2) {
return p1.getDay() - p2.getDay();
}
});
}
I would group them by the common characteristics and compare similar results.
for(List<Plant> plantGroup : allPlants.stream().collect(Collectors.groupingBy(
p -> p.camera+'/'+p.measurement+'/'+p.replicate)).values()) {
// compare the plants in the same group
}
There is a function called sorted which operates on a stream
selectPlants.stream().sorted(Comparator.comparingInt(i -> i.day)).collect(Collectors.toList());
I have a project where I save some data coming from different channels of a Soap Service, for example:
String_Value Long_timestamp Double_value String_value String_value Int_value
I can have many lines (i.e. 200), with different values, like the one above.
I thought that I could use an ArrayList, however data can have a different structure than the one above, so an ArrayList maybe isn't a good solution in order to retrieve data from it.
For example above I have, after the first two values that are always fixed, 4 values, but in another channel I may have 3, or 5, values. What I want retrieve data, I must know how many values have a particular line, and I think that Arraylist doesn't help me.
What solution could I use?
When you have a need to uniquely identify varying length input, a HashMap usually works quite well. For example, you can have a class:
public class Record
{
private HashMap<String, String> values;
public Record()
{
// create your hashmap.
values = new HashMap<String, String>();
}
public String getData(String key)
{
return values.get(key);
}
public void addData(String key, String value)
{
values.put(key, value);
}
}
With this type of structure, you can save as many different values as you want. What I would do is loop through each value passed from Soap and simply add to the Record, then keep a list of Record objects.
Record rec = new Record();
rec.addData("timestamp", timestamp);
rec.addData("Value", value);
rec.addData("Plans for world domination", dominationPlans);
You could build your classes representing the entities and then build a parser ... If it isn't in a standard format (eg JSON, YAML, ecc...) you have no choice to develop your own parser .
Create a class with fields.
class ClassName{
int numberOfValues;
String dataString;
...
}
Now create an ArrayList of that class like ArrayList<ClassName> and for each record fill that class object with numberOfValues and dataString and add in Arraylist.
I'm trying to build a form generating class and i might have hit a glitch somewhere in my logic statement.
You have two string arrays.
String[] fieldNames;
String[] fieldTypes;
Both their lengths should be the same. Each value in the fieldName[] corresponds to a value in the fieldTypes[] array.
I want to create various fields depending on the values stated in the fieldTypes array and assign the created fields the name specified in the fieldNames array.e.g.
String[] fieldNames = {"Name", "Phone", "Gender"}
String[] fieldTypes = {"TextFied","ComboBox", "RadioButton"}
The field types and names can vary. They can be whatever you want them to be.
Now, using the above info, how do i assign the fieldNames to the fieldTypes so I can use them in the data processing? i.e
TextField name = new TextField();
ComboBox phone = new ComboBox();
RadioButton gender = new RadioButton();
I've been mulling this over for a week now and there doesn't seem to be any solution to this online. Or rather I haven't been able to find one. I someone could point me in the right direction i'll be greatful
You could use a Map of String and Class, as such:
// This is for AWT - change class bound to whatever super class or interface is
// extended by the elements of the framework you are using
Map<String, Class<? extends Component>> fields = new LinkedHashMap<String, Class<? extends Component>>();
fields.put("Name", TextField.class);
The Map is a LinkedHashMap so you can keep the order of the keys.
Once you retrieve a value through the get method, you can get the class of the desired component and act upon.
Edit
Here's how to retrieve the component through reflexion. Note that it's not the only solution and might not be the "cleanest"...
try {
Component foo = fields.get("Name").newInstance();
System.out.println(foo.getClass());
}
catch (Throwable t) {
// TODO handle this better
t.printStackTrace();
}
Output:
class java.awt.TextField
I have a csv file with unknown amount of columns and row. The only thing I know is that each entry is separated by a comma. Can I use the split method to convert each line of the data into an array and then can I store that Array into an Arraylist. One of the things that concerns me is would I be able to rearrange the Arraylist alphabetically or numerically.
I would suggest using OpenCSV. If you just split on the comma separator, and you happen to have a single cell text containing a comma, but which is enclosed in double quotes to make it clear that it's a single cell, the split method won't work:
1, "I'm a single cell, with a comma", 2
3, hello, 4
OpenCSV will let you read each line as an array of Strings, handling this problem for you, and you can of course store each array inside a List. You will need a custom comparator to sort your list of lines. Search StackOverflow: the question of how to sort a list comes back twice a day here.
Yes, you can use:
String[] array = input.split("\",\"");
List<String> words = new ArrayList<String>(Arrays.asList(array))
Note that Arrays.asList(..) also returns a List, but you can't modify it.
Also note that the above split is on ",", because CVSs usually look like this:
"foo","foo, bar"
Using split with simple comma is not a fool proof one. If your column data contains a comma, csv would be stored something like a,"b,x",c. In such case split would fail.
I'm not a regex expert maybe someone could write a EMBEDDED_COMMA_DETECTING_REGEX or GIYF.
String[] array = input.split(EMBEDDED_COMMA_DETECTING_REGEX);
List<String> words = new ArrayList<String>(Arrays.asList(array));
There are several questions here so I'll cover each point individually.
Can I use the split method convert each line of the data into an array
This would work as you expect in the naive case. However, it doesn't know anything about escaping; so if a comma is embedded within a field (and properly escaped, usually by double-quoting the whole field) the simple split won't do the job here and will chop the field in two.
If you know you'll never have to deal with embedded commas, then calling line.split(",") is acceptable. The real solution however is to write a slightly more involved parse method which keeps track of quotes, and possibly backslash escapes etc.
...into an array than can I store that Array into an Arraylist
You certainly could have an ArrayList<String[]>, but that doesn't strike me as particularly useful. A better approach would be to write a simple class for whatever it is the CSV lines are representing, and then create instances of that class when you're parsing each line. Something like this perhaps:
public class Order {
private final int orderId;
private final String productName;
private final int quantity;
private final BigDecimal price;
// Plus constructor, getters etc.
}
private Order parseCsvLine(String line) {
String[] fields = line.split(",");
// TODO validation of input/error checking
final int orderId = Integer.parseInt(fields[0]);
final String productName = fields[1];
final int quantity = Integer.parseInt(fields[2]);
final BigDecimal price = new BigDecimal(fields[3]);
return new Order(orderId, productName, quantity, price);
}
Then you'd have a list of Orders, which more accurately represents what you have in the file (and in memory) than a list of string-arrays.
One of the things that concerns me is would I be able to rearrange the Arraylist according alphabetically or numerically?
Sure - the standard collections support a sort method, into which you can pass an instance of Comparator. This takes two instances of the object in the list, and decides which one comes before the other.
So following on from the above example, if you have a List<Order> you can pass in whatever comparator you want to sort it, for example:
final Comparator<Order> quantityAsc = new Comparator<Order>() {
public int compare(Order o1, Order o2) {
return o2.quantity - o1.quantity; // smaller order comes before bigger one
}
}
final Comparator<Order> productDesc = new Comparator<Order>() {
public int compare(Order o1, Order o2) {
if (o2.productName == null) {
return o1.productName == null ? 0 : -1;
}
return o2.productName.compareTo(o1.productName);
}
}
final List<Order> orders = ...; // populated by parsing the CSV
final List<Order> ordersByQuantity = Collections.sort(orders, quantityAsc);
final List<Order> ordersByProductZToA = Collections.sort(orders, productDesc);