Matching invariant strings with LINQ - c#

I got collection of structures with string property. Given an array of strings I want to check if there is any structure which inner string matches some of them in the array. I did it in this way:
struct S
{
public string s { get; set; }
}
private List<S> List = new List<S>(); // populated somewhere else
public bool Check(params string[] arr)
{
return (from s1 in List
select s1.s into s1
where !string.IsNullOrEmpty(s1)
join s2 in arr on s1.ToLowerInvariant() equals s2.ToLowerInvariant() select s1).Any();
}
Simply, I just want to achieve StringComparison.InvariantCultureIgnoreCase. Is it a proper way to do so? Is it efficient?

You could also use a HashSet, which should have a similar performance to the Dictionary:
var set = new HashSet<string>(
List.Select(x => x.s),
StringComparer.InvariantCultureIgnoreCase);
return arr.Any(set.Contains);

The most efficient way to do so is to create a dictionary based on the collection of structures you have.
var dictionary = list.ToDictionary(item=>item.s.ToLowerInvariant(),item=>item);
Then you could loop through your array of strings (O(n)):
foreach(item in array)
{
S value;
// This is an O(1) operation.
if(dictionary.TryGetValue(item.ToLowerInvariant(), out value)
{
// The TryGetValue returns true if the an item
// in the dictionary found with the specified key, item
// in our case. Otherwise, it returns false.
}
}

You could do something like this
class Program
{
struct MyStruct
{
public string Data { get; set; }
}
static void Main(string[] args)
{
var list = new List<MyStruct>();
list.Add(new MyStruct { Data = "A" });
list.Add(new MyStruct { Data = "B" });
list.Add(new MyStruct { Data = "C" });
var arr = new string[] { "a", "b" };
var result = (from s in list
from a in arr
where s.Data.Equals(a, StringComparison.InvariantCultureIgnoreCase)
select s).ToArray();
}
}

Related

Cast type which i don't know yet what is it

let's say i have list with properties like this:
Type ArrayType
String Value
i've got another list like this:
ArrayType=System.Int32
Value=44
Index=0
ArrayType=System.Int32
Value=11
Index=3
ArrayType=System.Int32
Value=7
Index=2
ArrayType=System.Int32
Value=5
Index=1
All values are in string !
I don't know what type will be it, int32 is only example. there will also be floats, strings, etc.
so... i'm creating new array of type which i readed from "ArrayType" property
dynamic newArray = Array.CreateInstance(typeOfArray, arraySize);
I know arraySize of coure
and the Arrays are created properly.
But now i want to copy all values (which are in string) to my new array:
but i have no idea how to cast it. i've tried something like this:
newArray.SetValue(element.Property.Value,element.Index);
it throw an exception that he can't write OBJECT to my array of ints
so then i've tried to cast in somehow:
newArray[element.Index] = Convert.ChangeType(element.Property.Value,element.Property.Type);
but it still can't cast the object
can someone help :) ?
I just made this code example:
var types = new[] { typeof(int), typeof(float), typeof(double) };
var elements = Enumerable.Range(1, 100)
.Select((value, index) => new Element(types[index % types.Length], value.ToString(), index));
var integers = elements.Where(element => element.ArrayType == typeof(int));
var array = Array.CreateInstance(typeof(int), 100);
Console.WriteLine(array.GetType());
foreach(var element in integers)
{
var value = Convert.ChangeType(element.Value, element.ArrayType);
array.SetValue(value, element.Index);
}
foreach(var value in array)
{
Console.WriteLine(value + " " + value.GetType());
}
Element class:
public class Element
{
public Type ArrayType { get; private set; }
public string Value { get; private set; }
public int Index { get; private set; }
public Element(Type type, string value, int index)
{
ArrayType = type;
Value = value;
Index = index;
}
}
Which just works
Ok, now all works :D thanks for help!
foreach (var element in groupedElement)
{
var newInstance = Activator.CreateInstance(element.Property.Type);
newInstance = element.Property.Value;
newArray.SetValue(Convert.ChangeType(newInstance, element.Property.Type, CultureInfo.InvariantCulture), element.Index);
}

linq - return common items from n number of lists

I'm writing a simple search page for our intranet application. When a user searches for n words I create n lists of hits and then want to return only the results that are common to all lists.
I have something working using List<int> thus:
var list1 = new List<int> {1,2,3,4,5,8,10};
var list2 = new List<int> {3,5,6,9,10,11};
var list3 = new List<int> {3,4,5,10};
var listOfLists = new List<List<int>>{list1,list2,list3};
var results = listOfLists.Aggregate((prevList, nextList) => prevList
.Intersect(nextList)
.ToList());
results.Dump(); //correctly returns 3,5,10, which are common to all lists
However if I try this with my SearchResults class I get no results. Here's the code:
//results from first search word "howard"
List<SearchResult> list1 = new List<SearchResult>();
list1.Add(new SearchResult ("a.html","howard kent"));
list1.Add(new SearchResult ("b.html","howard shaw")); //the common item
list1.Add(new SearchResult ("c.html","howard smith"));
list1.Add(new SearchResult ("d.html","howard moore"));
//results from second search word "shaw"
List<SearchResult> list2 = new List<SearchResult>();
list2.Add(new SearchResult ("e.html","jon shaw"));
list2.Add(new SearchResult ("b.html","howard shaw")); //the common item
list2.Add(new SearchResult ("f.html","chris shaw"));
//could be n further lists...
//make a list of lists
List<List<SearchResult>> searchLists = new List<List<SearchResult>>();
searchLists.Add(list1);
searchLists.Add(list2);
//find results that are common to all lists.
//Doesn't work - returns nil items, should return 1
var results = searchLists
.Aggregate((prevList, nextList) => prevList
.Intersect(nextList)
.ToList());
results.Dump();
}
class SearchResult
{
public string Url{get;set;}
public string SearchText { get; set; }
//constructor
public SearchResult(string url,string searchText)
{
Url = url;
SearchText = searchText;
}
How should I change the query to return the result I want?
That's because the SearchResult objects are not the same objects, although they have the same data. You can fix this by implementing the IEquatable<T> interface. http://msdn.microsoft.com/en-us/library/ms131187(v=vs.110).aspx
Ie.
public class SearchResult : IEquatable<SearchResult>
{
public string Url{get;set;}
public string SearchText { get; set; }
public bool Equals(SearchResult other)
{
if (other == null)
return false;
return string.Concat(this.Url, this.SearchText).Equals(string.Concat(other.Url, other.SearchText), StringComparison.OrdinalIgnoreCase);
}
}
Another solution would be to implement a IEqualityComparer<T> and pass it on to the Enumerable.Intersect method.
You can use Enumberable.Intersect to get the common items. But in your case you are comparing two object. So you need to implement the IEqualityComparer Interface and create your own comparer class.
I am assuming your SearchResult class like this
public class SearchResult
{
public SearchResult(string first, string second)
{
First = first;
Second = second;
}
public string First { get; set; }
public string Second { get; set; }
}
Now the IEqualityComparer interface implementation.
public class MyCompare : IEqualityComparer<SearchResult>
{
public bool Equals(SearchResult x, SearchResult y)
{
return (x.First == y.First) && ((x.Second == y.Second));
}
public int GetHashCode(SearchResult obj)
{
unchecked
{
int hash = 17;
hash = hash * 23 + obj.First.GetHashCode();
hash = hash * 23 + obj.Second.GetHashCode();
return hash;
}
}
}
The important point is within the GetHashCode where we need to incorporate both the properties instead of one. If we were comparing single property then it would be simple like this
return obj.A.GetHashCode();
But here we are comparing multiple properties.So that we need to change its implementation. Thanks to #Tim to their post
Now you can use the Intersect method to get the common items like this
var common = list1.Intersect<SearchResult>(list2,new MyCompare()).ToList();

Lambda expression not behaving as expected

I'm trying to construct a lambda expression that will match elements of one array with a second. Below is a simplified version of this query:
class Program
{
static void Main(string[] args)
{
string[] listOne = new string[] { "test1", "test2", "test3" };
MyClass[] listTwo = new MyClass[] { new MyClass("test1") };
string[] newVals = listOne.Where(p => listTwo.Select(e => e.Name).Equals(p)).ToArray();
//string[] newVals2 = listOne.Intersect(listTwo.Select(t => t.Name)).ToArray();
}
class MyClass
{
public MyClass(string name)
{
Name = name;
}
public string Name {get; set;}
}
}
I would expect newVals to return an array of 1 value, but it's empty. I realise that uncommenting myVals2 would achieve the same result, but the lists of classes differ more fundamentally than shown.
You are using Equals but you should use Contains. You are checking wheter IEnumerable<> is equal to p, but you want to check if IEnumerable<> contains p, so replace:
string[] newVals = listOne.
Where(p => listTwo.Select(e => e.Name).Equals(p)).
ToArray();
with
string[] newVals = listOne.
Where(p => listTwo.Select(e => e.Name).Contains(p)).
ToArray();
Try this:
string[] listOne = new string[] { "test1", "test2", "test3" };
MyClass[] listTwo = new MyClass[] { new MyClass("test1") };
string[] newVals = listOne
.Where(p => listTwo.Select(e => e.Name).Contains(p))
.ToArray();
listTwo.Select(e => e.Name) is a IEnumerable<string>
You probably want to perform a Join on the 2 collections.
var q =
listOne
.Join(
listTwo,
l2 => l2,
l1 => l1.Name,
(l2, l1) => new { l2, l1, });
You can change the selector (the last parameter) to suit your needs, if it's just values from listOne for example then have (l2, l1) => l1.
The other solutions will work, but maybe not as you would expect.
Using Linq-Objects Contains within a where clause will cause the entire of listTwo to be iterated for each entry in listOne.
How about something like this:
string[] newVals = listOne.Where(p => listTwo.Any(e => e.Name.Contains(p))).ToArray();
or to be more strict use == instead of Contains.
But if you want to obtain the items that are common between the 2 why not just call .Intersect()??
You are trying to perform a join, technically you are better off simplifying your linq statement to use a join. An example is included below.
static void Main(string[] args)
{
string[] listOne = new [] { "test1", "test2", "test3" };
MyClass[] listTwo = new [] { new MyClass("test1") };
string[] newVals = (from str1 in listOne
join str2 in listTwo.Select(e => e.Name) on str1 equals str2
select str1).ToArray();
foreach (var newVal in newVals)
{
Console.WriteLine(newVal);
}
//string[] newVals2 = listOne.Intersect(listTwo.Select(t => t.Name)).ToArray();
}
class MyClass
{
public MyClass(string name)
{
Name = name;
}
public string Name { get; set; }
}

Do a correct union on two List<SelectListItem>

retval.AddRange(oldList.Union(newList));
Both the oldList and newList are declared here
List<SelectListItem> oldList = new List<SelectListItem>();
List<SelectListItem> newList = new List<SelectListItem>();
I want to union these two lists, removing duplicate items that have the same "text" property. If there is a dupe between newList and oldList on the text property, it should keep the text/value pair of the newList.
The above union doesn't seem to be doing anything besides concat'ing the lists almost, don't know why.
What am I doing wrong?
I want to union these two lists, removing duplicate items that have the same "text" property.
The Union method will not handle this requirement. You could do this via:
retVal.AddRange(newList); // Since you want to keep all newList items, do this first
// Add in all oldList items where there isn't a match in new list
retVal.AddRange(oldList.Where(i => !newList.Any(n => i.Text == n.Text)));
From reading the documentation it appears that A.Union(B) in effect just adds to A those items from B that aren't already in A. That is:
When the object returned by this
method is enumerated, Union enumerates
first and second in that order and
yields each element that has not
already been yielded.
A quick test bears that out. So oldList.Union(newList) will keep the old values whereas newList.Union(oldList) will give you the new values.
Here's my test code:
class MyThing
{
public string Text { get; private set; }
public int Version { get; private set; }
public MyThing(string t, int v)
{
Text = t;
Version = v;
}
public override int GetHashCode()
{
return Text.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj == null)
return false;
MyThing other = obj as MyThing;
if (other == null)
return false;
return this.Text.Equals(other.Text);
}
}
static List<MyThing> oldList = new List<MyThing>()
{
new MyThing("Foo", 0),
new MyThing("Bar", 0),
new MyThing("Fooby", 0),
};
static List<MyThing> newList = new List<MyThing>()
{
new MyThing("Barby", 1),
new MyThing("Bar", 1)
};
static void DoIt()
{
var unionOldNew = oldList.Union(newList);
Console.WriteLine("oldList.Union(newList)");
foreach (var t in unionOldNew)
{
Console.WriteLine("{0}, {1}", t.Text, t.Version);
}
Console.WriteLine();
var unionNewOld = newList.Union(oldList);
Console.WriteLine("newList.Union(oldList)");
foreach (var t in unionNewOld)
{
Console.WriteLine("{0}, {1}", t.Text, t.Version);
}
}

convert a list of objects from one type to another using lambda expression

I have a foreach loop reading a list of objects of one type and producing a list of objects of a different type. I was told that a lambda expression can achieve the same result.
var origList = List<OrigType>(); // assume populated
var targetList = List<TargetType>();
foreach(OrigType a in origList) {
targetList.Add(new TargetType() {SomeValue = a.SomeValue});
}
Try the following
var targetList = origList
.Select(x => new TargetType() { SomeValue = x.SomeValue })
.ToList();
This is using a combination of Lambdas and LINQ to achieve the solution. The Select function is a projection style method which will apply the passed in delegate (or lambda in this case) to every value in the original collection. The result will be returned in a new IEnumerable<TargetType>. The .ToList call is an extension method which will convert this IEnumerable<TargetType> into a List<TargetType>.
If you know you want to convert from List<T1> to List<T2> then List<T>.ConvertAll will be slightly more efficient than Select/ToList because it knows the exact size to start with:
target = orig.ConvertAll(x => new TargetType { SomeValue = x.SomeValue });
In the more general case when you only know about the source as an IEnumerable<T>, using Select/ToList is the way to go. You could also argue that in a world with LINQ, it's more idiomatic to start with... but it's worth at least being aware of the ConvertAll option.
var target = origList.ConvertAll(x => (TargetType)x);
List<target> targetList = new List<target>(originalList.Cast<target>());
I believe something like this should work:
origList.Select(a => new TargetType() { SomeValue = a.SomeValue});
Here's a simple example..
List<char> c = new List<char>() { 'A', 'B', 'C' };
List<string> s = c.Select(x => x.ToString()).ToList();
var list1 = new List<Type1>();
var list2 = new List<Type2>();
list1.ForEach(item => list2.Add(new Type2() { Prop1 = value1 }));
Assume that you have multiple properties you want to convert.
public class OrigType{
public string Prop1A {get;set;}
public string Prop1B {get;set;}
}
public class TargetType{
public string Prop2A {get;set;}
public string Prop2B {get;set;}
}
var list1 = new List<OrigType>();
var list2 = new List<TargetType>();
list1.ConvertAll(x => new OrigType { Prop2A = x.Prop1A, Prop2B = x.Prop1B })
Or with a constructor & linq with Select:
public class TargetType {
public string Prop1 {get;set;}
public string Prop1 {get;set;}
// Constructor
public TargetType(OrigType origType) {
Prop1 = origType.Prop1;
Prop2 = origType.Prop2;
}
}
var origList = new List<OrigType>();
var targetList = origList.Select(s=> new TargetType(s)).ToList();
The Linq line is more soft! ;-)
If you need to use a function to cast:
var list1 = new List<Type1>();
var list2 = new List<Type2>();
list2 = list1.ConvertAll(x => myConvertFuntion(x));
Where my custom function is:
private Type2 myConvertFunction(Type1 obj){
//do something to cast Type1 into Type2
return new Type2();
}
for similar type class.
List<targetlist> targetlst= JsonConvert.DeserializeObject<List<targetlist>>(JsonConvert.SerializeObject(<List<baselist>));
If the types can be directly cast this is the cleanest way to do it:
var target = yourList.ConvertAll(x => (TargetType)x);
If the types can't be directly cast then you can map the properties from the orginal type to the target type.
var target = yourList.ConvertAll(x => new TargetType { SomeValue = x.SomeValue });
If casting when mapping from one list to another is required, from convertall, you can call a function to test the casting.
public int StringToInt(String value)
{
try
{
return Int32.Parse(value);
}
catch (Exception ex)
{
return -1;
}
}
[Fact]
public async Task TestConvertAll()
{
List<String> lstString = new List<String>{"1","2","3","4","5","6","7","8","9","10" };
List<int> lstInt = lstString.ConvertAll(new Converter<String, int>(StringToInt));
foreach (var item in lstInt)
{
output.WriteLine("{0}", item);
}
if (lstInt.Count()>0) {
Assert.True(true);
}
}
We will consider first List type is String and want to convert it to Integer type of List.
List<String> origList = new ArrayList<>(); // assume populated
Add values in the original List.
origList.add("1");
origList.add("2");
origList.add("3");
origList.add("4");
origList.add("8");
Create target List of Integer Type
List<Integer> targetLambdaList = new ArrayList<Integer>();
targetLambdaList=origList.stream().map(Integer::valueOf).collect(Collectors.toList());
Print List values using forEach:
targetLambdaList.forEach(System.out::println);

Categories

Resources