Comparison of elements from a JAVA list

Asked

Viewed 1,480 times

1

I would like you to shed some light on that exercise. I need to print on the console the name and price of the two most expensive products read from a file .csv but it is only printing the last name and price of the spreadsheet. I think the problem should be in the is used for comparison. I would like to know how I can make an efficient comparison of the elements on the list.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;


public class Exercicio {

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    // TODO code application logic here

    File estoque = new File("C:\\estoque.csv");

    try{
        String fileLines = new String();

        Scanner reader = new Scanner(estoque);
        reader.nextLine();

        List<Produto> produtos = new ArrayList<Produto>();
        Produto produto = null;
        String[] linha;

        while(reader.hasNext()){

            fileLines = reader.nextLine();
            linha = fileLines.split(";");


            produto = new Produto();
            produto.nomeProduto = linha[0];
            produto.marca = linha[1];

            try{
            produto.preco = Double.parseDouble(linha[2]);
            }catch(NumberFormatException e){
                produto.preco = 0;
            }
            try{
            produto.estoque = Integer.parseInt(linha[3]);
            }catch(NumberFormatException e){
                produto.estoque = 0;
            }
            produtos.add(produto);
        }

        /*for(Produto p : produtos){
            System.out.println(); // IMPRIMIR INFORMAÇÕES


        }*/

            double maiorPreco = 0;
            double segundoMaiorPreco = 0;
            double segundoMaior = 0;

        for(int i = 0; i < produtos.size()-1; i++){
               if(produto.preco > maiorPreco){
                   maiorPreco = produto.preco;
               }

           }
            System.out.println(maiorPreco);

    }catch(FileNotFoundException e){
    }
    }
 }

class Produto {

public String nomeProduto;
public  String marca;
public double preco;
public int estoque;

}

2 answers

1

We have some strategies to analyze here:

  1. keep the whole list and select the 2 largest ones
  2. keep the whole list and sort it at the end by selecting the 2 largest ones after ordering
  3. keep only 2 elements, the 2 larger ones, updating them every read
  4. create a data structure such that it maintains the n major past elements, with n == 2

There are some alternatives that are at least naive. I would say it would be foolish to use them:

  1. keep the entire list, remove the largest and element and separate it, then select the largest element from the remaining list
  2. maintain the entire list, select the largest element, then select the largest element that is smaller than the previously selected element

Particularly I would go for alternative 3. But let’s see in order. Before, I would like to present some auxiliary functions to sort the code.

Ancillary functions

The first one I’d like is to sort out the filler Produto from a reading line. Then, I would like to create a comparison between two class objects Produto, returning the result of the comparison. Finally, it costs nothing to have a function that turns a product into a String ready to be printed.

Creation of a Produto from a line

Here, the intention is to receive a line and turn into Produto. It can be implemented in several ways. I will suggest here creating a Supplier<Produto>, until the moment it generates null, which should be seen as end of reading.

To create this object provider, I will use a function to generate it. I will also make a function Function<String,Produto> to do the parse of the CSV.

public Produto parseProdutoLinhaCsv(String linha) {
    String []colunas = fileLines.split(";");

    Produto produto = new Produto();
    produto.nomeProduto = colunas[0];
    produto.marca = colunas[1];

    try {
        produto.preco = Double.parseDouble(colunas[2]);
    } catch(NumberFormatException e) {
        produto.preco = 0;
    }
    try {
        produto.estoque = Integer.parseInt(colunas[3]);
    } catch(NumberFormatException e) {
        produto.estoque = 0;
    }

    return produto;
}

public Supplier<Produto> fornecedorProduto(Scanner scanArquivo) {
    return () -> {
        if (scanArquivo.hasNext()) {
            return parseProdutoLinhaCsv(scanArquivo.nextLine());
        }
        return null;
    };
}

To do the reading, I will use the following iteration:

[... linhas anteriores ...]
Scanner scanArquivo = new Scanner(estoque);
for (Supplier<Produto> fornecedorProduto, Produto prod = fornecedorProduto.get(); prod != null; prod = fornecedorProduto.get()) {
    // ... faz coisas enquanto lê
}
[... fim da leitura ...]

Comparing two Produtos

Here, the comparison takes place through your field preco. I will make a sign function to be used for sorting, and also a boolean function to simply know if p1 is more expensive than p2:

public int cmpProduto(Produto p1, Produto p2) {
    return p1.preco < p2.preco? -1: p1.preco > p2.preco? +1: 0;
}

public boolean maisCaro(Produto p1, Produto p2) {
    return p1.preco > p2.preco;
}

String presentation

In this case, I will have a "presenter" who will consume a Produto and generate an output in the stdout/sysout. To that end, I will create a separate function just to transform from Produto for String, so if I want to, for example, print in a file, just use this function Produto |-> String

public String produto2string(Produto p) {
    return p != null? "" + p.nomeProduto + ": $" + p.preco: "sem produto";
}

public Consumer<Produto> apresentadorProduto(Function<Produto,String> transformaProduto, Consumer<String> apresentador) {
    return (p) -> apresentador.accept(transformaProduto(p));
}

To create the desired presenter:

[... linhas anteriores ...]
Consumer<Produto> apresentador = apresentadorProduto(this::produto2string, System.out::println);
[... linhas posteriores, que alguma deve usar o apresentador ...]

Several solutions

Keep the whole list and select the 2 largest ones

Running time: o(k)

Maximum used memory: o(k)

This was the strategy you tried to use. I will point out which points of problems.

The reading itself you have already done. I will change here slightly to follow the example. But the iteration itself is wrong. Starting with the fact that it does not go through all the elements. The last element, index produtos.size() - 1, is not being considered.

You’ll need three distinct variables here:

  • the iteration variable
  • the variable indicating who is the largest product
  • the variable indicating who is the second largest product

We can start, we can consider that the first two elements of the list are our first candidates the largest and second largest. So we need to secure who’s the biggest and then keep the second highest in the right position. After that, just update these variables with the relevant values as the other values are read.

[... linhas anteriores ...]

List<Produto> produtos = new ArrayList<>();
Scanner scanArquivo = new Scanner(estoque);
for (Supplier<Produto> fornecedorProduto = fornecedorProduto(scanArquivo), Produto prod = fornecedorProduto.get(); prod != null; prod = fornecedorProduto.get()) {
    produtos.add(prod);
}

Produto maisCaro;
Produto segundoMaisCaro;

if (produtos.size() == 0) {
    maisCaro = null;
    segundoMaisCaro = null;
} else if (produtos.size() == 1) {
    maisCaro = produtos.get(0);
    segundoMaisCaro = null;
} else {
    Produto candidato;

    maisCaro = produtos.get(0);
    candidato = produtos.get(1);

    if (maisCaro(candidato, maisCaro)) {
        // o candidato é mais caro do que o primeiro elemento
        // precisa "bolhar" o antigo primeiro elemento para "baixo"
        segundoMaisCaro = maisCaro;
        maisCaro = candidato;
    } else {
        // o candidato deve ocupar a segunda posição
        segundoMaisCaro = candidato;
    }

    // lista já começa ignorando os dois primeiros elementos
    for (int i = 2; i < produtos.size(); i++) {
        candidato = produtos.get(i);
        if (maisCaro(candidato, maisCaro)) {
            // o candidato é mais caro do que o primeiro elemento
            // precisa "bolhar" o antigo primeiro elemento para "baixo"
            segundoMaisCaro = maisCaro;
            maisCaro = candidato;
        } else if (maisCaro(candidato, segundoMaisCaro)) {
            // o candidato é o mais caro do que o segundo elemento, substitui ele
            segundoMaisCaro = candidato;
        }
    }
}

// ao sair daqui, eu tenho preenchido os dois mais caros (ou nulo, caso tenha menos elementos)

Consumer<Produto> apresentador = apresentadorProduto(this::produto2string, System.out::println);
apresentador.accept(maisCaro);
apresentador.accept(segundoMaisCaro);

[... linhas posteriores ...]

Keep the whole list and sort it at the end by selecting the 2 largest ones after ordering

Running time: o(k log(k))

Maximum used memory: o(k)

Here the idea is to sort the list and get only the first elements (the most expensive). The standard of the natural order is to sort upwards, but this would put the higher prices pro final. To avoid this, I will take the "reverse comparator".

Note that the total run time is longer than the previous one, because we are now using a comparative sorting algorithm (List.sort uses Timsort). The memory used by TimSort is o(k), therefore maintaining the asymptotic complexity constant space.

[... linhas anteriores ...]

List<Produto> produtos = new ArrayList<>();
Scanner scanArquivo = new Scanner(estoque);
for (Supplier<Produto> fornecedorProduto = fornecedorProduto(scanArquivo), Produto prod = fornecedorProduto.get(); prod != null; prod = fornecedorProduto.get()) {
    produtos.add(prod);
}


Comparator<Produto> cmpNatural = this::cmpProduto;
produtos.sort(cmpNatural.reversed());

Produto maisCaro;
Produto segundoMaisCaro;

if (produtos.size() == 0) {
    maisCaro = segundoMaisCaro = null;
} else if (produtos.size() == 1) {
    maisCaro = produtos.get(0);
    segundoMaisCaro = null;
} else {
    maisCaro = produtos.get(0);
    segundoMaisCaro = produtos.get(1);
}

Consumer<Produto> apresentador = apresentadorProduto(this::produto2string, System.out::println);
apresentador.accept(maisCaro);
apresentador.accept(segundoMaisCaro);

[... linhas posteriores ...]

Keep only 2 elements, the 2 larger ones, updating them each read

Running time: o(k)

Maximum used memory: o(1)

Here, the idea is particularly distinct from the previous ones. No more than 3 variables of the type Produto:

  • the most expensive of all
  • the second most expensive
  • a new candidate to enter

The idea is to make it so lucky that I control it right in the lobby. As an immediate consequence, it is not necessary to keep all created objects in a vector, so the actual working memory used is constant (obviously that creating objects will cause some of them to remain unreachable in memory until the GC runs and clears them):

List<Produto> produtos = new ArrayList<>();
Scanner scanArquivo = new Scanner(estoque);
Supplier<Produto> fornecedorProduto = fornecedorProduto(scanArquivo);

Produto maisCaro = fornecedorProduto.get();
Produto segundoMaisCaro = fornecedorProduto.get();

// mantendo os dois primeiros ordenados
if (segundoMaisCaro != null && maisCaro(segundoMaisCaro, maisCaro)) {
    Produto swap = maisCaro;
    maisCaro = segundoMaisCaro;
    segundoMaisCaro = maisCaro;
}

for (Produto prod = fornecedorProduto.get(); prod != null; prod = fornecedorProduto.get()) {
    if (maisCaro(prod, maisCaro)) {
        // precisa "bolhar" o antigo primeiro elemento para "baixo"
        segundoMaisCaro = maisCaro;
        maisCaro = prod;
    } else if (maisCaro(prod, segundoMaisCaro)) {
        // preciso só trocar o último produto
        segundoMaisCaro = prod;
    }
}

Consumer<Produto> apresentador = apresentadorProduto(this::produto2string, System.out::println);
apresentador.accept(maisCaro);
apresentador.accept(segundoMaisCaro);

[... linhas posteriores ...]

Then I continue the other 3 options

1

Here I will not optimize the code(the other answer already presented a lot of good solutions), instead I will just indicate the error that makes the program print only the highest price.

Long version

The problem is here:

for(int i = 0; i < produtos.size()-1; i++){
       if(produto.preco > maiorPreco){
            maiorPreco = produto.preco;
       }
}

There is no operation that takes the index item i of the list produtos and stores in produto. Consequently, the object stored in produto is the last product read in the previous loop.

To solve the problem, put the following operation before the comparison:

produto = produtos.get(i);

The final code should look like this:

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;


public class Exercicio {

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    // TODO code application logic here

    File estoque = new File("C:\\estoque.csv");

    try{
        String fileLines = new String();

        Scanner reader = new Scanner(estoque);
        reader.nextLine();

        List<Produto> produtos = new ArrayList<Produto>();
        Produto produto = null;
        String[] linha;

        while(reader.hasNext()){

            fileLines = reader.nextLine();
            linha = fileLines.split(";");


            produto = new Produto();
            produto.nomeProduto = linha[0];
            produto.marca = linha[1];

            try{
            produto.preco = Double.parseDouble(linha[2]);
            }catch(NumberFormatException e){
                produto.preco = 0;
            }
            try{
            produto.estoque = Integer.parseInt(linha[3]);
            }catch(NumberFormatException e){
                produto.estoque = 0;
            }
            produtos.add(produto);
        }

        /*for(Produto p : produtos){
            System.out.println(); // IMPRIMIR INFORMAÇÕES


        }*/

            double maiorPreco = 0;
            double segundoMaiorPreco = 0;
            double segundoMaior = 0;

        for(int i = 0; i < produtos.size()-1; i++){
               produto = produtos.get(i); //Isto altera o produto sendo comparado.
               if(produto.preco > maiorPreco){
                   maiorPreco = produto.preco;
               }

           }
            System.out.println(maiorPreco);

    }catch(FileNotFoundException e){
    }
    }
 }

class Produto {

public String nomeProduto;
public  String marca;
public double preco;
public int estoque;

}

If you also want to save the second most expensive element, change the second loop for this:

for(int i = 0; i < produtos.size()-1; i++){
   produto = produtos.get(i);
   if(produto.preco > maiorPreco){
      segundoMaiorPreco = maiorPreco;
      maiorPreco = produto.preco;
   }
}
System.out.println(maiorPreco); //Maior preço
System.out.println(segundoMaiorPreco); //Segundo maior preço

If you want to optimise the code, the previous answer already presented a good set of options.

TL; DR version

You forgot to update the variable produto last for.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.