【问题标题】:Why does this unused stream have an impact on the result?为什么这个未使用的流对结果有影响?
【发布时间】:2020-08-06 13:49:10
【问题描述】:

为什么以下有效未使用行(在方法中:getAllDefinedVars)对最终结果有影响:
List collect = allVars. stream().filter(v -> false).collect(Collectors.toList());

如果我删除整个方法和调用此方法的一行代码(generateOneSetOfBools 中的第一行),我最终会得到另一个结果。
我会期待这样的行为,如果......

  1. 提到的行对列表 allVars 或任何其他变量有影响
  2. 将使用流的结果

据我所知,这一切都没有发生。因此,删除整个方法应该不会影响结果。

为了说服自己,您可以第一次使用包含流的方法运行 main,第二次不使用此方法,然后比较输出。

public class PairwiseMain {
    
    private static PairwisePermutator pp = new PairwisePermutator();

    public static void main(String[] args) {
        for(int i = 0; i < 20; i++) {
            pp.permutate(i);
            System.out.println("----------------");
        }
    }

}



import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class PairwisePermutator {
    
    private int n;
    
    public Stream<boolean[]> permutate(int n) {
        this.n = n;
        List<Var> vars = generateRequiredVars(n);
        List<TupleSet> table = generateTable(vars);
        generateCombinations(table, vars);
        return null;
    }
    
    private void generateCombinations(List<TupleSet> table, List<Var> vars) {
        TupleSet start = findStartTupleSet(table);
        if(start == null) {
            return; 
        }else {
            Tuple t = start.tuples.get(0);
            start.setVars(t);
            generateOneSetOfBools(table, vars, new HashSet<>(Arrays.asList(start)));
        }
        System.out.println(Arrays.toString(vars.toArray()));
        resetAllVars(vars);
        generateCombinations(table, vars);
    }

    private void resetAllVars(List<Var> vars) {
        vars.stream().forEach(v -> v.value = null);
    }

    private void generateOneSetOfBools(List<TupleSet> table, List<Var> allVars, Set<TupleSet> start) {
        List<Var> alreadyDefinedVars = getAllDefinedVars(allVars); //REMOVAL OF THIS LINE SHOULD HAVE NO IMPACT
        Map<TupleSet, Integer> relevant = findRelevantTuplesInOtherTupleSets(table, start);
        boolean changes = false;
        boolean existsMultipleOptions = false;
        TupleSet minimalMultipleOptions = null;
        int minimalMultipleOptionsNumber = Integer.MAX_VALUE;
        for(Map.Entry<TupleSet, Integer> entry : relevant.entrySet()) {
            if(entry.getValue() == -1) {
                removeTuple(entry.getKey());
                start.add(entry.getKey());
                changes = true;
            }else if(entry.getValue() == 1) {   
                changes = true;
                removeTuple(entry.getKey(), table, allVars);
                start.add(entry.getKey());
            }else if(entry.getValue() >= 2) {    
                existsMultipleOptions = true;
                if(entry.getValue() < minimalMultipleOptionsNumber) {
                    minimalMultipleOptionsNumber = entry.getValue();
                    minimalMultipleOptions = entry.getKey();
                }
            }
        }
        if(!changes && existsMultipleOptions) {
            removeRandomTuple(minimalMultipleOptions, start);
        }else if(!changes) { 
            setVars(table);
        }
        if(areAllVarsDefined(allVars)) {
            removeMatchingTuples(table);
            return;
        }
        generateOneSetOfBools(table, allVars, start);
    }

    private List<Var> getAllDefinedVars(List<Var> allVars) {
        List<Var> collect = allVars.stream().filter(v -> false).collect(Collectors.toList());
        return collect;
    }

    private void removeMatchingTuples(List<TupleSet> table) {
        for(TupleSet ts : table) {
            boolean v1 = ts.x.value;
            boolean v2 = ts.y.value;
            Tuple toRemove = null;
            for(Tuple t : ts.tuples) {
                if(t.a == v1 && t.b == v2) {
                    toRemove = t;
                }
            }
            if(toRemove != null) {
                ts.setVars(toRemove);
            }
        }
    }

    private boolean areAllVarsDefined(List<Var> allVars) {
        return allVars.stream().filter(v -> v.value != null).count() == n;
    }

    private void removeRandomTuple(TupleSet minimalMultipleOptions, Set<TupleSet> start) {
        Tuple toRemove = minimalMultipleOptions.tuples.get(0);
        minimalMultipleOptions.setVars(toRemove);
        start.add(minimalMultipleOptions);
    }

    private void setVars(List<TupleSet> table) {
        boolean foundOne = false;
        for(TupleSet ts : table) {
            if(ts.x.value == null && ts.y.value == null) {
                foundOne = setVars(ts);
            }
        }
        if(!foundOne) {
            for(TupleSet ts : table) {
                if(ts.x.value == null || ts.y.value == null) {
                    if(ts.tuples.isEmpty()){
                        ts.x.value = true;
                        ts.y.value = false;
                    }else {
                        ts.setVars(ts.tuples.get(0));
                    }
                }
            }
        }
        
    }

    private boolean setVars(TupleSet ts) {
        boolean foundOne;
        if(ts.tuples.isEmpty()){
            ts.x.value = true;
            ts.y.value = false;
            foundOne = true;
        }else {
            ts.setVars(ts.tuples.get(0));
            foundOne = true;
        }
        return foundOne;
    }

    private void removeTuple(TupleSet ts, List<TupleSet> table, List<Var> vars) {
        Tuple toRemove = null;
        if(ts.x.value != null) {
            for(Tuple t : ts.tuples) {
                if(t.a == ts.x.value) {
                    toRemove = t;
                }
            }
        }else if(ts.y.value != null) {
            for(Tuple t : ts.tuples) {
                if(t.b == ts.y.value) {
                    toRemove = t;
                }
            }
        }
        if(toRemove != null)
            ts.setVars(toRemove);
    }

    private void removeTuple(TupleSet ts) {
        boolean v1 = ts.x.value;
        boolean v2 = ts.y.value;
        Tuple toRemove = null;
        for(Tuple t : ts.tuples) {
            if(t.a == v1 && t.b == v2) {
                toRemove = t;
            }
        }
        if(toRemove != null) {
            ts.setVars(toRemove);
        }
    }

    private Map<TupleSet, Integer> findRelevantTuplesInOtherTupleSets(List<TupleSet> table, Set<TupleSet> alreadyVisited) {
        Map<TupleSet, Integer> freedomMap = new HashMap<>();
        for(TupleSet ts : table) {
            if(!alreadyVisited.contains(ts)) {
                int degreeOfFreedom = calculateDegreeOfFreedom(ts);
                freedomMap.put(ts, degreeOfFreedom);            }
        }
        return freedomMap;
    }
    
    private int calculateDegreeOfFreedom(TupleSet ts) {
        List<Var> alreadyDefinedVars = new ArrayList<>();
        if(ts.x.value != null) {
            alreadyDefinedVars.add(ts.x);
        }
        if(ts.y.value != null) {
            alreadyDefinedVars.add(ts.y);
        }
        int degreeOfFreedom = 0;
        if(areBothDefinied(alreadyDefinedVars)) {
            degreeOfFreedom = -1; 
        }else if(isExactlyOneDefinied(alreadyDefinedVars)) {
            Var defined = getDefinedVar(alreadyDefinedVars);
            if(defined == ts.x) {
                degreeOfFreedom = (int) ts.tuples.stream().filter(t -> t.a == ts.x.value).count();
            }else if(defined == ts.y) {
                degreeOfFreedom = (int) ts.tuples.stream().filter(t -> t.b == ts.y.value).count();
            }
        }else if(alreadyDefinedVars.isEmpty()) {
            degreeOfFreedom = ts.tuples.size(); 
        }
        return degreeOfFreedom;
    }

    private Var getDefinedVar(List<Var> alreadyDefinedVars) {
        return alreadyDefinedVars.get(0);
    }

    private boolean isExactlyOneDefinied(List<Var> alreadyDefinedVars) {
        return alreadyDefinedVars.size() == 1;
    }

    private boolean areBothDefinied(List<Var> alreadyDefinedVars) {
        return alreadyDefinedVars.size() == 2;
    }

    private TupleSet findStartTupleSet(List<TupleSet> table) {
        TupleSet startingTupleSet = null;
        for(TupleSet ts : table) {
            if(!ts.tuples.isEmpty()) {
                startingTupleSet = ts;
            }
        }
        return startingTupleSet;
    }

    private List<Var> generateRequiredVars(int n) {
        return IntStream.range(0, n).mapToObj(number -> new Var()).collect(Collectors.toList());
    }
    
    private List<TupleSet> generateTable(List<Var> vars) {
        List<TupleSet> tupleSets = new ArrayList<>();
        int kStart = 1;
        for(int i = 0; i < vars.size(); i++) {
            for(int k = kStart; k < vars.size(); k++) {
                tupleSets.add(getInitSet(vars.get(i), vars.get(k)));
            }
            kStart++;
        }
        return tupleSets;
    }
    
    private TupleSet getInitSet(Var x, Var y){
        return new TupleSet(x, y, (new ArrayList<>(Arrays.asList( new Tuple(true, true),
                                            new Tuple(true, false),
                                            new Tuple(false, true),
                                            new Tuple(false, false)))));
    }
    
    private static class TupleSet{
        Var x;
        Var y;
        ArrayList<Tuple> tuples;
        
        TupleSet(Var x, Var y, ArrayList<Tuple> tuples){
            this.x = x;
            this.y = y;
            this.tuples = tuples;
        }
        
        public void setVars(Tuple t) {
            x.value = t.a;
            y.value = t.b;
            tuples.remove(t);
        }

        @Override
        public String toString() {
            return "["+x+","+y+"]"+tuples;
        }
    }
    
    private static class Var{
        public Boolean value;
        
        @Override
        public String toString() {
            return value == null ? "null" : value.toString();
        }
    }
    
    private static class Tuple{
        public Boolean a;
        public Boolean b;
        
        public Tuple(Boolean a, Boolean b) {
            this.a = a;
            this.b = b;
        }
        
        @Override
        public String toString() {
            return "("+a+","+b+")";
        }
    }

}

影响的一个例子是运行 pp.permutate(5),输出如下:
使用流:运行发布的代码

[true, true, true, true, true]  
[false, false, false, true, false]  
[false, false, false, false, true]  
[true, true, true, false, false]  
[true, false, false, true, false]  
[false, false, true, true, false]  
[true, true, true, false, false] 

没有流:只删除 generateOneSetOfBool() 中的第一行

[true, true, true, true, true]  
[false, false, false, true, false]  
[false, false, false, false, true]  
[true, true, true, false, false]  
[false, true, true, true, false]  
[true, false, true, true, false]  
[true, true, false, false, false]  

两个输出不同,例如最后一行

【问题讨论】:

  • filter(v -&gt; false) 表示忽略列表的所有数据返回一个空列表
  • 是的,但这个列表从未使用过,allVars 保持不变。
  • 所以你声称allVars.stream().filter(v -&gt; false).collect(Collectors.toList()) 改变了allVars 的元素?我理解正确吗?它以什么方式影响他们?我对这个问题有点困惑。
  • 我刚刚运行了代码,两种方式都得到了相同的结果。也许你正在做其他事情?
  • 附带说明,因为我在不同的帖子中多次看到这个奇怪的System.out.println(Arrays.toString(vars.toArray()));,你能告诉我它来自哪里吗?是否有使用和推荐这种结构的书籍、教程或类似的东西?我的意思是,当我想打印vars 时,我首先想到的是System.out.println(vars);,但一定有很多人认为最好先将整个集合复制到一个数组,然后是另一个现在变得必要的实用方法,只是为了获得相同的字符串输出。

标签: java java-8 java-stream


【解决方案1】:

您获得的结果差异与List collect = allVars.stream().filter(v -&gt; false).collect(Collectors.toList()); 行无关。问题是您的算法是不确定的。我已经获取了您的代码并针对相同的输入多次运行它:

for(int i = 0; i < 20; i++) {
    pp.permutate(5);
    System.out.println("----------------");
}

是否有你在谈论的那一行都没关系 - 我得到了你提到的两个输出(只有这两个变体出现):

[true, true, true, true, true]
[false, false, false, true, false]
[false, false, false, false, true]
[true, true, true, false, false]
[true, false, false, true, false]
[false, false, true, true, false]
[true, true, true, false, false]
----------------
[true, true, true, true, true]
[false, false, false, true, false]
[false, false, false, false, true]
[true, true, true, false, false]
[false, true, true, true, false]
[true, false, true, true, false]
[true, true, false, false, false]

我没有逐行浏览你的代码,所以我不确定,但我猜你的一些集合不能保证元素的顺序。

然而,有趣的是,当我多次运行main 时,不同版本的输出出现的顺序总是相同的(或者至少在我尝试过的 5 次中)。更重要的是,当你提到的那一行被删除时,这个顺序就会改变——但在 main 调用之间保持不变。当我们再加上在不同的机器上它的行为不同的事实时,我的结论是它可能与程序在内存中的放置方式有关。

【讨论】:

    猜你喜欢
    • 2013-05-19
    • 1970-01-01
    • 2015-02-08
    • 1970-01-01
    • 1970-01-01
    • 2020-11-13
    • 2019-12-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多