<jupyter_start><jupyter_text>Sequence Types Sequence types have the general concept of a first element, a second element, and so on. Basically an ordering of the sequence items using the natural numbers. In Python (and many other languages) the starting index is set to `0`, not `1`.So the first item has index `0`, the second item has index `1`, and so on.Python has built-in mutable and immutable sequence types.<jupyter_code># Iterables
# Something that can be iterated over.
astr = 'apple'
for w in astr:
    print(w)<jupyter_output>a
p
p
l
e<jupyter_text>Sequences (and even some iterables) may support max and min as long as the data types in the collection can be ordered in some sense<jupyter_code>a = [100, 300, 200]
min(a), max(a)
s = {'p', 'y', 't', 'h', 'o', 'n'}
min(s), max(s)<jupyter_output><empty_output><jupyter_text>We can concatenate sequences using the + operator:<jupyter_code>print([1, 2, 3] + [4, 5, 6])
print((1, 2, 3) + (4, 5, 6))
# Repetition
'abc' * 5
# Finding things in Sequences
s = "gnu's not unix"
try:
    idx = s.index('n', 13)
except ValueError:
    print('not found')
# Slicing
s = 'python'
l = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(s[5:8])
print(l[-5:])
# Hashing
# Immutable sequences generally support a hash method that we'll discuss in detail in the section on mapping types:
l = (1, 2, 3)
print(hash(l))
t = (1, 2, [10, 20])
print(hash(t))<jupyter_output>2528502973977326415<jupyter_text>Mutable SequencesWhen dealing with mutable sequences, we have a few more things we can do - essentially adding, removing and replacing elements in the sequence.This **mutates** the sequence. The sequence's memory address has not changed, but the internal **state** of the sequence has.<jupyter_code># Replacing Elements
l = [1, 2, 3, 4, 5]
print(id(l))
l[0] = 'a'
print(id(l), l)
# Appending and Extending
l = [1, 2, 3]
print(id(l))
l.append(4)
print(l, id(l))

print('====Extending====')
l = [1, 2, 3]
print(id(l))
l = l + [4]
print(id(l), l)
# Removing Elements
l = [1, 2, 3, 4]
print(id(l))
popped = l.pop(1)
print(id(l), popped, l)
# Inserting Elements
l = [1, 2, 3, 4]
print(id(l))
l.insert(1, 'a')
print(id(l), l)
# Reversing a Sequence
l = [1, 2, 3, 4]
print(id(l))
l.reverse()
print(id(l), l)
# Copying Sequences
l = [1, 2, 3, 4]
print(id(l))
l2 = l.copy()
print(id(l2), l2)<jupyter_output>139796420260480
139796420260240 [1, 2, 3, 4]<jupyter_text>Custom Sequences* `__getitem__` will receive either an integer (for simple indexing), or a slice object* `__len__` will return the length of a sequence.* concatenation (`+`)* in-place concatenation (`+=`)* repetition (`*`)* in-place repetition (`*=`)* index assignment (`seq[i]=val`)* slice assignment (`seq[i:j]=iter` and `seq[i:j:k]=iter`)* append, extend, in, del, pop<jupyter_code>from functools import lru_cache
# Create a fibonacci sequence
class Fib:
    def __init__(self, n):
        self.n = n
        
    def __getitem__(self, s):
        if isinstance(s, int):
            if s < 0:
                # Convert negtive number to positive number first.
                s = self.n + s
            elif s < 0 or s >= self.n:
                raise IndexError
            return Fib._fib(s)
        else:
            # Slice 
            range_tuple  = s.indices(self.n)
            rng = range(*range_tuple)
            return [Fib._fib(i) for i in rng]
    
    def __len__(self):
        return self.n
    
    def __repr__(self):
        return f'Fib({self.n})'
    
    def __add__(self, other):
        if isinstance(other, Fib):
            return [self, other]
        else:
            raise TypeError('Please add a Fib!')
    
    @staticmethod
    @lru_cache(2**50)
    def _fib(n):
        if n == 0:
            return 0
        elif n == 1:
            return 1
        else:
            return Fib._fib(n - 1) + Fib._fib(n - 2)
f = Fib(20)
f
f[10]
f[8:11]
len(f)
f2 = Fib(10)
print(f + f2)<jupyter_output>[Fib(20), Fib(10)]<jupyter_text>List Comprehensions<jupyter_code>[i ** 2 for i in range(10) if i % 2]
# We can rerange it this way to make it more readable.
[i ** 2
for i in range(10)
if i % 2]
[(a, b)
for a in range(10)
for b in range(10,21)
if b/2 == a]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1434357874672385, 'fraction_numerical': 0.04288777698355969, 'mean_word_length': 3.2968270214943707, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 22, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '660761', 'license': 'MIT', 'n_tokens_mistral': 1691, 'n_tokens_neox': 1528, 'n_words': 526, 'path': 'deep_dive/2_iteration_generators/0_sequences.ipynb', 'repo_name': 'UpcaseM/Learning_PY_hardway', 'size': '4197'}