Resolved count DataGridView numbers that match Array

titojd

Well-known member
Joined
Oct 12, 2018
Messages
63
Programming Experience
1-3
I have a text file that is generated daily and can contain several lines with 15 numbers drawn from 01 to 25 in each line, the text can have one line or several lines, it changes every day.

I need to compare with an array 'Result' and count how many numbers in each line are equal to the numbers in the array, it's like a raffle, the Text lines are bets, the Array would be the 'Result'

I'm doing it with Dictionary but the result is not what I expected, it's getting like this.
teste.png


I need it to stay like this...
teste2.png


I'm using the following code

C#:
private IDictionary<int, int> GetResultFromTextFile(IEnumerable<int> src)
        {
            var filePath = @"C:\BoaSorte\Banco\testeResultado.txt";
            var delimiter = new[] { ' ' };
            var dict = File.ReadLines(filePath)
                .Where(line => !string.IsNullOrEmpty(line))
                .Aggregate(new Dictionary<int, int>(), (d, line) =>
                {
                    var values = line
                    .Split(delimiter, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => int.Parse(x));
                    var matchCount = values.Where(v => src.Contains(v)).Count();

                    if (matchCount <= 15)
                    {
                        if (d.ContainsKey(matchCount))
                            d[matchCount]++;
                        else
                            d[matchCount] = matchCount;
                    }
                    return d;
                });

            return dict;
        }



        private void button1_Click(object sender, EventArgs e)
        {
            int outVal;

            if (UltimoResultado.Any(Acertos => !int.TryParse(Acertos.Text, out outVal)))
            {
                MessageBox.Show("Valores Invalidos...");
                return;
            }
            var arr = UltimoResultado.Select(linha => int.Parse(linha.Text));
            var result = GetResultFromTextFile(arr).ToList();
            for (int i = 0; i < result.Count; i++)
            {
                    dgHits.Rows[I].Cells["Acertos"].Value = result[I].ToString();               
            }
        }[/I][/I]
 
Solution
Hello @titojd
As I saw while writting your "raffle" app, it is much easier than what it looked like. As @Skydiver proposes, you need no Dictionary, you don't even need a 2 D array as I thought first. You use a Dictionary when you need a collection of KeyValuePairs that consist in an unique key and its value, for example, the game about translating words. Your job here is just processing lines from a TXT file and adding them to the DataGridView.
In my example, I treated the numbers as strings, because of the prefix zeros (like 05, 08, etc.). I must clarify that if I hadn't read what Skydiver wrote about the Intersect() method, I would have had to write some more code for that logic. So, thank you, Skydiver. What my example...
Anyway, part of the issue here is that you are using your UI as your data model. You are storing the values you got from the web service into UI controls instead of just holding on to your Resultado object. If you just hold on to that object, then you would simply use:
C#:
count = contents.Intersect(resultado.listaDezenas).Count();

As an side, a few other things not directly related to your problem:
1) You are using HttpClient incorrectly. You should only create one static instance and use it for the lifetime of your application.
2) Lines 7-10 and 20-23 are essentially the same. Why not just combine the two chunks of code?
3) Do you really need the variable controles?
4) You should really avoid using var result = CallSomeMethodAsync().Result. The correct thing to do is to write var result = await CallSomeMethodyAsync(). The former is synchronous and can make your UI non-responsive if you are calling the code in your UI thread, or just monopolizes a thread if you are not using the UI thread. The latter is asynchronous and makes your UI seem more responsive if calling the code in your UI thread, or allows the CPU to context switch to another thread if you are not using the UI thread.
 
Last edited:
For future note, both these links are required reading for anyone who uses HttpClient themselves directly

https://www.aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/
https://josef.codes/you-are-probabl...-wrong-and-it-is-destabilizing-your-software/

If you don't want to use it directly, and want to make your life easier when downloading stuff, see Flurl - in your case your code for downloading the lotto results would be like await "https://servicebus2.caixa.gov.br/portaldeloterias/api/lotofacil".GetJsonAsync<LottoRoot>()

--

To parse json easily you copy your JSON, go to app.quicktype.io, set a name for the class, set a namespace, set C#, paste the JSON into the blue area and copy the code out of the white area

This generates you nice classes that are to C# standard naming convention and Just Work to deserialize the data:

1691730324012.png


When using Flurl and QuickType take note to set QuickType to newtonsoft mode, as Flurl doesn't use System.Text.Json. Or, use Flurl to download a string and use the string as per the comments in the QuickType code e.g.

C#:
  var r = LottoRoot.FromJson(await "https://servicebus2.caixa.gov.br/portaldeloterias/api/lotofacil".GetStringAsync());
 
ps, if you have arrays/lists that a) contain your bought tickets numbers and contain your lotto results, then setting the count of matching numbers is quite easy:

C#:
        var tickets = new []{
          new[] { "01","02","04","05","06","07","09","10","13","14","16","17","19","21","25"},
          new[] { "02","03","04","06","07","08","09","10","13","16","17","19","21","23","25"},
          new[] { "01","02","03","06","07","08","10","11","13","14","16","19","20","23","24"},
          new[] { "02","04","05","07","10","11","12","14","17","18","21","22","23","24","25"},
          new[] { "04","05","07","08","10","11","12","13","16","17","18","20","23","24","25"},
          new[] { "01","02","07","08","10","11","12","14","16","17","18","20","22","23","25"},
          new[] { "03","04","06","07","10","11","12","13","14","16","17","18","22","23","25"}
        };
     
        var result = new[] { "01","02","03","05","06","07","09","10","13","14","16","17","19","21","25" };
     
     
        var ticketsWithCount = tickets.Select(t =>
                t.Concat(new[]{
                    t.Intersect(result).Count().ToString()
                }).ToArray()
            ).ToArray();


This Selects each ticket (an array of numbers) in tickets, and Concats onto the end of the ticket numbers, a new array with just one member; the count of items in the ticket array that Intersect with the lotto result array

Even if you weren't using Intersect (this is very slow, don't do it):

C#:
        var ticketsWithCount = tickets.Select(t =>
                t.Concat(new[]{
                    t.Count(te => result.Contains(te)).ToString()
                }).ToArray()
            ).ToArray();

This is the same as before except the Count is arrived at by doing a "for each number in the ticket, ask the result array if it Contains the number; if the response is true, the result is counted. If not, it isn't"

If this way makes more sense to you, convert the `results` array to a HashSet (call ToHashSet on it and store the retruned value, and use THAT in your Contains. Contains on a HashSet is much faster than on a List/Array )
 
Last edited:
Hi @titojd
@cjard 's solution is better than mine, but I tested the code and it works.
The classes:

C#:
namespace WinFormsAppRaffle
{
    public class LotoFacil
    {
        public bool acumulado { get; set; }
        public string dataApuracao { get; set; }
        public string dataProximoConcurso { get; set; }
        public string[] dezenasSorteadasOrdemSorteio { get; set; }
        public bool exibirDetalhamentoPorCidade { get; set; }
        public int? id { get; set; }
        public int indicadorConcursoEspecial { get; set;}
        public string[] listaDezenas { get; set; }
        public string[] listaDezenasSegundoSorteio { get; set; }
        public MunicipioUFGanhadores[] listaMunicipioUFGanhadores { get; set; }
        public RateioPremio[] listaRateioPremio { get; set; }

    }

    public class MunicipioUFGanhadores
    {
        public int ganhadores { get; set; }
        public string municipio { get; set; }
        public string nomeFantasiaUL { get; set; }
        public int posicao { get; set; }
        public string serie { get; set; }
        public string uf { get; set; }
    }

    public class RateioPremio
    {
        public string descricaoFaixa { get; set; }
        public int faixa { get; set; }
        public int numeroDeGanhadores { get; set; }
        public double valorPremio { get; set; }
    }
}
And the code:
C#:
using System.Text.Json;

namespace WinFormsAppRaffle
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string[] lines = File.ReadAllLines(textBox2.Text);
            string[] line, contents, result;
            int count;
            result = LoadResultArray();
            foreach(var num in result)
                textBox1.Text += num + " ";
            for (int i = 0; i < lines.Length; i++)
            {
                contents = lines[i].Split(' ');
                count = contents.Intersect(result).Count();
                line = new string[16];
                for (int j = 0; j < 15; j++)
                    line[j] = contents[j];
                line[15] = count.ToString();
                dataGridView1.Rows.Add(line);
            }
        }

        private string[] LoadResultArray()
        {
            string[] result = null;
            var url = @"https://servicebus2.caixa.gov.br/portaldeloterias/api/lotofacil";
            HttpClient lotofacil = new HttpClient();
            var response = lotofacil.GetAsync(url).Result;

            if (response.IsSuccessStatusCode)
            {
                var json = response.Content.ReadAsStringAsync().Result;
                var obj = JsonSerializer.Deserialize<LotoFacil>(json);
                result = obj.listaDezenas;
            }
            return result;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.ColumnCount = 16;
            for (int i = 0; i < 15; i++)
            {
                dataGridView1.Columns[i].HeaderText = (i < 9 ? "0" : "") + (i + 1).ToString();
                dataGridView1.Columns[i].Width = 35;
            }
            dataGridView1.Columns[15].HeaderText = "Success";
            dataGridView1.Columns[15].Width = 35;
        }
    }
}
I didn't finish to write the LotoFacil class properties because you don't need them, if you need you can add the missing properties. Also some properties I saw equalized to null I don't know the real type, and I had to guess, maybe in another json they come with some value and it's not the one I wrote, check that.
Again, cjard's solution is better, because as I can see it writes the classes automatically.
But, by now, you have what you needed. You can see the other properties of the LotoFacil object with the Intellisense. It includes the number of winners, that is into an array.
Regards
Pablo
 

Attachments

  • raffle.png
    raffle.png
    8.7 KB · Views: 5
This:
C#:
dataGridView1.Columns[i].HeaderText = (i < 9 ? "0" : "") + (i + 1).ToString();
can be simplified to:
C#:
dataGridView1.Columns[i].HeaderText = $"{i:D2}";
 
This code:
C#:
string[] result = null;
var url = @"https://servicebus2.caixa.gov.br/portaldeloterias/api/lotofacil";
HttpClient lotofacil = new HttpClient();
var response = lotofacil.GetAsync(url).Result;

if (response.IsSuccessStatusCode)
{
    var json = response.Content.ReadAsStringAsync().Result;
    var obj = JsonSerializer.Deserialize<LotoFacil>(json);
    result = obj.listaDezenas;
}
return result;

can probably be simplified to:
C#:
//$ BUGBUG: Should be declared and initialized static
HttpClient lotofacil = new HttpClient();

try
{
    //$ BUGBUG: This should use await
    var json = lotofacil.GetStringAsync("https://servicebus2.caixa.gov.br/portaldeloterias/api/lotofacil").Result;
    var obj = JsonSerializer.Deserialize<LotoFacil>(json);
    return obj.listaDezenas;   
}
catch
{
    //$ TODO: Handle errors here
}
//$ REVIEW: Is returning null really a good idea?
return null;
 
Is there any particular reason why you need to do everything using LINQ extension methods?

Anyway, the key LINQ extension method you want to take advantage of is https://learn.microsoft.com/en-us/dotnet/api/system.linq.enumerable.intersect?view=net-7.0. Once that returns an enumberable with the intersecting items, just call Count() on that enumerable to find out how many numbers match.
 
I managed to solve half of the problem, but a result appears for each line, the repeated lines in the number of hits only one appears, the others are empty,

C#:
private IDictionary<int, int> GetResultFromTextFile(IEnumerable<int> src)
        {
            var filePath = @"C:\BoaSorte\Banco\testeResultado.txt";
            var delimiter = new[] { ' ' };
            var dict = File.ReadLines(filePath)
                .Where(line => !string.IsNullOrEmpty(line))
                .Aggregate(new Dictionary<int, int>(), (d, line) =>
                {
                    var values = line
                    .Split(delimiter, StringSplitOptions.RemoveEmptyEntries)
                    .Select(x => int.Parse(x));
                    int matchCount = values.Intersect(src).Count();

                        if (matchCount <= 15) { d[matchCount] = matchCount; }

                    return d;
                });

            return dict;
        }
C#:
 for (int i = 0; i < result.Count; i++)
            { dgHits.Rows[i].Cells["Acertos"].Value = result[i].Value;}
 
Last edited by a moderator:
Why would you expect a different result given this line of code?
C#:
if (matchCount <= 15) { d[matchCount] = matchCount; }
You are making a dictionary were the key is the value, and the value happens to be the match count.

And then later, you are trying to pull values from that dictionary assuming that the key is the row number in the second of chunk of code you have above.

Why do you even need to use a dictionary?

I'm assuming that you are reading from your text file twice: once to get the values and put into the grid, and a second time in that first chunk of code you have above. Why? Why count the matches as each line is read from the file and populate the data grid with the read values from the file and the match count at the same time?
 
Hello Paratrooper, thank you very much for answering.
you are right in the analysis, I was actually reading it twice,
in fact, I've done and re-done this code so many times that I've already messed with my head,
following your guidance, my code is like this now,
C#:
 private IDictionary<int, int> GetResultFromTextFile(IEnumerable<int> src)
        {
            var filePath = @"C:\BoaSorte\Banco\testeResultado.txt";
            var delimiter = new[] { ' ' };
            var dict = new Dictionary<int, int>();

            foreach (var line in File.ReadLines(filePath).Where(line => !string.IsNullOrEmpty(line)))
            {
                var values = line.Split(delimiter, StringSplitOptions.RemoveEmptyEntries)
                                 .Select(x => int.Parse(x));
                var matchCount = values.Count(v => src.Contains(v));

                if (matchCount <= 15)
                {
                    if (!dict.ContainsKey(matchCount))
                    {
                        dict[matchCount] = matchCount;                                             
                    }

                    //dict[matchCount]++;
                }
            }
            return dict;
        }
[/ICODE]
[ICODE]
    private void button1_Click(object sender, EventArgs e)
        {
            int outVal;

            if (UltimoResultado.Any(Acertos => !int.TryParse(Acertos.Text, out outVal)))
            {
                MessageBox.Show("Valores Invalidos...");
                return;
            }
            var arr = UltimoResultado.Select(Rows => int.Parse(Rows.Text));
            var result = GetResultFromTextFile(arr).ToList();

            for (int i = 0; i < result.Count; i++)
            {
                dgHits.Rows[i].Cells["Acertos"].Value = result[i].Value;
            }
        }
this is how it is showing on the grid

chaverepetida.png


I need to show how many hits are in each line, comparing with the numbers generated in the array,
EX: line 1 --- 10 hits,
line2 --- 8 hits,
row 3 --- 10 hits,
line 4 --- 11 hits,
line 5 --- 11 hits,
row 6 --- 7 hits.

the code I have, shows me the total lines with 10 hits,
the total lines with 11 hits,
the total rows with 7 hits, Just exemplifying
*that's not what I need.
what i need each line to have the number of hits at the end of the line itself.
I'll leave an image to try to understand.
so it would have to stay
teste2.png

doesn't have to be in order
I'm not being able to make this happen...
 
Last edited by a moderator:
Hello @titojd
As I saw while writting your "raffle" app, it is much easier than what it looked like. As @Skydiver proposes, you need no Dictionary, you don't even need a 2 D array as I thought first. You use a Dictionary when you need a collection of KeyValuePairs that consist in an unique key and its value, for example, the game about translating words. Your job here is just processing lines from a TXT file and adding them to the DataGridView.
In my example, I treated the numbers as strings, because of the prefix zeros (like 05, 08, etc.). I must clarify that if I hadn't read what Skydiver wrote about the Intersect() method, I would have had to write some more code for that logic. So, thank you, Skydiver. What my example does is, read each line from the TXT file (you have to write the path in the textBox2), split it into 15 strings (the numbers), Intersect them with the Result array (you have to write the numbers in its TextBox (textBox1), separated by 1 space, and with the prefix zeros), and put the 15 numbers and the "success" number in the string array, and finally add that line to the grid's rows.
I would also like to clarify that I have been helped very much by people (especially @cjard) from this Forum, so I think it is fair that I help other people in it, when I can.
I hope this helps you.
Pablo
The code (see the image too, please):
C#:
namespace WinFormsAppRaffle
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            string[] lines = File.ReadAllLines(textBox2.Text);
            string[] line, contents, result;
            int count;
            result = textBox1.Text.Split(' ');
            for (int i = 0; i < lines.Length; i++)
            {
                contents = lines[i].Split(' ');
                count = contents.Intersect(result).Count();
                line = new string[16];
                for (int j = 0; j < 15; j++)
                    line[j] = contents[j];
                line[15] = count.ToString();
                dataGridView1.Rows.Add(line);
            }

        }

        private void Form1_Load(object sender, EventArgs e)
        {
            dataGridView1.ColumnCount = 16;
            for (int i = 0; i < 15; i++)
            {
                dataGridView1.Columns[i].HeaderText = (i < 9 ? "0" : "") + (i + 1).ToString();
                dataGridView1.Columns[i].Width = 35;
            }
            dataGridView1.Columns[15].HeaderText = "Success";
            dataGridView1.Columns[15].Width = 35;
        }
    }
}
 

Attachments

  • raffle.png
    raffle.png
    8.7 KB · Views: 11
Solution
hello @Pablo, I am happy with your answer, thank you very much.
it worked as well as in the image, however, my Result comes on an array
Label[ ] ResultUltimat = new Label[15] created dynamically, where Array ' ResultUltimat' is populated by a json file received via url.

how would I replace this TextBox1.Text with the ResultUltimat[ ].Text?
tried my way here but it just returned zeros on every line
 
You shouldn't use an array of Label objects, you use them only to show a readonly text in the Form. You should use directly an array of string to load the numbers from the json file. And then if you want to show the numbers from the Result array in Labels, you create the array of Label and pass the numbers to their Text property. I suppose you'll not have problems retrieving the numbers as strings, but if you do, you can show me that json file and I will see. Probably, beyond the very very bad code of the array of Label, you are reading the json file incorrectly.
 
so I load my Label array with json
C#:
 private void CarregaResultado()
        {
            var url = @"https://servicebus2.caixa.gov.br/portaldeloterias/api/lotofacil";
            HttpClient lotefacil = new HttpClient();
            var resposta = lotofacil.GetAsync(url).Result;

            if (resposta.IsSuccessStatusCode)
            {
                var listaDezenas = resposta.Content.ReadAsStringAsync().Result;
                var listaDezenass = JsonConvert.DeserializeObject<Resultado>(listaDezenas);

                Control[] controles = ResultUltimat;

                for (int i = 0; i < controles.Length; i++)
                {
                    int valor = Convert.ToInt32(listaDezenass.listaDezenas[I]);
                    controles[I].Text = valor.ToString("D2");
                }
            }
            if (resposta.IsSuccessStatusCode)
            {
                var numero = resposta.Content.ReadAsStringAsync().Result;
                var sorteio = JsonConvert.DeserializeObject<Resultado>(número);

                txtConcurso.Text = sorteio.numero.ToString();
                lblData.Text = sorteio.dataApuracao.ToString();
            }
        }
 
Last edited by a moderator:
Back
Top Bottom