Optimizing the ArrayList Condition - java

I have some values in my Array list and I have to remove some particular values based on conditions.I am using IF condition but there are many conditions to compare so I need to optimize the comparison time.for e.g. my list is
Msisdn_array={45,85,79,60,502}
and if condition match then remove the entry.
Below are the Conditions, Is there any possible way to optimize this conditions.
if(Msisdn_array.contains("60") && Msisdn_array.contains("910"))
{
Msisdn_array.remove("60");
Msisdn_array.remove("910");
}
if(Msisdn_array.contains("75") && Msisdn_array.contains("500"))
{
Msisdn_array.remove("75");
Msisdn_array.remove("500");
}
if(Msisdn_array.contains("76") && Msisdn_array.contains("502"))
{
Msisdn_array.remove("76");
Msisdn_array.remove("502");
}
if(Msisdn_array.contains("61") && Msisdn_array.contains("911"))
{
Msisdn_array.remove("61");
Msisdn_array.remove("911");
}
if(Msisdn_array.contains("77") && Msisdn_array.contains("503"))
{
Msisdn_array.remove("77");
Msisdn_array.remove("503");
}
if(Msisdn_array.contains("78") && Msisdn_array.contains("505"))
{
Msisdn_array.remove("78");
Msisdn_array.remove("505");
}
if(Msisdn_array.contains("79") && Msisdn_array.contains("507"))
{
Msisdn_array.remove("79");
Msisdn_array.remove("507");
}
if(Msisdn_array.contains("62") && Msisdn_array.contains("912"))
{
Msisdn_array.remove("62");
Msisdn_array.remove("912");
}
if(Msisdn_array.contains("63") && Msisdn_array.contains("913"))
{
Msisdn_array.remove("63");
Msisdn_array.remove("913");
}
if(Msisdn_array.contains("64") && Msisdn_array.contains("914"))
{
Msisdn_array.remove("64");
Msisdn_array.remove("914");
}
if(Msisdn_array.contains("65") && Msisdn_array.contains("915"))
{
Msisdn_array.remove("65");
Msisdn_array.remove("915");
}
if(Msisdn_array.contains("66") && Msisdn_array.contains("916"))
{
Msisdn_array.remove("66");
Msisdn_array.remove("916");
}
if(Msisdn_array.contains("67") && Msisdn_array.contains("917"))
{
Msisdn_array.remove("67");
Msisdn_array.remove("917");
}
if(Msisdn_array.contains("68") && Msisdn_array.contains("918"))
{
Msisdn_array.remove("68");
Msisdn_array.remove("918");
}
if(Msisdn_array.contains("69") && Msisdn_array.contains("919"))
{
Msisdn_array.remove("69");
Msisdn_array.remove("919");
}
if(Msisdn_array.contains("70") && Msisdn_array.contains("920"))
{
Msisdn_array.remove("70");
Msisdn_array.remove("920");
}
if(Msisdn_array.contains("71") && Msisdn_array.contains("921"))
{
Msisdn_array.remove("71");
Msisdn_array.remove("921");
}
if(Msisdn_array.contains("72") && Msisdn_array.contains("922"))
{
Msisdn_array.remove("72");
Msisdn_array.remove("922");
}
if(Msisdn_array.contains("73") && Msisdn_array.contains("923"))
{
Msisdn_array.remove("73");
Msisdn_array.remove("923");
}
if(Msisdn_array.contains("74") && Msisdn_array.contains("924"))
{
Msisdn_array.remove("74");
Msisdn_array.remove("924");
}
if(Msisdn_array.contains("80") && Msisdn_array.contains("926"))
{
Msisdn_array.remove("80");
Msisdn_array.remove("926");
}
if(Msisdn_array.contains("81") && Msisdn_array.contains("927"))
{
Msisdn_array.remove("81");
Msisdn_array.remove("927");
}
if(Msisdn_array.contains("82") && Msisdn_array.contains("928"))
{
Msisdn_array.remove("82");
Msisdn_array.remove("928");
}

One potential optimization is that you could don't need to check if the second item is in the list. Instead just attempt to remove it. If it was removed, the remove method will return true and you can also remove the first item.
if(Msisdn_array.contains("60") && Msisdn_array.remove("910")){
Msisdn_array.remove("60");
}
If you don't want to write out each if statement, you could store the groups in a Map, with the first item as the key and the second item as the value.
Map<String, String> rules = new HashMap<>();
rules.put("60", "910");
rules.put("75", "500");
rules.put("76", "502");
...
...
for(Entry entry : rules.values()) {
if(Msisdn_array.contains(entry.getKey()) && Msisdn_array.remove(entry.getValue())){
Msisdn_array.remove(entry.getKey());
}
}

I think you only need to extract a method to check if all of a group values are existed in List and then remove all. For example:
private void removeIfAllExist(List<String> list, String[] values) {
for (String v : values) {
if (!list.contains(v)) {
return;
}
}
list.removeAll(Arrays.asList(values));
}
public void yourLogic() {
List<String> list = new ArrayList<>(Arrays.asList("45", "85", "79", "60", "502"));
String[][] conditions = new String[][]{
new String[]{"60", "910"},
new String[]{"75", "500"},
new String[]{"76", "502"},
new String[]{"61", "911"},
new String[]{"77", "503"},
// more conditions
};
for (String[] values : conditions) {
removeIfAllExist(list, values);
}
}

If you do not iterate through the list often, I suggest you use a Set.
Search in this collection is fast.

contains is an O(n) operation. The list is traversed until the element is found. So every time you call contains you are potentially traversing the entire list.
An optimization might be to traverse the list once, manually, and check if the elements exist, and then do your removes afterwards, at the cost of some extra memory to store the boolean variables:
boolean exists_72 = false;
boolean exists_922 = false;
for(String element : Msisdn_array) {
if(element.equals("72")) {
exists_72 = true;
} else if(element.equals("922")) {
exists_922 = true;
}
}
if(exists_72 && exists_922) }
Msisdn_array.remove("72");
Msisdn_array.remove("922");
}

as remove returns a boolean is sucessfully removed you could do
if (list.remove ("60") && list.remove ("90"))
{
// everything ok
}
else {
// put back "60"
list.add ("60");
}
but personally I would go for readability a just create a method
removeBoth (String one, String two) {
if(list.contains(one) && list.contains(two))
{
list.remove(one);
list.remove(two);
}
}

It's slow because of List.contains being slow and also because List.remove being even slower (as it must move all elements with bigger indexes in order to fill the gap). It's ugly because of code repetition.
Msisdn_array is against Java naming convention and it's no array, so lets call it inputList. Let's use a HashSet for the lookups and another one to track what should be removed.
class Worker {
private final Set<String> inputListAsSet = new HashSet<>();
private final Set<String> removals = new HashSet<>();
public static void process(List<String> inputList) {
final Worker worker = new Worker(inputList);
worker.removeIfBothPresent("60", "910");
worker.removeIfBothPresent("75", "500");
worker.removeIfBothPresent("76", "502");
worker.removeIfBothPresent("61", "911");
worker.removeIfBothPresent("72", "922");
worker.removeIfBothPresent("73", "923");
worker.removeIfBothPresent("74", "924");
worker.removeIfBothPresent("80", "926");
worker.removeIfBothPresent("81", "927");
worker.removeIfBothPresent("82", "928");
inputList.removeAll(worker.removals);
}
private Worker(List<String> inputList) {
inputListAsSet.addAll(inputList);
}
private void removeIfBothPresent(String first, String second) {
if (inputListAsSet.contains(first) && inputListAsSet.contains(second)) {
removals.add(first);
removals.add(second);
}
}
}
Instead of using a class instance, the sets could be passed as arguments, but creating a worker seems cleaner. Note that despite the optimizations, my code is shorter than the original. In case of duplicates, it's not exactly equivalent.

Related

How do I check in an operator that current element is last element?

Context:
To process a Flowable<Item>, I need to first process the first item and then depending on that either accumulate all items into a single item (reduce) OR apply a simple map on each item without any accumulation (map).
One way I can think of requires operator to be aware that current element is last element.
Is there any such operator which is aware whether current element is last element ? I can't use buffer because then it'll always fetch 2 elements even when accumulation shouldn't be done.
AtomicReference<Item> itemRef = new AtomicReference();
itemRef.set(new Item());
Flowable<Item> accumulateOrProcessFlowable = source.
flatMap(item -> {
if(item.shouldBeAccumulated()) {
//Accumulate data into reference
itemRef.set(itemRef.get().addData(item.getData()));
//Return empty to throw away consumed item;
return Flowable.empty();
} else {
item.updateProperty();
return Flowable.just(item);
}
})
.applyIfLastElement(item -> {
if (item.shouldBeAccumulated()) {
return Flowable.just(itemRef.get());
}
})
Below is how you can do it (in RxJava 2.x which is very close to RxJava 3.x). The trick is to use defer (the best way to encapsulate state for a Flowable so that it can be subscribed to many times) and concatWith. defer also enables lazy evaluation in the case of last. Notice also as a performance improvement that you may not care about I used one element arrays instead of AtomicReference objects (to avoid unnecessary volatile reads, sets etc).
Flowable<Integer> result = Flowable.defer(() -> {
boolean[] isFirst = new boolean[] { true };
Integer[] state = new Integer[1];
Maybe<Integer> last = Maybe.defer(() -> {
if (state[0] == null) {
return Maybe.empty();
} else {
return Maybe.just(state[0]);
}
});
return source //
.flatMap(x -> {
if (state[0] != null || isFirst[0] && shouldBeAccumulated(x)) {
// accumulate
state[0] = state[0] == null ? 0 : state[0] + x;
isFirst[0] = false;
return Flowable.empty();
} else {
isFirst[0] = false;
return Flowable.just(x);
}
})
.concatWith(last);
});

Is there a way to merge this 2?

I'm running out of idea of how will I merge this 2 conditions, it has the same return so I need to merge
if ((StringUtils.isBlank(ext))) {
return true;
}
for (String str : INVALID_EXTENSION_ARR) {
if (ext.matches(str)) {
return true;
} else if (ext.matches(str.toLowerCase())) {
return true;
}
}
You don't need a loop.
Populate INVALID_EXTENSION_ARR with values in lowercase:
private static final List<String> INVALID_EXTENSION_ARR = Arrays.asList("foo", "bar", "whatever"); // Note: All in lowercase!
Then it's just one line:
return StringUtils.isBlank(ext) || INVALID_EXTENSION_ARR.contains(ext.toLowerCase());
Note: I have assumed when you used matches() you meant to use equals().
——-
If the list of acceptable extensions is “large” (say, more than 10), you’ll get better performance if you use a Set instead of a List:
private static final Set<String> INVALID_EXTENSION_ARR = new HashSet<>(Arrays.asList("foo", "bar", "whatever"));
Or for recent java versions:
private static final Set<String> INVALID_EXTENSION_ARR = Set.of("foo", "bar", "whatever");
But you would be unlikely to notice much difference unless the size was more than say 100.
Assuming that the loop will always be entered into,
for (String str : INVALID_EXTENSION_ARR) {
if (StringUtils.isBlank(ext) || ext.matches(str)) {
return true;
} else if (ext.matches(str.toLowerCase())) {
return true;
}
}
but I think that way that had it was easier to read and does not need to re-evaluate StringUtils.isBlank(ext) every time.
It is helpful if you provide more context, but this is one of the ways you can compact it.
for (String str : INVALID_EXTENSION_ARR) {
if (StringUtils.isBlank(ext) || ext.toLowerCase().matches(str.toLowerCase())) {
return true;
}
}

Iterating on list with stream in java 8

what would be most optimal way to rewrite this iteration with java 8 stream().
for (name : names){
if(name == a){
doSomething();
break;
}
if(name == b){
doSomethingElse();
break;
}
if(name == c){
doSomethingElseElse();
break;
}
}
Basically, iterating over the list with 3 conditions if any of them is satisfied want to break loop and in every condition want to call different method.
You can use anyMatch to find the first element matching one of your conditions and terminate. Use side effects for calling the processing methods :
boolean found =
names.stream()
.anyMatch (name -> {
if (name.equals(a)) {
doSomething();
return true;
} else if (name.equals(b)) {
doSomethingElse ();
return true;
} else if (name.equals(c)) {
doSomethingElseElse ();
return true;
} else {
return false;
}
}
);
Pretty ugly, but does what you asked in a single iteration.
The answer by Eran is definitely the straightforward way of performing the search. However, I would like to propose a slightly different approach:
private static final Map<String, Runnable> stringToRunnable = new HashMap<>();
{
stringToRunnable.put("a", this::doSomething);
stringToRunnable.put("b", this::doSomethingElse);
stringToRunnable.put("c", this::doSomethingElseElse);
}
public static void main(String[] args) {
List<String> names = Arrays.asList("1", "2", "b", "a");
names.stream()
.filter(stringToRunnable::containsKey)
.findFirst()
.ifPresent(name -> stringToRunnable.get(name).run());
}
private void doSomethingElseElse() {
}
private void doSomethingElse() {
}
public void doSomething() {
}
The part that does the job is the code below, but I added it to a main() function assuming a, b, and c are strings. However, the idea would work with any datatype.
names.stream()
.filter(stringToRunnable::containsKey)
.findFirst()
.ifPresent(name -> stringToRunnable.get(name).run());
The idea is to keep a map of keys and Runnables. By having Runnable as value it is possible to define a void method reference without parameters. The stream first filters away all values not present in the map, then finds the first hit, and executes its method if found.
Collection collection;
collection.forEach(name->{
if(name.equals(a))){
doSomething();
}
if(name.equals(b)){
doSomethingElse();
}
if(name.equals(c)){
doSomethingElseElse();
}
});

How to check if Iterator.next() is == null?

How do I check if the next element in the list is null ?
while(it.hasNext()){
System.out.println(it.next()+"\n");
}
this is how I tried, but when the last element is null it prints it as null.
I tried to change it
while(it.hasNext()){
if(it.next()==null){
}else
System.out.println(it.next()+"\n");
}
but this just makes it worst because some of the elements don't even print!
This is my Iteration method/anonymous class
public Iterator<Filmi> iterator3DFilms ()throws FilmiException{
if(filmList.isEmpty())
throw new FilmiException("No Films on the list");
return new Iterator<Filmi>(){
private int index=0;
public boolean hasNext(){
return index <filmList.size();
}
public Filmi next(){
Filmi lb = filmList.get(index++);
if(lb.is3D()== true)
return lb;
if(hasNext())
return next();
return null;
}
public void remove(){}
};
}
The null print only happens at the last element
Thank you.
Naturally, code like
if (it.next() == null){
} else {
System.out.println(it.next()+"\n");
}
will consume every other non-null element, as you are observing. Plus calling it.next() without checking it.hasNext() is a recipe for disaster.
Why not write
Foo/*ToDo - use the correct type here*/ foo = it.next()
if (foo != null){
/*ToDo*/
}
instead?
No it cannot work this way because if it.next() is not null you call it.next() twice which will make you skip a value that could not even be available.
Use a variable instead as next:
Object o = it.next();
if (o != null) {
...
}
you should use stream instead of iterator.
filmList.stream().filter(film->film!=null).filter(film->film.is3D())
Edit:
or, if you'r not in Java 8 :
Predicate<Film> isNotNullAnd3D = new Predicate<Person>() {
public boolean apply(Film f) {
return f != null && f.is3D();
}
};
Collection2.filter(filmList, isNotNullAnd3D)
You never mentioned why you use iterators explicitly in the first place.
Why not use implicit iterator notation like this ? :
for (Film film : filmList) {
if (film != null ){
....
}
}
Additionally to what others said: in case you are doing a for-each loop with a primitive type like int
for (int node : input) {
doSomething(node);
}
you might want to consider using the Wrapper class instead:
for (Integer node : input) {
if (node == null) throw new IllegalArgumentException();
doSomething(node);
}

Find unique superdirectories in a set of Java File objects

Given an unordered set of Java File objects, I want to eliminate all those that are subdirectories (or files in subdirectories) of other Files in the set. For instance, given:
File("/john/paul/george/ringo")
File("/foo/bar/baz")
File("/foo/bar/elvis")
File("/john/paul")
File("/john/jacob")
File("/foo/bar/baz/qux/quux")
File("/john/")
File("/foo/bar/")
the set should reduce to:
File("/foo/bar/")
File("/john/")
I can imagine cobbling something together based on converting to Strings with File.getAbsolutePath() and sorting (possibly by length first, then lexicographically), but surely this is a solved problem, or at least somebody must already have written utility methods for things like determining whether one File is an ancestor of another.
I'm not the best of programmers, but here's my shot at it. There's quite a bit of recursion involved.
class UniqueDirectories {
private final Map<String, UniqueDirectories> directories = new HashMap<String, UniqueDirectories>();
private boolean locked = false;
public UniqueDirectories() {}
private UniqueDirectories(boolean locked) {
this.locked = locked;
}
public boolean add(String path) {
if (path == null)
return false;
if (path.isEmpty())
return false;
if (locked)
return false;
String[] tokens = path.split("(?<!^)[/\\\\]", 2);
UniqueDirectories existingDirectory = directories.get(tokens[0]);
if (existingDirectory == null) {
if (tokens.length < 2 || tokens[1].isEmpty()) {
directories.put(tokens[0], new UniqueDirectories(true));
return true;
} else {
UniqueDirectories newDirectory = new UniqueDirectories(false);
directories.put(tokens[0], newDirectory);
return newDirectory.add(tokens[1]);
}
} else if (tokens.length >= 2 && !tokens[1].isEmpty()) {
return existingDirectory.add(tokens[1]);
} else {
directories.put(tokens[0], new UniqueDirectories(true));
return true;
}
}
public List<String> toList(char delimiter) {
List<String> list = new ArrayList<String>();
for (Map.Entry<String, UniqueDirectories> entry : directories.entrySet()) {
if (entry.getValue().directories.size() == 0) {
list.add(entry.getKey());
} else {
for (String subdirectory : entry.getValue().toList(delimiter)) {
list.add(entry.getKey() + delimiter + subdirectory);
}
}
}
return list;
}
public static void main(String[] args) {
String[] testPaths = {
"/john/paul/george/ringo",
"/foo/bar/baz",
"/foo/bar/elvis",
"/john/paul",
"/john/jacob",
"/foo/bar/baz/qux/quux",
"/john/",
"/foo/bar/"
};
UniqueDirectories directories = new UniqueDirectories();
for (String path : testPaths) {
directories.add(path);
}
System.out.println(directories.toList('/'));
}
}
Sorry about the lack of comments. Too lazy to go back and do it, but at least the code's relatively short. I haven't put it through rigorous testing either (just used the ones you provided), so you may want to use some more test cases.
The biggest thing to be careful with is consistency. If you start a path with a delimiter, all paths should start with one. Conversely, if one path does not start with a delimiter, the rest shouldn't either. Otherwise, they will be treated as different paths. You could easily fix this by removing the lookbehind in the regex though; I just wasn't sure how you wanted it to behave.

Categories