# 38. Python zip and unzip

Here are Python code snippets demonstrating how to use `zip` to combine iterables and how to use `*` for unpacking, including examples of both zipping and unzipping iterables:

***

**1. Basic Usage of `zip`**

This example shows how to zip two lists together.

```python
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']

zipped = zip(list1, list2)
zipped_list = list(zipped)
print(zipped_list)
# Output: [(1, 'a'), (2, 'b'), (3, 'c')]
```

***

**2. Unzipping with `zip` and `*`**

To unzip, you can use the `*` operator with `zip` to unpack the elements back into separate lists.

```python
zipped_list = [(1, 'a'), (2, 'b'), (3, 'c')]
unzipped = zip(*zipped_list)

list1, list2 = list(unzipped)
print(list1)  # Output: [1, 2, 3]
print(list2)  # Output: ['a', 'b', 'c']
```

***

**3. Zipping Multiple Iterables**

You can zip more than two iterables together.

```python
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
list3 = [True, False, True]

zipped = zip(list1, list2, list3)
zipped_list = list(zipped)
print(zipped_list)
# Output: [(1, 'a', True), (2, 'b', False), (3, 'c', True)]
```

***

**4. Zipping with Different Lengths**

If the iterables have different lengths, `zip` will stop when the shortest iterable is exhausted.

```python
list1 = [1, 2, 3]
list2 = ['a', 'b']

zipped = zip(list1, list2)
zipped_list = list(zipped)
print(zipped_list)
# Output: [(1, 'a'), (2, 'b')]
```

***

**5. Using `zip` with Dictionaries**

You can use `zip` to create dictionaries by pairing keys and values.

```python
keys = ['name', 'age', 'city']
values = ['Alice', 30, 'New York']

zipped_dict = dict(zip(keys, values))
print(zipped_dict)
# Output: {'name': 'Alice', 'age': 30, 'city': 'New York'}
```

***

**6. Zipping and Unzipping with Iterables of Unequal Lengths**

You can handle unequal lengths by using `itertools.zip_longest` for zipping.

```python
import itertools

list1 = [1, 2, 3]
list2 = ['a', 'b']

zipped = itertools.zip_longest(list1, list2, fillvalue=None)
zipped_list = list(zipped)
print(zipped_list)
# Output: [(1, 'a'), (2, 'b'), (3, None)]
```

***

**7. Unzipping and Repacking**

This shows how you can unzip a zipped iterable and then repack it.

```python
zipped_list = [(1, 'a'), (2, 'b'), (3, 'c')]
unzipped = zip(*zipped_list)
list1, list2 = list(unzipped)

repacked = list(zip(list1, list2))
print(repacked)
# Output: [(1, 'a'), (2, 'b'), (3, 'c')]
```

***

**8. Unpacking with `*` to Pass Arguments**

You can use `*` to unpack a zipped object and pass it as arguments to a function.

```python
def print_info(id, name):
    print(f"ID: {id}, Name: {name}")

data = [(1, 'Alice'), (2, 'Bob'), (3, 'Charlie')]

for item in data:
    print_info(*item)
# Output:
# ID: 1, Name: Alice
# ID: 2, Name: Bob
# ID: 3, Name: Charlie
```

***

**9. Zipping with a Custom Function**

You can also zip elements from multiple iterables using a custom function to combine them.

```python
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']

zipped = zip(list1, list2)
combined = [f"Number: {num}, Letter: {letter}" for num, letter in zipped]
print(combined)
# Output: ['Number: 1, Letter: a', 'Number: 2, Letter: b', 'Number: 3, Letter: c']
```

***

**10. Zipping and Iterating with Indices**

You can use `zip` with `enumerate` to iterate over elements with indices.

```python
list1 = ['apple', 'banana', 'cherry']
list2 = ['red', 'yellow', 'dark red']

zipped = zip(list1, list2)
for idx, (fruit, color) in enumerate(zipped):
    print(f"{idx}: {fruit} is {color}")
# Output:
# 0: apple is red
# 1: banana is yellow
# 2: cherry is dark red
```

***

These code snippets provide a variety of use cases for `zip` and unpacking with `*`, showing how to combine iterables, handle different lengths, and use the zipped data in different contexts.
