【问题标题】:Combine values of a list of data that are almost equal合并几乎相等的数据列表的值
【发布时间】:2019-07-16 04:41:14
【问题描述】:

所以我之前问过,但似乎我对我在说什么还不够清楚,所以我现在试图让它更清楚: 我想做的是为导入准备数据。我得到的数据是人为的,效率不高,所以我删除了不必要的条目并尽可能地组合数据。

它用于配置器之类的东西。我得到的数据看起来像这样:

123 : 45 : AB = 12 这意味着:如果选项 1 是 1 OR 2 OR 3,选项 2 是 4 OR 5,选项 3 是 A OR B,则结果将为 1 AND 2

我创建了一个类似这样的类:

Class Options{
    String opt1;
    String opt2;
    String opt3;
    String optResult;

    //and some other stuff

    boolean hasSameOptions(Options o){
        return opt1.equals(o.opt1) && opt2.equals(o.opt2) && opt3.equals(o.opt3);
    }

    public void AddOptions(String options) {
        for (String s : options.split("")) {
            if (!optResult.contains(s)) {
                optResult = optResult + s;
            }
        }
    }

}

现在,数据是重复的,可以合并。喜欢:

12 : 45 : AB = 12
12 : 45 : AB = 3
12 : 45 : AB = 4

这实际上意味着:12 : 45 : AB = 1234

所以,我所做的是将字符串分开以仅获得单个值和结果,例如:

1 : 4 : A = 12
1 : 4 : B = 12
1 : 5 : A = 12 
//and so on.

我列出所有这些值,然后尝试再次组合它们以获得更有效的列表。

我要做的第一步是获取所有具有相同选项但结果不同的对象并组合结果。事情是这样的:

public static List<Options> cleanList(List<Options> oldList) {

    List<Options> newList = new ArrayList<>();
    for (Options item : oldList) {
        Options temp = findEqualOptions(newList, item);
        if (temp != null)
            temp.AddOptions(item.optResult);
        else
            newList.add(item);
    }

    return newList;
}

public static <T> T findByProperty(Collection<T> col, Predicate<T> filter) {
    return col.stream().filter(Objects::nonNull).filter(filter).findFirst().orElse(null);
}

public static Options findEqualOptions(List<Options> list, Options opt) {
    return findByProperty(list, d -> d.hasSameOptions(opt));
}

在那之后,我尝试通过组合只有一个不同值的元素来进一步压缩列表。例如:

1 : 2 : A = 12
1 : 3 : A = 12 
 -> 1 : 23 : A = 12

我是这样做的:

for (int i = 0; i < list.size(); i++) {
    for (int j = i + 1; j < list.size(); j++) {
        Option o1 = list.get(i);
        Option o2 = list.get(j);
        int diff1 = 0;
        int diff2 = 0;
        int diff3 = 0;
        int diff4 = 0;


        if(!o1.opt1.equals(o2.opt1))
            diff1 = 1;
        if(!o1.opt2.equals(o2.opt2))
            diff2 = 1;

        //and so on

        if((diff1+diff2+diff3+diff4)>1)
            continue;

        if(diff1 == 1)
            o1.opt1 = o1.opt1 + o2.opt1;

        //and so on...


        list.remove(j--);


    }
}

我会一直这样做,直到没有更多更改为止。它运作良好,但缓慢。尤其是方法 cleanList()。 有人知道如何让它变得更好吗?我尝试使用流来直接获取等于选项的整个列表,如下所示:

public static <T> List<T> findByMultipleValue(Collection<T> col, Predicate<T> filter) {
    return col.stream().filter(filter).collect(Collectors.toList());
}

public static List<Options> getEqualOptionsList(List<Options> optList, Options opt){
    return findByMultipleValue(optList, o -> o.hasSameOptions(opt));
}

但这让它慢了很多。

附言。 :它不是完整的代码,只是我试图做的一个例子。我希望这次更容易理解:)

【问题讨论】:

  • 也许将每个“等式”的左侧表示为一个字符串,就像它所写的一样,并将其存储在散列中,其中散列中的值是组合的可能结果。所以你会阅读第一个,从 LHS 中提取字符串,发现它不在你的哈希中,所以添加它。下次您点击相同的 LHS 时,您将获取现有值并附加新值,而不是添加。哈希查找非常快,所以你会在这部分得到 O(n)。
  • @JosephLarson 感谢您的提示!

标签: java list merge java-stream


【解决方案1】:

可能不是最优雅或最佳的解决方案,但这里已经是一种快速方法,可以根据您的描述给出结果。它使用@Joseph Larson 的评论中提出的 HashMap

我选择了一组 char 以确保其中的值不重复,但可以随意调整 :)

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

class Scratch {
    public static class Option{
        String opt1;
        String opt2;
        String opt3;
        String optResult;

        public Option(String opt1, String opt2, String opt3, String optResult) {
            this.opt1 = opt1;
            this.opt2 = opt2;
            this.opt3 = opt3;
            this.optResult = optResult;
        }

        public static String merge(String a, String b){
            StringBuilder value = new StringBuilder();
            Set<Character> result = new HashSet<>();
            for(char c : a.toCharArray()){
                result.add(c);
            }
            for(char c : b.toCharArray()){
                result.add(c);
            }
            for(char c : result){
                value.append(c);
            }
            return value.toString();
        }

        public Option(Option a, Option b) {
            this(merge(a.opt1, b.opt1), merge(a.opt2, b.opt2), merge(a.opt3, b.opt3), merge(a.optResult, b.optResult));
        }

        String getKey(){
            return String.join(":", opt1, opt2, opt3);
        }

        int distance(Option option){
            int diff1 = this.opt1.equals(option.opt1)?0:1;
            int diff2 = this.opt2.equals(option.opt2)?0:1;
            int diff3 = this.opt3.equals(option.opt3)?0:1;
            int diff4 = this.optResult.equals(option.optResult)?0:1;
            return diff1 + diff2 + diff3 + diff4;
        }

        public String toString(){
            return getKey();
        }
    }
    public static void main(String[] args) {
        Option[] data = new Option[]{
                new Option("12", "45", "AB", "12"),
                new Option("12", "45", "AB", "3"),
                new Option("12", "45", "AB", "4"),
                new Option("12", "45", "AC", "1"),
                new Option("12", "45", "AC", "12"),
                new Option("3", "45", "AC", "13"),
                new Option("12", "45", "AD", "12"),
        };

        mergeExact(data);
        mergeClose(data, 1);
    }

    private static void mergeClose(Scratch.Option[] data, int distance){
        Map<Option, Set<Character>> buffer = new HashMap<>();
        for(Option option : data) {
            boolean found = false;
            Option toDelete = null;
            for(Map.Entry<Option, Set<Character>> entry : buffer.entrySet()){
                if(option.distance(entry.getKey()) <= distance){
                    Option merged = new Option(entry.getKey(), option);
                    for(char c : option.optResult.toCharArray()){
                        entry.getValue().add(c);
                    }
                    buffer.put(merged, entry.getValue());
                    toDelete = entry.getKey();
                    found = true;
                    break;
                }
            }
            if(found) {
                buffer.remove(toDelete);
            }else{
                Set<Character> set = new HashSet<>();
                for(char c : option.optResult.toCharArray()){
                    set.add(c);
                }
                buffer.put(option, set);
            }
        }
        System.out.println(String.format("merge with distance of %d:: %s", distance, buffer));

    }

    private static void mergeExact(Scratch.Option[] data) {
        Map<String, Set<Character>> buffer = new HashMap<>();
        for(Option option : data){
            Set<Character> item = buffer.computeIfAbsent(option.getKey(), k -> new HashSet<>());
            for(char c : option.optResult.toCharArray()){
                item.add(c);
            }
        }
        System.out.println("exact merge:: "+buffer);
    }


}

输出是

exact merge:: {3:45:AC=[1, 3], 12:45:AD=[1, 2], 12:45:AC=[1, 2], 12:45:AB=[1, 2, 3, 4]}
merge with distance of 1:: {12:45:AB=[1, 2, 3, 4], 3:45:AC=[1, 3], 12:45:ACD=[1, 2]}

编辑:错过了部分问题,更新以在差异接近时添加合并。这部分在优化方面可能比第一个更糟糕,但它是一个工作基础:)

【讨论】:

  • 你错过了左侧的字符串被分解为核心元素以做出单一陈述的部分,我编辑了我的帖子,因为我认为它不是很好看。我使用你的解决方案来“只是”cleanList 方法,它的速度要快得多,仍然需要在我的实现上做一些工作,但非常感谢!
猜你喜欢
  • 2016-07-16
  • 2022-06-21
  • 1970-01-01
  • 2018-10-04
  • 2018-08-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多