I have a file with lines of string. Each line represent a collection of key value, for example:
Name=JUI;Type=HomeUser;Address=Belgium;Address=Liege;Address=Street
Name=Tim;Type=HomeUser;Address=Belgium;Address=Hasselt;Address=Street
Name=Kim;Type=Proff;Address=Germany;Address=Dusseldorf;Address=Street
Name=Ils;Type=Proff;Address=Germany;Address=Munich;Address=Street
Name=Jan;Type=Student;Address=Germany;Address=Frankfurt;Address=Street
Name=Dav;Type=Student;Address=France;Address=Mitz;Address=Street
Name=Soli;Type=HomeUser;Address=France;Address=Lyon;Address=Street
Name=Mik;Type=HomeUser;Address=Switzerland;Address=Zurich;Address=Street
Name=Peter;Type=Blocked;Address=Netherland;Address=Enschede;Address=Street
Name=Maz;Type=Blocked;Address=Germany;Address=Achen;Address=Street
Name=Jo;Type=Teacher;Address=Belgium;Address=Antwerpen;Address=Street
How can I do the following:
Get the names where type is HomeUser
Get the types where Address =Germany (problem there are 3 address key in earch line)
Get the name where address =Lyon
Is there is a simple way to do that?
In all of these cases, the answer is really simple when you've got a better data representation - you can just use LINQ.
However, the first step will be to parse the data. Model it something like this:
public class User // ???
{
public string Name { get; private set; }
public string Type { get; private set; } // Should this be an enum?
public IList<string> Addresses { get; private set; }
// Could make this a constructor if you really want... I like the
// explicit nature of the static factory method.
public static User ParseLine(string line)
{
// TODO: Split line into components etc
}
}
One you've got a List<User> your queries will be really easy - but it's important to separate "put data into a more natural representation" from "do interesting operations with data".
This is a much more general point than just this particular example, but always try to get your data into a natural, useful representation as early as you can, and then keep it in that representation for as long as you can. Only deal with an awkward representation (typically a string) at the boundaries of your code, if possible.
Create a Regex to parse the item: "Name=(.+?);Type=(.+?);Address=(.+?) etc."
Then you could create a class to hold all the information
class Record { public string Name; public string Type; public string Address; public string Address2; public string Address3}
then match each line with the regex, fill the fields from Match groups and create an instance of the class and add these to a List<Record> records.
Now you can easily search with linq for:
type is HomeUser : records.Where(p=>p.Type=="HomeUser")
Address is Germany : records.Where(p=>p.Address=="Germany")
Address is Lyon: records.Where(p=>p.Address=="Lyon")
you could easily extend this example to look in all 3 address fields
It would be easier to first define a struct
struct MyStruct
{
public string Name, Type /* etc.*/ ;
}
After that you'll need to split your input
string[] arrayOfInputs = inpuString.Split(new char[]{Environment.NewLine, '\n', '\r'}) // splits your input, such that every element represents a line
List<MyStruct> myStruct = new List<MyStruct>;
foreach (string s in arrayOfInputs)
{
string[] arrayOfFields = s.Split(';');
// arrayOfFields[0] == "Name=name"
// arrayOfFields[1] == "Type=type"
// etc. extract needed info
myStruct.Add(new MyStruct(/* arguments go here */))
}
Now that you have extracted your data and put them into a list of structs, you may search for the required data using Linq
string theNameImLookingFor = from element in myStruct
where element.Type == "HomeUser"
|| element.Address[0] == "Lyon"
|| element.Address[1] == "Lyon"
|| element.Address[2] == "Lyon"
select element.Name;
string theTypeImLookingFor = from element in myStruct
// etc.
select element.Type;
Alternatively you can do it like this:
string tNILF = myStruct.Where(element => element.Type == "HomeUser" /* || etc. */).Select(element => element.Name);
One way to do this is to read the key-value pairs into a collection of dynamic objects. Once this is done, you can use the dynamic runtime to query the dynamic objects using LINQ:
To create a collection of dynamic objects:
var users = str.Split("\r\n".ToArray(), StringSplitOptions.RemoveEmptyEntries)
.Select(x => x.Split(';')
.Select(p => p.Split('='))
.ToLookup(s => s[0], s => s[1])
.ToDictionary(l => l.Key, l => (l.Count() > 1)
? (object)l.ToList() : (object)l.First())
.ToExpando());
Note the use of this extension method:
public static dynamic ToExpando(this IDictionary<string, object> dict)
{
IDictionary<string, object> expando = new ExpandoObject();
foreach (var kv in dict)
expando[kv.Key] = kv.Value;
return expando;
}
Then you can run the queries you're interested in:
1.Get the names where type is HomeUser:
var homeUsers = users.Where(u => u.Type == "HomeUser")
.Select(u => u.Name);
2.Get the types where Address =Germany (Note: .Address is a List<string>):
var typesInGermany = users.Where(u=>u.Address.Contains("Germany"))
.Select(u => u.Type).Distinct();
3.Get the name where address =Lyon:
var namesInLyon = users.Where(u => u.Address.Contains("Lyon"))
.Select(u => u.Name);
Related
I have two list
class obj1
{
public string country{ get; set; }
public string region{ get; set; }
}
class obj2
{
public string country{ get; set; }
public string region { get; set; }
public string XYZ { get; set; }
public bool ToBeChanged{ get; set; }
}
first list looks like:
List<obj1> alist = new List<obj1>();
alist.Add("US", "NC");
alist.Add("US", "SC");
alist.Add("US", "NY");
second list (List<obj2> alist2) may make 1000 of entries with many combination of country and region.
I need to update the property "ToBeChanged" to "True" if second (alist2) list properties (country and region) matches to first(alist1) and false in otherwise.
Please help.
Thanks,
Vaibhav
Two points from the comments, and my thoughts:
Some aren't sure exactly what your matching criteria is. But to me it seems fairly clear that you're matching on 'country' and 'region'. Nevertheless, in the future, state this explicitly.
You got one comment criticizing your choice of variable names. That criticism is fully justified. Code is far easier to maintain when you have little hints as to what it's doing, and variable names are crucial for that.
Now, regarding my particular solution:
In the code below, I've renamed some of your objects to make them clear in their purpose. I'd like to rename 'obj2', but I'll leave that to you because I'm not exactly sure what you're intending to do with it, and I definitely don't know what 'XYZ' is for. Here are the renamed classes, with some added constructors to aid in list construction.
class RegionInfo {
public RegionInfo(string country, string region) {
this.country = country;
this.region = region;
}
public string country{ get; set; }
public string region{ get; set; }
}
class obj2 {
public obj2 (string country, string region, string XYZ) {
this.country = country;
this.region = region;
this.XYZ = XYZ;
}
public string country{ get; set; }
public string region { get; set; }
public string XYZ { get; set; }
public bool ToBeChanged{ get; set; }
}
I'm using a LINQ Join to match the two lists, outputting only the 'obj2' side of the join, and then looping the result to toggle the 'ToBeChanged' value.
var regionInfos = new List<RegionInfo>() {
new RegionInfo("US", "NC"),
new RegionInfo("US", "SC"),
new RegionInfo("US", "NY")
};
var obj2s = new List<obj2> {
new obj2("US", "NC", "What am I for?"),
new obj2("US", "SC", "Like, am I supposed to be the new value?"),
new obj2("CA", "OT", "XYZ doesn't have a stated purpose")
};
var obj2sToChange = obj2s
.Join(
regionInfos,
o2 => new { o2.country, o2.region },
reg => new { reg.country, reg.region },
(o2,reg) => o2
);
foreach (var obj2 in obj2sToChange)
obj2.ToBeChanged = true;
obj2s.Dump(); // using Linqpad, but you do what works to display
This results in:
country
region
XYZ
ToBeChanged
US
NC
What am I for?
True
US
SC
Like, am I supposed to be the new value?
True
CA
OT
XYZ doesn't have a stated purpose
False
First of all, with LINQ you can never change the source. You can only extract data from the source. After that you can use the extracted data to update the source.
I need to update the property "ToBeChanged" to "True" if second (alist2) list properties (country and region) matches to first(alist1) and false in otherwise.
This is not a proper requirement. alist1 is a sequence of obj1 objects. I think, that you want the property ToBeChanged of a certain obj2 to be true if any of the obj1 items in alist1 has a [country, region] combination that matches the [country, region] combination of the obj2 concerned.
requirement Get all obj2 in alist2, that have a [country, region] combination that matches any of the [country, region] combinations of the obj1 objects in alist1.
You probably thought about using Where for this. Something like "Where [country, region] combination in the other list". Whenever you need to find out if an item is in another list, consider to use one of the overloads of Enumerable.Contains
The problem is, that the [Country, Region] combination in every obj2 can be converted to an object of class obj1, but if you want to check if they are equal, you will have a compare by reference, while you want a compare by value.
There are two solutions for this:
create an EqualityComparer that compares obj1 by Value
create [Country, Region] as anonymous type. Anonymous types always compare by value.
The latter is the most easy, so we'll do that one first.
Use anonymous types for comparison
First convert alist into anonymous type containing [Country, Region] combinations:
var eligibleCountryRegionCombinations = alist.Select(obj1 => new
{
Country = obj1.Country,
Region = obj1.Region,
});
Note that I don't use ToList at the end: the enumerable is created, but the sequence has not been enumerated yet. In LINQ terms this is called lazy or deferred execution.
IEnumerable<obj2> obj2sThatNeedToBeChanged = alist2.Select(obj2 => new
{
CountryRegionCombination = new
{
Country = obj2.Country,
Region = obj2.Region,
},
Original = obj2,
})
.Where(item => eligibleCountryRegionCombinations.Contains(
item.CountryRegionCombination))
.Select(item => item.Original);
CountryRegionCombination is an anonymous type of the same type as the anonymous items in eligibleCountryRegionCombinations. Therefore you can use Contains. Because the items are anonymous type, the equality comparison is comparison by value.
The final select will remove the anonymous type, and keep only the Original.
Note that the query is still not enumerated.
foreach (var obj2 in obj2sThatNeedToBeChanged.ToList())
{
obj2.ToBeChanged = true;
}
It can be dangerous to change the source that you are enumerating. In this case it is not a problem, because the field that you change is not used to create the enumeration. Still I think it is safer, because of possible future changes, to do a ToList before you start changing the source.
Create an equality comparer
One of the overload of Enumerable.Contains has a parameter comparer. This expects an IEqualityComparer<obj1>
class Obj1Comparer : EqualityComparer<obj1>
{
public static IEqualityComparer<obj1> ByValue {get;} = new Obj1Comparer();
private static IEqualityComparer<string> CountryComparer => StringComparer.OrdinalIgnoreCase;
private static IEqualityComparer<string> RegionComparer => StringComparer.OrdinalIgnoreCase;
public override bool Equals (obj1 x, obj1 y)
{
if (x == null) return y == null; // true if both null, false if x null, but y not null
if (y == null) return false; // because x not null
// optimization:
if (Object.ReferenceEquals(x, y)) return true;
if (x.GetType() != y.GetType()) return false;
return CountryComparer.Equals(x.Country, y.Country)
&& RegionComparer.Equals(x.Region, y.Region);
}
To make it easy to change equality of countries, I created a separate comparer for countries and for regions. So if later you want to compare case sensitive, or if you change Country from string to a foreign key to a table of countries, then changes will be minimal.
You also need to override GetHashCode. If x equals y, then GetHashCode should rerturn the same value. Not the other way round: if x and y different they may return the same hash code. However, code will be more efficient if you have more different Hash codes.
public override int GetHashCode (obj1 x)
{
if (x == null) return 87966354; // just a number
return CountryComparer.GetHashCode(x.Country)
^ RegionComparer.GetHashCode(x.Region);
}
Which HashCode you return depends on how often this will be called, for instance in dictionaries, comparers like Contains, etc.
How "different" are the Countries and Regions? A different Country will probably also mean a different region. So maybe it is efficient enough if you only calculate the Hash code for the Country. If a Country has many, many regions, then it will probably be better to calculate the hash code for regions as well If a Region is only in one Country (OberAmmerGau is probably only in Germany), or in only a few Regions (how many regions "New Amsterdam" will there be?), then you won't have to check the Country at all.
Because we have an equality comparer, we don't need to convert alist to an anonymous type, we can specify that Contains should compare by value.
IEqualityComparer<obj1> comparer = Obj1Comparer.ByValue;
IEnumerable<obj2> obj2sThatNeedToBeChanged = alist2.Select(obj2 => new
{
Obj1 = new Obj1
{
Country = obj2.Country,
Region = obj2.Region,
},
Original = obj2,
})
.Where(item => alist.Contains(item.CountryRegionCombination, comparer))
.Select(item => item.Original);
Fast method: Extension method
The fastest method, and maybe also the most simple one, is to create an extension method.
private static IEqualityComparer<string> CountryComparer => StringComparer.OrdinalIgnoreCase;
private static IEqualityComparer<string> RegionComparer => StringComparer.OrdinalIgnoreCase;
public static IEnumerable<Obj2> WhereSameLocation(
this IEnumerable<Obj2> source,
IEnumerable<Obj1> obj1Items)
{
// TODO: what to do if source == null?
foreach (Obj2 obj2 in source)
{
// check if there is any obj1 with same [Country, Region]
if (obj1Items
.Where(obj1 => CountryComparer.Equals(obj2.Country, obj1.Country)
&& RegionComparer.Equals(obj2.Region, obj1.Region))
.Any())
{
yield return obj2;
}
}
}
Usage:
IEnumerable<Obj1> alist = ...
IEnumerable<Obj2> alist2 = ...
IEnumerable<obj2> obj2sThatNeedToBeChanged = alist2.WhereSameLocation(alist);
I have a field that looks like:
public Dictionary<ClassA, List<ClassB>> MyDict;
Assume that:
public class ClassA
{
public string Name;
public int Id;
}
public class ClassB
{
public string Tag;
public string Text;
}
I'm trying to define a query that is of IEnumerable<KeyValuePair<ClassA,IEnumerable<ClassB>> type where I define a condition on the value of ClassB.Tag. I tried things like:
IEnumerable<KeyValuePair<ClassA,IEnumerable<ClassB>> q =
MyDict.Where(pair => pair.Value.Any(b => b.Tag == "a tag"));
But obviously the above is not what I need because it returns the whole List<ClassB> if any item matches that condition, while what I want is to return an IEnumrable or a List of items that match the condition.
dotNetFiddle demo
You need to construct the IEnumerable from a call to ToDictionary, where you use a projection to only take the matching BClass from the list and only take the result from that set where values in the BClass list were actually matched.
IEnumerable<KeyValuePair<ClassA,List<ClassB>>> q = MyDict.ToDictionary(
k => k.Key,
k => k.Value.Where(b => b.Tag == "10").ToList()
).Where(kv => kv.Value.Any());
I am using c# WPF for developing a Windows Application.
The application requires a class as follows
public class Limits
{
public String col1
{
get;
set;
}
public String col2
{
get;
set;
}
public String col3
{
get;
set;
}
}
I am using a List to Store Objects like:-
List myList<Limits> = new List<Limits>();
"myList" has around 15000 Objects.
Now, I want to search this myList for a particular attribute.
Eg: I want to find out the object that has col1 set as "abc".
How can I use Binary Search for this problem?
First of all, the list has to be sorted on the col1 property for you to be able to use binary search at all.
You would need a comparer that compares the col1 property:
public class LimitsComparer : IComparer<Limits> {
public int Compare(Limits x, Limits y) {
return x.col1.CompareTo(y.col1);
}
}
Then you can use that to do the binary search:
int index = myList.BinarySearch(new Limits { col1 = "abc" }, new LimitsComparer());
The index returned is:
The zero-based index of item in the sorted List, if item is found;
otherwise, a negative number that is the bitwise complement of the
index of the next element that is larger than item or, if there is no
larger element, the bitwise complement of Count.
You can also use the Where method to get the objects that has that property:
List<Limits> result = myList.Where(x => x.col1 == "abc").ToList();
Although that is not quite as efficient, you should still consider if that is a better solution as it's easier to implement and gives a result that is easier to handle. Also (and this might be more important), it works even if the list isn't sorted on col1.
You could use somthing like this.
myList.Where(i => i.col1 == "abc").ToList();
Use a dictionary where the keys are stored in a hash table. Linq will create the cdictionary easily.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication41
{
class Program
{
static void Main(string[] args)
{
List<Limits> myList = new List<Limits>();
//dictionary with unique keys
Dictionary<string, Limits> dict1 = myList.AsEnumerable()
.GroupBy(x => x.col2, y => y)
.ToDictionary(x => x.Key, y => y.FirstOrDefault());
//dictionary with keys having multiple values
Dictionary<string, List<Limits>> dict2 = myList.AsEnumerable()
.GroupBy(x => x.col2, y => y)
.ToDictionary(x => x.Key, y => y.ToList());
Limits abc = dict1["abc"];
}
}
public class Limits
{
public String col1 { get; set; }
public String col2 { get; set; }
public String col3 { get; set; }
}
}
unless you explicitly want to use binary search, you should use the standard Linq functions available to you. Unless your list is already sorted, this might be more efficient than binary sort.
var myList = new List<Limits> {....}
var entry = myList.Where(l => l.col1== "abc").FirstOrDefault();
if(entry == null)
{ // no match found }
If you really want binary search, ref Can LINQ use binary search when the collection is ordered?
I have a Dictionary that contains thread Information Dictionary<String,Thread>
"2FF"
"2IE"
"7CH"
etc
what i know is integers 2,7 etc what i want to know that in Dictionary how many strings contain the given integer if it is there then get that string
Eg
String GetString(int integer)
{
//if Dictionary contains given intgr return whole string in which that integer is present
}
}
With LINQ syntax:
var matchingThreads = from pair in dictionary
where pair.Key.StartsWith(number.ToString())
select pair.Value;
With traditional syntax:
var matchingThreads = dictionary
.Where(pair => pair.Key.StartsWith(number.ToString()))
.Select(pair => pair.Value);
If you only need to count them and you don't care about the Thread objects, you can use:
int count = dictionary.Keys.Count(key => key.StartsWith(number.ToString()))
Note that you need a using System.Linq directive.
Maybe a List<CustomClass> would be a better choice here where CustomClass would look like:
public sealed class CustomClass
{
public Thread Thread { get; set; }
public string String { get; set; }
}
(Better property names are alway good, of course :-) )
A dictionary is not sutitable if you do not know the exact keys or only parts of them.
You could then use LINQ to find out what you want, e.g.:
int count = list.Where(c => c.String.StartsWith(integer.ToString())).Count();
//or
IEnumerable<string> strings = list.Where(c => c.String.StartsWith(integer.ToString())).Select(c => c.String);
public IEnumerable<string> GetMatchingKeys(int value)
{
var valueText = value.ToString();
return _dictionary.Keys.Where(key => key.Contains(valueText));
}
I have asked this question about using the a Linq method that returns one object (First, Min, Max, etc) from of a generic collection.
I now want to be able to use linq's Except() method and I am not sure how to do it. Perhaps the answer is just in front on me but think I need help.
I have a generic method that fills in missing dates for a corresponding descriptive field. This method is declared as below:
public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection, string datePropertyName, string descriptionPropertyName)
{
Type type = typeof(T);
PropertyInfo dateProperty = type.GetProperty(datePropertyName);
PropertyInfo descriptionProperty = type.GetProperty(descriptionPropertyName);
...
}
What I want to accomplish is this. datePropertyName is the name of the date property I will use to fill in my date gaps (adding default object instances for the dates not already present in the collection). If I were dealing with a non-generic class, I would do something like this:
foreach (string description in descriptions)
{
var missingDates = allDates.Except(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
...
}
But how can I do the same using the generic method FillInMissingDates with the dateProperty and descriptionProperty properties resolved in runtime?
I think the best way would be to define an interface with all of the properties that you want to use in your method. Have the classes that the method may be used in implement this interface. Then, use a generic method and constrain the generic type to derive from the interface.
This example may not do exactly what you want -- it fills in missing dates for items in the list matching a description, but hopefully it will give you the basic idea.
public interface ITransactable
{
string Description { get; }
DateTime? TransactionDate { get; }
}
public class CompletedTransaction : ITransactable
{
...
}
// note conversion to extension method
public static void FillInMissingDates<T>( this IEnumerable<T> collection,
string match,
DateTime defaultDate )
where T : ITransactable
{
foreach (var trans in collection.Where( t => t.Description = match ))
{
if (!trans.TransactionDate.HasValue)
{
trans.TransactionDate = defaultDate;
}
}
}
You'll need to Cast your enumeration to ITransactable before invoking (at least until C# 4.0 comes out).
var list = new List<CompletedTransaction>();
list.Cast<ITransactable>()
.FillInMissingDates("description",DateTime.MinValue);
Alternatively, you could investigate using Dynamic LINQ from the VS2008 Samples collection. This would allow you to specify the name of a property if it's not consistent between classes. You'd probably still need to use reflection to set the property, however.
You could try this approach:
public IEnumerable<T> FillInMissingDates<T>(IEnumerable<T> collection,
Func<T, DateTime> dateProperty, Func<T, string> descriptionProperty, string desc)
{
return collection.Except(collection
.Where(d => descriptionProperty(d) == desc))
.Select(d => dateProperty(d));
}
This allows you to do things like:
someCollection.FillInMissingDates(o => o.CreatedDate, o => o.Description, "matching");
Note that you don't necessarily need the Except() call, and just have:
.. Where(d => descriptionProperty(d) != desc)
foreach (string description in descriptions)
{
var missingDates = allDates.Except<YourClass>(originalData.Where(d => d.Description == desc).Select(d => d.TransactionDate).ToList());
}
In fact, almost all LINQ extension in C# have a generic possible value. (Except and Except)
If you're going to identify the property to be accessed by a string name, then you don't need to use generics. Their only purpose is static type safety. Just use reflection to access the property, and make the method work on a non-generic IEnumerable.
Getting Except result with multiple properties working with custom data class is not allowed.
You have to use it like this: (given in msdn 101 LINQ Samples)
public void Linq53()
{
List<Product> products = GetProductList();
List<Customer> customers = GetCustomerList();
var productFirstChars =
from p in products
select p.ProductName[0];
var customerFirstChars =
from c in customers
select c.CompanyName[0];
var productOnlyFirstChars = productFirstChars.Except(customerFirstChars);
Console.WriteLine("First letters from Product names, but not from Customer names:");
foreach (var ch in productOnlyFirstChars)
{
Console.WriteLine(ch);
}
}
Having the key, you can handle your data accordingly :)