6.2 Sets

A set stores each distinct value once. A set of allowed team names lets a program check whether a name is recognized. A set of order identifiers lets a program check whether an identifier has already appeared.

Sets are unordered collections. They do not retain element positions or insertion order, so indexing and slicing are unavailable. The printed order can vary. Keep a list when the sequence or number of repeated observations matters.

6.2.1 Creating sets and checking membership

Create a nonempty set by enclosing elements in curly braces {} and separating them with commas. Use set() for an empty set. Empty braces {} create a different collection type, a dictionary, which is introduced later. Table 1 shows sets containing integers, strings, and mixed values.

Table 1: Examples of sets in Python.
Set example Description
{1, 2, 3, 4, 5} A set of integers.
{"apple", "banana", "cherry"} A set of strings.
{1, "apple", 2, "banana"} A mixed set of integers and strings.
set() An empty set.

Set elements must be hashable. A hash value is an integer Python uses internally to locate a value. Hashable values have a stable hash value and consistent equality comparisons. Strings and integers meet that requirement; lists and mutable sets do not. A tuple is hashable when all its elements are hashable. The Python set documentation defines these requirements and the absence of positional order.

The membership operators in and not in return True or False. Listing 1 checks team names against a set of allowed names. The spelling and capitalization of each string must match.

Listing 1: Check membership in a set of allowed team names.
allowed_teams = {'Assembly', 'Packing'}
print('Assembly' in allowed_teams)
print('Dispatch' in allowed_teams)
print('Dispatch' not in allowed_teams)
print(len(allowed_teams))

The results appear in Listing 2. len() counts distinct elements in the set.

Listing 2: Membership results and the number of allowed team names.
True
False
True
2

6.2.2 Adding and removing elements

Sets are mutable, so their contents can change. add() adds one element; adding an element already present leaves the set unchanged. remove() removes an element and raises KeyError if it is absent. discard() removes an element if present and otherwise leaves the set unchanged.

Listing 3 separates the value returned by add() from the changed set. add(), remove(), discard(), and clear() return None. Assigning their return value to the variable holding the set would lose that variable's reference to the set.

Listing 3: Change a set and inspect the returned value separately from its contents.
teams = {'Assembly', 'Packing'}
returned = teams.add('Assembly')
print('Return value:', returned)
print('Distinct teams:', len(teams))

teams.add('Dispatch')
teams.remove('Packing')
teams.discard('Unknown')
print('Teams after changes:', sorted(teams))

sorted() returns a sorted list for a predictable display. The set itself still has no positional order. Listing 4 shows the results.

Listing 4: Output from adding an existing team, adding Dispatch, and removing Packing.
Return value: None
Distinct teams: 2
Teams after changes: ['Assembly', 'Dispatch']

6.2.3 Transforming lists to sets

The set() constructor creates a new set from the elements of a list, retaining each distinct element once. The original list is unchanged. Listing 5 converts a list of numbers to a set.

Listing 5: Converting a list of numbers to a set containing each distinct number once. The printed order is unspecified.
numbers = [1, 2, 3, 4, 5, 1, 2, 3]
unique_numbers = set(numbers)

print(unique_numbers)  # One possible display: {1, 2, 3, 4, 5}

Conversion is useful for identifying distinct values and applying set operations. A set alone cannot tell you how often each value occurred. Listing 6 keeps both the original observations and the set.

Listing 6: Compare observation count with distinct-value count while retaining the source list.
order_ids = ['A01', 'A02', 'A02', 'A03']
distinct_ids = set(order_ids)
print('Observations:', len(order_ids))
print('Distinct identifiers:', len(distinct_ids))
print('Original observations:', order_ids)
print('Distinct identifiers for display:', sorted(distinct_ids))

Listing 7 shows that there are four observations and three distinct identifiers. Their counts differ because A02 occurs twice.

Listing 7: Counts and values before and after converting order identifiers to a set.
Observations: 4
Distinct identifiers: 3
Original observations: ['A01', 'A02', 'A02', 'A03']
Distinct identifiers for display: ['A01', 'A02', 'A03']

6.2.4 Checking allowed values and duplicate identifiers

An allowed-value check tests whether a value belongs to the agreed set. Listing 8 checks a supplied list of team names. The output keeps each observation, including the repeated Assembly name. An unrecognized name is reported for review; the original list remains available.

Listing 8: Check each observed team name against Assembly and Packing without removing observations.
allowed_teams = {'Assembly', 'Packing'}
team_names = ['Assembly', 'Packing', 'Dispatch', 'Assembly']

for team in team_names:
    print(team, 'recognized:', team in allowed_teams)

The results appear in Listing 9.

Listing 9: Results for all four team observations, including the unrecognized Dispatch name.
Assembly recognized: True
Packing recognized: True
Dispatch recognized: False
Assembly recognized: True

An identifier is a value used to distinguish one item from another. If each order should appear once, a repeated order identifier needs investigation. The set seen_ids in Listing 10 records the identifiers encountered so far. Check membership before adding the current identifier.

Listing 10: Report repeated order identifiers while retaining the original list of observations.
order_ids = ['A01', 'A02', 'A02', 'A03', 'A01']
seen_ids = set()

for order_id in order_ids:
    if order_id in seen_ids:
        print('Repeated identifier:', order_id)
    seen_ids.add(order_id)

print('Observations:', len(order_ids))
print('Distinct identifiers:', len(seen_ids))

The first A01 and A02 are added to the set when first encountered. Their later occurrences are already present and are reported in Listing 11. The membership check reports a repeated identifier; deciding how to correct the source data requires the meaning of the observations. Keep the original observations until that decision is made.

Listing 11: Repeated identifiers and counts for the five supplied order observations.
Repeated identifier: A02
Repeated identifier: A01
Observations: 5
Distinct identifiers: 3

6.2.5 Finding common values

An intersection contains the values present in both sets. For example, two teams may have lists of order identifiers to review. Their intersection identifies the orders appearing on both lists. The intersection() method returns a new set and leaves both input sets unchanged. Listing 12 finds the common identifiers.

Listing 12: Find order identifiers present on both review lists.
assembly_ids = ['A03', 'A01', 'A03', 'A02']
packing_ids = ['A02', 'A03', 'A04']
assembly_set = set(assembly_ids)
packing_set = set(packing_ids)
common_ids = assembly_set.intersection(packing_set)

print('Common identifiers:', sorted(common_ids))
print('Assembly observations:', assembly_ids)

Listing 13 shows the common identifiers in sorted order for display. Repeated observations remain in the source lists.

Listing 13: Common identifiers and the unchanged Assembly review list.
Common identifiers: ['A02', 'A03']
Assembly observations: ['A03', 'A01', 'A03', 'A02']

A required output order needs a separate rule. To return common identifiers in their first-occurrence order on the Assembly list, scan that list from left to right. Use the Packing set to check membership and a second set to record identifiers already added to the result. Append each qualifying identifier once to a result list. For these observations, that rule gives ['A03', 'A02']. Iterating over the intersection set would not preserve that order.

6.2.6 Sorting distinct values

sorted() can turn a set into a list in ascending order. For numbers, ascending order places smaller values before larger ones. Converting the observations to a set removes repetitions; sorting specifies the output order. Both operations create new collections.

Some tasks also restrict the values to include. In Listing 14, each row contains observations, and only integers from 1 through 10, inclusive, qualify. type(value) is int checks for an integer exactly. Boolean values such as True have type bool and are excluded by that check. The nested loops visit every value in every row before the distinct qualifying values are sorted.

Listing 14: Collect and sort distinct integer observations in the inclusive range from 1 through 10.
observations = [[4, 6, 4], [0, 11, 1], [True, 8]]
accepted = set()

for row in observations:
    for value in row:
        if type(value) is int and 1 <= value <= 10:
            accepted.add(value)

print('Accepted values:', sorted(accepted))
print('Original observations:', observations)

Listing 15 shows that the repeated 4 appears once, the out-of-range values are excluded, and the rows remain unchanged.

Listing 15: Sorted qualifying integers and the unchanged source rows.
Accepted values: [1, 4, 6, 8]
Original observations: [[4, 6, 4], [0, 11, 1], [True, 8]]

6.2.7 Further study

Set methods and operations

Sets provide methods for adding and removing elements, and for set operations such as union, intersection, and difference. Table 2 lists the methods for s = {1, 2, 3, 4, 5}. Each row starts with a fresh set, so the rows are independent. A displayed set lists its elements without promising their printed order.

Table 2: Set methods and operations. Each row independently starts with s = {1, 2, 3, 4, 5}; set display order is unspecified.
Set method Description Example Return value or resulting elements
set.add(element) Adds an element to the set. s.add(6) Returns None; s becomes {1, 2, 3, 4, 5, 6}.
set.remove(element) Removes an element. Raises KeyError if absent. s.remove(3) Returns None; s becomes {1, 2, 4, 5}.
set.discard(element) Removes an element if present. An absent element causes no error. s.discard(3) Returns None; s becomes {1, 2, 4, 5}.
set.pop() Removes and returns an arbitrary element. Raises KeyError if empty. s.pop() Returns any one element, such as 1; s loses that element.
set.clear() Removes all elements. s.clear() Returns None; s becomes set().
len(set) Returns the number of elements in the set. len(s) 5
element in set Returns True if the element is present in the set, False otherwise. 2 in s True
set.union(other) Returns a new set with elements from both sets. s.union({4, 5, 6}) {1, 2, 3, 4, 5, 6}
set.intersection(other) Returns a new set with elements common to both sets. s.intersection({3, 4, 5, 6}) {3, 4, 5}
set.difference(other) Returns a new set with elements in the set but not in the other set. s.difference({3, 4}) {1, 2, 5}
set.symmetric_difference(other) Returns a new set with elements in either set but not in both sets. s.symmetric_difference({4, 5, 6}) {1, 2, 3, 6}
set.issubset(other) Returns True if the set is a subset of the other set. s.issubset({1, 2, 3, 4, 5, 6}) True
set.issuperset(other) Returns True if the set is a superset of the other set. s.issuperset({1, 2, 3}) True

A union contains all elements from either set. An intersection contains elements common to both sets. A difference contains elements in the first set that are absent from the second. A symmetric difference contains elements that appear in exactly one set. These methods return a new set and leave s unchanged.

A set is a subset of another when every element of the first occurs in the second. It is a superset when it contains every element of the second. issubset() and issuperset() return Boolean values. pop() selects an arbitrary element; it has no promise of selecting the smallest, first inserted, or random element.

Set comprehension

Set comprehension creates a new set by applying an expression to each element of an iterable, such as a list or set. An optional condition selects the elements to process. Repeated resulting values appear once. Table 3 shows the expression and conditional forms.

Table 3: Examples of set comprehension in Python.
Set comprehension Description Example Resulting elements
{expression for element in iterable} Applies the expression to each element in the iterable. {x**2 for x in {1, 2, 3, 4, 5}} {1, 4, 9, 16, 25}
{expression for element in iterable if condition} Applies the expression to each element that satisfies the condition. {x**2 for x in {1, 2, 3, 4, 5} if x % 2 == 0} {4, 16}

6.2.8 Exercises

The following design exercises give optional practice in collecting distinct values and finding ordered common values.

6.2.Q1 Collect sorted distinct integer values

A table of observations is stored as a list of rows. Each row is a list containing integers or Boolean values. Design an algorithm that uses a set to collect every distinct qualifying integer, then returns those integers as a list in ascending order.

Qualifying integers are from 1 through 10, inclusive. Exclude values outside that range and exclude Boolean values. Leave every source row unchanged. Return [] if the table is empty or no values qualify. Rows may have different lengths, including zero.

The input in Listing 16 must produce [1, 3, 6, 10].

Listing 16: Rows containing repeated integers and values outside the accepted range.
data = [[10, 3, 3], [-2, 6, 12], [1, 10, 0]]

Present your algorithm in Python, pseudocode, or a flowchart. If you use Python, name the function sorted_distinct_values(data). You may use sorted(). Give checks with inputs and expected results for repetitions, range boundaries, Boolean values, and empty input.

6.2.Q2 Find common values in their original order

Two measurement lists can contain repeated numbers. Design an algorithm that receives first and second and returns a new list of the numbers present in both inputs. Use sets to check membership.

  • Each common number must appear once, in the order of its first occurrence in first.
  • Both inputs contain finite integers or decimal numbers. Compare numbers by their value; 1 and 1.0 count as the same number.
  • Leave both input lists unchanged. Return [] when either list is empty or when the lists have no common numbers.

For the inputs in Listing 17, the required result is [8, 3, 1]. The order of the second list does not determine the output order.

Listing 17: Two measurement lists with repeated numbers and different orders.
first = [8, 3, 8, 5, 1, 3]
second = [1, 8, 8, 3, 9]
  1. Present your algorithm in pseudocode, a flowchart, or Python. If you use Python, name the function ordered_common_values(first, second).
  2. Give checks with inputs and expected results for repeated numbers, empty input, and lists with no common numbers.

Use membership checks to report duplicate order identifiers in the next exercise.

6.2.Q3 Report duplicate identifiers

An order log contains the identifiers in Listing 18. Each identifier should describe one order, so repeated identifiers need review. Retain the original list and its observations.

Listing 18: Six order-log observations, including identifiers that occur repeatedly.
order_ids = ['A01', 'A02', 'A02', 'A03', 'A01', 'A02']

Write a Python function find_duplicate_ids(order_ids) that uses sets to check identifiers and returns a list of repeated identifiers. Include each repeated identifier once, in the order in which its second occurrence is encountered. For the supplied input, return ['A02', 'A01'].

Assume every identifier is a nonempty string. Compare the strings exactly. The function must leave the input list unchanged and return an empty list when no identifier repeats. Check your function with cases that cover empty input, no repeats, and one identifier appearing more than twice.

For optional tracing practice, compare the presence-array approach below with the set-based approach you used for sorted distinct values.

6.2.M1 Nested-list processing

In Listing 19, the Python function x receives a non-empty two-dimensional list of integers and returns a list.

Listing 19: A Python function that processes two-dimensional integer data (Question 6.2.M1 Nested-list processing).
def x(data):
    present = [False] * 11

    for row in data:
        for value in row:
            if type(value) is int and 1 <= value <= 10:
                present[value] = True

    result = []

    for value in range(1, 11):
        if present[value]:
            result.append(value)

    return result

Considering the two-dimensional list in Table 4, what is returned when x is called with that list?

Table 4: Input data (Question 6.2.M1 Nested-list processing).
A B C D
1 7 2 11 10
2 11 1 2 1
3 9 7 0 -1

What is the returned list?

  • [1, 2, 7, 9, 10]
  • [7, 2, 11, 10, 1, 2, 9]
  • [2, 0, 0, 0, 0, 0, 2, 0, 1, 1]
  • [7, 2, 11, -1, 10, 1, 2, 9, 7, 0, -1]
  • range(start, stop, step) supplies integers from start toward, but excluding, stop.

    • step defaults to 1, and range(stop) starts at 0.
    • list(range(2, 7, 2)) is [2, 4, 6]; list(range(5, 1, -2)) is [5, 3].
    • A range is empty when the step cannot reach the stop; a zero step raises ValueError.
  • list collects the supplied values in a new list.

  • items.append(value) adds one item at the end of the list and returns None. Starting with items = [8], items.append(3) changes it to [8, 3]. An appended list remains one nested item; an empty list accepts its first item the same way.