Compare value to array of strings using StartsWith - c#

I have an array:
string[] exceptions = new string[] { "one", two", "one_1", "three" };
.. I want to be able to say:
var result = from c in myCollection
where not c.Property[3].Value.StartWith(exceptions)
select c;
So I want myCollection to be filtered to only show those records whose Property[3].Value does not StartWith a value in the exceptions array. I know StartsWith doesn't take a collection so I'm unsure if this is possible via LINQ or not.
Is this possible in LINQ?! Or am I trying to shoehorn my problem into a LINQ solution?
EDIT: I should say, Contains is not an option since I only want to exclude elements whose property startswith the exception string.

var result = myCollection.Where(c =>
exceptions.All(e =>
!c.Property[3].Value.StartsWith(e));

Try this:
string[] exceptions = new string[] { "one", "two", "one_1", "three" };
var result = from c in myCollection
where !exceptions.Any(exception =>
c.Property[3].Value.StartsWith(exception))
select c;

You could use IndexOfAny (and check result is index position zero) as that takes a collection.

You can select the collection of item you don't want then do a IEnumerable.Except().
I should look like this :
var result = from c in myCollection
where not c.Property[3].Value.StartWith(exceptions)
select c;
var finalResult = myCollection.Except(myCollection.Select(i => i.StartWith(exception)));

var result = myCollection
.where(
rs=>exceptions
.where(
rs1=>!rs.property[3].value.startsWith(rs1)
)
)

Related

Concatenating strings in two lists to create a third list

I have two lists of items, can you please guide me how I can concatenate values of both and add concatenated value into third list as a value.
For example if List<string> From has A,B,C and List<string> To has 1,2,3 then List<string> All should have A1,B2,C3. I'd preferably like to use a lambda expression.
Use Linq's Zip extension method:
using System.Linq;
...
var list1 = new List<string> { "A", "B", "C" };
var list2 = new List<string> { "1", "2", "3" };
var list3 = list1.Zip(list2, (x, y) => x + y).ToList(); // { "A1", "B2", "C3" }
That's not concatenation - that's matching two sequences pairwise. You do it with LINQ's Zip method:
Zip applies a specified function to the corresponding elements of two sequences, producing a sequence of the results.
var res = from.Zip(to, (a,b) => a + b).ToList();
If item's count are equal in both lists then you can do:
var list3 = list1.Select((item, index) => item + list2[index]).ToList();

How do I access things returned by GroupBy

Ridiculously simple question that for the life of me I cant figure out. How do I 'get' at the values returned by GroupBy?
Take simple example below. I want to print out the first value that occurs more than once. Looking at the output in the watch window (image below) it sort of suggests that list3[0][0] might get at "one". But it gives me an error.
Note, I'm looking for the general solution - ie understanding what GroupBy returns.
Also, I would like to use the watch window to help me figure out for my self how I would access variables (as I find much of MSDN reference incomprehensible) - is this possible?
var list1 = new List<String>() {
"one", "two", "three", "one", "two"};
var list3 = list1
.GroupBy(x => x)
.Where(x => x.Count() > 1)
.ToList();
Console.WriteLine("list3[0][0]=" + list3[0][0]); //error
While the VS debugger shows you an "index" number because the underlying type is a collection, the grouping is exposed as an IGrouping<T> that does not have an indexer. If you just want the first item in the first group do:
Console.WriteLine("list3[0][0] =" + list3.First().First());
If you want to see all if the items you cam loop through the groupings:
int gi = 0, ii = 0;
foreach(var g in list3)
{
foreach(item i in g)
{
Console.WriteLine("list3[{0}][{1}] = {2}", gi, ii, i);
ii++;
}
gi++;
}
You are looking for the .Key property, as GroupBy returns an IEnumerable containing IGrouping elements.
If you look at the documentation of GroupBy you'll see it returns a IEnumerable<IGrouping<TKey, TSource>>.
IGrouping<TKey,TSource> has a single property Key and itself inherits IEnumerable<TElement>.
So you can enumerate over the list of items returned from a call to GroupBy and each element will have a Key property (which is whatever you grouped by) as well as enumerate each item (which will be the list of items grouped together)
Hopefully this demonstrates a bit clearer. Given a class:
public class Person
{
public string Name{get;set;}
public int Age{get;set;}
}
And a list:
var people = new List<Person>{
new Person{Name="Jamie",Age=35},
new Person{Name="Bob",Age=45},
new Person{Name="Fred",Age=35},
};
Grouping and enumerating as follows:
var groupedByAge = people.GroupBy(x => x.Age);
foreach(var item in groupedByAge)
{
Console.WriteLine("Age:{0}", item.Key);
foreach(var person in item)
{
Console.WriteLine("{0}",person.Name);
}
}
Gives this output:
Age:35
Jamie
Fred
Age:45
Bob
Live example: http://rextester.com/OWPR50756
GroupBy return an IEnumerable<IGrouping<TKey, TSource>> where each IGrouping<TKey, TElement> object contains a sequence of objects and a key it's not a Multidimensional Array which can be accessed by index [][].
To access the first element try this
static void Main(string[] args)
{
var list1 = new List<String>() {
"one", "two", "three", "one", "two"};
var list3 = list1
.GroupBy(x => x)
.Where(x => x.Count() > 1)
.ToList();
Console.WriteLine("list3[0][0]=" + list3[0].ToList()[0].ToString());
//OR Console.WriteLine("list3[0][0]=" + list3[0].First());
}

How to do "nothing" in the else part of ternary operator

I want to filter values of a list based on whether or not they are contained in some other list. If an element is in the list I will select it, else I want to skip it or basically do nothing.
Below is what I tried to do. How can I achieve this?
List<string> sheetNames = new List<string>() {"1","10"};
List<string> projects= new List<string>() {"1","2","3","4","5","6","7"};
IEnumerable<string> result =
sheetNames.Select(x => projects.Contains(x)
? x
: /*Want to do nothing here */);
You can use Enumerable.Intersect method to get the common values from the two lists.
IEnumerable<string> commonValues = projects.Intersect(sheetNames);
List<string> sheetNames = new List<string>() {"1","10"};
List<string> projects= new List<string>() {"1","2","3","4","5","6","7"};
IEnumerable<string> result = sheetNames.Where(x => projects.Contains(x));

List Collection Contains exact string

I have a List Collection and say that i am adding 3 items to them.
list.Add(new ContentDomain() { Id = "1" , Content = "aaa,bbb,ccc,ddd"});
list.Add(new ContentDomain() { Id = "2" , Content = "aa,bb,cc,dd"});
list.Add(new ContentDomain() { Id = "3" , Content = "a,b,c,d"});
Now what i want is to fetch the rows that have just 'a' in the Content attribute.
Like i tried something like
list = list.Where(x => x.Content.ToLower().Contains("a")).ToList();
but that would give me all the three rows.
i want to search in a string for the exact string only.
list.Where(x => x.ToString().ToLower().Split(',').Where(a => a.Trim() == "a").Any()).ToList();
edit: Changed Count() > 0 to Any() for better performance
Convert it to an array of strings, and find the string in the array.
list = list.Where(x => x.Content.ToLower().Split(',').IndexOf("a")>= 0).ToList();
Try this:
IList<ContentDomain> returned = new List<ContentDomain>();
foreach(ContentDomain myList in list)
{
var ret = myList.Content.Split(',');
bool exists = (from val in ret
where val.Contains('a')
select true).FirstOrDefault();
if (exists)
returned.Add(myList);
}

Using lambda expressions to get a subset where array elements are equal

I have an interesting problem, and I can't seem to figure out the lambda expression to make this work.
I have the following code:
List<string[]> list = GetSomeData(); // Returns large number of string[]'s
List<string[]> list2 = GetSomeData2(); // similar data, but smaller subset
List<string[]> newList = list.FindAll(predicate(string[] line){
return (???);
});
I want to return only those records in list in which element 0 of each string[] is equal to one of the element 0's in list2.
list contains data like this:
"000", "Data", "more data", "etc..."
list2 contains data like this:
"000", "different data", "even more different data"
Fundamentally, i could write this code like this:
List<string[]> newList = new List<string[]>();
foreach(var e in list)
{
foreach(var e2 in list2)
{
if (e[0] == e2[0])
newList.Add(e);
}
}
return newList;
But, i'm trying to use generics and lambda's more, so i'm looking for a nice clean solution. This one is frustrating me though.. maybe a Find inside of a Find?
EDIT:
Marc's answer below lead me to experiment with a varation that looks like this:
var z = list.Where(x => list2.Select(y => y[0]).Contains(x[0])).ToList();
I'm not sure how efficent this is, but it works and is sufficiently succinct. Anyone else have any suggestions?
You could join? I'd use two steps myself, though:
var keys = new HashSet<string>(list2.Select(x => x[0]));
var data = list.Where(x => keys.Contains(x[0]));
If you only have .NET 2.0, then either install LINQBridge and use the above (or similar with a Dictionary<> if LINQBridge doesn't include HashSet<>), or perhaps use nested Find:
var data = list.FindAll(arr => list2.Find(arr2 => arr2[0] == arr[0]) != null);
note though that the Find approach is O(n*m), where-as the HashSet<> approach is O(n+m)...
You could use the Intersect extension method in System.Linq, but you would need to provide an IEqualityComparer to do the work.
static void Main(string[] args)
{
List<string[]> data1 = new List<string[]>();
List<string[]> data2 = new List<string[]>();
var result = data1.Intersect(data2, new Comparer());
}
class Comparer : IEqualityComparer<string[]>
{
#region IEqualityComparer<string[]> Members
bool IEqualityComparer<string[]>.Equals(string[] x, string[] y)
{
return x[0] == y[0];
}
int IEqualityComparer<string[]>.GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
#endregion
}
Intersect may work for you.
Intersect finds all the items that are in both lists.
Ok re-read the question. Intersect doesn't take the order into account.
I have written a slightly more complex linq expression that will return a list of items that are in the same position (index) with the same value.
List<String> list1 = new List<String>() {"000","33", "22", "11", "111"};
List<String> list2 = new List<String>() {"000", "22", "33", "11"};
List<String> subList = list1.Select ((value, index) => new { Value = value, Index = index})
.Where(w => list2.Skip(w.Index).FirstOrDefault() == w.Value )
.Select (s => s.Value).ToList();
Result: {"000", "11"}
Explanation of the query:
Select a set of values and position of that value.
Filter that set where the item in the same position in the second list has the same value.
Select just the value (not the index as well).
Note I used:
list2.Skip(w.Index).FirstOrDefault()
//instead of
list2[w.Index]
So that it will handle lists of different lengths.
If you know the lists will be the same length or list1 will always be shorter then list2[w.Index] would probably a bit faster.

Categories