<jupyter_start><jupyter_text>Tuples_Tuples_ are a sequential data structure. They are immutable and their items can be accessed by index (order is preserved).<jupyter_code>tuple(())
type((1)) # pay attention here!
type((1,))
point = 0, 1
x, y = point
date = "April", 2
point = (5, 10)
_, y = point
y
point[0]<jupyter_output><empty_output><jupyter_text>⚠️ _Searching for an item in a large tuple is slow. Each item must be checked._<jupyter_code>animals = ("tiger", 2, "cat", 3, "wolf", 1)
animals.index("cat")
animals.count("wolf")<jupyter_output><empty_output><jupyter_text>ℹ️ _Parentheses are not required but can improve the code readability:_<jupyter_code># Bad
xs = 42,
x, = xs
x
# God
xs = (42, )
[x] = xs
x<jupyter_output><empty_output><jupyter_text>Slices<jupyter_code>person = ("Guido", "<NAME>", "January", 31, 1956)
name, birthday = person[:2], person[2:]
name
birthday<jupyter_output><empty_output><jupyter_text>Named Slices 💡<jupyter_code>NAME, BIRTHDAY = slice(2), slice(2, None)
person[NAME]
person[BIRTHDAY]<jupyter_output><empty_output><jupyter_text>Reversing[Why is reversing a list with slicing slower than reverse iterator](https://stackoverflow.com/questions/16465046/why-is-reversing-a-list-with-slicing-slower-than-reverse-iterator)<jupyter_code>tuple(reversed((1, 2, 3)))
(1, 2, 3)[::-1]<jupyter_output><empty_output><jupyter_text>Concatenation<jupyter_code>xs, ys = (1, 2), (3, )
id(xs), id(ys)
# Tuples are copied when we concatenate them
id(xs + ys)<jupyter_output><empty_output><jupyter_text>Comparisonℹ️ _Tuples and lists are **compared lexicographically** using comparison of corresponding elements._<jupyter_code>(1, 2, 3) < (1, 2, 4)
(1, 2, 3, 4) < (1, 2, 4)
(1, 2) < (1, 2, 42)
(None, 1) < (2, 4)<jupyter_output><empty_output><jupyter_text>collections.namedtuple<jupyter_code>from collections import namedtuple
Person = namedtuple("Person", ["name", "age"])
p = Person("Guido", age=64)
p._fields
p._asdict()
p._replace(name="Andrey")<jupyter_output><empty_output><jupyter_text>Lists<jupyter_code>[1, 2, 3, 4]
["a", "b", "c", "d"]<jupyter_output><empty_output><jupyter_text>Initialization<jupyter_code>[0] * 2
[""] * 2<jupyter_output><empty_output><jupyter_text>⚠️ _Initial value is not copied:_<jupyter_code>chunks = [[0]] * 2  # matrix 2x1 with zero values
chunks
chunks[0][0] = 42
chunks<jupyter_output><empty_output><jupyter_text>ℹ️ _This problem can pops up on the tech interview._ Better way – **use list comprehensions**:<jupyter_code>chunks = [[0] for _ in range(2)]
chunks
chunks[0][0] = 42
chunks<jupyter_output><empty_output><jupyter_text>Appending and extending<jupyter_code>xs = [1, 2, 3]
xs.append(42)
xs
xs.extend({-1, -2})
xs<jupyter_output><empty_output><jupyter_text>InsertionInsert is a relatively slow operation. On practice would be better to add an item to the end of list and sort the whole container.<jupyter_code>xs = [1, 2, 3]
xs.insert(0, 4)  # where 4 is an index
xs
xs.insert(-1, 42)
xs<jupyter_output><empty_output><jupyter_text>Changing sublist in-place<jupyter_code>xs = [1, 2, 3]
xs[:2] = [0] * 2
xs<jupyter_output><empty_output><jupyter_text>Concatenation<jupyter_code>xs, ys = [1, 2], [3]
id(xs), id(ys)
id(xs + ys)<jupyter_output><empty_output><jupyter_text>_In-place_ concatenation:<jupyter_code>xs += ys  # xs = xs.extend(ys)
id(xs)<jupyter_output><empty_output><jupyter_text>Reasons against `+=` Example 1:<jupyter_code>xs = []
def f():
    xs += [42]
f()<jupyter_output><empty_output><jupyter_text>Example 2:<jupyter_code>xs = []
xs += "abc"
xs<jupyter_output><empty_output><jupyter_text>`extend` accept an iterable object. Therefore this would work.  Removal<jupyter_code>xs = [1, 2, 3]
del xs[:2]  # del works also with slices
xs
xs = [1, 2, 3]
del xs[:]
xs
xs = [1, 2, 3]
xs.pop(1)  # returns removed value
xs
xs = [1, 1, 0]
xs.remove(1)  # removes first element occurrence
xs<jupyter_output><empty_output><jupyter_text>Reversing<jupyter_code>list(reversed([1, 2, 3]))
xs = [1, 2, 3]
xs.reverse()  # in-place operation, returns None
xs<jupyter_output><empty_output><jupyter_text>Sortinghttps://bugs.python.org/file4451/timsort.txt<jupyter_code>xs = [3, 2, 1]
sorted(xs), xs
xs.sort()  # in-place operation, returns None
xs
xs = [3, 2, 1]
xs.sort(key=lambda x: x % 2, reverse=True)
xs<jupyter_output><empty_output><jupyter_text>Stack, Queue<jupyter_code>stack = []
stack.append(1)
stack.append(2)
stack
q = []
q.append(1)
q.append(2)
q.pop(0)  # warn: copies the whole list!
q<jupyter_output><empty_output><jupyter_text>Deque<jupyter_code>from collections import deque
q = deque([1, 2, 3])
q.appendleft(0)
q
q.append(4)
q
q.popleft()
q[0]<jupyter_output><empty_output><jupyter_text>---<jupyter_code>q = deque([1, 2], maxlen=2)
q.appendleft(0)
q
q.append(2)
q<jupyter_output><empty_output><jupyter_text>SetsA set is an **unordered** collection **without duplicate elements**. It allows to test item membership very fast, but doesn't support indexing.<jupyter_code>letters = set('somerandomstringwithrepeatableletters')
letters
xs, ys, zs = {1, 2}, {2, 3}, {3, 4}
set.union(xs, ys, zs)  # xs | ys | zs
set.intersection(xs, ys, zs)  # xs & ys & zs
set.difference(xs, ys, zs)  # xs - ys - zs
xs.isdisjoint(ys)
xs <= ys  # xs ⊆ ys
xs < xs  # xs ⊂ xs
xs | ys >= xs  # xs ∪ ys ⊇ xs<jupyter_output><empty_output><jupyter_text>Adding to a set and updating<jupyter_code>seen = set()
seen.add(42)  # adds a single element into the set
seen
seen.update([1, 2])  # adds sequence of elements into the set
seen
seen.update([], [1], [2], [3])
seen<jupyter_output><empty_output><jupyter_text>Removal<jupyter_code>seen = {1, 2, 3}
seen.remove(3)
seen
seen.remove(100500)
seen.discard(100500)
seen
seen.clear()
seen<jupyter_output><empty_output><jupyter_text>frozensetℹ️ Sets in Python are hash sets. It means it can contain only hashable objects.<jupyter_code>{set(), set()}
{frozenset(), frozenset()}<jupyter_output><empty_output><jupyter_text>`frozenset` supports all operations around sets except addition and removal.  DictionariesDicts and sets are **not ordered** data structures. They are not guarantee an order in which elements will be produced while iterating over them._Dict's retaining insertion order is guaranteed for Python 3.7._* [Are dictionaries ordered in Python 3.6+?](https://stackoverflow.com/questions/39980323/are-dictionaries-ordered-in-python-3-6?rq=1)* [[Python-Dev] Guarantee ordered dict literals in v3.7?](https://mail.python.org/pipermail/python-dev/2017-December/151283.html)* [[Python-Dev] More compact dictionaries with faster iteration](https://mail.python.org/pipermail/python-dev/2012-December/123028.html)* [[Python-Dev] Python 3.6 dict becomes compact and gets a private version; and keywords become ordered](https://mail.python.org/pipermail/python-dev/2016-September/146327.html)⚠️ Dictionary keys can only be **immutable** (TypeError: unhashable type).ℹ️ Looking for a key in a large dictionary is extremely fast.<jupyter_code>type({})
d = dict(foo="bar")
dict(d)  # (shallow) a copy
dict(d, boo="baz")  # copy the dict with adding a new key
dict.fromkeys(["foo", "bar"])
dict.fromkeys("abcd", 0)  # warn: creates a dict from an iterable object<jupyter_output><empty_output><jupyter_text>💥 Dictionary creation with `fromkeys` and mutable objects:<jupyter_code>d = dict.fromkeys("abcd", [])
d
d["a"].append(x)
d<jupyter_output><empty_output><jupyter_text>💡 Use list comprehensions:<jupyter_code>d = {ch: [] for ch in "abcd"}
d
d["a"].append(x)
d<jupyter_output><empty_output><jupyter_text>Keys and values<jupyter_code>d = dict.fromkeys(["foo", "bar"], 42)
d.keys()
d.values()
d.items()
len(d.items())
42 in d.values()<jupyter_output><empty_output><jupyter_text>Additionally, keys support some set operations:<jupyter_code>d.keys() & {"foo"}<jupyter_output><empty_output><jupyter_text>Iterating through a dict<jupyter_code>{v for v in d.values()}<jupyter_output><empty_output><jupyter_text>⚠️ We can't modify a dict during iteration:<jupyter_code>for k in d:
    del d[k]<jupyter_output><empty_output><jupyter_text>💡 Workaround:<jupyter_code>for k in set(d):
    del d[k]
d<jupyter_output><empty_output><jupyter_text>Getting a value<jupyter_code>d = {"foo": "bar"}
d["foo"]
d["boo"]
d.get("boo", 42)<jupyter_output><empty_output><jupyter_text>It's much better (even performance-wise) than doing something like this:<jupyter_code>if "boo" not in d:
    value = 42
else:
    value = d["boo"]<jupyter_output><empty_output><jupyter_text>Adding to a dict and updating<jupyter_code>d["fizz"] = "buzz"
d<jupyter_output><empty_output><jupyter_text>[setdefault(key[, default])](https://docs.python.org/3/library/stdtypes.htmldict.setdefault)If _key_ is in the dictionary, return its value. If not, insert _key_ with a value of _default_ and return _default_. _default_ defaults to `None`.<jupyter_code>d = {"foo": "bar"}
d.setdefault("foo", "???")
d.setdefault("boom", 42)
d<jupyter_output><empty_output><jupyter_text>---<jupyter_code>d = {}
d.update([("foo", "bar")])
d.update(boo=42)
d
d = {}
# or
d.update([("foo", "bar")], boo=42)
d<jupyter_output><empty_output><jupyter_text>Removal<jupyter_code>del d["boo"]
d
d.pop("foo")
d
d["what"] = "?"
d
d.clear()
d<jupyter_output><empty_output><jupyter_text>Merge two dicts<jupyter_code>x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
x.update(y)
x<jupyter_output><empty_output><jupyter_text>Python 3.5+<jupyter_code>x = {'a': 1, 'b': 2}
y = {'b': 3, 'c': 4}
z = {**x, **y}
z<jupyter_output><empty_output><jupyter_text>collections.defaultdict 💡 _It can be very helpful for working with graphs._<jupyter_code>g = {"a": {"b"}, "b": {"c"}}
g["a"]<jupyter_output><empty_output><jupyter_text>How we can add graph edges `("b", "a")` and `("c", "a")`?<jupyter_code>g["b"].add("a")
g["c"].add("a")<jupyter_output><empty_output><jupyter_text>Using `defaultdict`:<jupyter_code>from collections import defaultdict
g = defaultdict(set, **{"a": {"b"}, "b": {"c"}})
g
g["c"].add("a")
g<jupyter_output><empty_output><jupyter_text>collections.OrderedDict 🆕 As of Python 3.7, a new improvement is:> the insertion-order preservation nature of dict objects has been declared to be an official part of the Python language spec.This means there is no real need for `OrderedDict` anymore. They are **almost** the same.In Python 3.8, `dict` and `dictviews` are now _iterable_ in reversed insertion order using `reversed()`. `move_to_end` is the last major difference between `dict` and `OrderedDict`.  collections.Counter[PyMOTW-3: Counter — Count Hashable Objects](https://pymotw.com/3/collections/counter.html)> A Counter is a container that keeps track of how many times equivalent values are added. It can be used to implement the same algorithms for which other languages commonly use bag or multiset data structures.<jupyter_code>from collections import Counter
c = Counter(["foo", "foo", "foo", "bar"])
c["foo"] + 1
c<jupyter_output><empty_output><jupyter_text>`Counter` supports all dictionary methods as well as implementing a few additional methods:<jupyter_code>c.pop("foo")
c["boo"]  # doesn't raise an exception
c = Counter(foo=4, bar=-1)
list(c.elements())
c.most_common(1)
c.update(["bar"])
c
c.subtract({"foo": 2})
c<jupyter_output><empty_output><jupyter_text>---<jupyter_code>c1 = Counter(foo=4, bar=-1)
c2 = Counter(foo=2,  bar=2)
c1 + c2  # c1[k] + c2[k]
c1 - c2  # c1[k] - c2[k]
c1 & c2  # min(c1[k], c2[k])
c1 | c2  # max(c1[k], c2[k])<jupyter_output><empty_output><jupyter_text>Arrays- Numerical data types- Must all be the same typevs Lists:- Store anything- Store any type<jupyter_code>from array import array

scores = array('d')
scores.append(97)
scores.append(98)
scores
scores[1]<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.158749248346362, 'fraction_numerical': 0.026458208057727, 'mean_word_length': 6.7665110073382255, 'pattern_counts': {'":': 7, '<': 220, '<?xml version=': 0, '>': 217, 'https://': 8, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '53', 'dir': 'jupyter-structured-clean-dedup', 'id': '409829', 'license': 'MIT', 'n_tokens_mistral': 4816, 'n_tokens_neox': 4549, 'n_words': 1078, 'path': '04_container_datatypes_and_collections.ipynb', 'repo_name': 'akrisanov/python_notebook', 'size': '11645'}