{"id":25832549,"url":"https://github.com/helcsnewsxd/famaf-computer_science-programming_paradigms-lab3","last_synced_at":"2025-02-28T21:39:53.291Z","repository":{"id":213568644,"uuid":"657153445","full_name":"helcsnewsxd/famaf-computer_science-programming_paradigms-lab3","owner":"helcsnewsxd","description":"Laboratorio 3 de la materia de Paradigmas de la Programación de la Licenciatura en Ciencias de la Computación de FAMAF (UNC)","archived":false,"fork":false,"pushed_at":"2024-07-23T21:54:04.000Z","size":2030,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2024-07-24T00:56:58.961Z","etag":null,"topics":["distributed-computing","famaf-unc","feed-reader","java","lab","optimization","spark","university-project"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/helcsnewsxd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-22T12:39:12.000Z","updated_at":"2024-07-24T00:57:00.283Z","dependencies_parsed_at":"2024-07-24T01:15:34.461Z","dependency_job_id":null,"html_url":"https://github.com/helcsnewsxd/famaf-computer_science-programming_paradigms-lab3","commit_stats":null,"previous_names":["helcsnewsxd/paradigmas23_lab3","helcsnewsxd/famaf-computer_science-programming_paradigms-lab3"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/helcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/helcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/helcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/helcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/helcsnewsxd","download_url":"https://codeload.github.com/helcsnewsxd/famaf-computer_science-programming_paradigms-lab3/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241245484,"owners_count":19933295,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["distributed-computing","famaf-unc","feed-reader","java","lab","optimization","spark","university-project"],"created_at":"2025-02-28T21:39:52.789Z","updated_at":"2025-02-28T21:39:53.279Z","avatar_url":"https://github.com/helcsnewsxd.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lab 3 Informe\n\nIntegrantes:\n\nGuillermo de Ipola\n\nEmanuel Nicolas Herrador\n\nJuan Bratti\n\n- [Introducción](#introducción)\n    - [Código 1: Guillermo de Ipola](#código-1-guillermo-de-ipola)\n    - [Código 2: Juan Bratti](#código-2-juan-bratti)\n    - [Código 3: Emanuel Nicolas Herrador](#código-3-emanuel-nicolas-herrador)\n- [Desarrollo Segunda Parte](#desarrollo-segunda-parte)\n\n# Introducción\n\nEn esta segunda parte del laboratorio 3, lo que hicimos fue ver el trabajo individual de los miembros del grupo y elegir la mejor alternativa para implementar las funcionalidades de la entrega grupal.\n\nHubo tres caminos distintos para poder adaptar el código del laboratorio al framework Spark. Esos códigos los detallaremos aquí abajo con sus ventajas y sus desventajas.\n\n## Código 1: Guillermo De Ipola\n\nEn esta primera alternativa se configuró el contexto de Spark con SparkCOnf y JavaSparkContext.\n\n```cpp\nSparkConf conf = new SparkConf().setAppName(\"FeedReader\").setMaster(\"local[*]\");\nJavaSparkContext sc = new JavaSparkContext(conf);\n```\n\nLa adaptación de Spark se concentro en el archivo Main.java y fue la siguiente:\n\nSe optó por paralelizar de forma distribuida principalmente la lista de suscripciones en una variable `rSubList` para luego con un `flatMap` aplicado sobre la misma, obtener una lista de pares conteniendo por un lado los **feeds** de cada suscriptción y, por otro lado, cualquier error que pueda haber surgido en el procesamiento de los feeds. Devolvemos un iterador sobre estas tuplas. Luego, filtramos de los pares obtenidos, aquellos feeds que pudieron ser procesados correctamente.\n\n```cpp\n\nvar feeds = rSubList.flatMap((simpleSubscription -\u003e {\n            List\u003cFunction0\u003cTuple2\u003cFeed, String\u003e\u003e\u003e frs = new ArrayList\u003c\u003e();\n\n            for (int j = 0, szj = simpleSubscription.getUrlParametersSize(); j \u003c szj; j++) {\n                final int i = j;\n                frs.add(() -\u003e {\n                    try {\n                        return new Tuple2(simpleSubscription.parse(i), null);\n                    } catch (InvalidUrlTypeToFeedException e) {\n                        return new Tuple2(null,\n                                \"Invalid URL Type to get feed in \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (HttpRequestException e) {\n                        return new Tuple2(null,\n                                \"Error in connection: \" + e.getMessage() + \" \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (EmptyFeedException e) {\n                        return new Tuple2(null,\n                                \"Empty Feed in \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (MalformedURLException e) {\n                        return new Tuple2(null,\n                                \"Malformed URL exception en subscripcion \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (IOException e) {\n                        return new Tuple2(null,\n                                \"IO exception en subscripcion \" + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (ParserConfigurationException e) {\n                        return new Tuple2(null,\n                                \"Parse error in \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (ParseException e) {\n                        return new Tuple2(null,\n                                \"Parse error in \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    } catch (SAXException e) {\n                        return new Tuple2(null,\n                                \"SAX Exception in \"\n                                        + simpleSubscription.getFormattedUrlForParameter(i));\n                    }\n                });\n            }\n\n            return frs.iterator();\n        })).mapToPair(Function0::apply);\n\n        var parsedFeeds = feeds.filter((feedErrorTuple) -\u003e {\n            return feedErrorTuple._2() == null \u0026\u0026 feedErrorTuple._1() != null;\n        }).map(Tuple2::_1);\n```\n\nLuego de la obtención de los feeds, se decide si lo que se quiere obtener es un print normal de las noticias, u obtener sus named entities. \n\n1. En el primer caso, simplemente se utiliza el método `foreach` sobre los feeds obtenidos para hacer un `prettyPrint` de los mismos.\n2. En el segundo caso, se vuelve a utilizar el método `flatMap` para obtener un RDD que contenga los artículos de cada feed. Si la lista de artículos no es nula, se devuelve un iterador sobre los mismos. Si es nula, se devuelve Collections.emptyIterator() para descartar esos feeds devolviendo un iterador vacío.\n    \n    Luego aplicamos otro `flatMap` para obtener las entidades nombradas de cada artículos. Aquí se utiliza `computeNamedEntities`. Si el procesamiento de las entidades no es exitosa, también se devuelve `Collections.emptyIterator()` para descartar esos artículos.\n    \n    A estas entidades obtenidas, se les aplica un `filter` para descartar las que sean iguales a null.\n    \n    Luego, con `mapToPair` y `reduceByKey`, creamos pares clave-valor para cada entidad y su frecuencia, y reducirlas al sumarlas.\n    \n    Finalmente, con `map` y `foreach` se obtienen la información de cada entidad y se imprime por pantalla.\n    \n\n```cpp\nif(normalPrint) {\n            // Filter out feeds and print them\n            // Print feed to user\n            parsedFeeds.foreach(Feed::prettyPrint);\n        } else {\n            parsedFeeds.flatMap(feed -\u003e {\n                if(feed.getArticleList() != null) {\n                    return feed.getArticleList().iterator();\n                } else {\n                    return Collections.emptyIterator();\n                }\n            }).flatMap(article -\u003e {\n                Heuristic heur = new QuickHeuristic();\n                article.computeNamedEntities(heur);\n                if(article.getNamedEntityList() != null) {\n                    return article.getNamedEntityList().iterator();\n                } else {\n                    return Collections.emptyIterator();\n                }\n            }).filter(Objects::nonNull).mapToPair(namedEntity -\u003e new Tuple2\u003c\u003e(namedEntity.getName(), namedEntity)).reduceByKey((n1, n2) -\u003e {\n                var n = new NamedEntity(n1.getName(), n1.getCategory(), n1.getFrequency() + n1.getFrequency());\n                n.setTheme(n1.getTheme());\n                return n;\n            }).map(Tuple2::_2).foreach(namedEntity -\u003e {\n                System.out.println(namedEntity.getName());\n                System.out.println(namedEntity.getFrequency());\n                System.out.println(namedEntity.getCategory());\n                System.out.println(namedEntity.getTheme());\n                System.out.println(namedEntity.getClass().toString());\n                System.out.println(\"-----------\");\n            });\n        }\n```\n\nA los errores obtenidos en los procesamientos de los feeds y los artículos, se los imprime al final del programa:\n\n```cpp\n// Filter out Errors and print them\n        var subscriptionErrors = feeds.filter((feedErrorTuple) -\u003e {\n            return feedErrorTuple._2() != null;\n        }).map(Tuple2::_2);\n        if(!subscriptionErrors.isEmpty()) {\n            System.out.println(\"==================================================\");\n            System.out.println(\n                    \"There was a total of \" + subscriptionErrors.count() + \" errors in the creation of the Feeds:\");\n            subscriptionErrors.foreach((s) -\u003e {\n                System.out.print(\"  - \");\n                System.out.println(s);\n            });\n        }\n```\n\n### Ventajas\n\nSe paraleliza el procesamiento de los feeds, los artículos y se aprovecha reduce para poder computar la frecuencia de las entidades nombradas eficientemente. Buen aprovechamiento de las capacidades de Spark en relación a la computación distribuida.\n\n### Desventajas\n\nSI bien se utilizan las capacidades de Spark en la búsqueda de entidades nombradas desde Main.java, podría haberse expandido su uso a otros objetos y clases como por ejemplo, para la función `computeNamedEntities` que hace el cómputo en sí de las entidades nombradas en la clase *Article*, o para el procesamiento de las suscripciones en las clases del archivo Subscriptions.java.\n\nSi bien tal vez esta adición hubiera ralentizado el funcionamiento en general del programa en nuestro contexto, en un cluster de tamaño considerable podría haber significado una ventaja sobre la complejidad.\n\n## Código 2: Juan Bratti\n\nEn esta alternativa se decidió usar SparkConf y JavaSparkContext para configurar el contexto de Spark.\n\nAdemás, se hizo uso de una clase extra llamada SparkContextHolder que lo que permite es poder trasladar nuestro contexto de spark utilizado en el archivo Main.java a otros objetos y clases. Esta clase lo que hace es tener métodos para poder guardar el respectivo contexto, además de métodos para poder cerrarlo y obtenerlo.\n\nInicialización de Spark:\n\n```cpp\nSparkConf conf = new SparkConf().setAppName(\"NamedEntity Recognizer\").setMaster(\"local[*]\");\nSparkContextHolder sparkHolder = new SparkContextHolder();\nJavaSparkContext sparkContext = new JavaSparkContext(conf);\nsparkHolder.setSparkContext(sparkContext);\n```\n\nClase de Holder extra:\n\n```java\npublic class SparkContextHolder implements Serializable{\n    private static transient JavaSparkContext sparkContext;\n\n    public JavaSparkContext getSparkContext() {\n        return sparkContext;\n    }\n\n    public void setSparkContext(JavaSparkContext context) {\n        sparkContext = context;\n    }\n\n    public void closeSparkContext() {\n        if (sparkContext != null) {\n            sparkContext.close();\n        }\n    }\n}\n```\n\nA partir de esto, lo que se hizo fue utilizar Spark en el parseo de suscripciones del archivo JSON que se encuentra en el *filepath* dado, en el procesamiento de los feeds y artículos, y en el cómputo de las entidades nombradas.\n\nPara el parseo de las suscripciones, se creó un objeto de Subscriptions y se llamo a parse, pasando como argumento el filepath y además el objeto que contiene nuestro contexto de Spark.\n\n```java\nSubscriptions subscriptions = new Subscriptions();\n       try {\n            subscriptions.parse(subscriptionsFilePath, sparkHolder);\n       } catch (IOException e) {\n                subscriptionErrors.add(\"Error parsing subscriptions file: \" + e.getMessage());\n       }\n```\n\nEn el método parse de Subscriptions, se obtuvo el contexto del holder, y se creó un RDD para paralelizar el procesamiento del contenido de nuestro archivo JSON en el *filepath*. Para ello, en nuestro RDD jsonData se carga el contenido del archivo usando métodos de Spark y se aplica `flatMap` al RDD para extraer, usando la librería Gson, el contenido del archivo JSON en una lista de objetos de tipo Subscription (clase intermedia que se usa como auxiliar, tal vez innecesaria).\n\nLuego de esta transformación, se vuelve aplicar `flatMap` para poder pasar los datos de esta clase auxiliar, a nuestra clase `SimpleSubscription`, que es la que usamos a lo largo de toda la implementación. Finalmente se llama a `collect()` para recolectar todos las transformaciones realizadas en cada `SimpleSubscription`, y guardar las mismas en la lista `subscriptionsList` definida en Subscription.Java para ser accedidas más tarde en nuestro programa.\n\n```\nJavaSparkContext sparkContext = sparkHolder.getSparkContext();\n\n        JavaRDD\u003cString\u003e jsonData = sparkContext.wholeTextFiles(subscriptionsFilePath).values();\n\n        JavaRDD\u003cSubscription\u003e subscriptionsRDD = jsonData.flatMap(json -\u003e {\n            Gson gson = new Gson();\n            Type type = new TypeToken\u003cList\u003cSubscription\u003e\u003e() {}.getType();\n            List\u003cSubscription\u003e subscriptions = gson.fromJson(json, type);\n            return subscriptions.iterator();\n        });\n\n        // Extract the required fields\n        JavaRDD\u003cSimpleSubscription\u003e simpleSubscriptionsRDD = subscriptionsRDD.map(subscription -\u003e {\n            String url = subscription.getUrl();\n            String urlType = subscription.getUrlType();\n            List\u003cString\u003e urlParams = subscription.getUrlParams();\n\n            SimpleSubscription simpleSubscription = new SimpleSubscription();\n            simpleSubscription.setUrl(url);\n            simpleSubscription.setUrlType(urlType);\n\n            if (urlType.equals(\"rss\")) {\n                simpleSubscription.setParser(new RssParser());\n            } else if (urlType.equals(\"reddit\")) {\n                simpleSubscription.setParser(new RedditParser());\n            }\n\n            for (String param : urlParams) {\n                simpleSubscription.addUrlParameter(param);\n            }\n\n            return simpleSubscription;\n        });\n\n        List\u003cSimpleSubscription\u003e simpleSubscriptions = simpleSubscriptionsRDD.collect();\n        for (SimpleSubscription simpleSubscription : simpleSubscriptions) {\n            this.addSimpleSubscription(simpleSubscription);\n        }\n```\n\nLuego de parsear las suscripciones, se prosigue con el parseo de los feeds de cada suscripción.\n\nPara esto, se crea un RDD que contiene la lista de suscripciones y se aplica `flatMap` para que en cada suscripción de la lista, se extraiga su feed con el método parse del objeto `SimpleSubscription`. Se hacen un par de control de errores en el caso de que el parseo del feed no haya sido exitoso.\n\n```java\nJavaRDD\u003cSimpleSubscription\u003e subscriptionsRDD = sparkContext.parallelize(subscriptions.getSubscriptionList());\n\n            JavaRDD\u003cFeed\u003e feedsRDD = subscriptionsRDD.flatMap(subscription -\u003e {\n                List\u003cFeed\u003e feeds = new ArrayList\u003c\u003e();\n                for (int j = 0, szj = subscription.getUrlParametersSize(); j \u003c szj; j++) {\n                    try {\n                    Feed feed = subscription.parse(j);\n                    feeds.add(feed);\n                    } catch (InvalidUrlTypeToFeedException e) {\n                        subscriptionErrors.add(\n                                \"Invalid URL Type to get feed in \" + subscription.getFormattedUrlForParameter(j));\n                    } catch (HttpRequestException e) {\n                        subscriptionErrors.add(\n                                \"Error in connection: \" + e.getMessage() + \" \" + subscription.getFormattedUrlForParameter(j));\n                    } catch (EmptyFeedException e) {\n                        subscriptionErrors.add(\n                                \"Empty Feed in \" + subscription.getFormattedUrlForParameter(j));\n                    } catch (MalformedURLException e) {\n                        subscriptionErrors.add(\n                                \"Malformed URL exception en subscription \" + subscription.getFormattedUrlForParameter(j));\n                    } catch (IOException e) {\n                        subscriptionErrors.add(\n                                \"IO exception en subscription \" + subscription.getFormattedUrlForParameter(j));\n                    } catch (ParserConfigurationException e) {\n                        subscriptionErrors.add(\n                                \"Parse error in \" + subscription.getFormattedUrlForParameter(j));\n                    } catch (SAXException e) {\n                        subscriptionErrors.add(\n                                \"SAX Exception in \" + subscription.getFormattedUrlForParameter(j));\n                    }\n                }\n                return feeds.iterator();\n            });\n```\n\nLuego del `flatMap`, que devuelve un iterador sobre los feeds extraídos, se decide qué hacer de acuerdo a las necesidades del usuario:\n\n1. Si se quiere printear sólo los feeds, se utiliza `foreach` y `prettyPrint` sobre cada uno de ellos para imprimirlos.\n    \n    ```cpp\n    feedsRDD.foreach(feed -\u003e feed.prettyPrint());\n    ```\n    \n2. Si se desean buscar y printear las entidades nombradas:\n    \n    Usamos un RDD para los artículos, en donde aplicamos la `flatMap` al RDD que contiene los feeds para extraer todos los artículos. Luego, usando un RDD para la lista de las entidades nombradas, se aplica a cada articulo en el RDD anteriormente mencionado, la función `processNamedEntities` con `flatMap` , pasandole el artículo siendo procesado en ese momento + la heurística + el holder del contexto de Spark.\n    \n    ```java\n    JavaRDD\u003cArticle\u003e articlesRDD = feedsRDD.flatMap(feed -\u003e feed.getArticleList().iterator());\n    JavaRDD\u003cList\u003cNamedEntity\u003e\u003e namedEntitiesRDD = articlesRDD.map(article -\u003e processNamedEntities(article, heur, sparkHolder));\n    ```\n    \n    La función `processNamedEntities` llama a computeNamedEntities pasando así el holder con nuestro contexto de Spark. Retorna la lista de entidades nombradas del artículo pasado.\n    \n    ```java\n    public static List\u003cNamedEntity\u003e processNamedEntities(Article article, Heuristic heur, SparkContextHolder sparkHolder) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException {\n            // Procesar las entidades nombradas en el artículo utilizando la heurística proporcionada\n            article.computeNamedEntities(heur, sparkHolder);\n            // Devolver la lista de entidades nombradas encontradas en el artículo\n            return article.getNamedEntityList();\n        }\n    ```\n    \n    Finalmente, se le pasa el sparkHolder a `computeNamedEntities` para poder usar los métodos mapToPair y reduceByKey en la búsqueda de las entidades. Para ello se tiene un RDD con las palabras del artículo en cuestión, y usando filter, mapToPair y reduceByKey se ponen todas las palabras en una tupla nombre-frecuencia de forma tal que se reduzcan las instancias de las palabras iguales, sumando entre sí sus frecuencias, teniendo una contabilización de todas las entidades distintas en nuestro artículo. Luego se aplica el proceso para ver si la palabra es una entidad o no, categorizandola según la heurística.\n    \n    ```java\n    JavaRDD\u003cString\u003e wordsRDD = sparkContext.parallelize(Arrays.asList(text.split(\" \")));\n            \n    JavaPairRDD\u003cString, Integer\u003e entityWordsRDD = wordsRDD\n                .filter(s -\u003e h.isEntity(s))\n                .mapToPair(s -\u003e new Tuple2\u003c\u003e(s, 1))\n                .reduceByKey((freq1, freq2) -\u003e freq1 + freq2);\n    \n                JavaRDD\u003cNamedEntity\u003e namedEntitiesRDD = entityWordsRDD.map(tuple -\u003e {\n                String name = tuple._1();\n                NamedEntity ne = this.getNamedEntity(name);\n    \n                if (ne == null){\n                    Class\u003c? extends NamedEntity\u003e categoryClass = h.getCategory(name);\n                    if(categoryClass == null) {\n                        categoryClass = OtherEntityOtherThemes.class;\n                    }\n                    ne = categoryClass.getDeclaredConstructor().newInstance();\n                    ne.setFrequency(tuple._2());\n                    ne.setName(name);\n                }\n                return ne;\n            });\n    ```\n    \n    Finalmente se llama a `collect()` para poder devolver la lista de entidades del artículo.\n    \n    ```\n    List\u003cNamedEntity\u003e namedEntities = namedEntitiesRDD.collect();\n    this.namedEntityList.addAll(namedEntities);\n    ```\n    \n    y en Main.java se imprime esta lista con usando `foreach` en cada elemento del RDD que contiene la lista de entidades nombradas\n    \n    ```\n    namedEntitiesRDD.foreach(namedEntitiesList -\u003e {\n                        for (NamedEntity namedEntity : namedEntitiesList) {\n                            System.out.println(namedEntity.getName());\n                            System.out.println(namedEntity.getFrequency());\n                            System.out.println(namedEntity.getCategory());\n                            System.out.println(namedEntity.getTheme());\n                            System.out.println(namedEntity.getClass().toString());\n                            System.out.println(\"-----------\");\n                        }\n                    });\n    ```\n    \n\n### Ventajas\n\nSe pudo aplicar el contexto de Spark a varios de los objetos y clases relacionados con el procesamiento de algún tipo de dato: de suscripciones, de feeds y artículos, de entidades nombradas.\n\n### Desventajas\n\nEl código podría haberse escrito de forma más corta, clara y eficiente, además de que creemos que es la implementación que más tarda en procesar lo pedido debido a que se usa Spark en muchos objetos y clases (por ejemplo, “estaría de más” usarlo en `computeNamedEntities`).\n\n## Código 3: Emanuel Nicolas Herrador\n\nEn esta implementación se decidió usar SparkSession para configurar el contexto de Spark. Ésta decisión fue para poder facilitar el uso del mismo contexto de Spark en otros objetos y clases que no sean aquellos incluidos en el archivo de Main.java.\n\n```cpp\n// Configuración de la sesión de Spark\nSparkSession sparkSession = SparkSession\n          .builder()\n          .appName(\"feedReader\")\n          .master(\"local[100]\")\n          .getOrCreate();\nJavaSparkContext spark = new JavaSparkContext(sparkSession.sparkContext());\n```\n\nEl approach que hubo en este código fue intentar expandir las funcionalidades de Spark a otros archivos, no sólo a Main.java. Para ello, también se adaptó el código de el parseo de las suscripciones en el archivo Subscriptions.java.\n\nLo primero que se hizo fue crear un objeto Subscriptions para hacer el posterior parseo del archivo que contiene las urls, paralelizando el poder de cómputo.\n\n```cpp\nSubscriptions subscriptions = new Subscriptions(sparkSession);\nsubscriptions.parse(subscriptionsFilePath);\n```\n\nVeamos que se le pasa la sesión de Spark a la llamada de Subscriptions. Ésto es porque se incluyó un campo del objeto para contener a nuestro contexto de Spark y poder utilizarla en el método *****parse*****.\n\n```java\npublic Subscriptions(SparkSession sparkSession) {\n        super();\n        this.subscriptionsList = new ArrayList\u003c\u003e();\n\t\t\t\t// Nuevo campo!\n        this.sparkSession = sparkSession;\n    }\n```\n\nDe esta forma, a la hora de llamar al método parse de subscriptions, si el contexto de Spark no es null, podemos paralelizar el funcionamiento de la función de la siguiente forma:\n\nSe agrega a `arrObjString` la representación JSON de cada objeto dentro de nuestro archivo que se encuentra en el *filepath* (archivo del que queremos sacar los campos que nos interesan) y con esa información se crea una Dataset de Spark para poder convertir la lista `arrObjString` en algo que Spark pueda paralelizar.\n\nCon esta nueva variable `objStringDataset` y su posterior transformación con `flatMap`, podemos empezar a crear las SimpleSubscriptions, extrayendo de los strings en el dataset, los campos que son de nuestro interés. Con todas las SimpleSubscriptions hacemos una lista de las mismas que se devuelve al llamar `collect()` .\n\n```java\n// Preparo la lista de JSONObject a paralelizar\n// Considero Strings porque JSONObject no es Serializable\nList\u003cString\u003e arrObjString = new ArrayList\u003c\u003e();\nfor (int i = 0, szi = arr.length(); i \u003c szi; i++)\n    arrObjString.add(arr.getJSONObject(i).toString());\n    JavaRDD\u003cString\u003e objStringDataset = sparkSession.createDataset(arrObjString, Encoders.bean(String.class)).javaRDD();\n\n    List\u003cSimpleSubscription\u003e simpleSubscriptionList = objStringDataset\n    // Creo la simpleSubscription y la instancio\n         .flatMap(objString -\u003e {\n             JSONObject obj = new JSONObject(objString);\n\n             SimpleSubscription simpleSubscription = new SimpleSubscription();\n             simpleSubscription.setUrl(obj.getString(\"url\"));\n             String urlType = obj.getString(\"urlType\");\n             simpleSubscription.setUrlType(urlType);\n\n             // Inyectar parser adecuado\n             if (urlType.equals(\"rss\")) {\n                  simpleSubscription.setParser(new RssParser());\n             } else if (urlType.equals(\"reddit\")) {\n                  simpleSubscription.setParser(new RedditParser());\n             }\n\n             JSONArray arrUrlParams = obj.getJSONArray(\"urlParams\");\n             for (int j = 0, szj = arrUrlParams.length(); j \u003c szj; j++)\n                  simpleSubscription.addUrlParameter(arrUrlParams.getString(j));\n\n             return Collections.singletonList(simpleSubscription).iterator();\n         })\n         // Obtengo la lista de simpleSubscriptions\n         .collect();\n\n    // Seteo la lista obtenida\n    setSubscriptionsList(simpleSubscriptionList);\n```\n\nAsí es como se adaptó Spark para el parseo de las suscripciones.\n\nLuego, se utilizó un RDD  de SimpleSubscriptions para poder paralelizar la extracción de los feeds de la lista de suscripciones. Se uso `flatMap` para poder transformar los datos del RDD tuplas del tipo feed-error y así extraer los feeds de cada suscripción, expresando así en la tupla si se produce un error en el parseo. \n\n```java\n// Paralelizo la lista de las subscripciones para hacerlo de forma concurrente\nJavaRDD\u003cSimpleSubscription\u003e subscriptionList = spark.parallelize(subscriptions.getSubscriptionList());\n// Obtengo todos los feeds\n// Se consideran tuplas (feed, error). Una es null y la otra es dato (se usa\n// para diferenciar)\nJavaRDD\u003cTuple2\u003cFeed, String\u003e\u003e feeds = subscriptionList\n       // Separo las subscripciones por sus parámetros\n       .flatMap(simpleSubscription -\u003e {\n            List\u003cTuple2\u003cSimpleSubscription, Integer\u003e\u003e feedConstructorOptionsList = new ArrayList\u003c\u003e();\n            for (int i = 0, szi = simpleSubscription.getUrlParametersSize(); i \u003c szi; i++)\n               feedConstructorOptionsList.add(new Tuple2\u003c\u003e(simpleSubscription, i));\n            return feedConstructorOptionsList.iterator();\n        })\n        // Obtengo el feed en base a los parámetros considerados (subscripción y\n        // urlParameter)\n        // Se devuelve en el formato (feed, error) siendo solo una null en cada tupla\n        .flatMap(feedOptions -\u003e {\n             try {\n                Feed actualFeed = feedOptions._1().parse(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(actualFeed, null)).iterator();\n                    } catch (InvalidUrlTypeToFeedException e) {\n                        String actualError = \"Invalid URL Type to get feed in \"\n                                + feedOptions._1().getFormattedUrlForParameter(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(null, actualError)).iterator();\n                    } catch (IOException e) {\n                        String actualError = \"IO exception in subscription \"\n                                + feedOptions._1().getFormattedUrlForParameter(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(null, actualError)).iterator();\n                    } catch (HttpRequestException e) {\n                        String actualError = \"Error in connection: \" + e.getMessage() + \" \"\n                                + feedOptions._1().getFormattedUrlForParameter(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(null, actualError)).iterator();\n                    } catch (ParserConfigurationException | ParseException e) {\n                        String actualError = \"Parse error in \"\n                                + feedOptions._1().getFormattedUrlForParameter(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(null, actualError)).iterator();\n                    } catch (SAXException e) {\n                        String actualError = \"SAX Exception in \"\n                                + feedOptions._1().getFormattedUrlForParameter(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(null, actualError)).iterator();\n                    } catch (EmptyFeedException e) {\n                        String actualError = \"Empty Feed in \"\n                                + feedOptions._1().getFormattedUrlForParameter(feedOptions._2());\n                        return Collections.singletonList(new Tuple2\u003cFeed, String\u003e(null, actualError)).iterator();\n                    }\n                });\n```\n\nDe los pares feed-error obtenidos, se pule el RDD resultante para eliminar aquellos feeds nulos o que contengan errores.\n\n```java\n// Preparo la lista de feeds obtenidos\n        JavaRDD\u003cFeed\u003e feedList = feeds\n                .filter(actualFeed -\u003e actualFeed._1() != null)\n                .flatMap(actualFeed -\u003e Collections.singletonList(actualFeed._1()).iterator());\n\n        // Preparo la lista de errores que sucedieron\n        JavaRDD\u003cString\u003e errorList = feeds\n                .filter(actualFeed -\u003e actualFeed._2() != null)\n                .flatMap(actualFeed -\u003e Collections.singletonList(actualFeed._2()).iterator());\n```\n\nFinalmente, si se desea únicamente hacer un printeo de los feeds, se llama a `prettyPrint` para cada feed no nulo usando `foreach`.\n\n```java\n// Muestra los feeds al usuario\nfeedList.foreach(Feed::prettyPrint);\n```\n\nSi se desea obtener las entidades nombradas, se procesan las entidades en un RDD aparte que nace de la transformación del RDD que contiene la lista de feeds, luego de aplicarles `flatMap` a cada articulo de la lista de artículos de cada feed. Luego se procede a imprimir por pantalla la información de cada entidad.\n\n```java\n// Heurística en uso\n            Heuristic heuristicUsed = new QuickHeuristic();\n\n            JavaRDD\u003cnamedEntity.entities.NamedEntity\u003e namedEntities = feedList\n                    // Obtengo todos los artículos\n                    .flatMap(feed -\u003e feed.getArticleList().iterator())\n                    // Obtengo las namedEntity\n                    .flatMap(article -\u003e {\n                        article.computeNamedEntities(heuristicUsed);\n                        return article.getNamedEntityList().iterator();\n                    });\n\n            // Muestro las namedEntity en pantalla\n            namedEntities.foreach(namedEntity -\u003e {\n                System.out.println(namedEntity.getName());\n                System.out.println(namedEntity.getFrequency());\n                System.out.println(namedEntity.getCategory());\n                System.out.println(namedEntity.getTheme());\n                System.out.println(namedEntity.getClass().toString());\n                System.out.println(\"-----------\");\n            });\n```\n\nFinalmente se imprimen los errores que se hayan generado.\n\n```java\nif (!errorList.isEmpty()) {\n            System.out.println(\"==================================================\");\n            System.out.println(\n                    \"There was a total of \" + errorList.count() + \" errors in the creation of the Feeds:\");\n            errorList.foreach(error -\u003e System.out.println(\"  - \" + error));\n        }\n```\n\n### Ventajas\n\nLa ventaja de este código es que se también se ha podido expandir el uso de Spark en otros contextos que no sean los del archivo *********Main.java*********. Por ejemplo, se pudo paralelizar el parseo de las suscripciones, por lo que en un cluster de máquinas decente, se podría haber aumentado aún más la eficiencia de nuestro programa.\n\nAdemás, se cree que si bien se expandió el uso de Spark en otros objetos, también la complejidad de la misma es la más óptima.\n\n# Desarrollo Segunda Parte\n\nEl código que decidimos utilizar fue el código 3 de nuestro compañero Emanuel Nicolas Herrador. \n\nSobre la implementación de nuestro compañero, los cambios principales que realizamos fueron en Main.java; se agregó la funcionalidad que busca los artículos que contengan una determinada palabra o entidad nombrada.\n\nRecapitulando la implementación de nuestro compañero, se obtienen los feeds en un RDD de artículos (luego del parseo ya mencionado y detallado anteriormente):\n\n```cpp\nJavaRDD\u003cArticle\u003e articleList = feedList\n// Obtengo todos los artículos\n                .flatMap(feed -\u003e feed.getArticleList().iterator());\n```\n\ny se decide lo siguiente, implementando la nueva funcionalidad:\n\n1. Si lo que se quiere es obtener los artículos de los feeds impresos, se indicará que se debe ingresar una oración/palabra/entidad para buscar los artículos que contengan esa oración o palabra clave.\n    \n    Para esto, se usa un objeto Scanner para leer lo ingresado por el usuario. Lo que se haya ingresado, se guarda en una variable `rawSearchTerms` que luego se utiliza para dividir la cadena contenida en palabras y almacenar cada una de ellas en un set de strings `searchTerms`.\n    \n    ```cpp\n    // Obtener el input de búsqueda sobre los feeds por parte del usuario\n    System.out.println(\"=====================  ¿Qué quiere buscar? Escríbalo en una oración y aprete Enter =====================\");\n    Scanner scanner = new Scanner(System.in);\n    String rawSearchTerms = scanner.nextLine();\n    Set\u003cString\u003e searchTerms = new java.util.HashSet\u003c\u003e(Collections.emptySet());\n    \n    var terms = rawSearchTerms.split(\" \");\n    Collections.addAll(searchTerms, terms);\n    \n    scanner.close();\n    System.out.println(\"===================== Solicitud recibida con éxito. La estamos procesando. =====================\");\n    ```\n    \n    Luego, usando `flatMap` sobre el RDD que contiene nuestros artículos, se aplica a cada artículo la función `computeNamedEntities`. Además, se crea para cada entidad nombrada un par artículo-entidad_nombrada. Estos pares se guardan en la lista `namedEntityForArticleList` . La llamada a `flatMap` devuelve un iterador para cada elemento de `namedEntityForArticleList` y guarda esos iteradores en la variable `sortedArticle`. \n    \n    ```cpp\n    List\u003cArticle\u003e sortedArticles = articleList\n                        // Obtengo pares (artículo, entidad)\n                        .flatMap(article -\u003e{\n                            // Computo las entidades del artículo\n                            article.computeNamedEntities(heuristicUsed);\n                            \n                            List\u003cNamedEntity\u003e namedEntityFullList = article.getNamedEntityList();\n                            List\u003cTuple2\u003cArticle, NamedEntity\u003e\u003e namedEntityForArticleList = new ArrayList\u003c\u003e();\n                            \n                            for(NamedEntity ne : namedEntityFullList){\n                                namedEntityForArticleList.add(new Tuple2\u003c\u003e(article, ne));\n                            }\n    \n                            return namedEntityForArticleList.iterator();\n                        })\n    ```\n    \n    A esta variable (`sortedArticle`) luego le aplicamos:\n    \n    - filter, para poder filtrar los pares de los artículos que tienen en sus entidades algunas de las palabras ingresadas por el usuario.\n        \n        ```cpp\n        // Filtro aquellos pares cuya entidad esté en la búsqueda del usuario\n        .filter(entityForArticle -\u003e searchTerms.contains(entityForArticle._2().getName()))\n        ```\n        \n    - mapToPair para poder cambiar en los pares restantes, el campo de entidad nombrada por su frecuencia.\n        \n        ```cpp\n        // Cambio esa NamedEntity por su frecuencia\n        .mapToPair(entityForArticle -\u003e new Tuple2\u003c\u003e(entityForArticle._1(), entityForArticle._2().getFrequency()))\n        ```\n        \n    - reduceByKey para poder sumar las frecuencias de cada artículo y obtener el número total de instancias por artículo en los pares\n        \n        ```cpp\n        // Sumo las frecuencias para cada artículo y obtengo su número para ordenar\n        .reduceByKey(Integer::sum)\n        ```\n        \n    - mapToPair y sortByKey de nuevo para poder ordenar los pares según el número de ocurrencias (frecuencia) de cada artículo de forma descendente\n        \n        ```cpp\n        // Swapeo para poder ordenar basándonos en el número de ocurrencias\n        .mapToPair(Tuple2::swap)\n        // Ordeno descendentemente\n        .sortByKey(false)\n        ```\n        \n    - y finalmente se usa map para quedarme sólo con los artículos y printearlos, y collect para obtener la lista de artículos respectiva.\n        \n        ```cpp\n        // Me quedo solo con los artículos a printear\n        .map(Tuple2::_2)\n        // Obtengo la lista para printear\n        .collect();\n        ```\n        \n    \n    Luego, se muestran los artículos ordenados (usando `prettyPrint`) según la cantidad de veces que aparecen las palabras ingresadas por el usuario en relación a las entidades nombradas de cada artículo.\n    \n    ```cpp\n    // Muestra los artículos al usuario\n    for(Article article : sortedArticles){\n       article.prettyPrint();\n    }\n    ```\n    \n2. Si lo que se quiere es printear solamente las entidades nombradas de todos los artículos, simplemente lo que se hace es usar `flatMap` para iterar sobre cada elemento del RDD que contiene los artículos (`articleList`), y aplicar `computeNamedEntities` con la heurística deseada para obtener la lista de entidades nombradas de cada artículo, y con `collect` y un `for`, imprimir todas ellas.\n    \n    ```cpp\n    // Obtengo las namedEntity\n    List\u003cNamedEntity\u003e namedEntities = articleList\n             .flatMap(article -\u003e {\n                  article.computeNamedEntities(heuristicUsed);\n                  return article.getNamedEntityList().iterator();\n              })\n              // Obtengo la lista de las entidades\n              .collect();\n                \n    // Muestro las namedEntity en pantalla\n    for(NamedEntity namedEntity : namedEntities){\n       System.out.println(namedEntity.getName());\n       System.out.println(namedEntity.getFrequency());\n       System.out.println(namedEntity.getCategory());\n       System.out.println(namedEntity.getTheme());\n       System.out.println(namedEntity.getClass().toString());\n       System.out.println(\"-----------\");\n    }\n    ```\n    \n\nPara el desarrollo de esta parte, no se hizo uso de inteligencias artificiales ni tampoco nos basamos en proyectos de ejemplo.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhelcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhelcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhelcsnewsxd%2Ffamaf-computer_science-programming_paradigms-lab3/lists"}