<jupyter_start><jupyter_text>Fundamentos de Organización de Datos Propuesta de Cátedra  IntroducciónEl principal eje propuesto en este documento trata de presentar un enfoque incremental para la organización de información en memoria secundaria. El principal hilo consta del siguiente flujo- Serialización: Como transformamos la data en una forma normal y distribuible independiente del lenguaje y de la memoria princiapl  - JSON  - XML  - YAML  - Struct  - Pickle- Archivos estructurados: Dado una fuerte serialización binaria estructurada, se propone implementar un mecanismo de archivos de longitud fija para almacenar la data serializada. Y el mecanismo inverso- Archivos dinámicos: Dado un buen esquema estructurado se propone un esquema de longitud variable para almacenar la información de forma binaria- Archivos de Texto: En un enfoque de colavoración se aplican tecnicas de serialización a strings en archivos normalizados entendibles por el usuario- Aplicaciones en el mundo real  - API  - Configuraciones  - Reportes  SerializaciónLa serialización *(serialization)* es el proceso de transformar las estructuras de datos en un formato que puede ser guardado o transmitido y luego reconstruido.El proceso contrario, de extraer la estructura de datos a partir de una secuencia de bytes, se lo conoce como deserialización *(deserialization/unmarshalling)*Un serializador puede estar descompuesto en dos pasos:- Normalización: Transformar la data en una estructura normal de tipo `(clave, valor)`.- Encodificación: Transformar la estructura normal en una cadena de bytes transmisible.![Symfony Serializer Component](https://symfony.com/doc/current/_images/serializer_workflow.png) Algunos ejemplos conocidos de formatos de encodificación, entre otros, son:- JSON- XML- YAML- Struct- Pickle- Ston Nosotros definiremos las dos operaciones de un serializador de la forma:- `serialize(data)`: Responsable de transformar un dato a un formato guardable- `deserialize(buffer)`: Responsable de transformar un formato guardable a una estructura de datos  EjemplosEl primer ejemplo a proponer será serializar una tupla de dos valores enteros `(x, y)` a una estructura binaria compuesta por un entero, un separador en blanco y otro entero `b{ixi}`<jupyter_code>from struct import Struct
# definimos la estructura binaria
a = Struct('ixi')

def serialize(*data):
    # enpaquetamos los elementos de la tupla en la estructura
    return a.pack(*data)

def deserialize(buffer):
    # desempaquetamos los elementos del buffer en base a la estructura
    return a.unpack(buffer)
serialize(10, 154)
deserialize(b'\n\x00\x00\x00\x00\x00\x00\x00\x9a\x00\x00\x00')<jupyter_output><empty_output><jupyter_text>Un ejemplo para la serialización del objeto `Point` a JSON podría ser:<jupyter_code>import json
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
class PointSerializer:
    def serialize(self, aPoint):
        return self.encode(self.normalize(aPoint))
    
    def deserialize(self, aJson):
        return self.denormalize(self.decode(aJson))
    
    def encode(self, data):
        return json.dumps(data)
    
    def decode(self, aJson):
        return json.loads(aJson)
    
    def normalize(self, aPoint):
        return { 'x': aPoint.x, 'y': aPoint.y }
    
    def denormalize(self, data):
        return Point(x=data['x'], y=data['y'])
p1 = Point(1, 5)
p2 = Point(-2, 3)

serializer = PointSerializer()
print(serializer.serialize(p1))
print(serializer.serialize(p2))

p3 = serializer.deserialize('{"x": 10, "y": 20}')
print(p3.x)
print(p3.y)<jupyter_output><empty_output><jupyter_text>Como hemos nombrado, nuesto serializador se compone de dos procesos, normalizar y encodificar. Si nos abstremos de dichos problemas podremos generar una estructura generica para realizar dicho proceso:<jupyter_code>class Serializer():
    def __init__(self, encoder, normalizer):
        self.normalizer = normalizer
        self.encoder = encoder
        
    def serialize(self, data):
        return self.encoder.encode(self.normalizer.normalize(data))
    
    def deserialize(self, data):
        return self.normalizer.denormalize(self.encoder.decode(data))<jupyter_output><empty_output><jupyter_text>Una ventaja muy positiva de dicho patron, es que el serializador es independiente de la forma de normalización y la forma de encodificación, es decir que es reutilizable para la mayoria de los problemas.Siguiendo con el ejemplo anterior, para poder usar dicho serializador con nuestra clase `Point`, deberiamos proporcionar un normalizador de puntos:<jupyter_code>class PointNormalizer():
    def normalize(self, aPoint):
        return { 'x': aPoint.x, 'y': aPoint.y }
    
    def denormalize(self, data):
        return Point(x=data['x'], y=data['y'])<jupyter_output><empty_output><jupyter_text>Este normalizador, es independiente de la forma de encodificación a utilizar, es decir que si queremos guardar nuestros objetos en formatos `XML` o `JSON` lo podremos reutilizar, junto al serializador, sin tener que repetir códigoSiguiendo con el ejemplo original, se plantea un encodificador de json que, el mismo, es independiente de la normalización y de la serialización. Es decir, es posible de reutilizarse para serializar otra información que sera normalizada de otra forma.<jupyter_code>class JsonEncoder():
    
    def encode(self, data):
        return json.dumps(data)
    
    def decode(self, aJson):
        return json.loads(aJson)
p1 = Point(1, 5)
p2 = Point(-2, 3)

serializer = Serializer(JsonEncoder(), PointNormalizer())
print(serializer.serialize(p1))
print(serializer.serialize(p2))

p3 = serializer.deserialize('{"x": 10, "y": 20}')
print(p3.x)
print(p3.y)<jupyter_output>{"x": 1, "y": 5}
{"x": -2, "y": 3}
10
20<jupyter_text>Podemos observar que no todos los objetos necesitan ser normalizados, ya que las estructuras usadas para normalizar ya lo están. Por lo tanto podriamos definir un normalizador por defecto que pase el objeto de largo por su cualidad de ya estar normalizado y setearlo como valor por defecto del serializador:<jupyter_code>class PassthroughNormalizer():
    def normalize(self, data):
        return data
    
    def denormalize(self, data):
        return data
class Serializer():
    def __init__(self, encoder, normalizer=None):
        self.normalizer = normalizer or PassthroughNormalizer()
        self.encoder = encoder
        
    def serialize(self, data):
        return self.encoder.encode(self.normalizer.normalize(data))
    
    def deserialize(self, data):
        return self.normalizer.denormalize(self.encoder.decode(data)) 
serializer = Serializer(JsonEncoder())
print(serializer.serialize([1, 2, 3, 4]))
print(serializer.serialize((10, 5)))
print(serializer.serialize({ 'nombre': 'Matías', 'apellido': 'Pierobon', 'edad': 21 }))<jupyter_output>[1, 2, 3, 4]
[10, 5]
{"nombre": "Mat\u00edas", "apellido": "Pierobon", "edad": 21}<jupyter_text>Archivos EstructuradosComo vimos en la sección anterior, logramos obtener una representación binaria de una tupla de dos enteros.Es logico entonces pensar que podemos estructurar un archivo tal que guarde un conjunto de dichos binarios para luego entenderlos como tuplas.Llamamos entonces, archivo estructurado a aquel archivo que almacena elementos de una estructura determinada, y cada elemento tiene el mismo tamaño determinante.Luego podemos decir que el i-ésimo elemento en ese archivo se encuentra en la posición `i*n` y se encuentra en los proximos `n` bytes; siendo `n` el tamaño de la estructura En python, un archivo se abre bajo la función `open(filename, mode)`. En particualr nuestro archivo contendrá elementos binarios y realizaremos operaciones de escritura y lectura; por lo tanto el modo será `'wb+'`.- `w`: Para tener privilegios de escritura- `b`: Por ser binario- `+`: Para escalar a lectura<jupyter_code>file = open('numeros.dat', 'rb+')
file.seek(0)
file.write(serialize(10, 10))
file.write(serialize(-5, 20))
file.write(serialize(-5, 20))
file.seek(0)
while True:
    try:
        # se lee tantos bytes como el tamaño de la estructura
        print(deserialize(file.read(a.size) )) # se deserializa el buffer
    except:
        break # Se sale del while por EOF<jupyter_output><empty_output><jupyter_text>Ejercicios<jupyter_code>with open('e1.dat', 'wb+') as file:
    a = Struct('i')
    file.write(a.pack(10))
    file.write(a.pack(15))
    file.write(a.pack(1))
    file.write(a.pack(5))
    file.write(a.pack(13))
with open('e2.dat', 'wb+') as file:
    a = Struct('i')
    file.write(a.pack(1))
    file.write(a.pack(2))
    file.write(a.pack(3))
    file.write(a.pack(4))
    file.write(a.pack(5))
def ejercicio1():
    '''
    Encontrar el maximo numero en un archivo de enteros
    '''
    a = Struct('i')
    maximo = 0
    file = open('e1.dat', 'rb')
    while True:
        try:
            n, = a.unpack(file.read(a.size))
            maximo = max(n, maximo)
        except:
            break
    print(maximo)
ejercicio1()
def ejercicio2():
    '''
    Actualizar un archivo de enteros. Dado cada elemento, su doble
    '''
    a = Struct('i')
    file = open('e2.dat', 'rb+')
    while True:
        try:
            n, = a.unpack(file.read(a.size))
            file.seek(file.tell() - a.size)
            file.write(a.pack(n*2))
        except:
            break
ejercicio2()
with open('e2.dat', 'rb') as file:
    try: 
        while True:
            print(Struct('i').unpack(file.read(4)))
    except:
        pass<jupyter_output><empty_output><jupyter_text>Como nos podemos dar cuenta, existe una estructura que siempre se repite la cual se puede identificar como```mientras pueda:    me quedo con el proximo elemento deserializado    lo procesosino:    dejo de procesar```    podemos entonces modularizar ese problema, el cual llamaremos iterar el archivo.<jupyter_code>def file_iterator(file, structure):
    file.seek(0)
    try:
        while True:
            yield structure.unpack(file.read(structure.size))
    except:
        return<jupyter_output><empty_output><jupyter_text>Luego podemos usar a nuestro archivo de la siguiente forma<jupyter_code>a = Struct('cxi')
turnos = open('turnos.dat', 'wb+')
for (cat, num) in file_iterator(turnos, a):
    print('Categoria: ', cat.decode("utf-8"))
    print('Numero: ', num)
# cargamos alguna información
turnos.seek(0)
turnos.write(a.pack(b'c', 15))
turnos.write(a.pack(b'a', 20))
turnos.write(a.pack(b'b', 1))
turnos.write(a.pack(b'c', 16))
turnos.write(a.pack(b'c', 17))
turnos.write(a.pack(b'a', 21))<jupyter_output><empty_output><jupyter_text>Archivos Dinámicos  Archivos de Texto  EjemplosConstruiremos un lenguaje de programación en el cual existen solamente 4 operaciones: - Asignación: `x = 10` - Incremento: `x +` - Inversión: `x !` - Traslación: `x -> 1` En el caso de la traslación se fija si la variable es igual a 0, y de serlo se mueve a la linea indicada<jupyter_code>def run(file, **vars):
    variables = dict(vars)
    
    # devuelve un numero si lo es, o el valor de la variable
    def get(var):
        if(var.isdigit()):
            return int(var)
        return variables[var]
    
    # asigna a la variable un valor
    def assing(var, value):
        variables[var] = get(value)
        return variables[var]
    
    #invierte la variable
    def invert(var):
        variables[var] = -variables[var]
        return variables[var]
    
    # incrementa la variable
    def increment(var):
        variables[var] += 1
        return variables[var]
    
    def translate(var, position):
        if(not (variables[var] == 0)): # si la variable no es 0
            file.seek(0) # va al principio del archivo
            for i in range(get(position)):
                file.readline() # se mueve tantas lineas como potition
    
    def interprete(statement):
        # statement es un arreglo donde:
        # s[0] = variable
        # s[1] = operacion
        # s[2] = valor (es opcional)
        
        var = statement[0]
        op = statement[1]
        
        if(var == '#'): # es un comentario
            return
        
        if(op == '='):
            return assing(var, statement[2])
        elif(op == '+'):
            return increment(var)
        elif(op == '!'):
            return invert(var)
        elif(op == '->'):
            return translate(var, statement[2])
            
    def serialize(line):
        return line.split()
        
    for line in file:
        print(interprete(serialize(line)))
with open('sumator.mat', 'w+') as file:
    file.write('# x = 10\n')
    file.write('# y = 5\n')
    file.write('tmp = x\n')
    file.write('y !\n')
    file.write('y +\n')
    file.write('tmp +\n')
    file.write('y -> 4\n')
    file.write('tmp !\n')
    file.write('tmp !')
    file.seek(0)
    run(file, x=10, y=5)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10611608881963382, 'fraction_numerical': 0.01238800155823919, 'mean_word_length': 3.5941302791696494, 'pattern_counts': {'":': 11, '<': 43, '<?xml version=': 0, '>': 46, 'https://': 1, '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': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '95693', 'license': 'MIT', 'n_tokens_mistral': 4304, 'n_tokens_neox': 4213, 'n_words': 1302, 'path': 'Propuesta1.ipynb', 'repo_name': 'matias-pierobon/fod', 'size': '12835'}