Sort Fileinfo list in asc order - arrays

I have a list of files as follows
10_2017
123_2018
500_2017
20_2019
100_2017
25_2017
32_2018
Which i want to be sorted like
10_2017
25_2017
100_2017
500_2017
32_2018
123_2018
20_2019
I can sort the array by Array.sort(f1,new FileInfoSort) if I read the years separate but I need it in one sorted array or list
I have tried several threads on here including this method. used f1.Sort(Function(x, y) x.Name.CompareTo(y.Name)) and Array.Sort(f1.toArray, New FileInfoSort) without any success
I have even tried to separate the files into folders by year and read them separately into lists and combining them which didnt seem to work either
Using the code
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) = d1.GetFiles("*.LBK", SearchOption.TopDirectoryOnly).ToList
'f1.Sort(Function(x, y) x.Name.CompareTo(y.Name))
f1.SortNatural(Function(x) x.Name)
Separating the files into folders by year and then reading it and combining
Dim d1 As New DirectoryInfo(AppFolder.User.data)
Dim d2 As List(Of DirectoryInfo) = New List(Of DirectoryInfo)
'data folders have names like "d_2019"
For Each d As DirectoryInfo In d1.GetDirectories
If d.Name.ToString.Substring(0, 1) = "d" Then d2.Add(d)
Next
Dim f1 As List(Of FileInfo) = New List(Of FileInfo)
For Each Dir As DirectoryInfo In d2
f1.AddRange(Dir.GetFiles("*.LBK", SearchOption.TopDirectoryOnly))
'trying to sort
'f1.Sort(Function(x, y) x.Name.CompareTo(y.Name))
f1.SortNatural(Function(x) x.Name)
Next
Module ListExt
<DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
Private Function StrCmpLogicalW(ByVal lhs As String, ByVal rhs As String) As Integer
End Function
<Extension()>
Sub SortNatural(Of T)(ByVal self As List(Of T), ByVal stringSelector As Func(Of T, String))
self.Sort(Function(lhs, rhs) StrCmpLogicalW(stringSelector(lhs), stringSelector(rhs)))
End Sub
<Extension()>
Sub SortNatural(ByVal self As List(Of String))
self.Sort(AddressOf StrCmpLogicalW)
End Sub
End Module
Any method Ive used so far outputs
10_2017
20_2019
25_2017
32_2018
100_2017
123_2018
500_2017
I have no idea how to approach this even. If such a list/array is not possible ideas of how i might structure my files so that i can read in the files in the order i prefer above would be welcome too!

You'll have to write code to parse the separate sections of the name and treat them as numbers. Any built-in comparer you use will treat strings as strings, where anything that starts with a 1 comes before anything that starts with a 2, even when the values are '100' and '2'. Even the so-called "natural" sorts are only good enough to check the first section.
You also need to strip the LBK extension from the name.
Dim splitChars() As Char = {"_"c}
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) =
d1.EnumerateFiles("*.LBK", SearchOption.TopDirectoryOnly).
OrderBy(Function(fi)
Dim parts = fi.Name.Replace(".LBK", "").Split(splitChars)
Return (Integer.Parse(parts(1)) * 1000) + Integer.Parse(parts(0))
End Function).
ToList()
For fun, here's a Regex version:
Dim exp As New Regex("(\d{1,3})_(\d{4})");
Dim d1 As New DirectoryInfo(AppFolder)
Dim f1 As List(Of FileInfo) =
d1.EnumerateFiles("*.LBK", SearchOption.TopDirectoryOnly).
OrderBy(Function(fi)
Dim parts = exp.Matches(fi.Name)(0).Groups
Return (Integer.Parse(parts(2).Value) * 1000) + Integer.Parse(parts(1).Value)
End Function).
ToList()
See it work here:
https://dotnetfiddle.net/UAae8P
I also wanted to comment on this line from one of the samples in the question:
If d.Name.ToString.Substring(0, 1) = "d" Then d2.Add(d)
It stood out as needing some attention. There's a ton of extra work going on here that isn't needed:
d.Name is already a string, no need to call ToString()
Since you just want one character you can access it via subscript, no need to call Substring()
You're comparing one character, no need to do string compares.
You really want this:
If d.Name(0) = "d"c Then d2.Add(d)
That's a lot less code, and it will perform so much better, and it will be worth your time to study this and understand why.

Related

Populating two one-dimensional arrays with text file

For my Visual Basic final, my program is required to read data from a text file into two different arrays, each being one-dimensional. The following is my code for doing so:
Option Explicit On
Option Infer Off
Option Strict On
Public Class frmMain
'Constant for filename and a dirty flag variable
Const INVENTORY_FILENAME As String = "inventory.txt"
Dim noFile As Boolean = False
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'Populates DVD listbox with text file data upon load
'Variable for reading the file
Dim myFile As IO.StreamReader
'Declaring arrays for DVD names and prices
Dim arrayDVD() As String
Dim arrayPrice() As Double
'Variables for populating arrays with respective data
Dim dvdName As String
Dim dvdPrice As Double
Dim i As Integer = 0
'Checking that file exists then reading data to each array
If IO.File.Exists(INVENTORY_FILENAME) Then
myFile = IO.File.OpenText(INVENTORY_FILENAME)
'Read data to arrays
Do Until myFile.Peek = -1
dvdName = myFile.ReadLine()
dvdPrice = Double.Parse(myFile.ReadLine())
arrayDVD = dvdName
arrayPrice = dvdPrice
'Using arrays to populate multicolumn listbox
lstDVD.Items.Add(arrayDVD(i) & arrayPrice(i))
i += 1
Loop
'Closing the file
myFile.Close()
End If
End Sub
End Class
The text file alternates names and prices of DVDs to be read as individual lines, making the arrays parallel:
Pulp Fiction
9.99
Jumanji
13.99
And so on...
I'm receiving a value type error code stating that I cannot convert 'String' to 'String()' or convert 'Double' to 'Double()' when setting the arrays' values equal to their respective variables. Is there a way to correct this? Thanks in advance!
These lines are wrong:
arrayDVD = dvdName
arrayPrice = dvdPrice
arrayDVD and arrayPrice are arrays. You need to assign to a specific element in each of those arrays:
arrayDVD(i) = dvdName
arrayPrice(i) = dvdPrice
Don't forget to make sure the arrays actually have enough elements for this.
Hint: ReDim Preserve is pretty much the least efficient way possible to make sure an array is big enough. Each use will allocate a brand new array, copy the elements one at a time, assign the new array to the old reference, and then release the old array. It does not preserve in-place. Nevertheless, if this is a 100-level course it might be what you are expected to do at this point.
Finally, you should never use Double when working with money (use Decimal instead).
Separate from the question, here is how I might approach this without the weird array limitation:
Private Iterator Function ReadInventoryFile(filePath As String) As IEnumerable(Of (String, Decimal))
Using rdr As New StreamReader(filePath)
Dim DVDName As String = Nothing
While (DVDName = rdr.ReadLine()) IsNot Nothing
Yield (DVDName, Decimal.Parse(rdr.ReadLine()))
End While
End Using
End Function
Const INVENTORY_FILENAME As String = "inventory.txt"
Private data As List(Of (String, Decimal))
Private Sub frmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try 'Replaces the File.Exists() check
data = ReadInventoryFile(INVENTORY_FILENAME).ToList()
For Each item As (String, Decimal) In data
lstDVD.Items.Add($"{item.Item1}{vbTab}{item.Item2:C}")
Next
Catch
' Actually do something here. Empty catch blocks are rarely correct.
' Note I catch at this level, rather than in the ReadFile() method.
End Try
End Sub

vb.net: List variables not values

what I need does not seem to be too special, but somehow - maybe i am googling the wrong key words - I failed to find anything on the web.
How can I store variables (or references to them?) in Lists / Arrays / or something like that in a way that when I apply a change to the list the change will also be applied to the variable?
Something like:
Dim myList As New List(Of Object)
Dim a As Integer = 5
myList.Add(a)
myList(0) = 10 'here i want a to change as well
If a = 10 Then
'This is exactly what I want
Else If a = 5 Then
'This is what i don't want but what I will get
End If
So how can I accomplish this?
An integer is a value type, not a reference type like a class. You have two copies of that value, once in the list and once in the variable. If you want that behavior you needed to store a reference type in the list. For example:
Public Class MyNumber
Public Sub New(number As int32)
Value = Number
End Sub
Public Property Value As Int32
End Class
Sub Main
Dim myList As New List(Of MyNumber)
Dim myFirstNumber As New MyNumber(5)
myList.Add(myFirstNumber)
myList(0).Value = 10
' Now both, myFirstNumber.Value and myList(0).Value is 10
End Sub

Visual Basic code works but is inelegant - any suggestions?

I am trying to stay ahead of my Year 12 Software class. Starting to work with records and arrays. I have answered the question, but the solution feels very clunky. I am hoping someone has suggestions/links for completing this task in a more efficient way.
The task: read in lines from a text file and into a structure, and then loop through that, populating four list boxes if an animal hasn't been vaccinated.
Here's the code:
Imports System.IO
Public Class Form1
'Set up the variables - customer record, total pets not vaccinated, total records in the file, and a streamreader for the file.
Structure PetsRecord
Dim custName As String
Dim address As String
Dim petType As String
Dim vacced As String
End Structure
Dim totNotVac As Integer
Dim totalRecCount As Integer
Dim PetFile As IO.StreamReader
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
End Sub
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
'set an array of records to store each record as it comes in. Limitation: you need to know how many records in the file. Set the array at 15 to allow for adding more in later.
Dim PetArray(15) As PetsRecord
'variables that let me read in a line and split it into sections.
Dim lineoftext As String
Dim i As Integer
Dim arytextfile() As String
'tell them what text file to read
PetFile = New IO.StreamReader("patients.txt")
totNotVac = 0
Try
totalRecCount = 0
' read each line in and split the lines into fields for the records. Then assign the fields from the array. Finally, reset the array and loop.
Do Until PetFile.Peek = -1
'read in a line of text
lineoftext = PetFile.ReadLine()
'split that line into bits separated by commas. these will go into the array.
arytextfile = lineoftext.Split(",")
'dunno whether this is the best way to do it, but stick the array bits into the record, and then clear the array to start again.
PetArray(totalRecCount).custName = arytextfile(0)
PetArray(totalRecCount).address = arytextfile(1)
PetArray(totalRecCount).petType = arytextfile(2)
PetArray(totalRecCount).vacced = arytextfile(3)
totalRecCount += 1
Array.Clear(arytextfile, 0, arytextfile.Length)
Loop
For i = 0 To PetArray.GetUpperBound(0)
If PetArray(i).vacced = "No" Then
lstVaccinated.Items.Add(PetArray(i).vacced)
lstCustomer.Items.Add(PetArray(i).custName)
lstAddress.Items.Add(PetArray(i).address)
lstPetType.Items.Add(PetArray(i).petType)
totNotVac += 1
lblVacTotal.Text = "The total number of unvaccinated animals is " & CStr(totNotVac)
End If
Next
Catch ex As Exception
MsgBox("Something went wrong with the file")
End Try
PetFile.Close()
End Sub
Private Sub btnExit_Click(sender As Object, e As EventArgs) Handles btnExit.Click
Close()
End Sub
End Class
And one line from the patient.txt file:
Richard Gere,16 Sunset Blvd,Gerbil,No
I hope this isn't out of place.
Regards,
Damian
If you want to use Streams be aware that they need to be disposed.
File.ReadAllLines returns an array of lines. Since the array is initialized where it is declared we don't need to specify a size.
If you use List(Of T) you do not have to know in advance the number of elements in the list. Avoids the limitation of array.
Using a For Each avoids having to use the size. The small c following "," tells the compiler that this is a Char which is what .Split is expecting. If you had Option Strict On, which you always should, you would have seen an error. You add items to the list by creating a New PetsRecord. The parametrized Sub New receives the values and sets the properties.
Don't change the display in the label on each iteration. Use an interpolated string (preceded by a $) which allows embedded variables surrounded by braces { }. It is not necessary to change the number to a string as it is implied by the interpolator. (Available in VS2015 and later)
Public Structure PetsRecord
Public Property custName As String
Public Property address As String
Public Property petType As String
Public Property vacced As String
Public Sub New(name As String, add As String, pType As String, vac As String)
custName = name
address = add
petType = pType
vacced = vac
End Sub
End Structure
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
Dim lines = File.ReadAllLines("patients.txt")
Dim lstPet As New List(Of PetsRecord)
For Each line In lines
Dim splits = line.Split(","c)
lstPet.Add(New PetsRecord(splits(0), splits(1), splits(2), splits(3)))
Next
Dim totNotVac As Integer
For Each pet In lstPet
If pet.vacced = "No" Then
lstVaccinated.Items.Add(pet.vacced)
lstCustomer.Items.Add(pet.custName)
lstAddress.Items.Add(pet.address)
lstPetType.Items.Add(pet.petType)
totNotVac += 1
End If
Next
lblVacTotal.Text = $"The total number of unvaccinated animals is {totNotVac}"
End Sub
If you don't need the 'PetsRecord' array to store data, take a look at the following code:
Dim totNotVac As Integer
Private Sub btnLoad_Click(sender As Object, e As EventArgs) Handles btnLoad.Click
File.ReadAllLines("your text file path").ToList().ForEach(Sub(x)
lstVaccinated.Items.Add(x.Split(","c)(0))
lstCustomer.Items.Add(x.Split(","c)(1))
lstAddress.Items.Add(x.Split(","c)(2))
lstPetType.Items.Add(x.Split(","c)(3))
totNotVac += 1
End Sub)
lblVacTotal.Text = "The total number of unvaccinated animals is " & CStr(totNotVac)
End Sub

Populating an array from an input file

I'm an extreme beginner with vb and coding in general and this is my first post on this site. I am running into a wall with a project I am working on. This is the smallest block of code in the project but all other functions will pull from the array I'm trying to populate. Essentially I need to populate an array with numbers representing the prices of DVDs from a .txt file. The .txt file is formatted as follows:
The Lord of the Rings, 10.50
Avatar, 5
Gangs of New York, 7.5
etc
Where 10.50 is the value I would want to assign to dblPrices(0). It is required to not change the format of the .txt file. So far, this is what I was using but when testing the output I'm getting back 0's:
'Declare variables.
Dim intCount As Integer = 0
Dim strLine As String
'Open the file for input.
inFile = IO.File.OpenText("availableDVDs.txt")
'Remove alpha characters from string, assign numeric values to array representing price.
Do Until inFile.Peek = -1
strLine = inFile.ReadLine.ToUpper.Replace("[A-Z]", "")
strLine = strLine.Replace(" ", "")
strLine = strLine.Replace(",", "")
Double.TryParse(strLine, dblPrices(intCount))
intCount += 1
Loop
This is related to a school project so I'm not necessarily looking for someone to do my work for me, but perhaps point me in the right direction. Thank you!
If you use List(Of T) (The T stands for Type, like Integer or String or your own type) then you don't need to know the size in advance like an array. Also you don't need to keep track of indexes. You can just use the .Add method and the new item is put at the end of the list.
You can get a head start on getting the data out of the file by using File.ReadAllLines which will return an array of lines in the text file. Then you can just .Split each line on the "," (comma) and use the second element of the resulting array. The little c after the comma in double quotes tells the compiler that you mean this a Char which is the datatype that .Split is expecting.
I used a Decimal datatype instead of Double. When working with money it is safer to use to get the answer you expect.
In the second code sample I used an Interpolated sting indicated by the $ preceding the string. Notice that it is very similar to String.Format only the variable is inserted directly in the braces instead of a placeholder. This is available in Visual Studio 2015 and later.
The array method...
Private Sub OPCode()
Dim Lines = IO.File.ReadAllLines("availableDVDs.txt")
Dim Prices(Lines.Count - 1) As Decimal
Dim index As Integer
Dim price As Decimal
For Each line In Lines
Dim SplitOnComma = line.Split(","c)
If Decimal.TryParse(SplitOnComma(1), price) Then
Prices(index) = price
index += 1
Else
MessageBox.Show(String.Format("Price for {0} is not valid.", SplitOnComma(0)))
End If
Next
End Sub
The list method...
Private Sub UsingList()
Dim Lines = IO.File.ReadAllLines("availableDVDs.txt")
Dim Prices As New List(Of Decimal)
Dim price As Decimal
For Each line In Lines
Dim SplitOnComma = line.Split(","c)
If Decimal.TryParse(SplitOnComma(1), price) Then
Prices.Add(price)
Else
MessageBox.Show($"Price for {SplitOnComma(0)} is not valid.")
End If
Next
End Sub
With Option Infer Off
Private Sub OPCode()
Dim Lines() As String = IO.File.ReadAllLines("availableDVDs.txt")
Dim Prices(Lines.Count - 1) As Decimal
Dim index As Integer
Dim price As Decimal
For Each line In Lines
Dim SplitOnComma() As String = line.Split(","c)
If Decimal.TryParse(SplitOnComma(1), price) Then
Prices(index) = price
index += 1
Else
MessageBox.Show(String.Format("Price for {0} is not valid.", SplitOnComma(0)))
End If
Next
End Sub

Why Won't My Array Sort? (Visual Basic)

I'm working on a project, for school, that reads text from a .txt file to an array. After doing that, I'm supposed to sort the array, alphabetically, and then list the contents in a listbox. Here is my code:
Imports System.IO
Public Class Form1
'Allow array to be accessed by the entire program
Public books(1) As String
Private Sub btnView_Click(sender As Object, e As EventArgs) Handles btnView.Click
'Declare variables
Dim sr As New StreamReader("C:\Users\Bryson\Desktop\BooksinStock.txt")
Dim book As String
Dim i As Integer = 0
'Establish loop to read contents of the text file into the array and list the array in the listbox
'the sr.Peek = -1 simply means that the reader has reached the end of the file and there is nothing more to be read
Do Until sr.Peek = -1
book = sr.ReadLine()
'ReDim allows the array to grow with the set amount of books in text file
ReDim books(books.Length + 1)
books(i) = book
i += 1
Loop
Array.Sort(books)
lstBoxInventory.Items.Add(books(i))
End Sub
End Class
However, when I run the program, I receive an error on the lstBoxInventory.Items.Add(books(i)) line that says "an unhandled exception of the type 'System.ArgumentNullException' occurred in System.Windows.Forms.Dll
I've tried to lay the code out in various ways to get the sort to work but keep coming up short. Does anyone know how to get rid of this null error?
The problem happening because "i" is larger than the highest index
Do Until sr.Peek = -1
book = sr.ReadLine()
ReDim books(books.Length + 1)
books(i) = book
i += 1 'This is adding 1 to the very end
Loop
Array.Sort(books)
lstBoxInventory.Items.Add(books(i)) 'When the items are being added it is trying to add an extra i that does not exist
edit
Honestly I would change the format to use ReadAllLines and list(of String)
Something like (Im writing code from memory)
Dim bookList as new List(of String)
Dim bookTextFile as String() = File.ReadAllLines("C:\booklist.txt")
for each book as String in bookTextFile
bookList.add(book)
next
bookList.Sort
Edit Again
Just using this
Dim bookList As String() = System.IO.File.ReadAllLines("C:\Users\Bryson\Desktop\BooksinStock.txt")
creates a single dimension array ..
Strings() are single Arrays String(,) are two dimensional Arrays
Honestly your whole homework could be
Dim bookList As String() = System.IO.File.ReadAllLines("C:\Users\Bryson\Desktop\BooksinStock.txt")
Array.Sort(BookList)
boom - done.
Test it using
for each book as String in BookList
Msgbox(book)
next
You could do
Dim bookList As String() = System.IO.File.ReadAllLines("C:\Users\Bryson\Desktop\BooksinStock.txt")
Dim books(bookList.Length - 1) As String 'This is the same as ReDim
For x As Integer = 0 To bookList.Length - 1
books(x) = bookList(x)
Next
Array.Sort(books)
but you would literally be saying bookList = books
But.... if you just want to get your code working, just try this
lstBoxInventory.Items.Add(books(i -1)) 'This takes away the extra i that you added

Resources