Related
I have a class with 3 properties:
private static class OrderListItem {
public int price;
public int size;
public Type type;
}
Items of this class are added in a list. List<OrderListItem> orders_book = new ArrayList<>();
I want to find an object in a list with a specified price. But size and type do not matter. Can I do something like this:
int index = orders_book.indexOf(new OrderListItem(price, **any size**, **any type**)); ?
Or I just must do a "loop" search?
I'm offering this answer as a possibility although it may exceed your actual requirements. Using some pre-defined predicates it allows any number of matching, consecutive items to be returned in a List. An empty list will be returned if no match is found.
But first, if you want the index (and maybe the value of the object) you can do it like this with no modification of your class.
this uses an IntStream to stream all possible indices.
it maps the index and the object at that index into a Map.entry
then it filters using the value of the entry against the desired attribute.
and returns the first one encountered or an Entry with -1 and null. (AbstractMap.SimpleEntry was used here to allow the null value)
int desiredPrice = 3;
Entry<Integer, OrderListItem> result = IntStream
.range(0, orderList.size())
.mapToObj(i -> Map.entry(i, orderList.get(i)))
.filter(e -> e.getValue().getPrice() == desiredPrice)
.findFirst().orElse(new AbstractMap.SimpleEntry<>(-1,null));
To facilitate the demonstration the following record is used in lieu of a class definition. A class can be directly substituted for this. A Type enum is also provided.
enum Type {
FICTION, TECHNICAL, BIOGRAPHY
}
private record OrderListItem(int getPrice, int getSize,
Type getType) {
#Override
public String toString() {
return String.format("[%s, %s, %s]", getPrice, getSize,
getType);
}
}
Now define some predicates. Each takes an orderListItem object and a Type. The comparison method (equals or == ) is dependent on the attribute type. This predicate and the desired attribute value will be supplied to a method as shown later. Even though you are only interested in price, the others were easy enough to add.
static BiPredicate<OrderListItem, Integer> CHECK_PRICE =
(order, val) -> order.getPrice() == val;
static BiPredicate<OrderListItem, Type> CHECK_TYPE =
(order, val) -> order.getType().equals(val);
static BiPredicate<OrderListItem, Integer> CHECK_SIZE =
(order, val) -> order.getSize() == val;
Some data
List<OrderListItem> orderList =
ArrayList<>(List.of(new OrderListItem(3, 20, Type.FICTION),
new OrderListItem(4, 20, Type.TECHNICAL),
new OrderListItem(5, 20, Type.TECHNICAL),
new OrderListItem(3, 30, Type.FICTION),
new OrderListItem(4, 30, Type.FICTION),
new OrderListItem(5, 30, Type.TECHNICAL),
new OrderListItem(3, 40, Type.FICTION),
new OrderListItem(4, 40, Type.BIOGRAPHY),
new OrderListItem(5, 40, Type.FICTION),
new OrderListItem(3, 50, Type.FICTION),
new OrderListItem(4, 50, Type.BIOGRAPHY),
new OrderListItem(5, 50, Type.BIOGRAPHY)));
Demo
List<OrderListItem> matches =
retrieve(orderList, CHECK_SIZE, 20, 2);
matches.forEach(System.out::println);
prints
[3, 20, FICTION]
[4, 20, TECHNICAL]
The method takes the following arguments.
the order list
the predicate for comparison
the attribute value to compare to
the desired return count of successful matches (a negative value will result in all matches being returned).
The method works by simple iteration over the orders, applying the predicate to the order and attribute. If a match is found, it is added to the return list and the count is decremented. When the count reaches 0 or the entire list is checked, the list will be returned.
public static <T> List<OrderListItem> retrieve(
List<OrderListItem> orders,
BiPredicate<OrderListItem, T> pred, T attribute,
int count) {
List<OrderListItem> matches = new ArrayList<>();
for (OrderListItem order : orders) {
if (pred.test(order, attribute)) {
matches.add(order);
if (--count == 0) {
break;
}
}
}
return matches;
}
Performance
Not the best test as compared to JMH but a rough idea.
First I added the data list to itself 20 times to create a large list.
The I repeated the above lookup for all records sans the printout.
for (int i = 0; i < 20; i++) {
orderList.addAll(orderList);
}
System.out.printf("%,d total objects%n",orderList.size());
long time = System.nanoTime();
List<OrderListItem> matches =
retrieve(orderList, CHECK_SIZE, 20, -1);
System.out.println("Execution time ~ "+(System.nanoTime()-time)/1e9 + " sec");
prints
12,582,912 total objects
Execution time ~ 0.632698253 sec
With no matches found for the entire list, the result was
Execution time ~ 0.033943752 sec.
For matching the entire list the result was
Execution time ~ 0.939900075 sec.
The difference in time is mostly due (imo) to the backing array having to be updated as the capacity is increased. This requires several recopyings of the array. By setting the initial capacity of the returned list to orders.size(), the result for matching and returning all elements is improved.
For matching the entire list with adjusted capacity the result was Execution time ~ 0.19100866 sec
I'm running Windows on an Intel 4 core, I7 processor.
I have no idea how any of the above relates to your requirements but its there for your edification.
If you need to retrieve an instance with a desired price, then you could use a collection stream with the filter and findAny operations.
int myPrice = /* any value you want */
//This will retrieve an instance with the disired price or null if none is found
OrderListItem item = orders_book.stream()
.filter(i -> i.getPrice() == myPrice)
.findFirst()
.orElse(null);
Alternatively, if you want to retrieve the index of the object with the desired price, then you have two options:
Redefine the equals method of your OrderListItem class, to equal two OrderListItems by their price, since the documentation of the indexOf method retrieves an element by its equals method. However, I wouldn't lean towards this solution, as in my opinion the sole price is not enough to equal two order items, but you definitely know better than me the kind of problem you're modelling. So, I'll leave the choice to you. Furthermore, if you redefine the equals method you should also redefine the hashCode method as the general hashcode contract states.
https://docs.oracle.com/javase/8/docs/api/java/util/List.html#indexOf-java.lang.Object-
Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element. More formally, returns the lowest index i such that (o==null ? get(i)==null : o.equals(get(i))), or -1 if there is no such index.
class OrderListItem {
public int price;
public int size;
public Type type;
/* ... your implementation ...*/
#Override
public int hashCode() {
return Objects.hash(price);
}
#Override
public boolean equals(Object obj) {
if (obj == null) return false;
if (this == obj) return true;
if (getClass() != obj.getClass()) return false;
OrderListItem other = (OrderListItem) obj;
return Objects.equals(price, other.price);
}
}
Not redefining the equals method, but retrieving the exact instance with a stream (like in the first snippet) and then using that same instance as a parameter for the indexOf invocation.
int myPrice = /* any value you want */
//This will retrieve an instance with the disired price or null if none is found
OrderListItem item = orders_book.stream()
.filter(i -> i.getPrice() == myPrice)
.findFirst()
.orElse(null);
//Retrieving the index
int index = orders_book.indexOf(item);
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;
Select sum(paidAmount), count(paidAmount), classificationName,
From tableA
Group by classificationName;
How can i do this in Java 8 using streams and collectors?
Java8:
lineItemList.stream()
.collect(Collectors.groupingBy(Bucket::getBucketName,
Collectors.reducing(BigDecimal.ZERO,
Bucket::getPaidAmount,
BigDecimal::add)))
This gives me sum and group by. But how can I also get count on the group name ?
Expectation is :
100, 2, classname1
50, 1, classname2
150, 3, classname3
Using an extended version of the Statistics class of this answer,
class Statistics {
int count;
BigDecimal sum;
Statistics(Bucket bucket) {
count = 1;
sum = bucket.getPaidAmount();
}
Statistics() {
count = 0;
sum = BigDecimal.ZERO;
}
void add(Bucket b) {
count++;
sum = sum.add(b.getPaidAmount());
}
Statistics merge(Statistics another) {
count += another.count;
sum = sum.add(another.sum);
return this;
}
}
you can use it in a Stream operation like
Map<String, Statistics> map = lineItemList.stream()
.collect(Collectors.groupingBy(Bucket::getBucketName,
Collector.of(Statistics::new, Statistics::add, Statistics::merge)));
this may have a small performance advantage, as it only creates one Statistics instance per group for a sequential evaluation. It even supports parallel evaluation, but you’d need a very large list with sufficiently large groups to get a benefit from parallel evaluation.
For a sequential evaluation, the operation is equivalent to
lineItemList.forEach(b ->
map.computeIfAbsent(b.getBucketName(), x -> new Statistics()).add(b));
whereas merging partial results after a parallel evaluation works closer to the example already given in the linked answer, i.e.
secondMap.forEach((key, value) -> firstMap.merge(key, value, Statistics::merge));
As you're using BigDecimal for the amounts (which is the correct approach, IMO), you can't make use of Collectors.summarizingDouble, which summarizes count, sum, average, min and max in one pass.
Alexis C. has already shown in his answer one way to do it with streams. Another way would be to write your own collector, as shown in Holger's answer.
Here I'll show another way. First let's create a container class with a helper method. Then, instead of using streams, I'll use common Map operations.
class Statistics {
int count;
BigDecimal sum;
Statistics(Bucket bucket) {
count = 1;
sum = bucket.getPaidAmount();
}
Statistics merge(Statistics another) {
count += another.count;
sum = sum.add(another.sum);
return this;
}
}
Now, you can make the grouping as follows:
Map<String, Statistics> result = new HashMap<>();
lineItemList.forEach(b ->
result.merge(b.getBucketName(), new Statistics(b), Statistics::merge));
This works by using the Map.merge method, whose docs say:
If the specified key is not already associated with a value or is associated with null, associates it with the given non-null value. Otherwise, replaces the associated value with the results of the given remapping function
You could reduce pairs where the keys would hold the sum and the values would hold the count:
Map<String, SimpleEntry<BigDecimal, Long>> map =
lineItemList.stream()
.collect(groupingBy(Bucket::getBucketName,
reducing(new SimpleEntry<>(BigDecimal.ZERO, 0L),
b -> new SimpleEntry<>(b.getPaidAmount(), 1L),
(v1, v2) -> new SimpleEntry<>(v1.getKey().add(v2.getKey()), v1.getValue() + v2.getValue()))));
although Collectors.toMap looks cleaner:
Map<String, SimpleEntry<BigDecimal, Long>> map =
lineItemList.stream()
.collect(toMap(Bucket::getBucketName,
b -> new SimpleEntry<>(b.getPaidAmount(), 1L),
(v1, v2) -> new SimpleEntry<>(v1.getKey().add(v2.getKey()), v1.getValue() + v2.getValue())));
I want to use Java 8 Lambda expression in following scenario but I am getting Local variable fooCount defined in an enclosing scope must be final or effectively final. I understand what the error message says, but I need to calculate percentage here so need to increment fooCount and barCount then calculate percentage. So what's the way to achieve it:
// key is a String with values like "FOO;SomethinElse" and value is Long
final Map<String, Long> map = null;
....
private int calculateFooPercentage() {
long fooCount = 0L;
long barCount = 0L;
map.forEach((k, v) -> {
if (k.contains("FOO")) {
fooCount++;
} else {
barCount++;
}
});
final int fooPercentage = 0;
//Rest of the logic to calculate percentage
....
return fooPercentage;
}
One option I have is to use AtomicLong here instead of long but I would like to avoid it, so later if possible I want to use parallel stream here.
There is a count method in stream to do counts for you.
long fooCount = map.keySet().stream().filter(k -> k.contains("FOO")).count();
long barCount = map.size() - fooCount;
If you want parallelisation, change .stream() to .parallelStream().
Alternatively, if you were trying to increment a variable manually, and use stream parallelisation, then you would want to use something like AtomicLong for thread safety. A simple variable, even if the compiler allowed it, would not be thread-safe.
To get both numbers, matching and non-matching elements, you can use
Map<Boolean, Long> result = map.keySet().stream()
.collect(Collectors.partitioningBy(k -> k.contains("FOO"), Collectors.counting()));
long fooCount = result.get(true);
long barCount = result.get(false);
But since your source is a Map, which knows its total size, and want to calculate a percentage, for which barCount is not needed, this specific task can be solved as
private int calculateFooPercentage() {
return (int)(map.keySet().stream().filter(k -> k.contains("FOO")).count()
*100/map.size());
}
Both variants are thread safe, i.e. changing stream() to parallelStream() will perform the operation in parallel, however, it’s unlikely that this operation will benefit from parallel processing. You would need humongous key strings or maps to get a benefit…
I agree with the other answers indicating you should use countor partitioningBy.
Just to explain the atomicity problem with an example, consider the following code:
private static AtomicInteger i1 = new AtomicInteger(0);
private static int i2 = 0;
public static void main(String[] args) {
IntStream.range(0, 100000).parallel().forEach(n -> i1.incrementAndGet());
System.out.println(i1);
IntStream.range(0, 100000).parallel().forEach(n -> i2++);
System.out.println(i2);
}
This returns the expected result of 100000 for i1 but an indeterminate number less than that (between 50000 and 80000 in my test runs) for i2. The reason should be pretty obvious.
I have a class Something which contains an instance variable Anything.
class Anything {
private final int id;
private final int noThings;
public Anything(int id, int noThings) {
this.id = id;
this.noThings = noThings;
}
}
class Something {
private final int parentId;
private final List<Anything> anythings;
private int getParentId() {
return parentId;
}
private List<Anything> getAnythings() {
return anythings;
}
public Something(int parentId, List<Anything> anythings) {
this.parentId = parentId;
this.anythings = anythings;
}
}
Given a list of Somethings
List<Something> mySomethings = Arrays.asList(
new Something(123, Arrays.asList(new Anything(45, 65),
new Anything(568, 15),
new Anything(145, 27))),
new Something(547, Arrays.asList(new Anything(12, 123),
new Anything(678, 76),
new Anything(98, 81))),
new Something(685, Arrays.asList(new Anything(23, 57),
new Anything(324, 67),
new Anything(457, 87))));
I want to sort them such that the Something objects are sorted depending on the total descending sum of the (Anything object) noThings, and then by the descending value of the (Anything object) noThings
123 = 65+15+27 = 107(3rd)
547 = 123+76+81 = 280 (1st)
685 = 57+67+87 = 211 (2nd)
So that I end up with
List<Something> orderedSomethings = Arrays.asList(
new Something(547, Arrays.asList(new Anything(12, 123),
new Anything(98, 81),
new Anything(678, 76))),
new Something(685, Arrays.asList(new Anything(457, 87),
new Anything(324, 67),
new Anything(23, 57))),
new Something(123, Arrays.asList(new Anything(45, 65),
new Anything(145, 27),
new Anything(568, 15))));
I know that I can get the list of Anythings per parent Id
Map<Integer, List<Anythings>> anythings
= mySomethings.stream()
.collect(Collectors.toMap(p->p.getParentId(),
p->p.getAnythings()))
;
But after that I'm a bit stuck.
Unless I'm mistaken, you can not do both sorts in one go. But since they are independent of each other (the sum of the nothings in the Anythings in a Something is independent of their order), this does not matter much. Just sort one after the other.
To sort the Anytings inside the Somethings by their noThings:
mySomethings.stream().map(Something::getAnythings)
.forEach(as -> as.sort(Comparator.comparing(Anything::getNoThings)
.reversed()));
To sort the Somethings by the sum of the noThings of their Anythings:
mySomethings.sort(Comparator.comparing((Something s) -> s.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum())
.reversed());
Note that both those sorts will modify the respective lists in-place.
As pointed out by #Tagir, the second sort will calculate the sum of the Anythings again for each pair of Somethings that are compared in the sort. If the lists are long, this can be very wasteful. Instead, you could first calculate the sums in a map and then just look up the value.
Map<Something, Integer> sumsOfThings = mySomethings.stream()
.collect(Collectors.toMap(s -> s, s -> s.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum()));
mySomethings.sort(Comparator.comparing(sumsOfThings::get).reversed());
The problem of other solutions is that sums are not stored anywhere during sorting, thus when sorting large input, sums will be calculated for every row several times reducing the performance. An alternative solution is to create intermediate pairs of (something, sum), sort by sum, then extract something and forget about sum. Here's how it can be done with Stream API and SimpleImmutableEntry as pair class:
List<Something> orderedSomethings = mySomethings.stream()
.map(smth -> new AbstractMap.SimpleImmutableEntry<>(smth, smth
.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum()))
.sorted(Entry.<Something, Integer>comparingByValue().reversed())
.map(Entry::getKey)
.collect(Collectors.toList());
There's some syntactic sugar available in my free StreamEx library which makes the code a little bit cleaner:
List<Something> orderedSomethings = StreamEx.of(mySomethings)
.mapToEntry(smth -> smth
.getAnythings().stream()
.mapToInt(Anything::getNoThings).sum())
.reverseSorted(Entry.comparingByValue())
.keys().toList();
As for sorting the Anything inside something: other solutions are ok.
In the end I added an extra method to the Something class.
public int getTotalNoThings() {
return anythings.stream().collect(Collectors.summingInt(Anything::getNoThings));
}
then I used this method to sort by total noThings (desc)
somethings = somethings.stream()
.sorted(Comparator.comparing(Something::getTotalNoThings).reversed())
.collect(Collectors.toList());
and then I used the code suggested above (thanks!) to sort by the Anything instance noThings
somethings .stream().map(Something::getAnythings)
.forEach(as -> as.sort(Comparator.comparing(Anything::getNoThings).reversed()));
Thanks again for help.