LINQ query is not working - vb.net

I am trying to create a dictionary from a list. I am trying to filter the list such that it contains the id that I am adding as a key to the dictionary. So dictionary would be Key, List - with id as Key.
I have no idea why the following won't work - although the rawlist contains ids, filteredlist is empty. I am checking the value within the loop. Fresh set of eyes please?
resultList is dictionary(string, List)
For Each c As String In listIds
Dim filteredlist = rawList.Where(Function(x) x.id.Trim().Contains(c.Trim())).ToList()
resultList.Add(c,filteredlist)
Next
I need the filtered list. I have tried Contains, Equals and "="
i.e.
Dim filteredlist = rawList.Where(Function(x) x.id.Trim().Equals(c.Trim())).ToList()
and
Dim filteredlist = rawList.Where(Function(x) x.id.Trim() = (c.Trim())).ToList()
Please look into it thanks!

Looks like you have it backwards. Typically you have a list for looking for something first and not iterate through that list which IMHO takes longer.
Filter Linq Child Collection by another List/Array
Public Class POC
Public Property Id As Integer
Public Property Desc As String
Public Property Orders As List(Of Order)
End Class
Public Class Order
Public Property Id As Integer
Public Property Desc As String
End Class
Private Shared Function GetOrders(numberOfOrders As Integer) As List(Of Order)
Dim orders = New List(Of Order)()
For i As Integer = 1 To numberOfOrders
orders.Add(New Order() With { .Id = i, .Desc = "{i} Order" })
Next
Return orders
End Function
Private Shared Function GetPOCOsAndOrders() As List(Of POC)
Return New List(Of POC)() From { _
New POC() With { .Id = 1, .Desc = "John", .Orders = GetOrders(1) },
New POC() With { .Id = 2, .Desc = "Jane", .Orders = GetOrders(2) },
New POC() With { .Id = 3, .Desc = "Joey", .Orders = GetOrders(3) }
End Function
Private Shared Sub Main(args As String())
Dim orders = New List(Of Integer)() From { 2, 3 }
Dim items = GetPOCOsAndOrders()
Dim peopleAndOrdersWhereOrderNumberIsGreaterThanTwo = items.Where(Function(x) x.Orders.Any(Function(y) orders.Contains(y.Id)))
'I should only get the last two people out of three and their orders
peopleAndOrdersWhereOrderNumberIsGreaterThanTwo.ToList().ForEach(Function(x) Console.WriteLine("{x.Id} {x.Desc} {x.Orders.Count}"))
Console.ReadLine()
End Sub

There are many silght variations possible with the limited information you have given us, but a working example is:
Option Infer On
Option Strict On
Module Module1
Public Class datum
Property Id As String
Property Name As String
Public Sub New()
' empty constructor
End Sub
Public Sub New(Id As String, Name As String)
Me.Id = Id
Me.Name = Name
End Sub
End Class
Sub Main()
' sample data...
Dim rawData = New List(Of datum)
rawData.Add(New datum("cat", "Lulu"))
rawData.Add(New datum("cat", "Uschi"))
rawData.Add(New datum("snake", "Sid"))
rawData.Add(New datum("fox", "Reynard"))
rawData.Add(New datum("mouse", "Jerry"))
' what to look for:
Dim listIds As New List(Of String) From {"CAT", "mouse", "ELK"}
' somewhere to store the results:
Dim dict As New Dictionary(Of String, List(Of String))
' filter by what to look for:
For Each c In listIds
Dim foundItems = rawData.Where(Function(x) String.Compare(x.Id, c, StringComparison.OrdinalIgnoreCase) = 0)
' only add items to the dictionary if the Id was found:
If foundItems.Count() > 0 Then
' use the case of the id from the raw data:
dict.Add(foundItems(0).Id, foundItems.Select(Function(f) f.Name).ToList())
End If
' alternative which includes dictionary entries where the Id was not found:
'dict.Add(If(foundItems.Count = 0, c, foundItems(0).Id), foundItems.Select(Function(f) f.Name).ToList())
Next
' show the dictionary contents:
For Each kvp In dict
Console.WriteLine(kvp.Key & ": " & String.Join(", ", kvp.Value))
Next
Console.ReadLine()
End Sub
End Module
Output:
cat: Lulu, Uschi
mouse: Jerry
I have included commented-out code for some variations you may want.

Related

Sort specific objects

I have tree classes as follows:
Public Class HtmlSection
Property Name As String
Property SubSections As List(Of HtmlSubSection)
End Class
Public Class HtmlSubSection
Property Name As String
Property SelectedSentences As List(Of HtmlSentence)
End Class
Public Class HtmlSentence
Property Sentence As String
Property Position As Integer
End Class
In below method i am searching for all sentences for each subsection belonging to specific section, at the end i sort those records by Position asc. However sometimes positions have to be changed (directly in sentences) because there could be gaps means after i do OrderBy it will be ordered but it could look like this below. Is there any easy way like linq to change that Positions of that sentences to avoid gaps let's say in the method i shown below.
2
5
77
1001
i would like to change positions starting from 0 in our example:
0
1
2
3
Method:
Public Function GetSelectedSentencesOnSectionLevel(section As HtmlSection) As List(Of HtmlSentence)
Dim sentencesList As New List(Of HtmlSentence)
For Each exSection As HtmlSection In _htmlFactory.SectionsList
If exSection.Name = section.Name Then
Dim sentencesList As New List(Of HtmlSentence)
If Not IsNothing(exSection.SubSections) Then
For Each exSubsection As HtmlSubSection In exSection.SubSections
If Not IsNothing(exSubsection.SelectedSentences) Then
For Each exSentence As HtmlSentence In exSubsection.SelectedSentences
sentencesList.Add(exSentence)
Next
End If
Next
End If
End If
Next
'sort sentences by Posiions ascending
sentencesList = sentencesList.OrderBy(Function(x) x.Position).ToList()
Return sentencesList
End Function
EDIT : more code for helpers:
global class:
Public Class HtmlFactory
Property SectionsList As List(Of HtmlSection)
Sub New()
SectionsList = New List(Of HtmlSection)
End Sub
Sub New(pSectionsList As List(Of HtmlSection))
_SectionsList = pSectionsList
End Sub
Public Sub AddSection(section As HtmlSection)
SectionsList.Add(section)
End Sub
....
Here you are a pure LINQ solution.
Dim index As Integer = -1
Dim sectionName As String
Dim allTheSections As List(Of HtmlSection)
Dim sentenceList = allTheSections _
.Where(Function(sect) _
sect.SubSections IsNot Nothing _
AndAlso sect.Name.Equals(sectionName, StringComparison.OrdinalIgnoreCase)) _
.SelectMany(Function(sect) sect.SubSections) _
.Where(Function(subSect) subSect.SelectedSentences IsNot Nothing) _
.SelectMany(Function(subSect) subSect.SelectedSentences) _
.OrderBy(Function(ss) ss.Position) _
.Select(Function(ss)
index += 1
Return New HtmlSentence With {.Position = index, .Sentence = ss.Sentence}
End Function) _
.ToList()
In this example, allTheSections is where you exSection does coming from.

Delete duplicates from list

I have the following class :
Public Class titlesclass
Public Property Link As String
Public Property Title As String
Public Function Clear()
Link.Distinct().ToArray()
Title.Distinct().ToArray()
End Function
End Class
And the following code :
For Each title As Match In (New Regex(pattern).Matches(content)) 'Since you are only pulling a few strings, I thought a regex would be better.
Dim letitre As New titlesclass
letitre.Link = title.Groups("Data").Value
letitre.Title = title.Groups("Dataa").Value
lestitres.Add(letitre)
'tempTitles2.Add(title.Groups("Dataa").Value)
Next
I tried to delete the duplicated strings using the simple way
Dim titles2 = lestitres.Distinct().ToArray()
And calling the class function :
lestitres.Clear()
But the both propositions didn't work , i know that i'm missing something very simple but still can't find what it is
Easier to use a class that already implements IComparable:
Dim query = From title In Regex.Matches(content, pattern).Cast(Of Match)
Select Tuple.Create(title.Groups("Data").Value, title.Groups("Dataa").Value)
For Each letitre In query.Distinct
Debug.Print(letitre.Item1 & ", " & letitre.Item2)
Next
or Anonymous Types:
Dim query = From title In Regex.Matches(content, pattern).Cast(Of Match)
Select New With {Key .Link = title.Groups("Data").Value,
Key .Title = title.Groups("Dataa").Value}
For Each letitre In query.Distinct
Debug.Print(letitre.Link & ", " & letitre.Title)
Next
Ok, Since I notice you are using a ClassHere is one option you can do in order to not add duplicate items to your List within a class.I'm using a console Application to write this example, it shouldn't be too hard to understand and convert to a Windows Form Application if need be.
Module Module1
Sub Main()
Dim titlesClass = New Titles_Class()
titlesClass.addNewTitle("myTitle") ''adds successfully
titlesClass.addNewTitle("myTitle") '' doesn't add
End Sub
Public Class Titles_Class
Private Property Title() As String
Private Property TitleArray() As List(Of String)
Public Sub New()
TitleArray = New List(Of String)()
End Sub
Public Sub addNewTitle(title As String)
Dim added = False
If Not taken(title) Then
Me.TitleArray.Add(title)
added = True
End If
Console.WriteLine(String.Format("{0}", If(added, $"{title} has been added", $"{title} already exists")))
End Sub
Private Function taken(item As String) As Boolean
Dim foundItem As Boolean = False
If Not String.IsNullOrEmpty(item) Then
foundItem = Me.TitleArray.Any(Function(c) -1 < c.IndexOf(item))
End If
Return foundItem
End Function
End Class
End Module
Another option would be to use a HashSet, It will never add a duplicate item, so even if you add an item with the same value, it wont add it and wont throw an error
Sub Main()
Dim titlesClass = New HashSet(Of String)
titlesClass.Add("myTitle") ''adds successfully
titlesClass.Add("myTitle") '' doesn't add
For Each title As String In titlesClass
Console.WriteLine(title)
Next
End Sub
With all of that aside, have you thought about using a Dictionary so that you could have the title as the key and the link as the value, that would be another way you could not have a list (dictionary) contain duplicate items

Split() doesn't work properly

well I'm doing a computing assessment and well I've ran into an issue with splitting a string. For some reason when the string splits the array stores the whole thing in Variable(0). The error that occurs is when it tries to assign TicketID(Index) a value, it says that the array is out of bound.
Here's the code:
Private Sub ReadInformation(ByRef TicketID() As String, CustomerID() As String, PurchaseMethod() As Char, NumberOfTickets() As Integer, FileName As String)
Dim Line, TextArray(3) As String
Dim Index As Integer
FileOpen(1, FileName, OpenMode.Input)
For Index = 0 To 499
Input(1, Line)
TextArray = Line.Split(",")
CustomerID(Index) = TextArray(0)
TicketID(Index) = TextArray(1)
NumberOfTickets(Index) = TextArray(2)
PurchaseMethod(Index) = TextArray(3)
MessageBox.Show(CustomerID(Index))
Next
FileClose()
End Sub
Here's the first 10 lines of the TextFile I'm trying to read:
C001,F3,10,S
C002,F3,2,O
C003,F3,3,S
C004,W2,9,S
C005,T3,10,S
C006,F3,2,S
C007,W1,3,O
C008,W3,1,O
C009,T2,2,S
C010,F2,9,O
Here's the Error Message I receive:
Error Message
I would use some Lists instead of arrays. In this way you don't have to worry about length of the arrays or if there are fewer lines than 500. Of course, using the more advanced NET Framework methods of the File.IO namespace is a must
Private Sub ReadInformation(TicketID As List(Of String), _
CustomerID As List(Of String), _
PurchaseMethod As List(Of Char), _
NumberOfTickets As List(Of Integer), _
FileName As String)
for each line in File.ReadLines(FileName)
Dim TextArray = Line.Split(","c)
if TextArray.Length > 3 Then
CustomerID.Add(TextArray(0))
TicketID.Add(TextArray(1))
' This line works just because you have Option Strict Off
' It should be changed as soon as possible
NumberOfTickets.Add(TextArray(2))
PurchaseMethod.Add(TextArray(3))
End If
Next
End Sub
You can call this version of your code declaring the 4 lists
Dim TicketID = New List(Of String)()
Dim CustomerID = New List(Of String)()
Dim PurchaseMethod = New List(Of Char)()
Dim NumberOfTickets = New List(Of Integer)()
ReadInformation(TicketID, CustomerID, PurchaseMethod, NumberOfTickets, FileName)
Another approach more Object Oriented is to create a class that represent a line of your data. Inside the loop you create instances of that class and add the instance to a single List
Public Class CustomerData
Public Property TicketID As String
Public Property CustomerID As String
Public Property NumberOfTickets As Integer
Public Property PurchaseMethod As Char
End Class
Now the loop becomes
Private Function ReadInformation(FileName As String) as List(Of CustomerData)
Dim custData = New List(Of CustomerData)()
For Each line in File.ReadLines(FileName)
Dim TextArray = Line.Split(","c)
if TextArray.Length > 3 Then
Dim data = new CustomerData()
data.CustomerID = TextArray(0)
data.TicketID = TextArray(1)
data.NumberOfTickets = TextArray(2)
data.PurchaseMethod = TextArray(3)
custData.Add(data)
End If
Next
return custData
End Function
This version requires the declaration of just one list
You can call this version of your code passing just the filename and receiving the result fo the function
Dim customers = ReadInformation(FileName)
For Each cust in customers
Console.WriteLine(cust.CustomerID)
...
Next
Or use it as an array
Dim theFirstCustomer = customers[0]
Console.WriteLine(theFirstCustomer.CustomerID)

Find duplicate in List(Of T) using LINQ VB .NET not working

I have a generic List(Of T) with a bunch of items that contain some duplicate values at .ItemID. I need the code to return only the duplicates, but my code below returns all of the items.
Dim items As List(Of ItemsClass.ItemCollection) = _
New List(Of ItemsClass.ItemCollection)(oleObj.ListOfItemsToCopy(listViewItems)) 'Returns a list of items
Dim duplicates = From c In items Where (c.ItemID.Count > 1) Select c 'Needs to return duplicate items
An example of the List(Of ItemCollection) items are as follows
ItemID, Make, Type Year
123456 Ford Falcon 1999
123457 Mazda CX6 2001
123456 Ford Ranger 2002
I need to group the ItemID and return all of the duplicate results
For those interested, the following worked well for me
Public Class Form1
Private Class ItemsCollection
Private _name As String
Public Property name() As String
Get
Return _name
End Get
Set(value As String)
_name = value
End Set
End Property
Private _data As String
Public Property data() As String
Get
Return _data
End Get
Set(value As String)
_data = value
End Set
End Property
End Class
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim items As List(Of ItemsCollection) = New List(Of ItemsCollection)()
items.Add(New ItemsCollection With {.name = "Test", .data = "Test2"})
items.Add(New ItemsCollection With {.name = "Test1", .data = "Test2"})
items.Add(New ItemsCollection With {.name = "Test3", .data = "Test4"})
For Each item As ItemsCollection In items.GroupBy(Function(x) x.data).Where(Function(x) x.Count > 1).SelectMany(Function(x) x)
ListBox1.Items.Add(item.name + " " + item.data)
Next
End Sub
End Class
Output
Test Test2
Test1 Test2
Here is come C# syntax for this it shows the operations for a general method to detect duplicates in LINQ, but it will need to be converted to VB.NET syntax as appropriate.
The code also takes the liberty to define T as "a type for a single item that has an ItemID property"; I am unsure why/how the ItemCollection type fits in.
IEnumerable<T> items = ..;
// Group the items
var groupsById = items.GroupBy(i => i.ItemID);
// Find the groups with more than one item
// (The items in the resulting groups are duplicates of each other)
var groupsWithDups = groupsById.Where(g => g.Length > 1);
// If the source is [1, 2, 4, 1, 2, 2] the output will
// contain the following [1, 1, 2, 2, 2].
IEnumerable<T> allDups = groupsWithDups.SelectMany(g => g);

How to convert a string of key/value pairs to HashTable or Dictionary or?

In VB.NET, how can I convert the following string into some kind of key/value type such as a Hashtable, Dictionary, etc?
"Name=Fred;Birthday=19-June-1906;ID=12345"
I want to extract Birthday or ID without having to split the string into an array.
EDIT: I'd prefer not to split the string into an array in case the format of the string changes later. I don't have control over the string. What if someone switches the order around or adds another element?
I’m currently unable to test this, lacking a VB compiler, but the following solution should also work, and it has the advantage of not requiring an explicit loop. It uses the Linq method ToDictionary and two nested Split operations:
Dim s = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim d = s.Split(";"c).Select(Function (kvp) kvp.Split("="c)) _
.ToDictionary( _
Function (kvp) kvp(0), _
Function (kvp) kvp(1))
First, we split on the outer delimiter (i.e. the semi-colon). From the resulting array, we select by splitting again, this time on =. The resulting array of arrays is converted to a dictionary by specifying that the first item is to become the key and the second is to become the value (the identifier kvp stands for “key-value pair”).
Since I can’t check the exact VB syntax and the above may contain subtle errors, here is the equivalent C# code (tested for correctness):
var s = "Name=Fred;Birthday=19-June-1906;ID=12345";
var d = s.Split(';').Select(kvp => kvp.Split('='))
.ToDictionary(kvp => kvp[0], kvp => kvp[1]);
Not sure why you don't want to split it. If you're sure there won't be any extra = or ; then you could just do:
Dim s As String = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim d As New Dictionary(Of String, String)
For Each temp As String In s.Split(";"c)
Dim index As Int32 = temp.IndexOf("="c)
d.Add(temp.Substring(0, index), temp.Substring(index + 1))
Next
Which might not be beautiful, but is very easy to understand.
input.Split(";"c) returns an array of key/value:
{ "Name=Fred", "Birthday=19-June-1906" , "ID=12345" }
so pair.Split("="c) returns { "Name", "Fred" } etc
If you want an alternative to doing a String.Split; there is always Regular Expressions as an alternative:
Dim map As Dictionary(Of String, String) = New Dictionary(Of String, String)
Dim match As Match = Regex.Match("Name=Fred;Birthday=19-June-1906;ID=12345", "(?<Name>[^=]*)=(?<Value>[^;]*);?")
While (match.Success)
map.Add(match.Groups("Name").Value, match.Groups("Value").Value)
match = match.NextMatch()
End While
The regular expression itself could be beefed up to better handle whitespace between key/value's and pair's but you hopefully get the idea. This should only pass through the string once to build up a string dictionary of keys and values.
Dim persSeparator as string=";"
Dim keyValSeparator as string="=";
Dim allPersons As New Dictionary(Of String, Person)
Dim str As String = "Name=Fred;Birthday=19-June-1906;ID=12345"
Dim parts As New List(Of String)(str.Split(persSeparator.ToCharArray)) 'why dont want you to split this string??
Dim person As New Person
For Each part As String In parts
Dim keyValue() As String = part.Split(keyValSeparator.toCharArray())
Select Case keyValue(0).ToUpper
Case "ID"
person.ID = keyValue(1)
Case "NAME"
person.Name = keyValue(1)
Case "BIRTHDAY"
person.BirthDay= keyValue(1)
End Select
Next
If Not allPersons.ContainsKey(person.ID) Then
allPersons.Add(person.ID, person)
End If
Public Class Person
Private _name As String
Private _birthday As String
Private _id As String = String.Empty
Public Sub New()
End Sub
Public Sub New(ByVal id As String)
Me._id = id
End Sub
Public Sub New(ByVal id As String, ByVal name As String)
Me._id = id
Me._name = name
End Sub
Public Sub New(ByVal id As String, ByVal name As String, ByVal birthday As String)
Me._id = id
Me._name = name
Me._birthday = birthday
End Sub
Public Property ID() As String
Get
Return Me._id
End Get
Set(ByVal value As String)
Me._id = value
End Set
End Property
Public Property Name() As String
Get
Return Me._name
End Get
Set(ByVal value As String)
Me._name = value
End Set
End Property
Public Property BirthDay() As String
Get
Return Me._birthday
End Get
Set(ByVal value As String)
Me._birthday = value
End Set
End Property
Public Overrides Function Equals(ByVal obj As Object) As Boolean
If TypeOf obj Is Person AndAlso Not obj Is Nothing Then
Return String.Compare(Me._id, DirectCast(obj, Person).ID) = 0
Else : Return False
End If
End Function
End Class
If you were just wanting to extract the birthday and ID from the string and place as a value pair in some sort of dictionary, for simplicity I would use regular expressions and then a generic dictionary (of string, valuepair structure). Something like this:
Imports System.Text.RegularExpressions
Imports System.Collections.Generic
Sub Main()
Dim Person As New Dictionary(Of String, ValuePair)
Dim s As String = "Name=Fred;Birthday=19-June-1906;ID=12"
Dim r As Regex = New Regex("Name=(.*);Birthday=(.*);ID=(.*$)")
Dim m As Match = r.Match(s)
Person.Add(CStr(m.Groups(1).Value), _
New ValuePair(CDate(m.Groups(2).Value), CInt(m.Groups(3).Value)))
Console.WriteLine(Person("Fred").Birthday.ToString)
Console.WriteLine(Person("Fred").ID.ToString)
Console.Read()
End Sub
Friend Structure ValuePair
Private _birthday As Date
Private _ID As Int32
Public ReadOnly Property ID() As Int32
Get
Return _ID
End Get
End Property
Public ReadOnly Property Birthday() As Date
Get
Return _birthday
End Get
End Property
Sub New(ByVal Birthday As Date, ByVal ID As Int32)
_birthday = Birthday
_ID = ID
End Sub
End Structure