Java: How to convert list to set then compare for missing values

Viewed 213

My goal is to locate and print any missing values in my data using the Set interface.

I have a POJO like below


public class Info {

    private final String folder;
    private final String version;
    private final String key;
        
    public static Builder builder() {
        return new Builder();
    }
    
    public static Builder builder(Info info) {
        return new Builder(info);
    }
        
    private Info(Builder builder) {
        this.folder = builder.folder;
        this.version = builder.version;
        this.key = builder.key;
        
    public String getFolder() {
        return folder;
    }
        
    public String getVersion() {
        return version;
    }
        
    public String getCreated() {
        return created;
    }
        
    // toString(), hashCode(), equals()
        
    // Builder class
        
    public Builder withFolder(String folder){
        this.folder = folder;
        return this;
    }

   // withVersion(String version)
   // withKey(String key)


    public Info build(){
        return new Info(this);    
    }

}

Using the below code, I create a list from my data source.

List<Info> newList = previousList.stream().map(m -> Info.builder()
            .withFolder(m.getFolder())
            .withKey(m.getKey())
            .withVersion(m.getVersion())
            .build())
            .collect(Collectors.toList());

// Printed output is below
list.forEach(System.out::println);

Below is output similar to my list output. Two example folders and two example missing files are shown. For each key, there are multiple version numbers.

// Output

[folder=folderA, version=3.48.137, key=fileA-sources.jar]
[folder=folderA, version=3.48.128, key=fileA-sources.jar]
[folder=folderA, version=3.48.125, key=fileA-sources.jar]
[folder=folderA, version=3.48.137, key=fileA-javadoc.jar]
[folder=folderA, version=3.48.128, key=fileA-javadoc.jar]
[folder=folderA, version=3.48.125, key=fileA-javadoc.jar]
[folder=folderA, version=3.48.137, key=fileA.jar]
[folder=folderA, version=3.48.128, key=fileA.jar]

// Missing [folder=folderA, version= 3.48.125, key=fileA.jar]

[folder=folderB, version=1.7.16, key=fileB.pom]
[folder=folderB, version=1.7.14, key=fileB.pom]
[folder=folderB, version=1.7.12, key=fileB.pom]
[folder=folderB, version=1.7.16, key=fileB.jar]
[folder=folderB, version=1.7.14, key=fileB.jar]

// Missing [folder=folderB, version 1.7.12, key=fileB.jar]

Below is my stream to convert the list into a set.

Set<Info> set = list.stream().map(m -> Info.builder()
            .withFolder(m.getFolderKey())
            .withVersion(m.getVersion())
            .withKey(m.getKey())
            .build()).collect(Collectors.toSet());

My goal is to locate and print any missing values in my data using a Set. Specifically the version numbers.

I want to compare the version numbers that are part of fileA-sources.jar, fileA-javadoc.jar, and fileA.jar in order to find any that are missing. See above Output for a commented example of a missing entry.

My data contains seven different file types and there are many similar entries to the two above.

I'm pretty new to Java and most likely missing some very elementary things. I realize that my plan may be completely off, so any help or a point in the right direction is appreciated. Thank you.

2 Answers

This is not fully baked, but it does seem to work.

1: Collect a set of keys from the actual entries

2: Collect a set of folder/version combinations from the actual entries

3: Build a set desired entries using combinations of collected keys and folder/version combinations

4: Filter-out actual entries from desired entries

import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Test {
    Set<FolderEntry> findMissing(Set<FolderEntry> actual) {
        Set<String> keys = actual.stream().map(e -> e.key).collect(Collectors.toSet());

        Set<FolderEntry> folderVersions = actual.stream()
                .map(e -> new FolderEntry(e.folder, e.version, null))
                .collect(Collectors.toSet());

        Set<FolderEntry> missing = folderVersions.stream()
                .flatMap(fv -> {
                    Stream.Builder<FolderEntry> builder = Stream.builder();
                    keys.forEach(key -> builder.accept(new FolderEntry(fv.folder, fv.version, key)));
                    return builder.build();
                })
                .filter(desired -> !actual.contains(desired))
                .collect(Collectors.toSet());
        
        return missing;
    }

    public static void main(String[] args) {
        Set<FolderEntry> folderAEntries = Set.of(
                new FolderEntry("folderA", "3.48.137", "fileA-sources.jar"),
                new FolderEntry("folderA", "3.48.128", "fileA-sources.jar"),
                new FolderEntry("folderA", "3.48.125", "fileA-sources.jar"),
                new FolderEntry("folderA", "3.48.137", "fileA-javadoc.jar"),
                new FolderEntry("folderA", "3.48.128", "fileA-javadoc.jar"),
                new FolderEntry("folderA", "3.48.125", "fileA-javadoc.jar"),
                new FolderEntry("folderA", "3.48.137", "fileA.jar"),
                new FolderEntry("folderA", "3.48.128", "fileA.jar")
                );
        new Test().findMissing(folderAEntries)
                .forEach(System.out::println);

        Set<FolderEntry> folderBEntries = Set.of(
                new FolderEntry("folderB", "1.7.16", "fileB.pom"),
                new FolderEntry("folderB", "1.7.14", "fileB.pom"),
                new FolderEntry("folderB", "1.7.12", "fileB.pom"),
                new FolderEntry("folderB", "1.7.16", "fileB.jar"),
                new FolderEntry("folderB", "1.7.14", "fileB.jar")
                );
        new Test().findMissing(folderBEntries)
                .forEach(System.out::println);
    }

    static class FolderEntry {
        final String folder;
        final String version;
        final String key;

        public FolderEntry(String folder, String version, String key) {
            this.folder = folder;
            this.version = version;
            this.key = key;
        }

        @Override
        public int hashCode() {
            return Objects.hash(folder, key, version);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            FolderEntry other = (FolderEntry) obj;
            return Objects.equals(folder, other.folder) && Objects.equals(key, other.key)
                    && Objects.equals(version, other.version);
        }

        @Override
        public String toString() {
            return "FolderInfo [folder=" + folder + ", version=" + version + ", key=" + key + "]";
        }
    }
}

Output:

FolderInfo [folder=folderA, version=3.48.125, key=fileA.jar]
FolderInfo [folder=folderB, version=1.7.12, key=fileB.jar]

I misunderstood your problem in the beginning and my initial answer was not helpful. I'm leaving it here for history:

If you already have a List<SetObject>, then you can obtain a Set<SetObject> by simply passing the list as an argument to the constructor for the Set implementation

Set<SetObject> set = new HashSet<>(list);

If you can futher explain the logic behind determining if something is missing I may be able to help further.

One way is to generate a full set based on known pattern. After, iterate through initial list and remove form full_generated_set similar elements. What remains in full_generated_set are missing elements.

public class TestFolder {

    public static void main(String[] args) 
    {
        //initial set
        List<FolderData> list = new ArrayList<FolderData>();
        list.add(new FolderData("folderB", "1.7.16", "fileB.pom"));
        list.add(new FolderData("folderB", "1.7.14", "fileB.pom"));
        list.add(new FolderData("folderB", "1.7.12", "fileB.pom"));
        list.add(new FolderData("folderB", "1.7.16", "fileB.jar"));
        list.add(new FolderData("folderB", "1.7.14", "fileB.jar"));
        
        List<String> requiredPattern = new ArrayList<String>();
        requiredPattern.add("pom");
        requiredPattern.add("jar");
        
        Set<String> set = list.stream().filter(t->t.folder.equals("folderB")).
                          map(t->t.version).distinct().
                          collect(Collectors.toSet());
        set.forEach(System.out::println);
        
        //create a full set
        Set<FolderData> fullSet = new HashSet<FolderData>();
        for(String version: set)
        {
            for(String pattern: requiredPattern)
            {
              fullSet.add(new FolderData("folderB",version,"fileB."+pattern));
            }
        }
        
        fullSet.forEach(System.out::println);
        //remove matching elements
        for(FolderData fd:list)
        {
            FolderData remove=null;
            for(FolderData haveFd:fullSet)
            {
                if(fd.folder.equals(haveFd.folder) && fd.version.equals(haveFd.version) && fd.key.equals(haveFd.key))
                {
                    remove = haveFd;
                    break;
                }
            }
            
            if(remove!=null) fullSet.remove(remove);
        }
        System.out.println("missing");
        fullSet.forEach(System.out::println);
        
    }
    
    static class FolderData
    {
        String folder;
        String version;
        String key;
        
        public FolderData(String folder, String version, String key)
        {
            this.folder = folder;
            this.version = version;
            this.key = key;
        }
        
        public String toString()
        {
            return folder+","+version+","+key;
        }
    }
}

An enhancement can be to update requiredPattern, so it's not needed to add manually cases.

requiredPattern =list.stream().map(t->t.key).distinct().collect(Collectors.toList());
//also update on generating full set to use the new pattern
fullSet.add(new FolderData("folderB",version,pattern));

Output

//version
1.7.16
1.7.14
1.7.12
//full generated set
folderB,1.7.12,fileB.pom
folderB,1.7.12,fileB.jar
folderB,1.7.16,fileB.jar
folderB,1.7.16,fileB.pom
folderB,1.7.14,fileB.pom
folderB,1.7.14,fileB.jar
//missing
folderB,1.7.12,fileB.jar
Related