<jupyter_start><jupyter_text>Dada una colección de documentos queremos encontrarfrases de 1 , 2 o 3 palabras que sean anagramas de otras. Porejemplo: (“Postmaster”, “Stamp store”) o (“A telescope” , “Tosee Place”) o (“The cockroach”, “cook catch her”). Esta tareaimplica una combinatoria muy difícil por lo que se decide usarMap-Reduce para paralelizarla. Usando Map-Reduce programarla solución a este problema listando todos los pares deanagramas entre frases de 1, 2 o 3 palabras. Como puedeverse en los ejemplos se ignoran mayúsculas y minúsculas ylos espacios en blanco, puntuación, etc. Suponer que existe lafunción word_tokenizer que recibe un texto y devuelve unvector de palabras ya convertidas a minúsculas y sinpuntuación.[Link](https://piazza.com/class_profile/get_resource/jkr2voxi1yw4wt/jkr2vqswk6n4yp) Usando el paquete de wikipedia obtendremos el texto de un articulo para tener de prueba<jupyter_code>from pyspark import SparkContext
sc = SparkContext.getOrCreate()

import wikipedia as wp
wp.set_lang('en')
import string
letters = set(list(string.ascii_lowercase) + list(string.ascii_uppercase)\
    +list(string.digits) + [' ','.','-','\n'])
sampleText = ''.join(x for x in wp.page("Alan turing").content if x in letters).replace('\n','.').split('.')
rdd = sc.parallelize(sampleText)<jupyter_output><empty_output><jupyter_text>En nuestro caso usaremos cada oracion del articulo como un documento. Para encontrar los anagramas pondremos todos los documentos en lowercase de modo que por ejemplo "Alter the" y "the later" sean anagramas. Tambien eliminaremos las frases que han quedado vacias.<jupyter_code>rdd = rdd.map(lambda y: y.lower()).filter(lambda x: len(x)>0)<jupyter_output><empty_output><jupyter_text>Vamos a definir una funcion que nos obtiene los n-gramas de n palabras de cada elemento de un rdd<jupyter_code>def ngrams(element,n):
    lis = []
    splitted = element.split()
    for ind in range(0,len(splitted)-n):
        lis.append(' '.join(splitted[ind:ind+n]))
    return lis<jupyter_output><empty_output><jupyter_text>Obtenemos los n-gramas para n 1,2,3 y unimos todo. Luego de esto, tenemos una lista de listas de n-gramas, aplicamos flatmap para obtener una sola lista con los elementos de cada una de las tres listas.<jupyter_code>rdd1 = rdd.map(lambda x: ngrams(x,1))
rdd2 = rdd.map(lambda x: ngrams(x,2))
rdd3 = rdd.map(lambda x: ngrams(x,3))
rddf = rdd1.union(rdd2.union(rdd3)).flatMap(lambda x: x)<jupyter_output><empty_output><jupyter_text>El enunciado nos pide encontrar los pares de anagramas en este rdd. Eso implica que tendran las mismas letras, los espacios no importan. Entonces uniremos los n elementos de cada uno de los n-gramas, eliminando los espacios y ordenando el string (podrian armarse sets de este mismo modo). Con esto armamos una tupla de (elemento ordenado,elemento). Agrupando segun la clave (el primer elemento de la tupla == el elemento ordenado) tenemos una lista de todos los anagramas por clave. Claramente debemos filtrar aquellos que hayan aparecido solo una vez.<jupyter_code>rddf = rddf.map(lambda x: (''.join(sorted(x.replace(' ',''))),x))
rddf = rddf.groupByKey().map(lambda x: list(set(x[1]))).filter(lambda x: len(x)>1)
rddf.take(20)<jupyter_output><empty_output><jupyter_text>En el output de la celda anterior podemos ver lo siguiente: casi todos los "anagramas" de multiples palabras son en realidad las mismas palabras ordenadas de distinto modo. Procedemos a arreglar esto:<jupyter_code>def uniqueWords(ngrams):
    r = []
    l = []
    for i in ngrams:
        iset = set(i.split())
        if set.difference(*(r+[iset])) != set([]):
            r.append(iset)
            l.append(i)
    return l
    
rddf = rddf.map(lambda x: uniqueWords(x)).filter(lambda x: len(x)>1)
rddf.collect()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10257759074171488, 'fraction_numerical': 0.008153603366649132, 'mean_word_length': 5.1537216828478964, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 28, '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': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '364212', 'license': 'MIT', 'n_tokens_mistral': 1369, 'n_tokens_neox': 1336, 'n_words': 425, 'path': 'spark/2015C1_1_Anagramas.ipynb', 'repo_name': 'CrossNox/ejs_spark', 'size': '3802'}