I have a List of object and the list is very big. The object is
class Sample {
String value1;
String value2;
String value3;
String value4;
String value5;
}
Now I have to search for a specific value of an object in the list. Say if value3=='three' I have to return those objects (My search is not always based on value3)
The list is
List<Sample> list = new ArrayList<Sample>();
What is the efficient way of doing it?
Thanks.
You can give a try to Apache Commons Collections.
There is a class CollectionUtils that allows you to select or filter items by custom Predicate.
Your code would be like this:
Predicate condition = new Predicate() {
boolean evaluate(Object sample) {
return ((Sample)sample).value3.equals("three");
}
};
List result = CollectionUtils.select( list, condition );
Update:
In java8, using Lambdas and StreamAPI this should be:
List<Sample> result = list.stream()
.filter(item -> item.value3.equals("three"))
.collect(Collectors.toList());
much nicer!
Using Java 8
With Java 8 you can simply convert your list to a stream allowing you to write:
import java.util.List;
import java.util.stream.Collectors;
List<Sample> list = new ArrayList<Sample>();
List<Sample> result = list.stream()
.filter(a -> Objects.equals(a.value3, "three"))
.collect(Collectors.toList());
Note that
a -> Objects.equals(a.value3, "three") is a lambda expression
result is a List with a Sample type
It's very fast, no cast at every iteration
If your filter logic gets heavier, you can do list.parallelStream() instead of list.stream() (read this)
Apache Commons
If you can't use Java 8, you can use Apache Commons library and write:
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;
Collection result = CollectionUtils.select(list, new Predicate() {
public boolean evaluate(Object a) {
return Objects.equals(((Sample) a).value3, "three");
}
});
// If you need the results as a typed array:
Sample[] resultTyped = (Sample[]) result.toArray(new Sample[result.size()]);
Note that:
There is a cast from Object to Sample at each iteration
If you need your results to be typed as Sample[], you need extra code (as shown in my sample)
Bonus: A nice blog article talking about how to find element in list.
If you always search based on value3, you could store the objects in a Map:
Map<String, List<Sample>> map = new HashMap <>();
You can then populate the map with key = value3 and value = list of Sample objects with that same value3 property.
You can then query the map:
List<Sample> allSamplesWhereValue3IsDog = map.get("Dog");
Note: if no 2 Sample instances can have the same value3, you can simply use a Map<String, Sample>.
I modifie this list and add a List to the samples try this
Pseudocode
Sample {
List<String> values;
List<String> getList() {
return values}
}
for(Sample s : list) {
if(s.getString.getList.contains("three") {
return s;
}
}
As your list is an ArrayList, it can be assumed that it is unsorted. Therefore, there is no way to search for your element that is faster than O(n).
If you can, you should think about changing your list into a Set (with HashSet as implementation) with a specific Comparator for your sample class.
Another possibility would be to use a HashMap. You can add your data as Sample (please start class names with an uppercase letter) and use the string you want to search for as key. Then you could simply use
Sample samp = myMap.get(myKey);
If there can be multiple samples per key, use Map<String, List<Sample>>, otherwise use Map<String, Sample>. If you use multiple keys, you will have to create multiple maps that hold the same dataset. As they all point to the same objects, space shouldn't be that much of a problem.
You can filter the list:
list.stream().filter(
sample -> sample.getValue4().equals("4")
).forEach(System.out::println)
I propose for+if.
Object result;
for (Object o: objects){
if (o.value3.equals("three")){
result=o;
break;
}
}
no streams, no guavas, I think it's simple.
Related
I need an immutable list where I can get derive a second immutable list preserving all elements of the previous list plus an additional element in Java (without additional libraries).
Note: This question is similar to What is an efficient and elegant way to add a single element to an immutable set? but I need a list and don't have Guava.
What I have tried so far:
var list = List.of(someArrayOfInitialElements);
var newList = Stream.concat(list.stream(), Stream.of(elementToAppend))
.collect(CollectorsCollectors.toUnmodifiableList());
That would work but creating a stream and copying elements one by one seems inefficient to me. You could basically bulk copy memory given that List.of() stores data in a field-based or array-based data structure.
Is there a more efficient solution than using streams? A better data structure in the Java standard library that I am missing?
I would create a new ArrayList append the element and then return that as an unmodifiable list. Something like,
private static <T> List<T> appendOne(List<T> al, T t) {
List<T> bl = new ArrayList<>(al);
bl.add(t);
return Collections.unmodifiableList(bl);
}
And to test it
public static void main(String[] args) {
List<String> al = appendOne(new ArrayList<>(), "1");
List<String> bl = appendOne(al, "2");
System.out.println(bl);
}
I get (unsurprisingly):
[1, 2]
See this code run at IdeOne.com.
The Answer by Frisch is correct, and should be accepted. One further noteā¦
Calling Collections.unmodifiableList produces a collection that is a view onto the original mutable list. So a modification to the original list will "bleed through" to the not-so-immutable second list.
This issue does not apply to the correct code shown in that Answer, because the new ArrayList object deliberately goes out-of-scope. Therefore that new list cannot be accessed for modification. But in other coding scenarios, this issue could be a concern.
List.copyOf
If you want an independent and truly immutable second list, use List.copyOf in Java 10+. This returns an unmodifiable list.
return List.copyOf( bl ) ;
Both answers are great, I would create a bit more generic solution:
private static <T> List<T> append(final List<T> al, final T... ts) {
final List<T> bl = new ArrayList<>(al);
for (final T t : ts) {
bl.add(t);
}
return List.copyOf(bl);
}
It can be used exactly like previous answer:
List<String> al = append(new ArrayList<>(), "1");
List<String> bl = append(al, "2");
System.out.println(bl);
But also slightly more efficient:
List<String> bl = append(new ArrayList<>(), "1", "2");
System.out.println(bl);
I have a List of Integers but I would like to take that List and convert it to a HashSet.
For example my list is as follows:
1234
5678
1234
7627
4328
But I would like to take that list and convert the string of integers to a HashSet so it doesn't include repeats. What is the best way to accomplish this?
My list is defined as
static List<Integer> list;
And my HashSet is defined as
static HashSet<String> set = new HashSet<String>(list);
My error is that I can't convert from int to string so what can I do to solve this?
One way is to use streams:
Set<String> set = list.stream()
.map(Object::toString)
.collect(Collectors.toSet());
First, you stream the list. Then, each element is converted to string and finally all elements are collected to a set. By default, Collectors.toSet() creates a HashSet, though this is not guaranteed by the specification.
If you want a guaranteed HashSet, you could use Collectors.toCollection(HashSet::new):
Set<String> set = list.stream()
.map(Object::toString)
.collect(Collectors.toCollection(HashSet::new));
using Java 8 streams:
set = list.stream().map(e -> e.toString()).collect(Collectors.toCollection(HashSet::new));
DEMO
or without using streams
for(Integer i : list) set.add(Integer.toString(i));
Assuming you are using an ArrayList as the instantiated form of List<>:
for (Integer value : list) {
set.add(value.toString());
}
This will iterate through your List and take each integer, convert it to a String, and add that value to your HashSet.
I have a list of objects that contains two string properties.
public class A {
public String a;
public String b;
}
I want to retrieve two Sets one containing property a and one b.
The naive approach is something long these lines:
List<A> list = ....
Set<String> listofa = new HashSet<>();
Set<String> listofb = new HashSet<>();
for (A item : list) {
if (item.a != null)
listofa.add(item.a);
if (item.b != null)
listofb.add(item.b);
}
Trying to do in a functional way in guava I ended up with this approach:
Function<String,A> getAFromList = new Function<>() {
#Nullable
#Override
public String apply(#Nullable A input) {
return input.a;
}
};
Function<String,A> getBFromList = Function<>() {
#Nullable
#Override
public String apply(#Nullable A input) {
return input.b;
}
};
FluentIterable<A> iterables = FluentIterable.from(list);
Set<String> listofAs = ImmutableSet.copyOf(iterables.transform(getAFromList).filter(Predicates.notNull()));
Set<String> listofBs = ImmutableSet.copyOf(iterables.transform(getBFromList).filter(Predicates.notNull()));
However this way I would iterate twice over the list.
Is there any way how to avoid iterating twice or multiple times ?
In general how does one solve these uses cases in a functional way in general (not only in guava/java) ?
Firstly you're after an optimisation - but if performance is key, use regular java methods over guava (i.e. your first method). See here.
I think because you want two results, at some point you will need to iterate twice (unless you pass in one of the sets to be populated but that that is definitely not a fp approach as it would not be a pure function).
However if iteration was expensive enough to need an optimisation you would iterate that once to an intermediate structure:
a_b_pairs = transformToJustAB(input) //single expensive iteration
list_of_a = transformA(a_b_pairs) //multiple cheaper iterations
list_of_b = transformB(a_b_pairs)
So the simple answer is that you have to iterate twice. Think about it. If you have N elements in your List you will need to do N inserts into the first Set and N inserts into the second Set. Functional or otherwise, you will have to iterate N twice whether it be on conversion (extraction) or insert.
If you were going for two Lists it would be different because you could create views and only iterate as needed.
This can be solved in one iteration with Multimaps.index:
Function<A, String> filterAB = new Function<A, String>() {
#Override
public String apply(A input) {
if (input.a != null) {
return "a";
}
if (input.b != null) {
return "b";
}
return "empty";
}
};
ImmutableListMultimap<String, A> partitionedMap = Multimaps.index(list, filterAB);
The output will be a Guava Multimap with three separate entries for:
an immutable list with all "a-not-null" objects under key "a".
an immutable list with all "b-not-null" objects under key "b".
and possibly an immutable list with objects where both a and b is null under key "empty".
What you're trying to achieve is partitioning or splitting of the collection using predicates.
With Guava, you can use Multimap.index. See related question and answer here.
In java suppose I have 2 lists
List<Object1> list1
List<Object2> list2
object1.getName(); returns a String
object2.getName(); return a String
is there any way to compare the names and get a difference of the two list
those 2 objects are defined in the 3rd party library, and I can't override the equals and compareto methods
I am in favour of googles Guava or commons collections library
but the Sets.symmetricDifference(Set1, Set2) ask for 2 to be passed in,
even i juse Sets.newHashSet(lis1) and Sets.newHashSet(lis2) to create two sets
but still they have difference type of objects in the sets.
or in commons CollectionUtils.disjunction(lis1, list2) the lists still has to contain the same object type
without doing 2 expensive for loops, is there any other way?
First, we'll build two maps, one for each list, mapping names to objects. Then we iterate over the differences between the key sets, processing whichever kind of object had that name. The maps let us avoid scanning through the list looking for the object with that name. (In using Map rather than Multimap, I'm relying on the asker's comment on another answer that within each list, names are unique. If you're still using Java 7, replace the method reference with a Function implementation.)
Map<String, Object1> map1 = Maps.uniqueIndex(list1, Object1::getName);
Map<String, Object2> map2 = Maps.uniqueIndex(list2, Object1::getName);
for (String name : Sets.difference(map1.keySet(), map2.keySet()))
processObject1(map1.get(name));
for (String name : Sets.difference(map2.keySet(), map1.keySet()))
processObject2(map2.get(name));
If all you want to do is build lists or sets of the objects in exactly one list, processObject1 and processObject2 can just add the objects to collections.
uniqueIndex's iteration order is that of the input iterable, and difference returns a SetView with the same iteration order as its first argument, so you can process objects in the order they appeared in the input lists, if that order is relevant to your problem.
Java 8 streams provide basically the same functionality:
Map<String, Object1> map1 = list1.stream().collect(Collectors.toMap(Function.identity(), Object1::getName));
Map<String, Object2> map2 = list2.stream().collect(Collectors.toMap(Function.identity(), Object2::getName));
map1.keySet().stream().filter(n -> !map2.keySet().contains(n)).map(map1::get).forEachOrdered(o1 -> processObject1(o1));
map2.keySet().stream().filter(n -> !map1.keySet().contains(n)).map(map2::get).forEachOrdered(o2 -> processObject1(o2));
Again, you can replace the forEachOrdered call with collect(Collectors.toList()) if you just want to collect the objects.
First you will have to transfor your lists to String based lists:
private static final class FromObject1ToName implements Function<Object1, String> {
#Override
public String apply(Object1 input) {
return input.name;
}
}
The same transformation has to be done for Object2
Then transform the input list:
Collection<String> transformed = Collections2.transform(list1, new FromObject1ToName());
//list1 is a List on Object1
Then create the multiset:
Multiset<String> multiset1 = HashMultiset.create();
multiset1.addAll(transformed);
Then simply do :
Multisets.difference(multiset1, multiset2) // multiset1 is from Object1 and multiset2 is from Object2
This will give you the difference and how many times it differes
If you need to know just the differences, then do the same transform, then load the Collection of strings in a Set adn then do Sets.symmetricDifference
Using Guava, try this. It works for me ->
Multisets.difference(multiset1,multiset2);
How to convert ArrayList to Multiset.
List x = new ArrayList();
x.add(3);.....
Multiset newX = HashMultiset.create();
newX.addAll(x);
I need to do the task of sorting HashMap keys which will be String always and store them in an ArrayList. I have written below piece of code
public static List<String> getSortedListByKeys(HashMap<String,String> keysDictionary)
{
List<String> sortedKeysList = null;
SortedSet<String> sortedKeysSet = null;
if(keysDictionary == null || keysDictionary.size() == 0){
return null;
}
sortedKeysSet = new TreeSet<>(keysDictionary.keySet());
sortedKeysList = new ArrayList<>(sortedKeysSet);
return sortedKeysList;
}
I have run it and it is working fine.
Just want to know if there is any better way to achieve the same
Since you want to sort the Strings based on natural ordering itself, you directly create a list and use Collections.sort() on it.
sortedKeysList = new ArrayList<>(keysDictionary.keySet());
Collections.sort(sortedKeysList);
This way you can avoid using a SortedSet as an intermediate step.
I think you should definitely consider to use a TreeMap, insertion and retrieval will be be a little bit slower (O(log(N) instead of O(1)) but you will have key already sorted in the right order.
The Java doc of TreeMap : http://docs.oracle.com/javase/6/docs/api/java/util/TreeMap.html
With Java 8 you could do it quite easily with the following:
Map<String, String> keysDictionary = new HashMap<>();
List<String> sortedKeys = keysDictionary.keySet().stream()
.sorted()
.collect(Collectors.toList());
With this you:
Obtain the key set, being a Set<String>.
Wrap it to a Stream<String>.
Sort it, via sorted().
Collect it into a List<Stream> supplied by Collectors.toList(), this will be an ArrayList<String> as no additional constraints have been specified.
It might be more burden than it would be in Java 7, but I would say it is about equal once you know the Streams API, it gets more fun when you put additional constraints. And then the real profit shows.