We have some strategies to analyze here:
- keep the whole list and select the 2 largest ones
- keep the whole list and sort it at the end by selecting the 2 largest ones after ordering
- keep only 2 elements, the 2 larger ones, updating them every read
- 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:
- keep the entire list, remove the largest and element and separate it, then select the largest element from the remaining list
- 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 Produto
s
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
You don’t need a list
– Jefferson Quesado
how not? can explain?
– César O. Araújo
Responding... hold the accounts
– Jefferson Quesado