Home > Mobile >  How to create a List of elements which Squares are present in the Original List using Java Streams
How to create a List of elements which Squares are present in the Original List using Java Streams

Time:05-28

I have the list of int - 2, 3, 6, 8, 14, 15, 9, 4.

I want to search such numbers whose square is present in the list, ex: 2 square 4 is present, so the output list should have 2.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamTest {

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);
        System.out.println(
                list.stream()
                        .map(i -> hasSquare(i, list))
                        .distinct()
                        .collect(Collectors.toList()));
    }

    private static Object hasSquare(Integer i, List<Integer> list) {

        if (list.contains(i * i)) {
            return i;
        } else {
            return 0;
        }
    }
}

This code is giving me the output [2, 3, 0], but I would like to get rid of 0.

Is there a way to do the same without using a third method?

CodePudding user response:

You could filter before collecting:

list.stream().map( i -> hasSquare(i,list)).filter(e->e!=0).distinct().collect(Collectors.toList()))

and also return Integer instead of Object when you are certain that your list contains Integers.

   private static Integer hasSquare(Integer i, List<Integer> list) {
        if(list.contains(i*i)){
            return i;
        } else return 0;
    }

CodePudding user response:

The problem lies within the stream and your method .

  1. You can easily use filter method in stream instead of map cause all you are doing is contains check in your method.
  2. You are returning the values and in else part returning 0 .That is why you are having 0 in your result . Instead of returning the existing value return only true or false that way it will be much easier to control and organized.
    private static boolean hasSquare(Integer i, List<Integer> list) {
            return list.contains(i*i); 
    }

Stream will be

System.out.println(list.stream().filter(i-> hasSquare(i, list)).collect(Collectors.toList()));

CodePudding user response:

Your method hasSquare() has a cost O(n) because method contains() needs to iterate over the given list. And it will give a quadratic overall time complexity. But we can do better.

In order to do this in a linear time, we need to create a HashSet containing all the elements from the source list. And then check if the squared value of each element in the source list is present in a set.

It can be written in a single stream-statement by using the built-in collector collectingAndThen() in conjunction with toSet(). As the second argument collectingAndThen() expects a function that is meant to produce the final result.

The function creates a stream over the given list, and filter() operation will take care of keeping only those elements which squared values are present in the set. Operation distinct() is required to insure uniqueness.

public static List<Integer> getSquares(List<Integer> list) {
    
    return list.stream()
        .collect(Collectors.collectingAndThen(
            Collectors.toSet(),
            set -> list.stream().filter(num -> set.contains(num * num))
                .distinct()
                .collect(Collectors.toList())));
}

A more simple approach is to create a HashSet separately (and I would rather prefer this version):

public static List<Integer> getSquares(List<Integer> list) {
    
    Set<Integer> set = new HashSet<>(list);
    
    return list.stream()
        .filter(num -> set.contains(num * num))
        .distinct()
        .collect(Collectors.toList());
}

main()

public static void main(String[] args) {
    List<Integer> source = List.of(2, 3, 6, 8, 14, 15, 9, 4);

    System.out.println(getSquares(source));
}

Output:

[2, 3]

A link to the Online Demo

Sidenote: if you're using Java 9 and above, List.of() is a preferred way to initialize the List because it doesn't create an intermediate array (if number of elements not greater than 10).

CodePudding user response:

You want to filter instead of mapping.

import java.util.*;
import java.util.stream.Collectors;

public class StreamTEst {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);
        List<Integer> result = list.stream()
           .filter(i -> list.contains(i*i))
           .distinct()
           .collect(Collectors.toList());
        System.out.println(result);
    }
}

If you start working with longer lists then you might want to create a hash set for faster lookup like this:

import java.util.*;
import java.util.stream.Collectors;

public class StreamTEst {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);
        Set<Integer> lookup = new HashSet<>(list);
        
        List<Integer> result = list.stream()
           .filter(i -> lookup.contains(i*i))
           .distinct()
           .collect(Collectors.toList());
        System.out.println(result);
    }
}

CodePudding user response:

My Solution: Takes advantage of constant lookup time of Set

import java.util.Set;
import java.util.HashSet;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {
    public static void main(String[] args) {
        List <Integer> list = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);
        Set <Integer> numbers = new HashSet <Integer> (list);
        List <Integer> result = list.stream()
            .filter((current) - > numbers.contains(current * current))
            .collect(Collectors.toList());

        System.out.println(result);
    }

}

Similar to your approach: List.contains() takes n (element count in your list) time to compute. Applying same operation to each element takes n*n time, which scales logarithmically, inefficient and expensive. Java stream executes in parallel, its fast but still your code not scales well.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Main {

    public static void main(String[] args) {
        List <Integer> list = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);
        System.out.println(
            list.stream()
            .filter(i -> hasSquare(i, list))
            .collect(Collectors.toList()));
    }

    private static boolean hasSquare(Integer i, List < Integer > list) {

        if (list.contains(i * i)) {
            return true;
        } else {
            return false;
        }
    }
}

CodePudding user response:

Your goal is achievable without using a third method. You could simply stream your list and use the filter operation to keep only the numbers whose square is equal to any number of the same list. Also, if you want to keep distinct elements, you could collect your elements in a Set instead of using an expensive operation like disntict.

List<Integer> list = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);

Set<Integer> setRes = list.stream()
        .filter(n -> list.stream().anyMatch(x -> x == n * n))
        .collect(Collectors.toSet());

System.out.println(setRes);

Here is a link to test the code:

https://www.jdoodle.com/iembed/v0/rrE

Output

[2, 3]

CodePudding user response:

I think below is one of straight-forward way:

List<Integer> list   = Arrays.asList(2, 3, 6, 8, 14, 15, 9, 4);
List<Integer> result =  list.stream().collect(ArrayList<Integer>::new, (x, y) -> {
            if (list.contains((int) Math.pow(y, 2)))
                x.add(y);
        }, (x, y) -> y.addAll(x));


System.out.println(result);
  • Related