Análise Profunda de Operações com Mapas e Conjuntos em ArkTS: do Básico ao Avançado

Análise Profunda de Operações com Mapas e Conjuntos em ArkTS: do Básico ao Avançado

Introdução

No desenvolvimento de aplicativos HarmonyOS, a gestão eficiente de estruturas de dados é fundamental para construir aplicações de alto desempenho. Como principal linguagem de desenvolvimento para HarmonyOS, o ArkTS oferece uma variedade de tipos de coleção, onde Mapas e Conjuntos, como estruturas centrais de pares chave-valor e valores únicos, desempenham um papel crucial em cenários de processamento de dados complexos. Este artigo explorará em profundidade os mecanismos internos, as utilizações avançadas e as melhores práticas na prática do desenvolvimento do ArkTS para Mapas e Conjuntos.

  1. Conceitos Fundamentais de Mapas e Conjuntos

1.1 A Natureza dos Mapas

Um Mapa é uma coleção de pares chave-valor, onde cada chave é única. Diferente dos objetos tradicionais, as chaves em um Mapa podem ser de qualquer tipo, incluindo objetos, funções e outros tipos complexos.

// Criação e uso básico de um Mapa
let mapaUsuarios = new Map<number, string>();
mapaUsuarios.set(1, "Carlos");
mapaUsuarios.set(2, "Mariana");
mapaUsuarios.set(3, "Rafael");

console.log(mapaUsuarios.get(1)); // Saída: Carlos
console.log(mapaUsuarios.has(4)); // Saída: false

1.2 Características dos Conjuntos

Um Conjunto é uma coleção de valores únicos, sendo a remoção automática de duplicatas sua característica principal. Em cenários onde é necessário garantir a unicidade dos elementos, os Conjuntos oferecem alta praticidade.

// Operações básicas com Conjuntos
let tagsUnicas = new Set<string>();
tagsUnicas.add("TypeScript");
tagsUnicas.add("HarmonyOS");
tagsUnicas.add("ArkTS");
tagsUnicas.add("TypeScript"); // Adição duplicada, não terá efeito

console.log(tagsUnicas.size); // Saída: 3
console.log(Array.from(tagsUnicas)); // Saída: ["TypeScript", "HarmonyOS", "ArkTS"]

  1. Análise Profunda dos Mecanismos de Implementação Internos

2.1 Implementação de Tabela Hash para Mapas

No ArkTS, os Mapas são baseados em tabelas hash, garantindo operações de busca, inserção e exclusão com complexidade de tempo O(1). Compreender seus mecanismos internos ajuda a escrever código mais eficiente.

// Demonstrando a natureza hash dos Mapas
class Usuario {
  id: number;
  nome: string;
  
  constructor(id: number, nome: string) {
    this.id = id;
    this.nome = nome;
  }
  
  // Método hashCode conceitual
  hashCode(): number {
    return this.id * 31 + this.nome.length;
  }
}

let mapaSessoes = new Map<Usuario, Date>();
let usuarioAtual = new Usuario(1001, "Beatriz");
mapaSessoes.set(usuarioAtual, new Date());

// Mesmo criando uma nova instância, esperamos encontrar o valor correspondente
let usuarioBusca = new Usuario(1001, "Beatriz");
console.log(mapaSessoes.get(usuarioBusca)); // Saída: undefined
// Nota: No ArkTS, quando objetos são usados como chaves, a comparação é por referência e não por conteúdo

2.2 Imlpementação de Conjuntos Baseada em Mapas

Conjuntos internamente podem ser vistos como Mapas que possuem apenas chaves sem valores, o que faz com que os Conjuntos herdem as características de alto desempenho dos Mapas.

  1. Operações Avançadas e Aplicações em Cenários Complexos

3.1 Iteração e Conversão de Mapas

No desenvolvimento prático, frequentemente precisamos realizar várias operações de conversão em Mapas.

// Operações em Mapas com estruturas de dados complexas
interface Produto {
  id: number;
  nome: string;
  preco: number;
  categoria: string;
}

let mapaProdutos = new Map<number, Produto>();
mapaProdutos.set(1, { id: 1, nome: "Smartphone", preco: 2999, categoria: "Eletrônicos" });
mapaProdutos.set(2, { id: 2, nome: "Fone de Ouvido Bluetooth", preco: 399, categoria: "Eletrônicos" });
mapaProdutos.set(3, { id: 3, nome: "Cadeira de Escritório", preco: 599, categoria: "Móveis" });

// Várias formas de conversão para array
let arrayProdutos = Array.from(mapaProdutos.values());
let entradasProdutos = Array.from(mapaProdutos.entries());
let idsProdutos = Array.from(mapaProdutos.keys());

// Filtragem avançada: produtos com preço superior a 500
let produtosCaros = new Map(
  Array.from(mapaProdutos.entries())
    .filter(([id, produto]) => produto.preco > 500)
);

// Agrupamento por categoria
let mapaCategorias = new Map<string, Produto[]>();
mapaProdutos.forEach((produto) => {
  if (!mapaCategorias.has(produto.categoria)) {
    mapaCategorias.set(produto.categoria, []);
  }
  mapaCategorias.get(produto.categoria)!.push(produto);
});

3.2 Operações Matemáticas com Conjuntos

Conjuntos suportam nativamente operações matemáticas de teoria dos conjuntos, o que é muito útil ao lidar com relações de dados.

// Implementação de operações de conjuntos
class ConjuntoAvancado<T> extends Set<T> {
  // União
  uniao(outroConjunto: Set<T>): ConjuntoAvancado<T> {
    return new ConjuntoAvancado([...this, ...outroConjunto]);
  }
  
  // Interseção
  intersecao(outroConjunto: Set<T>): ConjuntoAvancado<T> {
    return new ConjuntoAvancado([...this].filter(x => outroConjunto.has(x)));
  }
  
  // Diferença
  diferenca(outroConjunto: Set<T>): ConjuntoAvancado<T> {
    return new ConjuntoAvancado([...this].filter(x => !outroConjunto.has(x)));
  }
  
  // Diferença simétrica
  diferencaSimetrica(outroConjunto: Set<T>): ConjuntoAvancado<T> {
    return this.uniao(outroConjunto).diferenca(this.intersecao(outroConjunto));
  }
  
  // Verificação de subconjunto
  ehSubconjunto(outroConjunto: Set<T>): boolean {
    return [...this].every(x => outroConjunto.has(x));
  }
}

// Exemplo de uso
let conjuntoA = new ConjuntoAvancado([1, 2, 3, 4]);
let conjuntoB = new ConjuntoAvancado([3, 4, 5, 6]);

console.log(Array.from(conjuntoA.uniao(conjuntoB))); // [1, 2, 3, 4, 5, 6]
console.log(Array.from(conjuntoA.intersecao(conjuntoB))); // [3, 4]
console.log(Array.from(conjuntoA.diferenca(conjuntoB))); // [1, 2]

  1. Otimização de Performance e Gestão de Memória

4.1 Processamento de Dados em Grande Escala

Ao lidar com grandes volumes de dados, o uso correto de Mapas e Conjuntos é crucial para o desempenho.

// Padrões de uso otimizado de Mapas
class GerenciadorDadosOtimizado {
  private mapaDados: Map<string, any>;
  private chavesLRU: string[];
  private tamanhoMaximo: number;
  
  constructor(tamanhoMaximo: number = 1000) {
    this.mapaDados = new Map();
    this.chavesLRU = [];
    this.tamanhoMaximo = tamanhoMaximo;
  }
  
  set(chave: string, valor: any): void {
    // Se atingir o tamanho máximo, remove o item menos recentemente usado
    if (this.mapaDados.size >= this.tamanhoMaximo && !this.mapaDados.has(chave)) {
      const chaveLRU = this.chavesLRU.shift();
      if (chaveLRU) {
        this.mapaDados.delete(chaveLRU);
      }
    }
    
    this.mapaDados.set(chave, valor);
    
    // Atualiza a ordem LRU
    this.atualizarLRU(chave);
  }
  
  get(chave: string): any | undefined {
    const valor = this.mapaDados.get(chave);
    if (valor !== undefined) {
      this.atualizarLRU(chave);
    }
    return valor;
  }
  
  private atualizarLRU(chave: string): void {
    // Remove da posição atual
    const indice = this.chavesLRU.indexOf(chave);
    if (indice > -1) {
      this.chavesLRU.splice(indice, 1);
    }
    
    // Adiciona ao final
    this.chavesLRU.push(chave);
  }
  
  getEstatisticas(): { tamanho: number, taxaAcerto: number } {
    // Na aplicação real, haveria lógica de estatística mais complexa
    return {
      tamanho: this.mapaDados.size,
      taxaAcerto: 0.95 // Valor de exemplo
    };
  }
}

4.2 Cenários de Aplicação Sensíveis à Memória

Em dispositivos móveis, a gestão de memória é particularmente importante.

// Uso eficiente de memória com WeakMap
class CacheEficienteMemoria {
  private weakMap = new WeakMap<object, any>();
  
  // Usa objetos como chaves, quando o objeto é coletado pelo garbage collector, o valor correspondente é limpo automaticamente
  setParaObjeto(obj: object, dados: any): void {
    this.weakMap.set(obj, dados);
  }
  
  getParaObjeto(obj: object): any {
    return this.weakMap.get(obj);
  }
  
  // Para chaves não-objetos, podemos usar outra estratégia
  private mapaString = new Map<string, any>();
  private contadorReferencia = new Map<string, number>();
  
  setComContagemReferencia(chave: string, dados: any): void {
    this.mapaString.set(chave, dados);
    this.contadorReferencia.set(chave, (this.contadorReferencia.get(chave) || 0) + 1);
  }
  
  liberarReferencia(chave: string): void {
    const count = this.contadorReferencia.get(chave) || 0;
    if (count <= 1) {
      this.mapaString.delete(chave);
      this.contadorReferencia.delete(chave);
    } else {
      this.contadorReferencia.set(chave, count - 1);
    }
  }
}

  1. Estudo de Caso: Construindo um Sistema de Gerenciamento de Estado Eficiente

5.1 Gerenciamento de Estado Reativo Baseado em Mapas

// Implementação simplificada de gerenciamento de estado reativo
type Ouvinte<T> = (novoValor: T, valorAntigo: T) => void;

class EstadoReativo<T> {
  private valor: T;
  private ouvintes = new Map<symbol, Ouvinte<T>>();
  
  constructor(valorInicial: T) {
    this.valor = valorInicial;
  }
  
  get(): T {
    return this.valor;
  }
  
  set(novoValor: T): void {
    const valorAntigo = this.valor;
    this.valor = novoValor;
    
    // Notifica todos os ouvintes
    this.ouvintes.forEach(ouvinte => {
      try {
        ouvinte(novoValor, valorAntigo);
      } catch (erro) {
        console.error('Erro no ouvinte:', erro);
      }
    });
  }
  
  inscrever(ouvinte: Ouvinte<T>): () => void {
    const id = Symbol();
    this.ouvintes.set(id, ouvinte);
    
    // Retorna função de cancelamento de inscrição
    return () => {
      this.ouvintes.delete(id);
    };
  }
  
  // Atualização em lote, evitando acionar ouvintes frequentemente
  atualizarEmLote(atualizador: (atual: T) => T): void {
    const novoValor = atualizador(this.valor);
    this.set(novoValor);
  }
}

// Exemplo de uso
const estadoUsuario = new EstadoReativo({ nome: "Carlos", idade: 30 });

const cancelarInscricao = estadoUsuario.inscrever((novoValor, valorAntigo) => {
  console.log(`Informação do usuário atualizada: ${valorAntigo.nome} -> ${novoValor.nome}`);
});

estadoUsuario.set({ nome: "Mariana", idade: 30 });

5.2 Sistema de Rastreamento de Dependências Baseado em Conjuntos

// Sistema de rastreamento de dependências
class RastreadorDependencias {
  private dependencias = new Map<string, Set<() => void>>();
  
  // Adiciona dependência
  adicionarDependencia(chave: string, callback: () => void): void {
    if (!this.dependencias.has(chave)) {
      this.dependencias.set(chave, new Set());
    }
    this.dependencias.get(chave)!.add(callback);
  }
  
  // Remove dependência
  removerDependencia(chave: string, callback: () => void): void {
    const callbacks = this.dependencias.get(chave);
    if (callbacks) {
      callbacks.delete(callback);
      if (callbacks.size === 0) {
        this.dependencias.delete(chave);
      }
    }
  }
  
  // Dispara atualização
  notificar(chave: string): void {
    const callbacks = this.dependencias.get(chave);
    if (callbacks) {
      // Usa Set para evitar callbacks duplicados
      callbacks.forEach(callback => {
        try {
          callback();
        } catch (erro) {
          console.error('Erro no callback de dependência:', erro);
        }
      });
    }
  }
  
  // Obtém estatísticas de dependência
  getEstatisticasDependencias(): Map<string, number> {
    const stats = new Map<string, number>();
    this.dependencias.forEach((callbacks, chave) => {
      stats.set(chave, callbacks.size);
    });
    return stats;
  }
}

// Aplicação em componentes
class Componente {
  private rastreador: RastreadorDependencias;
  private id: string;
  
  constructor(rastreador: RastreadorDependencias, id: string) {
    this.rastreador = rastreador;
    this.id = id;
  }
  
  configurarDependencias(): void {
    // Escuta mudanças nos dados do usuário
    this.rastreador.adicionarDependencia('usuario.dados', this.aoMudarDadosUsuario.bind(this));
    // Escuta mudanças de tema
    this.rastreador.adicionarDependencia('app.tema', this.aoMudarTema.bind(this));
  }
  
  private aoMudarDadosUsuario(): void {
    console.log(`Componente ${this.id}: Dados do usuário atualizados`);
    // Re-renderiza o componente
  }
  
  private aoMudarTema(): void {
    console.log(`Componente ${this.id}: Tema atualizado`);
    // Atualiza o estilo do componente
  }
  
  destruir(): void {
    // Limpa todas as dependências
    this.rastreador.removerDependencia('usuario.dados', this.aoMudarDadosUsuario.bind(this));
    this.rastreador.removerDependencia('app.tema', this.aoMudarTema.bind(this));
  }
}

  1. Padrões Avançados e Melhores Práticas

6.1 Padrão de Conjuntos Imutáveis

Ao lidar com gerenciamento de estado, conjuntos imutáveis podem evitar efeitos colaterais inesperados.

// Classe utilitária para Mapas imutáveis
class MapaImutavel<K, V> {
  private readonly mapaInterno: Map<K, V>;
  
  constructor(entradas?: readonly (readonly [K, V])[] | null) {
    this.mapaInterno = new Map(entradas);
  }
  
  set(chave: K, valor: V): MapaImutavel<K, V> {
    const novoMapa = new Map(this.mapaInterno);
    novoMapa.set(chave, valor);
    return new MapaImutavel(novoMapa);
  }
  
  delete(chave: K): MapaImutavel<K, V> {
    const novoMapa = new Map(this.mapaInterno);
    novoMapa.delete(chave);
    return new MapaImutavel(novoMapa);
  }
  
  get(chave: K): V | undefined {
    return this.mapaInterno.get(chave);
  }
  
  has(chave: K): boolean {
    return this.mapaInterno.has(chave);
  }
  
  // Outros métodos implementados de forma similar...
  
  // Atualização em lote
  atualizarEmLote(atualizador: (atual: Map<K, V>) => Map<K, V>): MapaImutavel<K, V> {
    const novoMapa = atualizador(new Map(this.mapaInterno));
    return new MapaImutavel(novoMapa);
  }
}

// Exemplo de uso
let mapaImutavel = new MapaImutavel<number, string>([[1, "A"], [2, "B"]]);
let mapaAtualizado = mapaImutavel.set(3, "C").delete(1);

console.log(mapaImutavel.get(1)); // "A" - mapa original inalterado
console.log(mapaAtualizado.get(1));   // undefined
console.log(mapaAtualizado.get(3));   // "C"

6.2 Conjuntos Reforçados com Tipagem Segura

// Mapa com tipagem segura
type Validador<T> = (valor: T) => boolean;

class MapaTipado<K, V> {
  private mapa: Map<K, V>;
  private validadorChave?: Validador<K>;
  private validadorValor?: Validador<V>;
  
  constructor(
    validadorChave?: Validador<K>,
    validadorValor?: Validador<V>,
    entradas?: readonly (readonly [K, V])[] | null
  ) {
    this.validadorChave = validadorChave;
    this.validadorValor = validadorValor;
    this.mapa = new Map();
    
    if (entradas) {
      entradas.forEach(([chave, valor]) => {
        this.set(chave, valor);
      });
    }
  }
  
  set(chave: K, valor: V): this {
    if (this.validadorChave && !this.validadorChave(chave)) {
      throw new Error(`Chave inválida: ${chave}`);
    }
    
    if (this.validadorValor && !this.validadorValor(valor)) {
      throw new Error(`Valor inválido: ${valor}`);
    }
    
    this.mapa.set(chave, valor);
    return this;
  }
  
  get(chave: K): V | undefined {
    return this.mapa.get(chave);
  }
  
  // Outros métodos proxy...
  
  // Operações em lote com tipagem segura
  static apartirDeObjeto<T extends object>(obj: T): MapaTipado<string, any> {
    const entradas = Object.entries(obj);
    return new MapaTipado<string, any>(
      (chave): chave is string => typeof chave === 'string',
      () => true, // Aceita qualquer valor
      entradas
    );
  }
}

// Exemplo de uso
// Criar um Mapa que só pode armazenar números positivos
let mapaNumerosPositivos = new MapaTipado<string, number>(
  (chave): chave is string => chave.length > 0,
  (valor): valor is number => typeof valor === 'number' && valor > 0
);

mapaNumerosPositivos.set("idade", 25); // Sucesso
// mapaNumerosPositivos.set("invalido", -5); // Lança erro

  1. Testes de Performance e Comparação

7.1 Comparação de Performance: Mapa vs Objeto

// Função utilitária de teste de performance
class TestadorPerformance {
  static medirOperacao(operacao: () => void, iteracoes: number = 1000): number {
    const startTime = Date.now();
    for (let i = 0; i < iteracoes; i++) {
      operacao();
    }
    return Date.now() - startTime;
  }
  
  static compararMapaVsObjeto(iteracoes: number = 10000): void {
    // Teste de performance do Mapa
    const tempoMapa = this.medirOperacao(() => {
      const mapa = new Map();
      for (let i = 0; i < 100; i++) {
        mapa.set(`chave${i}`, i);
      }
      for (let i = 0; i < 100; i++) {
        const valor = mapa.get(`chave${i}`);
      }
    }, iteracoes);
    
    // Teste de performance do Objeto
    const tempoObjeto = this.medirOperacao(() => {
      const obj: { [chave: string]: number } = {};
      for (let i = 0; i < 100; i++) {
        obj[`chave${i}`] = i;
      }
      for (let i = 0; i < 100; i++) {
        const valor = obj[`chave${i}`];
      }
    }, iteracoes);
    
    console.log(`Tempo de operação do Mapa: ${tempoMapa}ms`);
    console.log(`Tempo de operação do Objeto: ${tempoObjeto}ms`);
    console.log(`Diferença de performance: ${((tempoObjeto - tempoMapa) / tempoMapa * 100).toFixed(2)}%`);
  }
}

// Executar teste de performance
TestadorPerformance.compararMapaVsObjeto();

Tags: ArkTS Mapas Conjuntos HarmonyOS estruturas-de-dados

Publicado em 6-9 03:06 por Thomas