Iterate an ArrayList to count by object attribute - java

I have an ArrayList of products already initialized. The Product constructor is:
public Product(String number, String type, double rentalDeposit, double rentalPrice, double lateFee, double buyPrice, int maxDuration)
The type is determined by an enumeration:
protected enum productType {Basket, BabySeat, Helmet, Headlight, Bell};
I pass in the type using the toString method for the enumeration. I need to iterate through the ArrayList<Product> (given by shop.getInventory()) that I have and count how many of each type there are, i.e. how many are of type Basket, BabySeat, Helmet, etc.
The Product class has a getType() method that returns a string.
for (Product.productType product : Product.productType.values()) {
int occurences = Collections.frequency(shop.getInventory(), product.toString());
}
I have tried using Collections.frequency, but it keeps returning 0 and I'm not sure why.
Is there another way to iterate through and find this amount without using a ton of if statements?

shop.getInventory() I'll assume has the type Collection<Product>. You can either define product such that .equals(Product) will check equality against the Product's internal type, or even more simply, shop.getInventory().stream().filter(item -> product.toString().equals(item.getType())).count(). (replace item.getType() with however you extract the type field from Product, like maybe item.type etc).

A simple method of counting items in a list that correspond to some condition is to use Collectors.groupingBy and Collectors.counting. Something like the following:
Map<ProductType,Long> counts = products.stream()
.collect(groupingBy(Product::getType, counting()));
If you're not familiar with streams, this statement can be read as 'turn the list into a stream of products, group the products by product type then count each of those groups creating a map from the type to the count.'

Related

Have to display the number of times an element has been added in my map JAVA

I've created a TreeMap with products.
And I want to count the number of times they repeat themselves, but have no clue what to code. Any hints? (I expect no code, just suggestions)
private static Map<Integer, String> shoppingCart() {
Map<Integer, String> result = new TreeMap<>();
result.put(1, "sausage");
result.put(2, "sausage");
result.put(3, "soup");
result.put(4, "egg");
result.put(5, "egg");
result.put(6, "tomato");
result.put(7, "sausage");
return result;
}
I was thinking about adding a counting variable, but still it doesn't fix the repeating problem.
Maybe not the best approach, but without modifying what you already have, you could use another map to store the products as keys and the quantity as the value for those keys:
Map<Integer, String> result = shoppingCart();
Map<String, Integer> productQuantities = new HashMap<>();
result.values().forEach(value ->
productQuantities.put(value,productQuantities.getOrDefault(value, 0) + 1));
To print the resulting map:
productQuantities.forEach((key, value) -> System.out.println(key + ":" + value));
I created a TreeMap with products, and i want to count the number of times they repeat themselves
Probably a different type of Map with keys representing items and values representing the corresponding count would be more handy. Something like:
NavigableMap<String, Integer> countByItem
Note: in order to access methods of the TreeMap like ceilingKey(), floorKey(), higherEntry(), lowerEntry(), etc. which are not defined in the Map interface you need to use NavigableMap as a type.
And it might make sense to make the item to be a custom object, instead of being a String. That would guard you from making typo, and it provides a possibility to gives useful behavior to Item
public class Item {
private int id;
private String name;
// constructor, getters, equals/hashCode, ect.
}
That's how map of items can be updated using Java 8 method merge(), which expects a key, a value and a function responsible for merging the old value and the new one:
NavigableMap<Item, Integer> countByItem = new TreeMap<>(Comparator.comparingInt(Item::getId));
countByItem.merge(new Item(1, "sausage"),1, Integer::sum);
countByItem.merge(new Item(1, "sausage"),1, Integer::sum);
countByItem.merge(new Item(2, "soup"),1, Integer::sum);
If you don't feel very comfortable with Java 8 functions, instead of merge() you can use combination of methods put() & getOrDefault():
Item sausage = new Item(1, "sausage");
countByItem.put(sausage, countByItem.getOrDefault(sausage, 0) + 1);
I can only guess at your goal. In your Map <Integer, String>, what does the Integer represent? Product number? Quantity? Sequence number? Something else?
If the Integer represents quantity, you have it backwards. It should be Map <String, Integer>. In a Map<X,Y>, the X represents the key. A Map allows fast lookup by the key. The Y is the value -- the information you want to find for a particular key, if the key is in the Map.
For example, if you want to add "sausage", you want to check if it is in the Map. If it isn't, put it into the Map with quantity 1. If it is, retrieve it and update the quantity.
If the Integer represents a sequence number (1st item, 2nd item, 3rd item, ...), you don't need a Map. Consider using an array or a data structure that preserves order, such as a List.
However, using an array or List still leaves you with the problem of how find how many of each item are in the list, when duplicates are allowed, as they are in your problem. To solve that, consider a Map<String, Integer> where the Integer (map value) is the quantity, and the String (map key) is the product name.
If I were doing this, I'd create classes to allow me to glue together related information. Here is part of a hypothetical example, which might be more realistic than you need:
public class Product {
private int upc; // product code, often represented with bar code
private Decimal price;
private String description;
private String shortDescription;
private ProductClass prodClass; // department, taxable, etc.
// etc. -- add needed fields, or remove irrelevant
// constructors, getters, setters,
Override .equals and .hashcode in Product. You use the UPC for those.
If you use implements Comparable<Product>, you have the possibility of using binary search, or a search tree.
public class Receipt {
private Decimal total;
private Decimal taxableTotal;
private Map <Product,Integer> shoppingCart; // Product, Quantity
// etc.
When each item is scanned, you can lookup the Product in the Map, and add it if not found, or update the quantity if found, as in the previous answers.

java: Collect and combine data in a list

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());

Manipulating data in java List<Object> in a structured way

I have the following class :
class Students{
int age;
int dept;
}
Lets say i have a List<Students> and I want to manipulate the list by doing simple calculations like : calculate the mean, calculate the middle value (e.g. (age+debt)/2), find the closest value to the mean and so on. How can I do this in a structured way?. I want to be in a position where I can use different combinations on the list. e.g. calculate mean of age // calculate mean of the middle value from age/debt, find the closest value of the age etc.
How should i approach this?. Would appreciate it if someone could point me in the right direction.
Apache Math has a nice Descriptive Statistics package that does this sought of thing.
http://commons.apache.org/proper/commons-math/userguide/stat.html#a1.2_Descriptive_statistics
If you're using Java 8 this works well with Lambdas:
DescriptiveStatistics stats = new DescriptiveStatistics();
students.forEach(s -> stats.add(s.age));
double mean = stats.getMean();
And to filter etc:
//Only students with an age > 18
students.stream.filter(s -> s.age > 18).forEach(s -> stats.add(s.age));
If you're not using Java 8 then simply foreach it.
You can create a separate class (StudentCalculator) that will require a List of Students (perhaps pass the List in the constructor) and have the instance methods perform calculations on the List.
Or you can create a utility (e.g. StudentCalculatorUtility) where you would define a series of methods that would accept a List of Students as a parameter, that would perform all the calculations you would need on the students(middle value,closest to mean, etc.)
There is a concept where you step through a list and perform an operation on each item in turn which may or may not change the item.
In this case, you want a method that takes an item from the list does some stuff and returns a running total.
int sumItems(Student stu, int sum){
return (stu.age + stu.debt)/2;
}
To use this method, either use either a forEach or an iterator.
Iterator itr = Students.iterator(); // assuming List<Student> Students = new List<Student>()
int sum = 0;
while(itr.hasnext()){
sum = sumItems(itr.next(), sum)
}
Now do something with your sum.

Parsing an object attribute type to a different type?

I have 2 collections: product and inventory. I am trying to enter new inventory for a store.
when I'm filling out inventory entry form I want the product number I enter to search the product list to resolve the correct name of the item.
I have a search method in my product collection but it takes an integer type. The product number is of type long.
How can I convert the product number to an integer so the search will function correctly?
//resolve product name to inventory item
int idx = prodL.search(Integer.parseInt(prodNo));
name = prodL.getProdName(idx);
Error: The method parseInt(java.lang.String) in the type java.lang.Integer is not applicable for the arguments (long)
Try this:
int idx = prodL.search((int)prodNo);
Please note this will truncate, so if prodno exceed Integer.MAX_VALUE, you will get some odd results.
You could wrap it in an Integer type and use Integer.longValue()

search object within java collection

first, I don't know if I'm organizing my data efficiently, the idea is that I have pairs of key/value.
public static class Freq implements Comparable {
String term;
double frequency;
public Freq( String term, double frequency ) {
this.term = term;
this.frequency = frequency;
}
public int compareTo(Object o) {
if(this.frequency == ((Freq) o).frequency)
return 0;
else if(this.frequency > ((Freq) o).frequency)
return 1;
else
return -1;
}
Now, I'm storing such objects within a collection: List<Freq> bla = new ArrayList<Freq>() as well as sorting it.
I'm interested to search for specific objects e.g. Freq.name = 'Bar' from the collection, which is sorted. How would I do that? Or I have to iterate the whole collection.
or is there other more efficient ways to do this?
You should use an associated collection such as a TreeMap, which keeps its elements sorted automatically. If you want to search sometimes based on name and sometimes on frequency, you can keep your elements in two maps at the same time, and use the suitable one for lookup.
Or if for some reason you want to stick with a sorted List, you can use Collections.binarySearch() to find elements in it.
You can use JFilter http://code.google.com/p/jfilter/
JFilter is a simple and high performance open source library to query collection of Java beans.
Key features
Support of collection (java.util.Collection, java.util.Map and Array) properties.
Support of collection inside collection of any depth.
Support of inner queries.
Support of parameterized queries.
Can filter 1 million records in few 100 ms.
Filter ( query) is given in simple json format, it is like Mangodb queries. Following are some examples.
{ "id":{"$le":"10"}
where object id property is less than equals to 10.
{ "id": {"$in":["0", "100"]}}
where object id property is 0 or 100.
{"lineItems":{"lineAmount":"1"}}
where lineItems collection property of parameterized type has lineAmount equals to 1.
{ "$and":[{"id": "0"}, {"billingAddress":{"city":"DEL"}}]}
where id property is 0 and billingAddress.city property is DEL.
{"lineItems":{"taxes":{ "key":{"code":"GST"}, "value":{"$gt": "1.01"}}}}
where lineItems collection property of parameterized type which has taxes map type property of parameteriszed type has code equals to GST value greater than 1.01.
{'$or':[{'code':'10'},{'skus': {'$and':[{'price':{'$in':['20', '40']}}, {'code':'RedApple'}]}}]}
Select all products where product code is 10 or sku price in 20 and 40 and sku code is "RedApple".

Categories