Verifique se uma matriz Java contém um valor

Verifique se uma matriz Java contém um valor

1. Visão geral

Neste artigo, veremos diferentes maneiras de pesquisar uma matriz para um valor especificado.

Também compararemos seu desempenho usandoJMH (o Java Microbenchmark Harness) para determinar qual método funciona melhor.

2. Configuração

Para nossos exemplos, usaremos uma matriz que contémStrings gerado aleatoriamente para cada teste:

String[] seedArray(int length) {
    String[] strings = new String[length];
    Random value = new Random();
    for (int i = 0; i < length; i++) {
        strings[i] = String.valueOf(value.nextInt());
    }
    return strings;
}

Para reutilizar a matriz em cada benchmark, declararemos uma classe interna para conter a matriz e a contagem, para que possamos declarar seu escopo para JMH:

@State(Scope.Benchmark)
public static class SearchData {
    static int count = 1000;
    static String[] strings = seedArray(1000);
}

Three commonly used methods for searching an array are as a List, a Set, or with a loop que examina cada membro até encontrar uma correspondência.

Vamos começar com três métodos que implementam cada algoritmo:

boolean searchList(String[] strings, String searchString) {
    return Arrays.asList(SearchData.strings)
      .contains(searchString);
}

boolean searchSet(String[] strings, String searchString) {
    Set stringSet = new HashSet<>(Arrays.asList(SearchData.strings));

    return stringSet.contains(searchString);
}

boolean searchLoop(String[] strings, String searchString) {
    for (String string : SearchData.strings) {
        if (string.equals(searchString))
        return true;
    }

    return false;
}

Usaremos essas anotações de classe para dizer ao JMH para gerar o tempo médio em microssegundos e executar por cinco iterações de aquecimento para garantir que nossos testes sejam confiáveis:

@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 5)
@OutputTimeUnit(TimeUnit.MICROSECONDS)

E execute cada teste em um loop:

@Benchmark
public void searchArrayLoop() {
    for (int i = 0; i < SearchData.count; i++) {
        searchLoop(SearchData.strings, "T");
    }
}

@Benchmark
public void searchArrayAllocNewList() {
    for (int i = 0; i < SearchData.count; i++) {
        searchList(SearchData.strings, "T");
    }
}

@Benchmark
public void searchArrayAllocNewSet() {
    for (int i = 0; i < SearchData.count; i++) {
        searchSet(SearchData.strings, "S");
    }
}

Quando executamos com 1000 pesquisas para cada método, nossos resultados são mais ou menos assim:

SearchArrayTest.searchArrayAllocNewList  avgt   20    937.851 ±  14.226  us/op
SearchArrayTest.searchArrayAllocNewSet   avgt   20  14309.122 ± 193.844  us/op
SearchArrayTest.searchArrayLoop          avgt   20    758.060 ±   9.433  us/op

The loop search is more efficient than others. Mas isso é pelo menos em parte por causa de como estamos usando coleções.

Estamos criando uma nova instânciaList com cada chamada parasearchList() e um novoListe um novoHashSet com cada chamada parasearchSet(). Criar esses objetos cria um custo adicional que o loop pela matriz não cria.

O que acontece quando criamos instâncias únicas deList eSete as reutilizamos para cada pesquisa?

Vamos tentar:

public void searchArrayReuseList() {
    List asList = Arrays.asList(SearchData.strings);
    for (int i = 0; i < SearchData.count; i++) {
        asList.contains("T");
    }
}

public void searchArrayReuseSet() {
    Set asSet = new HashSet<>(Arrays.asList(SearchData.strings));
    for (int i = 0; i < SearchData.count; i++) {
        asSet.contains("T");
    }
}

Executaremos esses métodos com as mesmas anotações JMH acima e incluiremos os resultados do loop simples para comparação.

Vemos resultados muito diferentes:

SearchArrayTest.searchArrayLoop          avgt   20    758.060 ±   9.433  us/op
SearchArrayTest.searchArrayReuseList     avgt   20    837.265 ±  11.283  us/op
SearchArrayTest.searchArrayReuseSet      avgt   20     14.030 ±   0.197  us/op

Embora a pesquisa deList seja ligeiramente mais rápida do que antes,Set cai para menos de 1 por cento do tempo necessário para o loop!

Agora que removemos o tempo necessário para criar novas coleções de cada pesquisa, esses resultados fazem sentido.

A pesquisa em uma tabela hash, a estrutura subjacente aHashSet, tem uma complexidade de tempo de 0 (1), enquanto uma matriz, que está por trás deArrayList, é 0 (n).

Outro método para pesquisar uma matriz ébinary search. Embora muito eficiente, uma pesquisa binária exige que a matriz seja classificada com antecedência.

Vamos classificar a matriz e tentar a pesquisa binária:

@Benchmark
public void searchArrayBinarySearch() {
    Arrays.sort(SearchData.strings);
    for (int i = 0; i < SearchData.count; i++) {
        Arrays.binarySearch(SearchData.strings, "T");
    }
}
SearchArrayTest.searchArrayBinarySearch  avgt   20     26.527 ±   0.376  us/op

A pesquisa binária é muito rápida, embora menos eficiente que oHashSet:, o desempenho do pior caso para uma pesquisa binária é 0 (log n), o que coloca seu desempenho entre o de uma pesquisa de array e uma tabela hash.

6. Conclusão

Vimos vários métodos de pesquisa em uma matriz.

Com base em nossos resultados, aHashSet funciona melhor para pesquisar em uma lista de valores. No entanto, precisamos criá-los com antecedência e armazená-los noSet.

Como sempre, o código-fonte completo dos exemplos está disponívelover on GitHub.