Repository: sympy/sympy

Issue:
Intersection gives wrong result
This is one of the tests in the test suite. The following demonstrates that Intersection gives the wrong result.

```python
>>> from sympy import *
>>> from sympy.abc import m,n,x
>>> r = S.Reals
>>> args = Tuple(r, FiniteSet(m,n,3), FiniteSet(m,n,x))
>>> reps = dict(m=1,n=2,x=3)
>>> args.subs(reps)
((-oo, oo), {1, 2, 3}, {1, 2, 3})
>>> Intersection(_)
{1, 2, 3}
>>> Intersection(args).subs(reps)
{3}
```

I am running tests on my `reduce` branch that fixes this.

Discussion:
smichr: @asmeurer , do you have any objections to the change to `fuzzy_bool` which will now output None for values that are neither True nor False. Although this breaks backward compatibility (b/c formerly `fuzzy_bool(x)` would give True) there were no tests that needed this behavior.

Patch:
diff --git a/sympy/core/logic.py b/sympy/core/logic.py
index 077f277c76..d12bee1562 100644
--- a/sympy/core/logic.py
+++ b/sympy/core/logic.py
@@ -64,11 +64,23 @@ def fuzzy_bool(x):
     """Return True, False or None according to x.
 
     Whereas bool(x) returns True or False, fuzzy_bool allows
-    for the None value.
+    for the None value and non-false values (which become None), too.
+
+    Examples
+    ========
+
+    >>> from sympy.core.logic import fuzzy_bool
+    >>> from sympy.abc import x
+    >>> fuzzy_bool(x), fuzzy_bool(None)
+    (None, None)
+    >>> bool(x), bool(None)
+    (True, False)
+
     """
     if x is None:
         return None
-    return bool(x)
+    if x in (True, False):
+        return bool(x)
 
 
 def fuzzy_and(args):
diff --git a/sympy/sets/sets.py b/sympy/sets/sets.py
index 4915c102aa..3b0ba18a62 100644
--- a/sympy/sets/sets.py
+++ b/sympy/sets/sets.py
@@ -1396,7 +1396,7 @@ def flatten(arg):
         args = flatten(args)
 
         if len(args) == 0:
-            raise TypeError("Intersection expected at least one argument")
+            return S.EmptySet
 
         # args can't be ordered for Partition see issue #9608
         if 'Partition' not in [type(a).__name__ for a in args]:
@@ -1435,6 +1435,72 @@ def __iter__(self):
 
         raise ValueError("None of the constituent sets are iterable")
 
+    @staticmethod
+    def _handle_finite_sets(args):
+        from sympy.core.logic import fuzzy_and, fuzzy_bool
+        from sympy.core.compatibility import zip_longest
+
+        new_args = []
+        fs_args = []
+        for s in args:
+            if s.is_FiniteSet:
+                fs_args.append(s)
+            else:
+                new_args.append(s)
+        if not fs_args:
+            return
+        s = fs_args[0]
+        fs_args = fs_args[1:]
+        res = []
+        unk = []
+        for x in s:
+            c = fuzzy_and(fuzzy_bool(o.contains(x))
+                for o in fs_args + new_args)
+            if c:
+                res.append(x)
+            elif c is None:
+                unk.append(x)
+            else:
+                pass  # drop arg
+        res = FiniteSet(
+            *res, evaluate=False) if res else S.EmptySet
+        if unk:
+            symbolic_s_list = [x for x in s if x.has(Symbol)]
+            non_symbolic_s = s - FiniteSet(
+                *symbolic_s_list, evaluate=False)
+            while fs_args:
+                v = fs_args.pop()
+                if all(i == j for i, j in zip_longest(
+                        symbolic_s_list,
+                        (x for x in v if x.has(Symbol)))):
+                    # all the symbolic elements of `v` are the same
+                    # as in `s` so remove the non-symbol containing
+                    # expressions from `unk`, since they cannot be
+                    # contained
+                    for x in non_symbolic_s:
+                        if x in unk:
+                            unk.remove(x)
+                else:
+                    # if only a subset of elements in `s` are
+                    # contained in `v` then remove them from `v`
+                    # and add this as a new arg
+                    contained = [x for x in symbolic_s_list
+                        if v.contains(x) == True]
+                    if contained != symbolic_s_list:
+                        new_args.append(
+                            v - FiniteSet(
+                            *contained, evaluate=False))
+                    else:
+                        pass  # for coverage
+
+            other_sets = Intersection(*new_args)
+            if not other_sets:
+                return S.EmptySet  # b/c we use evaluate=False below
+            res += Intersection(
+                FiniteSet(*unk),
+                other_sets, evaluate=False)
+        return res
+
     @staticmethod
     def reduce(args):
         """
@@ -1452,52 +1518,10 @@ def reduce(args):
         if any(s.is_EmptySet for s in args):
             return S.EmptySet
 
-        for s in args:
-            if s.is_FiniteSet:
-                # see which elements of the FiniteSet occur within
-                # all other sets in the intersection
-                other_args = [a for a in args if a != s]
-                res = FiniteSet(*[x for x in s if all(
-                    o.contains(x) == True for o in other_args)])
-                unk = [x for x in s if any(
-                    o.contains(x) not in (True, False) for o in other_args)]
-                if unk:
-                    new_other = []
-                    del_other = []
-                    for ival, val in enumerate(other_args):
-                        if val.is_FiniteSet:
-                            # collect expressions having symbols
-                            # from `val` and `s`
-                            symbol_in_val = [x for x in val if x.has(Symbol)]
-                            symbol_in_s = [x for x in s if x.has(Symbol)]
-                            del_other.append(ival)
-                            # if expression with symbols are same in `s` and `val`
-                            # then remove the non-symbol containing expressions
-                            # from `unk`, since they can not be contained
-                            if symbol_in_s == symbol_in_val:
-                                syms = FiniteSet(*symbol_in_s, evaluate=False)
-                                non_symbol_in_s = s - syms
-                                s = syms
-                                for x in non_symbol_in_s:
-                                    if x in unk:
-                                        unk.remove(x)
-                            else:
-                                fin = FiniteSet(*
-                                    [x for x in symbol_in_s
-                                    if val.contains(x) == True],
-                                    evaluate=False)
-                                if s != fin:
-                                    val = val - fin
-                                    new_other.append(val)
-
-                    for i in reversed(del_other):
-                        other_args.pop(i)
-                    other_sets = Intersection(*(other_args + new_other))
-                    if other_sets.is_EmptySet:
-                        return EmptySet()
-                    res += Intersection(
-                        s.func(*unk), other_sets, evaluate=False)
-                return res
+        # Handle Finite sets
+        rv = Intersection._handle_finite_sets(args)
+        if rv is not None:
+            return rv
 
         # If any of the sets are unions, return a Union of Intersections
         for s in args:
@@ -1789,7 +1813,7 @@ def _intersect(self, other):
         """
         if isinstance(other, self.__class__):
             return self.__class__(*(self._elements & other._elements))
-        return self.__class__(el for el in self if el in other)
+        return self.__class__(*[el for el in self if el in other])
 
     def _complement(self, other):
         if isinstance(other, Interval):


Test patch:
diff --git a/sympy/sets/tests/test_sets.py b/sympy/sets/tests/test_sets.py
index a61faac626..cc9065d56c 100644
--- a/sympy/sets/tests/test_sets.py
+++ b/sympy/sets/tests/test_sets.py
@@ -222,6 +222,7 @@ def test_intersect():
     assert Interval(0, 2).intersect(Union(Interval(0, 1), Interval(2, 3))) == \
         Union(Interval(0, 1), Interval(2, 2))
 
+    assert FiniteSet(1, 2)._intersect((1, 2, 3)) == FiniteSet(1, 2)
     assert FiniteSet(1, 2, x).intersect(FiniteSet(x)) == FiniteSet(x)
     assert FiniteSet('ham', 'eggs').intersect(FiniteSet('ham')) == \
         FiniteSet('ham')
@@ -919,7 +920,7 @@ def test_issue_Symbol_inter():
     assert Intersection(FiniteSet(m, n, x), FiniteSet(m, z), r) == \
         Intersection(r, FiniteSet(m, z), FiniteSet(n, x))
     assert Intersection(FiniteSet(m, n, 3), FiniteSet(m, n, x), r) == \
-        Intersection(r, FiniteSet(x), FiniteSet(3, m, n), evaluate=False)
+        Intersection(r, FiniteSet(3, m, n), evaluate=False)
     assert Intersection(FiniteSet(m, n, 3), FiniteSet(m, n, 2, 3), r) == \
         Union(FiniteSet(3), Intersection(r, FiniteSet(m, n)))
     assert Intersection(r, FiniteSet(mat, 2, n), FiniteSet(0, mat, n)) == \