VB.NET - Find a Substring in an ArrayList, StringCollection or List(Of String) - vb.net

I've got some code that creates a list of AD groups that the user is a member of, with the intention of saying 'if user is a member of GroupX then allow admin access, if not allow basic access'.
I was using a StringCollection to store this list of Groups, and intended to use the Contains method to test for membership of my admin group, but the problem is that this method only compares the full string - but my AD groups values are formatted as cn=GroupX, etc....
I want to be easily able to determine if a particular substring (i.e. 'GroupX') appears in the list of groups. I could always iterate through the groups check each for a substring representing my AD group name, but I'm more interested in finding out if there is a 'better' way.
Clearly there are a number of repositories for the list of Groups, and it appears that Generics (List(Of String)) are more commonly preferred (which I may well implement anyway) but there is no in-built means of checking for a substring using this method either.
Any suggestions? Or should I just iterated through the list of groups?
RESULT:
I've settled on using a List(Of), and I've borrowed from Dan's code to iterate through the list.

I don't think you're going to find a better method than enumerating over the collection*.
That said, here's a good way to do it that will be independent of collection type:
Public Function ContainsSubstring(ByVal objects As IEnumerable, ByVal substring As String) As Boolean
Dim strings = objects.OfType(Of String)()
For Each str As String in strings
If str.Contains(substring) Then Return True
Next
Return False
End Function
This is a good way to address the "which collection to use?" issue since basically all collections, generic or not (ArrayList, List(Of String), etc.), implement IEnumerable.
*Justification for why I believe this forthcoming.

Writing a helper function which will iterate through the items checking for substrings and returning you a Boolean flag seem to be your best bet.

You can use a predicate function for that. It's a boolean function which will help you to filter out some entries.
For example, to get non-hidden files from a list:
Public ReadOnly Property NotHiddenFiles As List(Of FileInfo)
Get
Dim filesDirInfo As New DirectoryInfo(FileStorageDirectory)
Return filesDirInfo.GetFiles.ToList.FindAll(AddressOf NotHiddenPredicate)
End Get
End Property
Private Function NotHiddenPredicate(ByVal f As FileInfo) As Boolean
Return Not ((f.Attributes And FileAttributes.Hidden) = FileAttributes.Hidden)
End Function

Related

Vb Net check if arrayList contains a substring

I am using myArrayList.Contains(myString) and myArrayList.IndexOf(myString) to check if arrayList contains provided string and get its index respectively.
But, How could I check if contains a substring?
Dim myArrayList as New ArrayList()
myArrayList.add("sub1;sub2")
myArrayList.add("sub3;sub4")
so, something like, myArrayList.Contains("sub3") should return True
Well you could use the ArrayList to search for substrings with
Dim result = myArrayList.ToArray().Any(Function(x) x.ToString().Contains("sub3"))
Of course the advice to use a strongly typed List(Of String) is absolutely correct.
As far as your question goes, without discussing why do you need ArrayList, because array list is there only for backwards compatibility - to select indexes of items that contain specific string, the best performance you will get here
Dim indexes As New List(Of Integer)(100)
For i As Integer = 0 to myArrayList.Count - 1
If DirectCast(myArrayList(i), String).Contains("sub3") Then
indexes.Add(i)
End If
Next
Again, this is if you need to get your indexes. In your case, ArrayList.Contains - you testing whole object [string in your case]. While you need to get the string and test it's part using String.Contains
If you want to test in non case-sensitive manner, you can use String.IndexOf

Sorting a SortedDictionary by key length in Visual Basic?

I'm writing a script that anonymizes participant data from a file.
Basically, I have:
A folder of plaintext participant data (sometimes CSV, sometimes XML, sometimes TXT)
A file of known usernames and accompanying anonymous IDs (e.g. jsmith1 as a known username, User123 as an anonymous ID)
I want to replace every instance of the known username with the corresponding anonymous ID.
Generally speaking, what I have works just fine -- it loads in the usernames and anonymous IDs into a dictionary and one by one runs a find-and-replace on the document text for each.
However, this script also strips out names, and it runs into some difficulty when it encounters names contained in other names. So, for example, I have two pairs:
John,User123
Johnny,User456
Now, when I run the find-and-replace, it may first encounter John, and as a result it replaces Johnny with User123ny, and then doesn't trigger Johnny.
The simplest solution I can think of is just to run the find-and-replace from longest key to shortest. To do that, it looks like I need a SortedDictionary.
However, I can't seem to convince Visual Basic to take my custom Comparer for this. How do you specify this? What I have is:
Sub Main()
Dim nameDict As New SortedDictionary(Of String, String)(AddressOf SortKeyByLength)
End Sub
Public Function SortKeyByLength(key1 As String, key2 As String) As Integer
If key1.Length > key2.Length Then
Return 1
ElseIf key1.Length < key2.Length Then
Return -1
Else
Return 0
End If
End Function
(The full details above are in case anyone has any better ideas for how to resolve this problem in general.)
I think it takes a class that implements the IComparer interface, so you'd want something like:
Public Class ByLengthComparer
Implements IComparer(Of String)
Public Function Compare(key1 As String, key2 As String) As Integer Implements IComparer(Of String).Compare
If key1.Length > key2.Length Then
Return 1
ElseIf key1.Length < key2.Length Then
Return -1
Else
'[edit: in response to comments below]
'Return 0
Return key1.Compare(key2)
End If
End Function
End Class
Then, inside your main method, you'd call it like this:
Dim nameDict As New SortedDictionary(Of String, String)(New ByLengthComparer())
You might want to take a look (or a relook) at the documentation for the SortedDictionary constructor, and how to make a class that implements IComparer.

Using IEnumerable(Of String) to read from different kinds of data sources

I am using the below variable to store a list of user ID strings. I then use the list to search for each user using an LDAP query.
Dim userIds As IEnumerable(Of String) =
{"testid1", "testid2", "testid3", "testid4", "testid5"}
That works, but the ID's are hard-coded. How do I make it read the ID's from a ListBox control instead? Would it be something like:
Dim userIds As IEnumerable(Of String) = ListBox1???
I would like to use the ListBox because I will plan to load the ListBox with a bunch of ID's from a text file.
Better yet, is it possible to use a TextBox? If it was a TextBox, I could just copy and paste the ID's that I need to query.
The contents of a ListBox control can be accessed using the ListBox.Items property. It returns a ListBox.ObjectCollection object, which implements IList, ICollection, and IEnumerable.
This is assuming you've added the contents programmatically, rather than binding to a DataSource. If you bound to a DataSource, as LarsTech suggests, you should use ListBox.DataSource.
If you wanted to use a TextBox control, you'd have to manually delimit each ID somehow. You could do this by putting only one ID per line, and then use the Split method to get each ID:
Dim ids as String() = myTextBox.Text.Split(new String() { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
I was going to write this as a comment, but it got a bit long and started involving code examples so I figured it would be better to post it as an answer, even though there is already an accepted answer which is very good.
Since I'm the one who originally gave you the code that used IEnumerable I feel like I should explain why I used it... IEnumerable is the lowest level interface that is implemented by all lists, collections, dictionaries, arrays, etc. Basically, anything that stores multiple data which can be looped through by a For Each loop, implements the IEnumerable interface. In fact, essentially the only thing that the IEnumerable interface supports is the ability to enumerate through its contents with a For Each loop. IEnumerable is just an interface, it's not a concrete object type. Therefore, when you create an IEnumerable variable, that means that variable can be used to reference (i.e. point to) any object that implements that interface (i.e. any object that can be enumerated with a For Each loop.
Therefore, in the following line, it's not creating an IEnumerable type of object. Or at least not in the concrete type sense. It's creating a specific type of object which happens to implement the IEnumerable interface and then sets the ids variable to point to it.
Dim userIds As IEnumerable(Of String) = {"1", "2", "3"}
The phrase {"1", "2", "3"} is actually a literal expression to represent an array of strings. In other words, that literal expression is the equivalent of doing the following:
Dim stringArray(2) As String
stringArray(0) = "1"
stringArray(1) = "2"
stringArray(2) = "3"
So, since the object containing the list of ID's is actually an array of strings, it could have been done like this:
Dim userIds() As String = {"1", "2", "3"}
However, since I wanted the code to work, regardless of the data source, I used the more general IEnumerable interface. Since the only thing that I actually required of the data was that it could be enumerated with a For Each loop, I didn't want to limit the flexibility of the code by requiring the input list of ID's to be of some higher-level specific object type. I didn't really care that the ID's were specifically an array, or a list, or a dictionary, or a collection. As long as it was something that I could loop through, that's all I cared about. By doing so, that made the code more flexible so that you could set the variable to any enumerable data source, such as the Items property of the ListBox. For instance, all of the following would have worked, without changing the rest of the code:
Dim userIds As IEnumerable(Of String) = {"1", "2", "3"}
Or
Dim userIdsArray() As String = {"1", "2", "3"}
Dim userIds As IEnumerable(Of String) = userIdsArray
Or
Dim userIdsArray(2) As String
userIdsArray(0) = "1"
userIdsArray(1) = "2"
userIdsArray(2) = "3"
Dim userIds As IEnumerable(Of String) = userIdsArray
Or
Dim userIds As IEnumerable(Of String) = ListBox1.Items.OfType(Of String)()
Or
Dim userIds As IEnumerable(Of String) = File.ReadAllLines("IDs.txt")
Or
Dim userIds As IEnumerable(Of String) = TextBox1.Text.Split({Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
Etc.
Since all of those above data sources implement the IEnumerable interface, the same userIds variable can be used to reference all of them.

Parallel.ForEach gives different results each time

Please help me to convert following loop to Parallel loop. I tried using Parallel.ForEach and ConcurrentBag instead of HashSet, but the wied thing is that "Matched" returns every time different results.
I can't figure it out... Is it because of Thread Safety issues?
Keywords list contains about 500 unique strings, each 1-3 words in lenght.
Items contains about 10000 items.
Original code:
Dim Items As IEnumerable(Of Item) = Db.Items.GetAll
Dim Keywords As HashSet(Of String)
Dim Matched As HashSet(Of Item)
For Each Item In Items
For Each Keyword In Keywords
If Regex.IsMatch(Headline, String.Format("\b{0}\b", Keyword), RegexOptions.IgnoreCase Or RegexOptions.CultureInvariant) Then
If Not Matched.Contains(Item) Then
Matched.Add(Item)
End If
End If
Next
Next
Attempt to convert it to
Dim Items As IEnumerable(Of Item) = Db.Items.GetAll
Dim Keywords As HashSet(Of String)
Dim Matched As Concurrent.ConcurrentBag(Of Item)
Threading.Tasks.Parallel.ForEach(Of Item)(Items, Sub(Item)
For Each Keyword In Keywords
If Regex.IsMatch(Item.Title, String.Format("\b{0}\b", Keyword), RegexOptions.IgnoreCase Or RegexOptions.CultureInvariant) Then
If Not Matched.Contains(Item) Then
Matched.Add(Item)
End If
Continue For
End If
Next
End If
Yes, your code certainly isn't thread-safe. Using thread-safe collections won't make your code automatically thread-safe, you still need to use them correctly.
Your problem is that after Contains() finishes but before Add() is called on one thread, Add() can be called on another thread (and the same can possibly also happen while Contains() executes).
What you need to do is to:
Use locking (this means you won't need to use a thread safe collection anymore); or
Use something like ConcurrentHashSet. There is no such class in .Net, but you can use ConcurrentDictionary instead (even if it doesn't fit your needs exactly). Instead of your call to Contains() and then Add(), you could do Matched.TryAdd(Item, True), where True is there just because ConcurrentDictionary needs some value.

vb.NET Select distinct... how to use it?

Coming from a C# background I am a bit miffed by my inability to get this simple linq query working:
Dim data As List(Of Dictionary(Of String, Object))
Dim dbm As AccessDBManager = GlobalObjectManager.DBManagers("SecondaryAccessDBManager")
data = dbm.Select("*", "T町丁目位置_各務原")
Dim towns As IEnumerable(Of String())
towns = data.Select(Function(d) New String() {d("町名_Trim").ToString(), d("ふりがな").ToString()})
towns = towns.Where(Function(s) s(0).StartsWith(searchTerms) Or s(1).StartsWith(searchTerms)).Distinct()
Call UpdateTownsListView(towns.ToList())
I pasted together the relevant bits, so hopefully there is no error here...
data is loaded from an access database and is a list with the data from each row stored as a dictionary.
In this case element from data has a field containing the name of a Japanese town and its reading and some other stuff like the row ID etc.
I have a form with a textbox. When the user types something in, I would like to retrieve from data the town names corresponding to the search terms without duplicates.
Right now the results contain loads of duplicates> How can I get this sorted to only get distinct results?
I read from some other posts that a key might be needed, but how can I declare this with extension methods?
Distinct uses the default equality comparer to compare values.
Your collection contains arrays of strings, so Distinct won't work the way you expected since two different arrays never equals each other (since ReferenceEquals would be used in the end).
A solution is to use the Distinct overload which takes an IEqualityComparer.
Class TwoStringArrayEqualityComparer
Implements IEqualityComparer(Of String())
Public Function Equals(s1 As String(), s2 As String()) As Boolean Implements IEqualityComparer(Of String()).Equals
' Note that checking for Nothing is missing
Return s1(0).Equals(s2(0)) AndAlso s1(1).Equals(s2(1))
End Function
Public Function GetHashCode(s As String()) As Integer Implements IEqualityComparer(Of String()).GetHashCode
Return (s(0) + s(1)).GetHashCode() ' probably not perfect :-)
End Function
End Class
...
towns = towns.Where(...).Distinct(new TwoStringArrayEqualityComparer())