import itertools

# 1. count() - Infinite iterator that counts from a given number
def test_count():
    count_iter = itertools.count(5, 2)  # Start at 5, increment by 2
    count_list = [next(count_iter) for _ in range(5)]  # Get the first 5 numbers
    print("count:", count_list)

# 2. cycle() - Infinite iterator that repeats the input iterable
def test_cycle():
    cycle_iter = itertools.cycle([1, 2, 3])  # Repeats the list [1, 2, 3]
    cycle_list = [next(cycle_iter) for _ in range(7)]  # Get 7 elements
    print("cycle:", cycle_list)

# 3. repeat() - Repeats an element a fixed number of times
def test_repeat():
    repeat_iter = itertools.repeat("hello", 3)  # Repeat "hello" 3 times
    repeat_list = list(repeat_iter)
    print("repeat:", repeat_list)

# 4. permutations() - Generates all permutations of an iterable
def test_permutations():
    perm_iter = itertools.permutations([1, 2, 3])  # Generate permutations of [1, 2, 3]
    perm_list = list(perm_iter)
    print("permutations:", perm_list)

# 5. combinations() - Generates all combinations of an iterable of a given length
def test_combinations():
    comb_iter = itertools.combinations([1, 2, 3], 2)  # 2-combinations of [1, 2, 3]
    comb_list = list(comb_iter)
    print("combinations:", comb_list)

# 6. product() - Computes the cartesian product of input iterables
def test_product():
    prod_iter = itertools.product([1, 2], ['a', 'b'])  # Cartesian product of [1, 2] and ['a', 'b']
    prod_list = list(prod_iter)
    print("product:", prod_list)

# 7. combinations_with_replacement() - Generates combinations with replacement
def test_combinations_with_replacement():
    comb_with_replacement_iter = itertools.combinations_with_replacement([1, 2, 3], 2)  # 2-combinations with replacement
    comb_with_replacement_list = list(comb_with_replacement_iter)
    print("combinations_with_replacement:", comb_with_replacement_list)

# 8. chain() - Combines multiple iterables into a single iterable
def test_chain():
    chain_iter = itertools.chain([1, 2], ['a', 'b'])  # Chain [1, 2] and ['a', 'b']
    chain_list = list(chain_iter)
    print("chain:", chain_list)

# 9. islice() - Iterates over a range of elements (like slicing)
def test_islice():
    islice_iter = itertools.islice([1, 2, 3, 4, 5], 1, 4)  # Slice from index 1 to 3
    islice_list = list(islice_iter)
    print("islice:", islice_list)

# 10. starmap() - Applies a function to items in iterables
def test_starmap():
    starmap_iter = itertools.starmap(lambda x, y: x + y, [(1, 2), (3, 4), (5, 6)])  # Add pairs of numbers
    starmap_list = list(starmap_iter)
    print("starmap:", starmap_list)

# 11. groupby() - Groups consecutive elements in an iterable by a key
def test_groupby():
    group_iter = itertools.groupby([1, 1, 2, 3, 3, 3, 4])  # Group consecutive elements
    group_list = [(key, list(group)) for key, group in group_iter]
    print("groupby:", group_list)

# 12. tee() - Returns multiple iterators from a single iterable
def test_tee():
    tee_iter1, tee_iter2 = itertools.tee([1, 2, 3], 2)  # Create two iterators
    tee_list1 = list(tee_iter1)
    tee_list2 = list(tee_iter2)
    print("tee:", tee_list1, tee_list2)

# 13. filterfalse() - Returns elements from the iterable for which the predicate is False
def test_filterfalse():
    filterfalse_iter = itertools.filterfalse(lambda x: x % 2 == 0, [1, 2, 3, 4, 5])  # Filter out even numbers
    filterfalse_list = list(filterfalse_iter)
    print("filterfalse:", filterfalse_list)

# 14. compress() - Filters elements from an iterable based on a selector
def test_compress():
    compress_iter = itertools.compress([1, 2, 3, 4, 5], [1, 0, 1, 0, 1])  # Keep elements where selector is 1
    compress_list = list(compress_iter)
    print("compress:", compress_list)

# 15. dropwhile() - Drops elements from the iterable until the predicate is False
def test_dropwhile():
    dropwhile_iter = itertools.dropwhile(lambda x: x < 3, [1, 2, 3, 4, 5])  # Drop elements until 3 is reached
    dropwhile_list = list(dropwhile_iter)
    print("dropwhile:", dropwhile_list)

# 16. takewhile() - Takes elements from the iterable while the predicate is True
def test_takewhile():
    takewhile_iter = itertools.takewhile(lambda x: x < 3, [1, 2, 3, 4, 5])  # Take elements while less than 3
    takewhile_list = list(takewhile_iter)
    print("takewhile:", takewhile_list)

# Test all the functions
def run_tests():
    test_count()
    test_cycle()
    test_repeat()
    test_permutations()
    test_combinations()
    test_product()
    test_combinations_with_replacement()
    test_chain()
    test_islice()
    test_starmap()
    test_groupby()
    test_tee()
    test_filterfalse()
    test_compress()
    test_dropwhile()
    test_takewhile()

# Run the tests
run_tests()