How can I find the average of all numbers in a textfile - vb.net

I'm ridiculously stuck on this one. My code below sums up all the numbers that are in the textfile Dailyfile and outputs the total to AverageFile. The problem is I don't want it to sum up. I want it to find out the average of all the numbers.
How can I do this?
Dim AverageFile As String = "C:\xxx\zzz\" & System.DateTime.Now.ToString("yyyyMMdd") & ".txt"
Dim DailyFile As String = "C:\xxx\xxx\" & System.DateTime.Now.ToString("yyyyMMdd") & ".txt"
Try
If System.IO.File.Exists(AverageFile) Then
Dim total As double = 0
For Each line As String In IO.File.ReadAllLines(DailyFile)
total += Double.Parse(line)
Next
Dim objWriter As New System.IO.StreamWriter(AverageFile, false)
objWriter.WriteLine(total.ToString)
objWriter.Close()
Else
'Nothing yet
End If
Catch ex As Exception
lbErrors.Items.Add(String.Concat(TimeOfDay & " Error 98: File or folder might not exist. Restart application... ", ex.Message))
End Try
The Dailyfile simply looks like this;
I've tried a bunch of variations on the total 0= double.parse(line), because I feel like thats where the problem lies. I've also tried diming the total as integer = 0. I'm new to the calculating, so I don't know how things go.

The average is just the total divided by the number of things you summed up. (Assuming you want to use the arithmetic mean, which is probably what you are looking for.)
Dim total As double = 0
Dim numOfLines As Integer = 0
For Each line As String In IO.File.ReadAllLines(DailyFile)
numOfLines += 1
total += Double.Parse(line)
Next
Dim average As Double = total / numOfLines
Dim objWriter As New System.IO.StreamWriter(AverageFile, false)
objWriter.WriteLine(average.ToString)
objWriter.Close()
What was missing in your code is just keeping track of the number of lines and dividing the sum by this number.
Just as an example: We are 3 people. I am 23 years old, you are 35 years old, our friend is 40 years old. The average of our ages would be (23 + 35 + 40) / 3 which is 32.666...

Either use CherryDT's approach to count the lines and divide the total through this number or use LINQ's Enumerable.Average, for example with this concise query:
Dim allNumbers = From line In IO.File.ReadLines(DailyFile)
Let num = line.TryGetDouble()
Where num.HasValue
Select num.Value
Dim average As Double = allNumbers.Average()
I've used following extension method to try-parse the string to a Nullable(Of Double):
Imports System.Runtime.CompilerServices
Module StringExtensions
<Extension()>
Public Function TryGetDouble(ByVal str As String) As Nullable(Of Double)
If str Is Nothing Then Return Nothing
Dim d As Double
If Double.TryParse(str.Trim(), d) Then
Return d
Else
Return Nothing
End If
End Function
End Module

Related

Showing the name of the most expensive product in vb

i'm pretty new to programming and i got stuck trying to write a program where you type in the name and prices of products and you get back the total, the name+ prices and the most expensive product. Everything works fine except showing the name of the most expensive product.
Here's what i've done
""
Public Class Mrj
Shared Sub main()
Dim i, n As Integer
Console.WriteLine("Enter the number of products")
n = Console.ReadLine()
Dim Products_name(n) As String
Dim Products_price(n), HT, TTC, TVA, max As Decimal
For i = 1 To n
Console.WriteLine("Enter the name of the product " & i)
Products_name(i - 1) = Console.ReadLine()
Console.WriteLine("Enter the price of the product " & i)
Products_price(i - 1) = Console.ReadLine()
HT = HT + Products_price(i - 1)
Next
For i = 1 To n
Console.WriteLine(Products_name(i - 1) & " " & Products_price(i - 1))
Next
TVA = 0.2 * HT
TTC = HT + TVA
Console.WriteLine("Total to pay " & TTC)
max = Products_price(0)
For i = 1 To n - 1
If max > Products_price(i) Then
Else
max = Products_price(i)
End If
Next
Console.WriteLine("The product the most expensive is" & max & Products_name(i))
End Sub
End Class
""
I think the problem is that you are using i to get the name of the most expensive product, but that index i is always i = n since you don't save the index of the maximum value.
You should add a new variable where you store the index everytime you get a new maximum value, and use it in the last line.
Your for loop should be something like this:
Dim max_index As Integer
For i = 1 To n - 1
If max > Products_price(i) Then
Else
max = Products_price(i)
max_index = i
End If
Next
Console.WriteLine("The product the most expensive is" & max & Products_name(max_index))
Try this out and check if it works.
Turn on Option Strict now and forever. Project Properties -> Compile tab. Also for future projects Tools -> Options -> Projects and Solutions -> VB Defaults
You cannot assume that a user will actually enter a number. Test with TryParse.
Arrays in vb.net are declared Products_name(upper bound). In this case that would be Products_name(n-1)
Instead of doing i - 1 for the indexes in the For loop, start our with For i = 0 to n-1
I decided to not use the parallel arrays. Instead I made a tiny class and declared a List(Of Product). I filled the list with the user input setting the Properties of the Product.
I used Linq instead of loops for sums and max. Not necessarily faster but can be accomplished in a single line of code.
I use interpolated strings to display results. When your string is preceded by a $, you can insert variables directly in the text surrounded by braces. The colon following Price indicates a formatting character. Here, I used a C for currency.
Public Class Product
Public Property Name As String
Public Property Price As Decimal
End Class
Sub main()
Dim ProductList As New List(Of Product)
Dim n As Integer
Console.WriteLine("Enter the number of products")
Integer.TryParse(Console.ReadLine, n)
For i = 1 To n
Dim p As New Product
Dim pr As Decimal
Console.WriteLine("Enter the name of the product " & i)
p.Name = Console.ReadLine()
Console.WriteLine("Enter the price of the product " & i)
Decimal.TryParse(Console.ReadLine, pr)
p.Price = pr
ProductList.Add(p)
Next
For Each p In ProductList
Console.WriteLine($"{p.Name} {p.Price:C}")
Next
Dim SubTotal As Decimal = ProductList.Sum(Function(item) item.Price)
Dim Tax As Decimal = 0.2D * SubTotal
Dim Total = SubTotal + Tax
Console.WriteLine($"Total to pay {Total:C}")
Dim Prod = ProductList.OrderByDescending(Function(p) p.Price).FirstOrDefault()
Console.WriteLine($"The product the most expensive is {Prod.Name} at {Prod.Price:C}")
Console.ReadKey()
End Sub

Vb.net Data is not being incremented and added to list

I'm having an issue trying to create a program that takes user input for a text file's location containing medical records. The diseases and number of patients are being added to a list. I'm having an issue where my console is printing 0 for both the total of XX unique diseases and YYY patient encounters. I am not getting any errors, just not the correct output.
I believe my issue is in my processData() sub, however I am unsure why it's printing back 0. Also, how do I go about keeping track of duplicate diseases that are added to the list as I'm trying to add a counter next to each time the disease is seen.
Sample from Disease.txt
3710079 JUDITH CLOUTIER 2012-08-04 Spastic Colonitis
3680080 VIRGINIA ALMOND 2012-07-25 Chronic Phlegm
3660068 ELLEN ENGLEHARDT 2012-04-06 Whooping Cough
3810076 LILLIAN KEMMER 2014-07-04 Scurvy
3630055 TERESA BANASZAK 2012-06-15 Scurvy
Output:
There were a total of 0 unique diseases observed.
A total of 0 patient encounters were held
Main():
' Global variables
Dim inputFile As String
Dim patientCounter = 0
Dim diseaseList As New List(Of String)
Dim dateList As New List(Of Date)
Sub Main()
Dim reportFile As String
Dim yn As String
Console.ForegroundColor = ConsoleColor.Yellow
Console.BackgroundColor = ConsoleColor.Blue
Console.Title = "Medical Practice Data Analysis Application"
Console.Clear()
Console.WriteLine("Please enter the path and name of the file to process:")
inputFile = Console.ReadLine
If (File.Exists(inputFile)) Then
' Call to processData sub if input file exists
processData()
Console.WriteLine(vbCrLf & "Processing Completed...")
Console.WriteLine(vbCrLf & "Please enter the path and name of the report file to generate")
reportFile = Console.ReadLine
File.Create(reportFile).Dispose()
If (File.Exists(reportFile)) Then
Console.WriteLine(vbCrLf & "Report File Generation Completed...")
Else
' Call to sub to end program if directory does not exist
closeProgram()
End If
' Get user input to see report
Console.WriteLine(vbCrLf & "Would you like to see the report file [Y/n]")
yn = Console.ReadLine
' If user inputs "y" or "Y" then print report
' Otherwise close the program
If (yn = "y" OrElse "Y") Then
printFile()
Else
closeProgram()
End If
Else
' Call to sub to end program if file does not exist
closeProgram()
End If
Console.ReadLine()
End Sub
processData Sub():
Public Sub processData()
Dim lines As String() = File.ReadAllLines(inputFile)
Dim tab
Dim dates
Dim diseaseCounter = 0
For Each line As String In lines
tab = line.Split(vbTab)
patientCounter += 1
dates = Date.Parse(line(3))
dateList.Add(dates)
'diseaseList.Add(line(4))
Dim disease As New disease(line(4))
diseaseList.Add(disease.ToString)
'diseaseList(line(4)).
For Each value In diseaseList
'If value.Equals(line(4)) Then disease.counter += 1
Next
Next
Dim uniqueDiseases As String() = diseaseList.Distinct().ToArray
End Sub
Disease.class
Class disease
Dim counter As Integer = 0
Dim name As String = ""
Sub New(newDisease As String)
name = newDisease
counter = 0
End Sub
End Class
printFile()
Sub printFile()
Dim muchoMedical As String = "MuchoMedical Health Center"
Dim diseaseReport As String = "Disease Report For the Period " & "earliest_date" & " through " & "latest_date"
Console.WriteLine(vbCrLf & muchoMedical.PadLeft(Console.WindowWidth / 2))
Console.WriteLine(diseaseReport.PadLeft(Console.WindowWidth / 2))
Console.WriteLine(vbCrLf & "There were a total of " & diseaseList.Count & " unique diseases observed")
Console.WriteLine("A total of " & patientCounter & " patient encounters were held")
Console.WriteLine(vbCrLf & "Relative Histogram of each disease")
For Each disease As String In diseaseList
Console.WriteLine(vbCrLf & disease & vbTab & " ")
Next
End Sub
closeProgram()
Sub closeProgram()
Console.WriteLine(vbCrLf & "File does not exist")
Console.WriteLine("Press Enter to exit the program...")
Console.ReadLine()
End Sub
You don't need a disease class, really, if the most complicated thing you are doing is counting disease occurrences (your disease class had no public members so I don't know what you were doing there anyway). You can simply do everything with a little LINQ.
' processing section
Dim lines = File.ReadAllLines(inputFile)
Dim splitLines = lines.Select(Function(l) l.Split({vbTab}, StringSplitOptions.RemoveEmptyEntries))
Dim diseaseGrouping = splitLines.GroupBy(Function(s) s(3))
Dim patients = splitLines.Select(Function(s) s(1))
Dim dates = splitLines.Select(Function(s) DateTime.Parse(s(2)))
' report section
Dim padAmount = CInt(Console.WindowWidth / 2)
Dim muchoMedical As String = "MuchoMedical Health Center"
Dim diseaseReport As String = $"Disease Report For the Period {dates.Min():d} through {dates.Max():d}"
Console.WriteLine()
Console.WriteLine(muchoMedical.PadLeft(padAmount))
Console.WriteLine(diseaseReport.PadLeft(padAmount))
Console.WriteLine()
Console.WriteLine($"There were a total of {diseaseGrouping.Count()} unique diseases observed.")
Console.WriteLine($"A total of {patients.Count()} patient encounters were held")
For Each diseaseAndCount In diseaseGrouping
Console.WriteLine()
Console.WriteLine($"{diseaseAndCount.Key}{vbTab}{diseaseAndCount.Count()}")
Next
I think your disease name is in index 3. You were looking at 4 originally. Maybe you have a tab between first and last name? Change it if I was wrong. This may apply to any or all of the indices.
Output:
MuchoMedical Health Center
Disease Report For the Period 4/6/2012 through 7/4/2014
There were a total of 4 unique diseases observed.
A total of 5 patient encounters were held
Spastic Colonitis 1
Chronic Phlegm 1
Whooping Cough 1
Scurvy 2
I think the main issue with your code as listed above is that in the processData sub you have:
For Each line As String In lines
tab = line.Split(vbTab)
patientCounter += 1
dates = Date.Parse(line(3))
dateList.Add(dates)
'diseaseList.Add(line(4))
Dim disease As New disease(line(4))
diseaseList.Add(disease.ToString)
'diseaseList(line(4)).
For Each value In diseaseList
'If value.Equals(line(4)) Then disease.counter += 1
Next
Next
I think you more likely mean to use tab(3) and tab(4) instead of line(3) and line(4) etc. You split the line into the "tab" variable but then don't use it. While you could rewrite everything and handle it differently, if you want to go with what you've got, I think that's your core error.
I liked your idea of a class. You can wrap up all your data in one list. I enhanced your class so it could contain all the data in the file. Public Properties are automatic properties that have Get, Set, and the Private fields that hold the data written by the compiler. I have added an Overrides of the .ToString because you were not getting the results you expected. We have the parameterized constructor like you have except expanded to include all the properties.
The magic comes in the Linq query. The d stands for an item in the diseaseList which is an instance of the Disease class. Then I added an order by clause which will produce the results in alphabetical order by DiseaseName which is a string. Grouping by the unique DiseaseName into a Group with Count.
Notice in the second For Each loop we have all the properties of the class available.
I happened to be in a Windows Forms app so I used Debug.Print. Just replace with Console.WriteLine. I leave to you the fancy formatting if you desire.
Public Class Disease
Public Property Name As String
Public Property DiagnosisDate As Date
Public Property DiseaseName As String
Public Property ID As Integer
Public Sub New(PatientID As Integer, PatientName As String, dDate As Date, sDisease As String)
ID = PatientID
Name = PatientName
DiagnosisDate = dDate
DiseaseName = sDisease
End Sub
'If you don't override ToString you will get the fully qualified name of the class
'You can return any combination of the Properties as long as the end
'result is a string
Public Overrides Function ToString() As String
Return Name
End Function
End Class
Public Sub processData()
Dim lines As String() = File.ReadAllLines(inputFile)
Dim diseaseList As New List(Of Disease)
For Each line As String In lines
'I was having trouble with the tabs so I changed it to a comma in the file
'3710079,JUDITH CLOUTIER,2012-08-04,Spastic Colonitis
'the small c following the "," tells the compiler that this is a Char
Dim tab = line.Split(","c)
Dim inputDate = Date.ParseExact(tab(2), "yyyy-MM-dd", CultureInfo.InvariantCulture)
Dim Studentdisease As New Disease(CInt(tab(0)), tab(1), inputDate, tab(3))
diseaseList.Add(Studentdisease)
Next
Dim diseaseGrouping = From d In diseaseList
Order By d.DiseaseName
Group By d.DiseaseName
Into Group, Count
For Each diseaseAndCount In diseaseGrouping
Debug.Print($"{diseaseAndCount.DiseaseName} {diseaseAndCount.Count()} ")
For Each d In diseaseAndCount.Group
Debug.Print($" {d.Name}, {d.DiagnosisDate.ToShortDateString}")
Next
Next
End Sub

Need help getting my points per hour function to work properly

I'm having difficulty getting my function to work right.
This function is supposed to estimate how many points per hour the user would get but instead it shows way too many numbers.
Dim now As DateTime = DateTime.Now
Private Function PointsPerHour(gainedpoints As String, totalpoints As String)
Dim firstvalue = gainedpoints
Dim secondvalue = firstvalue
Dim thirdvalue = totalpoints
Dim varJWG0 As String = "Points: "
Dim varJWG1 As String = thirdvalue
Dim varJWG2 As String = " Points Per Hour: "
Dim varJJM0 As Double = Double.Parse(thirdvalue.Replace(",", String.Empty)) - Double.Parse(secondvalue.Replace(",", String.Empty))
Dim timeSpan As TimeSpan = Now - DateTime.Now.ToLocalTime
Dim dbl_ As Double = varJJM0 / timeSpan.TotalHours * -1.0
Return (Convert.ToString(varJWG0 & varJWG1) & varJWG2) + dbl_.ToString("0.00")
End Function
Even if I do PointsPerHour(9, 91) it still outputs more than 1000.
What am I doing wrong?
So I am not entirely certain what it is that this method is trying to achieve in your current implementation.
Firstly the values firstValue and secondValue are the same, also firstValue does not seem to be used apart from assigning to secondValue, which just seems redundant to me.
The first problem I can see is that you have no time in this function. The timeSpan you are using to hold presumably the totalHours is not holding a useful value. you are subtracting Now from Now.ToLocalTime. You will either get 0 or your timezone offset from this equation.
Basically; assuming your 9, 91 example; you are getting ((91-9)/timeSpan) * -1).
If 9 is the points you are earning per hour, and 91 is total points, then 91/9 = hours. or if you give the hours 9 * hours = 91 (in this case 10.1).

Readline Error While Reading From .txt file in vb.net

I have a Streamreader which is trowing an error after checking every line in Daycounts.txt. It is not a stable txt file. String lines in it are not stable. Count of lines increasing or decresing constantly. Thats why I am using a range 0 to 167. But
Here is the content of Daycounts.txt: Daycounts
Dim HourSum as integer
Private Sub Change()
Dim R As IO.StreamReader
R = New IO.StreamReader("Daycounts.txt")
Dim sum As Integer = 0
For p = 0 To 167
Dim a As String = R.ReadLine
If a.Substring(0, 2) <> "G." Then
sum += a.Substring(a.Length - 2, 2)
Else
End If
Next
HourSum = sum
R.Close()
End Sub
If you don't know how many lines are present in your text file then you could use the method File.ReadAllLines to load all lines in memory and then apply your logic
Dim HourSum As Integer
Private Sub Change()
Dim lines = File.ReadAllLines("Daycounts.txt")
Dim sum As Integer = 0
For Each line In lines
If line.Substring(0, 2) <> "G." Then
sum += Convert.ToInt32(line.Substring(line.Length - 2, 2))
Else
....
End If
Next
HourSum = sum
End Sub
This is somewhat inefficient because you loop over the lines two times (one to read them in, and one to apply your logic) but with a small set of lines this should be not a big problem
However, you could also use File.ReadLines that start the enumeration of your lines without loading them all in memory. According to this question, ReadLines locks writes your file until the end of your read loop, so, perhaps this could be a better option for you only if you don't have something external to your code writing concurrently to the file.
Dim HourSum As Integer
Private Sub Change()
Dim sum As Integer = 0
For Each line In File.ReadLines("Daycounts.txt")
If line.Substring(0, 2) <> "G." Then
sum += Convert.ToInt32(line.Substring(line.Length - 2, 2))
Else
....
End If
Next
HourSum = sum
End Sub
By the way, notice that I have added a conversion to an integer against the loaded line. In your code, the sum operation is applied directly on the string. This could work only if you have Option Strict set to Off for your project. This setting is a very bad practice maintained for VB6 compatibility and should be changed to Option Strict On for new VB.NET projects

Progress bar with VB.NET Console Application

I've written a parsing utility as a Console Application and have it working pretty smoothly. The utility reads delimited files and based on a user value as a command line arguments splits the record to one of 2 files (good records or bad records).
Looking to do a progress bar or status indicator to show work performed or remaining work while parsing. I could easily write a <.> across the screen within the loop but would like to give a %.
Thanks!
Here is an example of how to calculate the percentage complete and output it in a progress counter:
Option Strict On
Option Explicit On
Imports System.IO
Module Module1
Sub Main()
Dim filePath As String = "C:\StackOverflow\tabSeperatedFile.txt"
Dim FileContents As String()
Console.WriteLine("Reading file contents")
Using fleStream As StreamReader = New StreamReader(IO.File.Open(filePath, FileMode.Open, FileAccess.Read))
FileContents = fleStream.ReadToEnd.Split(CChar(vbTab))
End Using
Console.WriteLine("Sorting Entries")
Dim TotalWork As Decimal = CDec(FileContents.Count)
Dim currentLine As Decimal = 0D
For Each entry As String In FileContents
'Do something with the file contents
currentLine += 1D
Dim progress = CDec((currentLine / TotalWork) * 100)
Console.SetCursorPosition(0I, Console.CursorTop)
Console.Write(progress.ToString("00.00") & " %")
Next
Console.WriteLine()
Console.WriteLine("Finished.")
Console.ReadLine()
End Sub
End Module
1rst you have to know how many lines you will expect.
In your loop calculate "intLineCount / 100 * intCurrentLine"
int totalLines = 0 // "GetTotalLines"
int currentLine = 0;
foreach (line in Lines)
{
/// YOUR OPERATION
currentLine ++;
int progress = totalLines / 100 * currentLine;
///print out the result with the suggested method...
///!Caution: if there are many updates consider to update the output only if the value has changed or just every n loop by using the MOD operator or any other useful approach ;)
}
and print the result on the same posititon in your loop by using the SetCursor method
MSDN Console.SetCursorPosition
VB.NET:
Dim totalLines as Integer = 0
Dim currentLine as integer = 0
For Each line as string in Lines
' Your operation
currentLine += 1I
Dim Progress as integer = (currentLine / totalLines) * 100
' print out the result with the suggested method...
' !Caution: if there are many updates consider to update the output only if the value has changed or just every n loop by using the MOD operator or any other useful approach
Next
Well The easiest way is to update the progressBar variable often,
Ex: if your code consist of around 100 lines or may be 100 functionality
after each function or certain lines of code update progressbar variable with percentage :)