Sort directory path alphabetically - vb.net

Below code is used for sorting Directories in vb.net.
Dim a As New List(Of String)
a.Add("a\b\c")
a.Add("a\b\c\d")
a.Add("a\b\c d")
a.Add("a\b\c d\e")
a.Add("a\b\c\d\f")
a.Sort(Function(x, Y) (x).CompareTo((Y)))
result:
a\b\c
a\b\c d
a\b\c d\e
a\b\c\d
a\b\c\d\f
In the result list directories with space is placed before "\".
There are more than 1500000 sub-directories and files
it takes around 50 seconds to sort(default method)
all other methods we tried is taking at least 400 seconds.
how to sort directory path alphabetically?
Is there any built in method to consider Backslash before space ?

You need to break the path up into individual folder names and compare each of them in turn until you find a difference. If there is no difference then you use the length to differentiate, i.e. higher-level folder comes first.
a.Sort(Function(x, y)
Dim xFolderNames As New List(Of String)
Dim yFolderNames As New List(Of String)
'Split first path into folder names.
Do Until String.IsNullOrEmpty(x)
xFolderNames.Insert(0, Path.GetFileName(x))
x = Path.GetDirectoryName(x)
Loop
'Split second path into folder names.
Do Until String.IsNullOrEmpty(y)
yFolderNames.Insert(0, Path.GetFileName(y))
y = Path.GetDirectoryName(y)
Loop
Dim result = 0
'Compare up to as many folders as are in the shortest path.
For i = 0 To Math.Min(xFolderNames.Count, yFolderNames.Count) - 1
result = xFolderNames(i).CompareTo(yFolderNames(i))
If result <> 0 Then
'A difference has been found.
Exit For
End If
Next
If result = 0 Then
'No difference was found so put the shortest path first.
result = xFolderNames.Count.CompareTo(yFolderNames.Count)
End If
Return result
End Function)
For good measure, here's a class that encapsulates that functionality:
Imports System.Collections.ObjectModel
Imports System.IO
Public Class FileSystemPath
Implements IComparable, IComparable(Of FileSystemPath)
Public ReadOnly Property FullPath As String
Public ReadOnly Property PartialPaths As ReadOnlyCollection(Of String)
Public Sub New(fileOrFolderPath As String)
FullPath = fileOrFolderPath
Dim partialPaths As New List(Of String)
Do Until String.IsNullOrEmpty(fileOrFolderPath)
partialPaths.Insert(0, Path.GetFileName(fileOrFolderPath))
fileOrFolderPath = Path.GetDirectoryName(fileOrFolderPath)
Loop
Me.PartialPaths = New ReadOnlyCollection(Of String)(partialPaths)
End Sub
Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
Return CompareTo(DirectCast(obj, FileSystemPath))
End Function
Public Function CompareTo(other As FileSystemPath) As Integer Implements IComparable(Of FileSystemPath).CompareTo
Dim result = 0
'Compare up to as many folders as are in the shortest path.
For i = 0 To Math.Min(PartialPaths.Count, other.PartialPaths.Count) - 1
result = PartialPaths(i).CompareTo(other.PartialPaths(i))
If result <> 0 Then
'A difference has been found.
Exit For
End If
Next
If result = 0 Then
'No difference was found so put the shortest path first.
result = PartialPaths.Count.CompareTo(other.PartialPaths.Count)
End If
Return result
End Function
Public Overrides Function ToString() As String
Return FullPath
End Function
End Class
It can be used with barely a change to your code:
Dim a As New List(Of FileSystemPath)
a.Add(New FileSystemPath("a\b\c"))
a.Add(New FileSystemPath("a\b\c\d"))
a.Add(New FileSystemPath("a\b\c d"))
a.Add(New FileSystemPath("a\b\c d\e"))
a.Add(New FileSystemPath("a\b\c\d\f"))
a.Sort()
Console.WriteLine(String.Join(Environment.NewLine, a))
Console.ReadLine()

Related

Binary Searching in a List and Referring to a Specific Column of the List

I have some two column data that I read from a file and put into a list then sort alphabetically.
//The file
Hummus,0.75
Chili,0.50
Tabouli,1.25
Tzatziki,0.50
//Declaring the variables and public properties
Dim extraList As List(Of extra)
Public Class extra
Implements IComparable(Of extra)
Public Property Name As String
Public Property Price As Decimal
'Public Property extraList As List(Of extra)
Public Function CompareTo(other As extra) As Integer Implements IComparable(Of extra).CompareTo
Return Me.Name.CompareTo(other.Name)
End Function
End Class
//Puts the data into a list and sorts it
Sub Get_Extras_List()
'Reads the extras file and puts the information into a list, splitting the name of the extra and the price into separate columns
Dim allExtras = From line In System.IO.File.ReadLines("C:\Users\ExtrasList.txt")
Let Columns = line.Split(","c)
Where Columns.Length = 2
Let Price = Decimal.Parse(Columns(1).Trim())
Let Name = Columns(0).Trim()
Select New extra With {.Name = Name, .Price = Price}
extraList = allExtras.ToList()
'Sort the list alphabetically
extraList.Sort()
End Sub
Now I need to code a method that allows the user to type in an extra and search for it using a binary search to see if it exists. So far I have tried this but it just doesn't work and even if it did how do I get it to return a true or false value? (If it exists or not?)
Sub Search_Extras_List()
Dim strSearchExtra As String = Console.ReadLine()
Console.WriteLine(vbLf & "BinarySearch for '{0}':", strSearchExtra)
Dim index As Integer =
List(Of extra).BinarySearch(extraList.Name, strSearchExtra)
End Sub
Finally I have to get the user to choose one of the extras and then add the price of it to the total price. How do I refer to the price? extraList.Price? extra.Price? etc.
If you want to do a binary search like that, you will need to write a comparer.
Referring to List(Of T).BinarySearch Method (T, IComparer(Of T)), it could be
Public Class ExtraComparer
Implements IComparer(Of extra)
Public Function Compare(ByVal x As extra, ByVal y As extra) As Integer Implements IComparer(Of extra).Compare
If x Is Nothing Then
If y Is Nothing Then
' If x is Nothing and y is Nothing, they're equal.
Return 0
Else
' If x is Nothing and y is not Nothing, y is greater.
Return -1
End If
Else
' If x is not Nothing...
If y Is Nothing Then
' ...and y is Nothing, x is greater.
Return 1
Else
' ...and y is not Nothing, compare the names of the two extras.
'
Return x.Name.CompareTo(y.Name)
End If
End If
End Function
End Class
Which you can test with
' Get some item from the data so we know it is present...
Dim a = extraList(2).Name
Dim lookFor As New extra With {.Name = a}
Dim idx = extraList.BinarySearch(lookFor, New ExtraComparer)
Console.WriteLine($"Index of {a} is {idx}.")
(although you would probably want to do a case-insensitive string comparison).
If the index returned is negative then the item was not found. (See the documentation linked to above for more details.)
However, you might find it easier to use LINQ:
Dim a = extraList(2).Name
Dim chosenExtra = extraList.FirstOrDefault(Function(x) x.Name.Equals(a, StringComparison.CurrentCultureIgnoreCase))
If chosenExtra IsNot Nothing Then
Console.WriteLine($"User chose {chosenExtra.Name} at a price of {chosenExtra.Price}")
Else
Console.WriteLine($"User's choice of ""{a}"" was not found.")
End If
I used a case-insensitive comparison in there.
Or Just present the user with a dropdown of the available extras so that they don't have to type it.

List all folders that are in any 3rd subdirectory from current

I would need to make an array list, displaying all folders that are in the 3rd subfolder from the current one.
Folder1/sub1folder/sub2folder/sub3folder
It has to be recursive. what I need is an array of strings that contains all the strings like above.
I do know how to look recursively into folders, but I do not know how to limit the search to the 3rd subfolder.
Thanks!
Here's my stab at it. I tested it and it works for me:
Dim resultList as List(Of String) = DirectorySearch(baseDirectoryPath, 0)
Function DirectorySearch(directoryPath As String, level As Integer) As List(Of String)
level += 1
Dim directories As String() = IO.Directory.GetDirectories(directoryPath)
Dim resultList As New List(Of String)
If level = 3 Then
For Each subDirectoryPath In directories
Dim result As String = GetFinalResult(subDirectoryPath)
resultList.Add(result)
Next
Else
For Each subDirectoryPath In directories
Dim partialResultList As List(Of String) = DirectorySearch(subDirectoryPath, level)
resultList.AddRange(partialResultList)
Next
End If
Return resultList
End Function
Private Function GetFinalResult(directoryPath As String) As String
Dim directoryInfo As New IO.DirectoryInfo(directoryPath)
Return String.Format("{0}/{1}/{2}/{3}",
directoryInfo.Parent.Parent.Parent.Name,
directoryInfo.Parent.Parent.Name,
directoryInfo.Parent.Name,
directoryInfo.Name)
End Function
If you had a recursive function which began at the current folder:
Public Function recurse(Optional depth As Integer = 0) As String()
Dim folderList As String()
If (depth < 3) Then
depth += 1
folderList = recurse(depth)
Else
'Do third subfolder analysis and set the output to folderList
Return folderList
End If
End Sub

Get the name of the object passed in a byref parameter vb.net

How can I get the name of the object that was passed byref into a method?
Example:
Dim myobject as object
sub mymethod(byref o as object)
debug.print(o.[RealName!!!!])
end sub
sub main()
mymethod(myobject)
'outputs "myobject" NOT "o"
end sub
I'm using this for logging. I use one method multiple times and it would be nice to log the name of the variable that I passed to it. Since I'm passing it byref, I should be able to get this name, right?
For minitech who provided the answer:
This would give you the parameter name in the method and it's type, but not the name of the variable that was passed byref.
using system.reflection
Dim mb As MethodBase = MethodInfo.GetCurrentMethod()
For Each pi As ParameterInfo In mb.GetParameters()
Debug.Print("Parameter: Type={0}, Name={1}", pi.ParameterType, pi.Name)
Next
If you put that in "mymethod" above you'd get "o" and "Object".
That's impossible. Names of variables are not stored in IL, only names of class members or namespace classes. Passing it by reference makes absolutely zero difference. You wouldn't even be able to get it to print out "o".
Besides, why would you ever want to do that?
Alternatively you could get the 'Type' of the object using reflection.
Example: (Use LinqPad to execute)
Sub Main
Dim myDate As DateTime = DateTime.Now
MyMethod(myDate)
Dim something As New Something
MyMethod(something)
End Sub
Public Class Something
Public Sub New
Me.MyProperty = "Hello"
End Sub
Public Property MyProperty As String
End Class
Sub MyMethod(Byref o As Object)
o.GetType().Name.Dump()
End Sub
Sorry to say, but this is your solution. I left (ByVal o As Object) in the method signature in case you're doing more with it.
Sub MyMethod(ByVal o As Object, ByVal name As String)
Debug.Print(name)
End Sub
Sub Main()
MyMethod(MyObject, "MyObject")
End Sub
Alternatively you could create an interface, but this would only allow you to use MyMethod with classes you design. You can probably do more to improve it, but as this code stands you can only set the RealName at creation.
Interface INamedObject
Public ReadOnly Property RealName As String
End Interface
Class MyClass
Implements INamedObject
Public Sub New(ByVal RealName As String)
_RealName = RealName
End Sub
Private ReadOnly Property RealName As String Implements INamedObject.RealName
Get
Return _RealName
End Get
End Property
Private _RealName As String
End Class
Module Main
Sub MyMethod(ByVal o As INamedObject)
Debug.Print(o.RealName)
End Sub
Sub Main()
Dim MyObject As New MyClass("MyObject")
MyMethod(MyObject)
End Sub
End Module
If your program is still in the same place relative to the code that made it, this may work:
' First get the Stack Trace, depth is how far up the calling tree you want to go
Dim stackTrace As String = Environment.StackTrace
Dim depth As Integer = 4
' Next parse out the location of the code
Dim delim As Char() = {vbCr, vbLf}
Dim traceLine As String() = stackTrace.Split(delim, StringSplitOptions.RemoveEmptyEntries)
Dim filePath As String = Regex.Replace(traceLine(depth), "^[^)]+\) in ", "")
filePath = Regex.Replace(filePath, ":line [0-9]+$", "")
Dim lineNumber As String = Regex.Replace(traceLine(depth), "^.*:line ", "")
' Now read the file
Dim program As String = __.GetStringFromFile(filePath, "")
' Next parse out the line from the class file
Dim codeLine As String() = program.Split(delim)
Dim originLine As String = codeLine(lineNumber * 2 - 2)
' Now get the name of the method doing the calling, it will be one level shallower
Dim methodLine As String = Regex.Replace(traceLine(depth - 1), "^ at ", "")
Dim methodName = Regex.Replace(methodLine, "\(.*\).*$", "")
methodName = Regex.Replace(methodName, "^.*\.", "")
' And parse out the variables from the method
Dim variables As String = Regex.Replace(originLine, "^.*" & methodName & "\(", "")
variables = Regex.Replace(variables, "\).*$", "")
You control the depth that this digs into the stack trace with the depth parameter. 4 works for my needs. You might need to use a 1 2 or 3.
This is the apparently how Visual Basic controls handle the problem.
They have a base control class that in addition to any other common properties these controls may have has a name property.
For Example:
Public MustInherit Class NamedBase
Public name As String
End Class
Public Class MyNamedType
Inherits NamedBase
public Value1 as string
public Value2 as Integer
End Class
dim x as New MyNamedType
x.name = "x"
x.Value1 = "Hello, This variable is name 'x'."
x.Value2 = 75
MySubroutine(x)
public sub MySubroutine(y as MyNamedType)
debug.print("My variable's name is: " & y.name)
end sub
The output in the intermediate window should be:
My variable's name is: x

compare and merge multiple files the text file using VB.NET

I have a multiple text files that I need to merge. but I need to compare the reference number before merge it.
below is the text file
Text 1
001Email
002Video
003SocialNetwork
Text 2
001Gmail
001Yahoo
002Youtube
002Metacafe
003Facebook
003Myspace
Text 3
www.gmail.com001
www.yahoo.com001
www.youtube.com002
www.myspace.com002
www.facebook.com003
www.myspace.com003
Output
001Email
001Gmail
www.gmail.com001
001Yahoo
wwww.yahoo.com001
002Video
002Youtube
www.youtube.com002
002Metacafe
www.metacafe.com002
003SocialNetwork
003Facebook
www.facebook.com003
003Myspace
www.myspace.com003
What will be the fastest way to deal it read line by line to compare. the text file consist of thousand of line
Here's what might possibly be an overly complex solution. The comments in the code should explain everything hopefully. The output doesn't match exactly what you have because I don't know how much order is important for everything. It sorts everything first by the reference number and then by the text portion of the string (excluding www.). The results you posted were in reference number order and then file parsing order and then alphabetical (002Metacafe came after 002Video). Let me know if that's important.
Option Explicit On
Option Strict On
Imports System.IO
Imports System.Text.RegularExpressions
Public Class Form1
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
''//List of files to process
Dim Files As New List(Of String)
Files.Add(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Desktop, "Text1.txt"))
Files.Add(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Desktop, "Text2.txt"))
Files.Add(Path.Combine(My.Computer.FileSystem.SpecialDirectories.Desktop, "Text3.txt"))
''//Will hold the current line being read
Dim Line As String
''//Holds our main collection of data
Dim MyData As New List(Of Data)
''//Loop through each file
For Each F In Files
''//Open the file for reading
Using FS As New FileStream(F, FileMode.Open, FileAccess.Read, FileShare.Read)
Using SR As New StreamReader(FS)
''//Read each line
Line = SR.ReadLine()
Do While Line IsNot Nothing
''//The data constructor handles parsing of the line
MyData.Add(New Data(Line))
''//Read next line
Line = SR.ReadLine()
Loop
End Using
End Using
Next
''//Our data implements IComparable(Of Data) so we can just sort the list
MyData.Sort()
''//Output our data
For Each D In MyData
Trace.WriteLine(D)
Next
Me.Close()
End Sub
End Class
Public Class Data
Implements IComparable(Of Data)
''//Our RegEx pattern for looking for a string that either starts or ends with numbers
Private Shared ReadOnly Pattern As String = "^(?<RefStart>\d+)?(?<Text>.*?)(?<RefEnd>\d+)?$"
Public Text As String ''//The _text_ portion of the data
Public Reference As String ''//The reference number stored as text
Public ReferenceAtStart As Boolean ''//Whether the reference number was found at the start or end of the line
Public ReadOnly Property ReferenceAsNum() As Integer ''//Numeric version of the reference number for sorting
Get
Return Integer.Parse(Me.Reference)
End Get
End Property
Public ReadOnly Property TextComparable() As String ''//Remove the www for sorting
Get
Return Me.Text.Replace("www.", "")
End Get
End Property
Public Sub New(ByVal line As String)
''//Sanity check
If String.IsNullOrEmpty(line) Then Throw New ArgumentNullException("line")
''//Parse the line
Dim M = Regex.Match(line, Pattern)
If M Is Nothing Then Throw New ArgumentException("Line does not conform to expected pattern")
''//If the RefStart has a value then the number is at the beginning of the string
If M.Groups("RefStart").Success Then
Me.ReferenceAtStart = True
Me.Reference = M.Groups("RefStart").Value
Else ''//Otherwise its at the end
Me.ReferenceAtStart = False
Me.Reference = M.Groups("RefEnd").Value
End If
Me.Text = M.Groups("Text").Value
End Sub
Public Function CompareTo(ByVal other As Data) As Integer Implements System.IComparable(Of Data).CompareTo
''//Compare the reference numbers first
Dim Ret = Me.ReferenceAsNum.CompareTo(other.ReferenceAsNum)
''//If they are the same then compare the strings
If Ret = 0 Then Ret = String.Compare(Me.TextComparable, other.TextComparable, StringComparison.InvariantCultureIgnoreCase)
Return Ret
End Function
Public Overrides Function ToString() As String
''//Reproduce the original string
If Me.ReferenceAtStart Then
Return String.Format("{0}{1}", Me.Reference, Me.Text)
Else
Return String.Format("{1}{0}", Me.Reference, Me.Text)
End If
End Function
End Class

How do you implement a IEqualityComparer<T> in VB.NET?

I have the following function that loops through a directory and checks for a specified folder and file:
Private Function VerifyPath(ByVal root As String, ByVal folder As String, _
ByVal file As String) As Boolean
Dim folders As New List(Of String), files As New List(Of String)
Dim oDir As New IO.DirectoryInfo(root)
For Each dir As IO.DirectoryInfo In oDir.GetDirectories
folders.Add(dir.Name.ToLower)
Next
If folders.Contains(folder) Then
For Each item As IO.FileInfo In oDir.GetFiles
files.Add(item.Name.ToLower)
Next
If files.Contains(file) Then
Return True
End If
End If
Return False
End Function
The reason I did this method is so I could make sure that the items in each list and the passed file/folder were all lower case, otherwise I would have done something like this:
If oDir.GetDirectories.Contains( _
New IO.DirectoryInfo(String.Format("{0}\{1}", root, folder))) Then
If oDir.GetFiles.Contains( _
New IO.FileInfo(String.Format("{0}\{1}", root, file))) Then
Return True
End If
End If
Return False
My colleague mentioned something to me earlier about being able to ignore case by using a comparer. The .Contains extension can have a comparer argument along with the value. I did some searching on google and MSDN, and came up with the following comparer:
Public Class dirCompare
Implements IEqualityComparer(Of IO.DirectoryInfo)
Dim theCompare As CaseInsensitiveComparer
Sub New()
theCompare = CaseInsensitiveComparer.DefaultInvariant
End Sub
Sub New(ByVal culture As CultureInfo)
theCompare = New CaseInsensitiveComparer(culture)
End Sub
Public Function Equals1(ByVal x As System.IO.DirectoryInfo, ByVal y As System.IO.DirectoryInfo) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of System.IO.DirectoryInfo).Equals
If theCompare.Compare(x.name, y.name) = 0 Then
Return True
Else
Return False
End If
End Function
Public Function GetHashCode1(ByVal obj As System.IO.DirectoryInfo) As Integer Implements System.Collections.Generic.IEqualityComparer(Of System.IO.DirectoryInfo).GetHashCode
Return obj.ToString.ToLower.GetHashCode
End Function
End Class
When it gets to the theCompare(x.name, y.name) = 0 line, it errors out and this is the error message:
At least one object must implement IComparable.
Anyone know what this error means and how to go about correcting it?
Well you could implement a comparer, but that would be the hard way in this case. You have a couple other options available instead.
The first is that there is already a case-insensitive comparer you can use. There are a couple actually. Look in your intellisense prompts under System.StringComparer.
The second is that strings already have a built-in way to specify a case-insensitive compare:
Dim s As String = "a"
Dim t As String = "A"
If s.Equals(t, StringComparison.InvariantCultureIgnoreCase) Then
''//...
End If
And a third is that any searchPattern passed to Directory.GetFiles() or Directory.GetDirectories() is passed directly to the operating system, and Windows is only case-aware for file names, not case-sensitive. So you can pass your folder and file as a search pattern and do your lookup that way.