import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; public class AVL { private Node root = new Node(null); // necessario para verificacao de quantos nos visitados pela funcao search // (desempenho) private int count = 0; public int getCount() { return count; } // Construtor public AVL() { this.root = null; } public void cleanup() { cleanupRec(root); root = null; } private void cleanupRec(Node node) { if (node != null) { cleanupRec(node.left); cleanupRec(node.right); remove(node.programa.getId()); } } public void saveTreeToCSV(PrintWriter writer) { saveTreeToCSVRec(root, writer); } private void saveTreeToCSVRec(Node node, PrintWriter writer) { if (node != null) { saveTreeToCSVRec(node.left, writer); ProgramaNetflix p = node.programa; String line = "\"" + p.getId() + "\"," + "\"" + p.getTitulo() + "\"," + "\"" + p.getShow_type() + "\"," + "\"" + p.getDescricao() + "\"," + p.getRelease_year() + "," + "\"" + p.getAge_certification() + "\"," + p.getRuntime() + "," + "\"" + p.getGeneros() + "\"," + "\"" + p.getProductionCountries() + "\"," + p.getTemporadas() + "," + "\"" + p.getImdb_id() + "\"," + p.getImdb_score() + "," + p.getImdb_votes() + "," + p.getTmdb_popularity() + "," + p.getTmdb_score(); writer.println(line); saveTreeToCSVRec(node.right, writer); } } // Métodos de inserção e busca public void insert(ProgramaNetflix programa) { root = insertRec(root, programa); } private Node insertRec(Node root, ProgramaNetflix programa) { if (root == null) { return new Node(programa); } if (programa.getId().compareTo(root.programa.getId()) < 0) { root.left = insertRec(root.left, programa); } else if (programa.getId().compareTo(root.programa.getId()) > 0) { root.right = insertRec(root.right, programa); } else { return root; } // Atualize a altura do nó atual root.height = 1 + Math.max(getHeight(root.left), getHeight(root.right)); // Verifique o fator de equilíbrio e faça as rotações necessárias int balance = getBalance(root); // Casos de rotação // Esquerda-Esquerda if (balance > 1 && programa.getId().compareTo(root.left.programa.getId()) < 0) { return rotateRight(root); } // Direita-Direita if (balance < -1 && programa.getId().compareTo(root.right.programa.getId()) > 0) { return rotateLeft(root); } // Esquerda-Direita if (balance > 1 && programa.getId().compareTo(root.left.programa.getId()) > 0) { root.left = rotateLeft(root.left); return rotateRight(root); } // Direita-Esquerda if (balance < -1 && programa.getId().compareTo(root.right.programa.getId()) < 0) { root.right = rotateRight(root.right); return rotateLeft(root); } return root; } public String search(String id) { count = 1; ProgramaNetflix programa = searchRec(root, id); if (programa != null) { return programa.getTitulo(); } else { return ""; } } private ProgramaNetflix searchRec(Node root, String id) { count++; // correspondente if (root == null || root.programa.getId().equals(id)) { return root != null ? root.programa : null; } // Se o ID a ser pesquisado for menor que o ID do nó atual, busca na subárvore // esquerda if (id.compareTo(root.programa.getId()) < 0) { return searchRec(root.left, id); } // Se o ID a ser pesquisado for maior que o ID do nó atual, busca na subárvore // direita return searchRec(root.right, id); } public int height() { return height(root); } // Método privado para calcular a altura da árvore private int height(Node root) { if (root == null) { return -1; // Árvore vazia tem altura -1 } int leftHeight = height(root.left); int rightHeight = height(root.right); // A altura da árvore é o máximo entre as alturas das subárvores esquerda e // direita return Math.max(leftHeight, rightHeight) + 1; } private int getHeight(Node node) { return (node != null) ? node.height : 0; } private int getBalance(Node node) { return (node != null) ? getHeight(node.left) - getHeight(node.right) : 0; } private Node rotateRight(Node y) { Node x = y.left; Node T2 = x.right; // Realiza a rotação x.right = y; y.left = T2; // Atualiza alturas y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; return x; } private Node rotateLeft(Node x) { Node y = x.right; Node T2 = y.left; // Realiza a rotação y.left = x; x.right = T2; // Atualiza alturas x.height = Math.max(getHeight(x.left), getHeight(x.right)) + 1; y.height = Math.max(getHeight(y.left), getHeight(y.right)) + 1; return y; } public boolean remove(String id) { if (search(id).isEmpty()) { return false; } root = removeRec(root, id); return true; } private Node removeRec(Node root, String id) { if (root == null) { return root; } // Encontra o nó a ser removido if (id.compareTo(root.programa.getId()) < 0) { root.left = removeRec(root.left, id); } else if (id.compareTo(root.programa.getId()) > 0) { root.right = removeRec(root.right, id); } else { // Nó com um filho ou sem filhos if (root.left == null) { return root.right; } else if (root.right == null) { return root.left; } // Nó com dois filhos: encontra o sucessor in-order (menor nó na subárvore // direita) root.programa = minValue(root.right); // Remove o sucessor in-order root.right = removeRec(root.right, root.programa.getId()); } // Atualiza a altura do nó atual root.height = 1 + Math.max(getHeight(root.left), getHeight(root.right)); // Verifica o fator de equilíbrio e realiza as rotações necessárias int balance = getBalance(root); // Casos de rotação // Esquerda-Esquerda if (balance > 1 && getBalance(root.left) >= 0) { return rotateRight(root); } // Direita-Direita if (balance < -1 && getBalance(root.right) <= 0) { return rotateLeft(root); } // Esquerda-Direita if (balance > 1 && getBalance(root.left) < 0) { root.left = rotateLeft(root.left); return rotateRight(root); } // Direita-Esquerda if (balance < -1 && getBalance(root.right) > 0) { root.right = rotateRight(root.right); return rotateLeft(root); } return root; } private ProgramaNetflix minValue(Node root) { ProgramaNetflix minValue = root.programa; while (root.left != null) { minValue = root.left.programa; root = root.left; } return minValue; } // 1 /////////////////////////////////////////// public void pontuacaoMediaPorPais() { Map somaPontuacaoPorPais = new HashMap<>(); Map contagemPorPais = new HashMap<>(); inorderCalculaPontuacaoPorPais(root, somaPontuacaoPorPais, contagemPorPais); // Calcula a média Map mediaPorPais = new HashMap<>(); for (Map.Entry entry : somaPontuacaoPorPais.entrySet()) { String pais = entry.getKey(); double somaPontuacao = entry.getValue(); int contagem = contagemPorPais.get(pais); double media = contagem > 0 ? somaPontuacao / contagem : 0.0; mediaPorPais.put(pais, media); } // Ordena os resultados por pontuação média em ordem decrescente List> sortedEntries = mediaPorPais.entrySet() .stream() .sorted((e1, e2) -> Double.compare(e2.getValue(), e1.getValue())) .collect(Collectors.toList()); // Exibe os 10 primeiros países int limite = Math.min(sortedEntries.size(), 10); for (int i = 0; i < limite; i++) { Map.Entry entry = sortedEntries.get(i); System.out.println(entry.getKey() + ": " + Double.parseDouble(String.format("%.3f", entry.getValue()))); } } private void inorderCalculaPontuacaoPorPais(Node no, Map somaPontuacaoPorPais, Map contagemPorPais) { if (no != null) { inorderCalculaPontuacaoPorPais(no.left, somaPontuacaoPorPais, contagemPorPais); List paises = no.programa.getProductionCountries(); double pontuacaoIMDB = no.programa.getImdb_score(); for (String pais : paises) { // Atualiza a soma da pontuação e a contagem para cada país somaPontuacaoPorPais.put(pais, somaPontuacaoPorPais.getOrDefault(pais, 0.0) + pontuacaoIMDB); contagemPorPais.put(pais, contagemPorPais.getOrDefault(pais, 0) + 1); } inorderCalculaPontuacaoPorPais(no.right, somaPontuacaoPorPais, contagemPorPais); } } // 2 /////////////////////////////////////////// public void filmesPorDecada() { Map filmesPorDecada = new HashMap<>(); inorderContadorPorDecada(root, filmesPorDecada); // Exibe os resultados filmesPorDecada.forEach((decada, quantidade) -> { System.out.println(decada + ": " + quantidade); }); } private void inorderContadorPorDecada(Node no, Map filmesPorDecada) { if (no != null) { inorderContadorPorDecada(no.left, filmesPorDecada); int anoLancamento = no.programa.getRelease_year(); String decada = getDecada(anoLancamento); // Incrementa a contagem para a década correspondente filmesPorDecada.put(decada, filmesPorDecada.getOrDefault(decada, 0) + 1); inorderContadorPorDecada(no.right, filmesPorDecada); } } private String getDecada(int ano) { int decada = (ano / 10) * 10; return decada + "s"; } // 3 /////////////////////////////////////////// public void mediaDuracaoPorClassificacao() { Map somaDuracaoPorClassificacao = new HashMap<>(); Map contagemPorClassificacao = new HashMap<>(); inorderMediaDuracaoPorClassificacao(root, somaDuracaoPorClassificacao, contagemPorClassificacao); // Exibe os resultados somaDuracaoPorClassificacao.forEach((classificacao, somaDuracao) -> { int contagem = contagemPorClassificacao.get(classificacao); double media = contagem > 0 ? somaDuracao / contagem : 0.0; Double mediaf = Double.parseDouble(String.format("%.2f", media)); System.out.println(classificacao + ": " + mediaf + " minutos em média"); }); } private void inorderMediaDuracaoPorClassificacao(Node no, Map somaDuracaoPorClassificacao, Map contagemPorClassificacao) { if (no != null) { inorderMediaDuracaoPorClassificacao(no.left, somaDuracaoPorClassificacao, contagemPorClassificacao); if (no.programa.getShow_type().equals("SHOW")) { String classificacao = no.programa.getAge_certification(); int duracao = no.programa.getRuntime(); // Atualiza a soma da duração e a contagem para a classificação indicativa somaDuracaoPorClassificacao.put(classificacao, somaDuracaoPorClassificacao.getOrDefault(classificacao, 0.0) + duracao); contagemPorClassificacao.put(classificacao, contagemPorClassificacao.getOrDefault(classificacao, 0) + 1); } inorderMediaDuracaoPorClassificacao(no.right, somaDuracaoPorClassificacao, contagemPorClassificacao); } } // 4 /////////////////////////////////////////// public void percentualPorClassificacaoEtaria() { Map map = new HashMap<>(); inorderContador(root, map); int total = map.values().stream() .mapToInt(Integer::intValue) .sum(); map.forEach((certificacao, quantidade) -> { double percentual = (double) quantidade / total * 100; Double percentualf = Double.parseDouble(String.format("%.2f", percentual)); System.out.println(certificacao + ": " + percentualf + "%"); }); } private void inorderContador(Node no, Map map) { if (no != null) { inorderContador(no.left, map); String certificacao = no.programa.getAge_certification(); int qtd = map.getOrDefault(certificacao, 0); map.put(certificacao, qtd + 1); inorderContador(no.right, map); } } // 5 /////////////////////////////////////////// public void paisesComMaisTitulos() { Map contagemPorPais = new HashMap<>(); inorderContadorPaises(root, contagemPorPais); // Ordena o mapa por contagem em ordem decrescente List> sortedEntries = contagemPorPais.entrySet() .stream() .sorted((e1, e2) -> Integer.compare(e2.getValue(), e1.getValue())) .collect(Collectors.toList()); // Exibe os 10 primeiros países e suas contagens int limite = Math.min(sortedEntries.size(), 10); for (int i = 0; i < limite; i++) { Map.Entry entry = sortedEntries.get(i); System.out.println(entry.getKey() + ": " + entry.getValue() + " títulos"); } } private void inorderContadorPaises(Node no, Map contagemPorPais) { if (no != null) { inorderContadorPaises(no.left, contagemPorPais); List paises = no.programa.getProductionCountries(); // Atualiza a contagem para cada país na lista for (String pais : paises) { contagemPorPais.put(pais, contagemPorPais.getOrDefault(pais, 0) + 1); } inorderContadorPaises(no.right, contagemPorPais); } } public List dataToStringList() { List result = new ArrayList<>(); dataToStringListRec(root, result); return result; } private void dataToStringListRec(Node root, List result) { if (root != null) { dataToStringListRec(root.left, result); result.add(root.programa.toString()); dataToStringListRec(root.right, result); } } }