# one element is missing while using Set( generator )

In the code:

import itertools
Q = [[factor(5),factor(13)],[factor(9),factor(7),factor(13)]]
print( sorted( Set( [lcm(*qq) for qq in itertools.product(*Q)] ) ) )
print( sorted( Set( (lcm(*qq) for qq in itertools.product(*Q)) ) ) )

first print produces the expected result:

[13, 5 * 7, 3^2 * 5, 5 * 13, 7 * 13, 3^2 * 13]

while second print produces a list with one element (3^2 * 5) missing:

[13, 5 * 7, 5 * 13, 7 * 13, 3^2 * 13]

Is this a bug?

ADDED. More weirdness - the following code

Q = [[factor(3), factor(9)], [factor(9), factor(7)]]
print( Set( [lcm(*qq) for qq in itertools.product(*Q)] ) )

produces a set with two equal elements:

Set of elements of [3^2, 3 * 7, 3^2, 3^2 * 7]

It seems that these issues are related to the fact that Factorization objects are not hashable, but why then does Set allow to create sets of them with all kinds of side effects?

edit retag close merge delete

1

and Set is not working correctly with iterators

( 2022-05-01 21:28:06 +0200 )edit
1

I can't reproduce the first issue: I get the same answer — the first one — when I repeat the print command. What version of Sage are you using? I agree with the final question: Set allows nonhashable objects, and it doesn't look very good, for example Set([[3,4], [3,4]]).

( 2022-05-01 23:09:01 +0200 )edit

I use Sage 9.5 The first issue can be also seen in SageMathCell: https://sagecell.sagemath.org/?q=ainesk

( 2022-05-02 00:00:07 +0200 )edit

Maybe it's been fixed in the 9.6 prerelease.

( 2022-05-02 06:34:59 +0200 )edit

I can reproduce with 9.6.rc3 on macOS 12.3.1

( 2022-05-02 08:35:56 +0200 )edit

Sort by » oldest newest most voted

The problem is this block in the code for Set:

try:
X = frozenset(X)
except TypeError:
return Set_object(X)
else:
return Set_object_enumerated(X)

When the entries of X are not hashable, frozenset(X) fails, but it still accesses X. If X is a generator, you lose the first entry of X this way. This change in sage.set.set might fix it:

diff --git a/src/sage/sets/set.py b/src/sage/sets/set.py
--- a/src/sage/sets/set.py
+++ b/src/sage/sets/set.py
@@ -198,11 +198,12 @@ def Set(X=None):
raise TypeError("Element has no defined underlying set")

try:
-        X = frozenset(X)
+        Y = list(X)
+        Y = frozenset(Y)
except TypeError:
-        return Set_object(X)
+        return Set_object(Y)
else:
-        return Set_object_enumerated(X)
+        return Set_object_enumerated(Y)

class Set_base():
more

Would there be an unneeded overhead for hashavle elements? Given various side effects, I'd rather disable Sets for unhashable elements.

( 2022-05-02 21:40:50 +0200 )edit

After the initial construction (this try ... except block), a Set with hashable elements will be an instance of a different Python class than one with unhashable elements, so I hope there is no overhead.

( 2022-05-02 21:52:32 +0200 )edit

Creating a list before creating a frozenset brings an overhead. Think about an object with millions of elements.

( 2022-05-02 22:00:15 +0200 )edit

As far as I can tell, there is no way to access an iterator without modifying it, and frozenset(X) accesses it. I don't know another way to keep the current functionality and also to fix this bug. Users can explicitly import and call Set_object_enumerated on frozenset(X) if they know that will work.

( 2022-05-02 23:37:22 +0200 )edit

Should I open a ticket for this issue?

( 2022-05-24 04:46:43 +0200 )edit

## Stats

Seen: 119 times

Last updated: May 02 '22