I have this case where I am filtering list into multiple lists based on criteria.
for(SomeObj someObj : someObjs) {
if(StringUtils.equalsIgnoreCase(someObj.getIndicator(), "Y")) {
beansWithY.add(someObj);
} else if(StringUtils.equalsIgnoreCase(someObj.getIndicator(), "N")) {
beansWithN.add(someObj);
} else {
beansWithIndNotValid.add(someObj);
}
}
This looks simple enough, but, I am wondering if this is possible using Lambdaj.
I came across grouping and it can be used like the following, but, it doesn't seem to cover the default scenario.
Group<SomeObj> group = group(listOfSomeObjs, by(on(SomeObj.class).getIndicator()));
After this the result will be the following:
Y group
N group
null group
a group for each and every invalid indicator ( like A, B, C...)
I am wondering if this can be made to work like the for loop mentioned above, if it is Y - go to a list/group, N - go to another list/group, everything else to one group.
You can't achieve this using "group" function. The group method you added in the question is the right way to approach what you need but you won't be able to cover the default case.
If you want to use LambdaJ to do what you want you have to combine filter (or select). Here you have the code for filter:
List<SomeObj> yList = filter(having(on(SomeObj.class).getIndicator(), equalTo("Y")), listOfSomeObjs);
List<SomeObj> nList = filter(having(on(SomeObj.class).getIndicator(), equalTo("N")), listOfSomeObjs);
List<SomeObj> dList = filter(having(on(SomeObj.class).getIndicator(), not(equalTo("Y"))).and(having(on(SomeObj.class).getIndicator(), not(equalTo("N")))), listOfSomeObjs);
Map<String, List<SomeObj>> map = new HashMap<>();
map.put("Y", yList);
map.put("N", nList);
map.put("D", dList);
Hope to help.
Related
I am new to Java 8. I have a list of custom objects of type A, where A is like below:
class A {
int id;
String name;
}
I would like to determine if all the objects in that list have same name. I can do it by iterating over the list and capturing previous and current value of names. In that context, I found How to count number of custom objects in list which have same value for one of its attribute. But is there any better way to do the same in java 8 using stream?
You can map from A --> String , apply the distinct intermediate operation, utilise limit(2) to enable optimisation where possible and then check if count is less than or equal to 1 in which case all objects have the same name and if not then they do not all have the same name.
boolean result = myList.stream()
.map(A::getName)
.distinct()
.limit(2)
.count() <= 1;
With the example shown above, we leverage the limit(2) operation so that we stop as soon as we find two distinct object names.
One way is to get the name of the first list and call allMatch and check against that.
String firstName = yourListOfAs.get(0).name;
boolean allSameName = yourListOfAs.stream().allMatch(x -> x.name.equals(firstName));
another way is to calculate count of distinct names using
boolean result = myList.stream().map(A::getName).distinct().count() == 1;
of course you need to add getter for 'name' field
One more option by using Partitioning. Partitioning is a special kind of grouping, in which the resultant map contains at most two different groups – one for true and one for false.
by this, You can get number of matching and not matching
String firstName = yourListOfAs.get(0).name;
Map<Boolean, List<Employee>> partitioned = employees.stream().collect(partitioningBy(e -> e.name==firstName));
Java 9 using takeWhile takewhile will take all the values until the predicate returns false. this is similar to break statement in while loop
String firstName = yourListOfAs.get(0).name;
List<Employee> filterList = employees.stream()
.takeWhile(e->firstName.equals(e.name)).collect(Collectors.toList());
if(filterList.size()==list.size())
{
//all objects have same values
}
Or use groupingBy then check entrySet size.
boolean b = list.stream()
.collect(Collectors.groupingBy(A::getName,
Collectors.toList())).entrySet().size() == 1;
Ive been searching SO about this question and most only have the problem with two arrays comparing by have a nested loop. My problem is quite the same but on a bigger scale. Suppose I have a 100 or thousand user on my app, and each user has the list of item it wants.
Something like this
User1 = {apple,orange,guava,melon,durian}
User2 = {apple, melon,banana,lemon,mango}
User3 = {orange,carrots,guava,melon,tomato}
User4 = {mango,carrots,tomato,apple,durian}
.
.
Nuser = ...
I wanted to see how many apples or oranges was listed from all the users array. So I am basically comparing but on a bigger scale. The data isn't static as well, A user can input an unknown fruit from the developers knowledge but on the users knowledge they can put it there so there can be multiple users that can put this unknown fruit and yet the system can still figure out how many is this unknown item was listed. Keep in mind this is a dynamic one. User can reach for example a 100 users depending how popular an app would be. I can't afford to do nested loop here.
PS this is not the exact problem but it is the simplest scenario I can think of to explain my problem.
PS: just to clarify, I dont intend to use 3rd party lib as well like guava. I am having a problem on proguard with it.
Edit
Just read that Original poster cannot use Java 8, which is a pity, because this would realy make it very easy!
Java 7 solution
final Map<String, Integer> occurencesByFruit = new HashMap<>();
for (User user : users) {
String[] fruits = user.getFruits();
for (String fruit : fruits) {
final Integer currentCount = occurencesByFruit.get(fruit);
if (currentCount == null) {
occurencesByFruit.put(fruit, 1);
} else {
occurencesByFruit.put(fruit, currentCount + 1);
}
}
}
Java 8 solution
I'd stream the users, flatMap() to the actual fruit elements, and then use Collectors.groupingBy() with a downstream collector Collectors.counting().
This will give you a Map where the keys are the fruits, and the values are the occurrences of each fruit throughout all your users.
List<User> users = Arrays.asList(/* ... */);
final Map<String, Long> occurencesByFruit = users.stream()
.map(User::getFruits)
.flatMap(Arrays::stream)
.collect(Collectors.groupingBy(f -> f, Collectors.counting()));
Seems it is a good possibility to use HashMap<Item, Integer> fruits. You could iterate over all Users (you would need to store all Users in some kind of list, such as ArrayList<User> users) and check the list of items chosen by each User (I suppose User should have a field ArrayList<Item> items in its body to store items). You could achieve it with something like that:
for (User user : users) { // for each User from users list
for (Item item : user.items) { // check each item chosen by this user
if (fruits.containsKey(item) { // if the fruit is already present in the items HashMap increment the amount of items
int previousNumberOfItems = fruits.get(item);
fruits.put(item, ++previousNumberOfItems);
else { // otherwise put the first occurrency of this item
fruits.put(item, 1);
}
}
}
I would either create an ArrayList containing a HashMap with strings and ints or use two ArrayLists (one of type String and one of type Integer). Then you can iterate over every entry in each of the user arrays (this is only a simple nested loop). For every entry in the current user array you check if there is already the same entry in the ArrayList you created additionally. If so, you increment the respective int. If not, you add a string and an int. In the end, you have the number of occurrences of all the fruit strings in the added ArrayLists, which is, if I understood you correctly, just what you wanted.
Let's suppose I've an object that looks like this:
public class Supermarket {
public String supermarketId;
public String lastItemBoughtId;
// ...
}
and I have two lists of supermarkets, one "old", another "new" (i.e. one is local, the other is retrieved from the cloud).
List<Supermarket> local = getFromLocal();
List<Supermarket> cloud = getFromCloud();
I would like to find all the pairs of Supermarket objects (given supermarketId) that have lastItemBoughtId different from one another.
The first solution I have in mind is iterating the first List, then inside the first iteration iterating the second one, and each time that local.get(i).supermarketId.equals(cloud.get(j).supermarketId), checking if lastItemBoughtId of the i element is different from the id of the j element. If it's different, I add the whole Supermarket object on a new list.
To be clearer, something like this:
List<Supermarket> difference = new ArrayList<>();
for (Supermarket localSupermarket : local) {
for (Supermarket cloudSupermarket : cloud) {
if (localSupermarket.supermarketId.equals(cloudSupermarket.supermarketId) &&
!localSupermarket.lastItemBoughtId.equals(cloudSupermarket.lastItemBoughtId))
difference.add(cloudSupermarket);
}
}
Clearly this looks greatly inefficient. Is there a better way to handle such a situation?
One solution :
Construct a Map of the Local supermarkets using the supermarketId as the key by running through the list once
Loop through the cloud list and do you comparison, looking up the local supermarket from your map.
i.e. O(n) instead of O(n2)
Here's a two-line solution:
Map<String, Supermarket> map = getFromLocal().stream()
.collect(Collectors.toMap(s -> s.supermarketId, s -> s));
List<Supermarket> hasDiffLastItem = getFromCloud().stream()
.filter(s -> !map.get(s.supermarketId).lastItemBoughtId.equals(s.lastItemBoughtId))
.collect(Collectors.toList());
I would put one of the lists in a Map with as key the Supermarket ID and as value the supermarket instance then iterate over the other getting from the Map and comparing the lastItemBoughtId.
I have noticed that you can pass "params" straight in to the boilerplate code below:
[fooInstanceList: Foo.list(params), fooInstanceTotal: Foo.count()]
Is it possible to pass "params" in as part of a Hibernate criteria for example the one below?
def c = Foo.createCriteria()
def results = c {
not { eq("bar","test") }
}
[fooInstanceList: results, fooInstanceTotal: results.size()]
I am looking to use the "max" and "offset" params so I can use it for paging for example. I would also like to use the equivalent of count that counts all non-paged results. I think results.size() would only give me paged results, instead of the desired non-paged results. How would I go about this?
You can use params while using the criteria. I suppose you have a typo of not using c.list
def c = Foo.createCriteria()
def results = c.list(params) {
not { eq("bar","test") }
}
Assuming params has max and offset.
Criteria returns a PagedResultList where you can get the totalCount from it. So
results.totalCount //results.getTotalCount()
should give you the total count, although there is always a second query fired to get the total count. In this case Hibernate does that for you instead of you doing it explicitly.
I'd like some sorthand for this:
Map rowToMap(row) {
def rowMap = [:];
row.columns.each{ rowMap[it.name] = it.val }
return rowMap;
}
given the way the GDK stuff is, I'd expect to be able to do something like:
Map rowToMap(row) {
row.columns.collectMap{ [it.name,it.val] }
}
but I haven't seen anything in the docs... am I missing something? or am I just way too lazy?
I've recently came across the need to do exactly that: converting a list into a map. This question was posted before Groovy version 1.7.9 came out, so the method collectEntries didn't exist yet. It works exactly as the collectMap method that was proposed:
Map rowToMap(row) {
row.columns.collectEntries{[it.name, it.val]}
}
If for some reason you are stuck with an older Groovy version, the inject method can also be used (as proposed here). This is a slightly modified version that takes only one expression inside the closure (just for the sake of character saving!):
Map rowToMap(row) {
row.columns.inject([:]) {map, col -> map << [(col.name): col.val]}
}
The + operator can also be used instead of the <<.
Check out "inject". Real functional programming wonks call it "fold".
columns.inject([:]) { memo, entry ->
memo[entry.name] = entry.val
return memo
}
And, while you're at it, you probably want to define methods as Categories instead of right on the metaClass. That way, you can define it once for all Collections:
class PropertyMapCategory {
static Map mapProperty(Collection c, String keyParam, String valParam) {
return c.inject([:]) { memo, entry ->
memo[entry[keyParam]] = entry[valParam]
return memo
}
}
}
Example usage:
use(PropertyMapCategory) {
println columns.mapProperty('name', 'val')
}
Was the groupBy method not available when this question was asked?
If what you need is a simple key-value pair, then the method collectEntries should suffice. For example
def names = ['Foo', 'Bar']
def firstAlphabetVsName = names.collectEntries {[it.charAt(0), it]} // [F:Foo, B:Bar]
But if you want a structure similar to a Multimap, in which there are multiple values per key, then you'd want to use the groupBy method
def names = ['Foo', 'Bar', 'Fooey']
def firstAlphabetVsNames = names.groupBy { it.charAt(0) } // [F:[Foo, Fooey], B:[Bar]]
Also, if you're use google collections (http://code.google.com/p/google-collections/), you can do something like this:
map = Maps.uniqueIndex(list, Functions.identity());
ok... I've played with this a little more and I think this is a pretty cool method...
def collectMap = {Closure callback->
def map = [:]
delegate.each {
def r = callback.call(it)
map[r[0]] = r[1]
}
return map
}
ExpandoMetaClass.enableGlobally()
Collection.metaClass.collectMap = collectMap
Map.metaClass.collectMap = collectMap
now any subclass of Map or Collection have this method...
here I use it to reverse the key/value in a Map
[1:2, 3:4].collectMap{[it.value, it.key]} == [2:1, 4:3]
and here I use it to create a map from a list
[1,2].collectMap{[it,it]} == [1:1, 2:2]
now I just pop this into a class that gets called as my app is starting and this method is available throughout my code.
EDIT:
to add the method to all arrays...
Object[].metaClass.collectMap = collectMap
I can't find anything built in... but using the ExpandoMetaClass I can do this:
ArrayList.metaClass.collectMap = {Closure callback->
def map = [:]
delegate.each {
def r = callback.call(it)
map[r[0]] = r[1]
}
return map
}
this adds the collectMap method to all ArrayLists... I'm not sure why adding it to List or Collection didn't work.. I guess that's for another question... but now I can do this...
assert ["foo":"oof", "42":"24", "bar":"rab"] ==
["foo", "42", "bar"].collectMap { return [it, it.reverse()] }
from List to calculated Map with one closure... exactly what I was looking for.
Edit: the reason I couldn't add the method to the interfaces List and Collection was because I did not do this:
List.metaClass.enableGlobally()
after that method call, you can add methods to interfaces.. which in this case means my collectMap method will work on ranges like this:
(0..2).collectMap{[it, it*2]}
which yields the map: [0:0, 1:2, 2:4]
What about something like this?
// setup
class Pair {
String k;
String v;
public Pair(def k, def v) { this.k = k ; this.v = v; }
}
def list = [ new Pair('a', 'b'), new Pair('c', 'd') ]
// the idea
def map = [:]
list.each{ it -> map.putAt(it.k, it.v) }
// verify
println map['c']