Linq Help for List - c#

I have a collection of object type A. I was wondering if I can create another collection, comprising of sub sets of A, such that if A[i].Something == 'a' && A[i+1].Something == 'b', then add it to new collection.
The new collection would be a List of KeyValue pairs such that (Key = A[i], Value = A[i+1])
I wanted to accomplish this using lambda exp. Could someone guide me ?

Since standard Linq doesn't support Lead (Lag) methods (have a look at More Linq if you insist on Linq-like solution), I suggest implementing a simple generator:
private static IEnumerable<KeyValue<MyClass, MyClass>> MakePairs(
IEnumerable<MyClass> source) {
if (null == source)
throw new ArgumentNullException("source");
MyClass prior = default(MyClass);
bool first = true;
foreach (var current in source) {
if (first) {
prior = current;
first = false;
continue;
}
if (prior != null && current != null &&
prior.Something == "A" && current.Something == "B") //TODO: put right condition
yield return new KeyValue(prior, current);
prior = current;
}
}
...
IEnumerable<MyClass> source = ...
var result = MakePairs(source).ToList();

Another way to get the key/value pairs is to zip the collection with all items except the first. Should theoretically work on any enumerable which preserves order. If 'coll' is your source:
coll.Zip(coll.Skip(1), (a1,a2) => new {Key = a1.Something, Value = a2.Something})
To get only for values 'a' and 'b':
coll.Zip(coll.Skip(1), (a1,a2) => new {Key = a1.Something, Value = a2.Something})
.Where(kv=>kv.Key == "a" && kv.Value == "b")

Would this work?
IEnumerable<string> list;
IEnumerable<string> list2 = list.Skip(1);
string test1 = "a";
string test2 = "b";
var result = list
.Zip(list.Skip(1),
(x, y) => Tuple.Create(x, y))
.Where(r => r.Item1 == test1 && r.Item2 == test2)
.ToDictionary(r => r.Item1,
r => r.Item2);

You can use Select, which has an overload to get the index, which is useful in this case to retrieve the next item in your list.
var newCollection = collection.Select
( (a, i) => new
{ A = a
, NextA = (i + 1) < collection.Length ? collection[i + 1] : null
}
);
From there on you can write the predicate you want:
var filteredCollection = newCollection.Where
(x => x.A.Something == "a"
&& x.NextA?.Something == "b"
);

OP has a collection so I started out with an ICollection:
public static IEnumerable<KeyValuePair<A, A>> KeyValueSelecting(ICollection<A> source) {
if (null == source) { throw new ArgumentNullException(nameof(source)); }
for (var i = 0; i < source.Count - 1; i++) {
var firstElement = source.ElementAtOrDefault(i);
if (firstElement?.Something != "A") { yield break; }
var seceondElement = source.ElementAtOrDefault(i + 1);
if (seceondElement?.Something != "B") { yield break; }
yield return new KeyValuePair<A, A>(firstElement, seceondElement);
}
}

Related

Order a list of Sorted Set of Strings by Count and then by strings

I have a custom class called PairString
public class PairString: IComparer<PairString>
{
public string first;
public string second;
public PairString(string f, string s)
{
first = f;
second = s;
}
public int Compare([AllowNull] PairString x, [AllowNull] PairString y)
{
if (x == null || y == null) return -1;
var f = string.Compare(x.first, y.first);
var s = string.Compare(x.second, y.second);
return f == s ? s : f;
}
}
I want to create groups by count and then by lexical order of strings in that groups, from a list of input PairString List. Below method does the grouping right. But when I try to sort the groups in lexical order for equal count groups, it throws "Atleast one object must implement IComparer error"
public static List<string> MaxItemAssociatoinGroup(List<PairString> input)
{
if (input == null || input.Count == 0) return null;
List<SortedSet<string>> output = new List<SortedSet<string>>();
foreach (var item in input)
{
if (output.Any(x => x.Contains(item.first) || x.Contains(item.second)))
{
//Take the set containing one or two or both items
var set1 = output.FirstOrDefault(x => x.Contains(item.first));
var set2 = output.FirstOrDefault(x => x.Contains(item.second));
if (set1 == null)
set2.UnionWith(new SortedSet<string> { item.first, item.second });
else if (set2 == null)
set1.UnionWith(new SortedSet<string> { item.first, item.second });
else if (set1 != set2)
{
set1.UnionWith(set2);
output.Remove(set2);
}
}
else
output.Add(new SortedSet<string>(new List<string>() { item.first, item.second }));
}
var maxlistAssociation = output.OrderByDescending(x => x.Count).First();
return new List<string>(maxlistAssociation);
}
I am not sure how to achieve lexical order for same count groups,
Sample input is
new PairString("item3","item4"),
new PairString("item3","item6"),
new PairString("item5","item6"),
new PairString("item2","item8"),
new PairString("item8","item9"),
new PairString("item1","item2")
it groups into 2 groups of equal count {item3,item4,item5,item6} & {item1,item2,item8,item9} but returns {item3,item4,item5,item6} as its first in the list. but I want the second group as it contains the item that lexicographically first than first group. what am I missing here?
It appears that you're missing a method that will compare two SortedSet<string> objects and return the one which comes first lexically. One way to do this is to compare each item from one set with the corresponding one in the other set, and return the first non-equal comparison:
public class SortedSetComparer<T> : IComparer<SortedSet<T>> where T : IComparable<T>
{
public int Compare(SortedSet<T> x, SortedSet<T> y)
{
// Null checks
if (x == null) return y == null ? 0 : 1;
if (y == null) return -1;
var minCount = Math.Min(x.Count, y.Count);
// Compare each item from one set with the corresponding one in the other set
for (var i = 0; i < minCount; i++)
{
var result = x.ElementAt(i).CompareTo(y.ElementAt(i));
// Return the first non-equal result
if (result != 0) return result;
}
// If all the items were equal, return the comparison of the Count
return x.Count.CompareTo(y.Count);
}
}
Then we can order our results (after sorting by size) by passing an instance of this class to the ThenBy method:
var maxlistAssociation = output
.OrderByDescending(x => x.Count)
.ThenBy(x => x, new SortedSetComparer<string>())
.First();
Depending on the behavior you want from this method, we could also incorporate the ordering by Count into our comparison method, so that it puts the sets with the most items first, then sorts them alphabetically:
public class SortedSetComparer<T> : IComparer<SortedSet<T>> where T : IComparable<T>
{
public int Compare(SortedSet<T> x, SortedSet<T> y)
{
// Null checks
if (x == null) return y == null ? 0 : 1;
if (y == null) return -1;
// Compare the counts first, in descending order
var countComparison = x.Count.CompareTo(y.Count);
if (countComparison != 0) return countComparison * -1;
// Then compare each item from one set lecially
// with the corresponding one in the other set
return x.Select((item, index) =>
x.ElementAt(index).CompareTo(y.ElementAt(index)))
.FirstOrDefault(result => result != 0);
}
}
And now we only need one OrderBy clause:
var maxlistAssociation = output
.OrderBy(x => x, new SortedSetComparer<string>())
.First();

Mapping a list of ints into a list of MinMax ranges with LINQ [duplicate]

When i have a list
IList<int> list = new List<int>();
list.Add(100);
list.Add(200);
list.Add(300);
list.Add(400);
list.Add(500);
What is the way to extract a pairs
Example : List elements {100,200,300,400,500}
Expected Pair : { {100,200} ,{200,300} ,{300,400} ,{400,500} }
The most elegant way with LINQ: list.Zip(list.Skip(1), Tuple.Create)
A real-life example: This extension method takes a collection of points (Vector2) and produces a collection of lines (PathSegment) needed to 'join the dots'.
static IEnumerable<PathSegment> JoinTheDots(this IEnumerable<Vector2> dots)
{
var segments = dots.Zip(dots.Skip(1), (a,b) => new PathSegment(a, b));
return segments;
}
This will give you an array of anonymous "pair" objects with A and B properties corresponding to the pair elements.
var pairs = list.Where( (e,i) => i < list.Count - 1 )
.Select( (e,i) => new { A = e, B = list[i+1] } );
You can use a for loop:
var pairs = new List<int[]>();
for(int i = 0; i < list.Length - 1; i++)
pairs.Add(new [] {list[i], list[i + 1]);
You can also use LINQ, but it's uglier:
var pairs = list.Take(list.Count - 1).Select((n, i) => new [] { n, list[i + 1] });
EDIT: You can even do it on a raw IEnumerable, but it's much uglier:
var count = list.Count();
var pairs = list
.SelectMany((n, i) => new [] { new { Index = i - 1, Value = n }, new { Index = i, Value = n } })
.Where(ivp => ivp.Index >= 0 && ivp.Index < count - 1) //We only want one copy of the first and last value
.GroupBy(ivp => ivp.Index, (i, ivps) => ivps.Select(ivp => ivp.Value));
More general would be:
public static IEnumerable<TResult> Pairwise<TSource, TResult>(this IEnumerable<TSource> values, int count, Func<TSource[], TResult> pairCreator)
{
if (count < 1) throw new ArgumentOutOfRangeException("count");
if (values == null) throw new ArgumentNullException("values");
if (pairCreator == null) throw new ArgumentNullException("pairCreator");
int c = 0;
var data = new TSource[count];
foreach (var item in values)
{
if (c < count)
data[c++] = item;
if (c == count)
{
yield return pairCreator(data);
c = 0;
}
}
}
Following solution uses zip method. Zip originalList and originalList.Skip(1) so that one gets desired result.
var adjacents =
originalList.Zip(originalList.Skip(1),
(a,b) => new {N1 = a, N2 = b});
Using .Windowed() from MoreLINQ:
var source = new[] {100,200,300,400,500};
var result = source.Windowed(2).Select(x => Tuple.Create(x.First(),x.Last()));
Off the top of my head and completely untested:
public static T Pairwise<T>(this IEnumerable<T> list)
{
T last;
bool firstTime = true;
foreach(var item in list)
{
if(!firstTime)
return(Tuple.New(last, item));
else
firstTime = false;
last = item;
}
}

Lambda expression or Linq to fetch filtered result out of keyvaluepair from list?

I don't know how to write expression/query to fetch the result from 2 level deep List containing KeyValuePair<object, object>
For example:
IList<ITaskData> taskDataList //1st Level
here IList contains collection of ITaskData and ITaskData contains
IList<KeyValuePair<object, object>> TaskParams { get; set; } //2nd Level
So suppose TaskParams have below key value pairs
Key : Location
Values: Stockroom, Salesfloor
Key : Iteration
Values : 1, 2
So, I need to fetch the List of TaskData which contains TaskParams values Stockroom and 1.
I can do easily by foreach loop but I wanted to use Linq / Lambda which is one liner and more easily maintainable.
Thanks a lot for support. Please let me know if you need more clarification.
Working code by foreach loop: I am getting desire output in taskDataListType1
IList<ITaskData> taskDataListType1 = new List<ITaskData>();
IList<KeyValuePair<object, object>> taskParams = null;
bool iteration = false;
bool location = false;
foreach (ITaskData taskData in taskDataList)
{
taskParams = taskData.TaskParams;
if (taskParams != null)
{
foreach (KeyValuePair<object, object> keyValuePair in taskParams)
{
if (keyValuePair.Key.ToString().Equals("ITERATION", StringComparison.OrdinalIgnoreCase))
{
if (int.Parse(keyValuePair.Value.ToString()) == 1)
{
iteration = true;
}
}
else if (keyValuePair.Key.ToString().Equals("LOCATION", StringComparison.OrdinalIgnoreCase))
{
if (keyValuePair.Value.ToString() == "StockRoom")
{
location = true;
}
}
if (iteration == true && location == true)
{
taskDataListType1.Add(taskData);
}
}
}
}
Strange but if I put below logic its not working I mean I am not getting any values in tasks1
IList<ITaskData> taskDataListType1 = new List<ITaskData>();
foreach (TaskData td in taskDataList)
{
var tasks1 = taskParams.Where(kvp => kvp.Key != null
&& kvp.Value != null
&& kvp.Key.ToString().Equals("LOCATION", StringComparison.OrdinalIgnoreCase)
&& kvp.Value.ToString() == "StockRoom"
&& kvp.Key.ToString().Equals("ITERATION", StringComparison.OrdinalIgnoreCase)
&& int.Parse(kvp.Value.ToString()) == 1
);
}
Output of above query is screenshot below:
If you insist on using object in KeyValuePair, then your example would look like this:
IList<ITaskData> taskDataList = new List<ITaskData>
{
new ITaskData
{
TaskParams = new List<KeyValuePair<object,object>>
{
new KeyValuePair<object, object>("Location", "Stockroom"),
new KeyValuePair<object, object>("Location", "Salesfloor"),
new KeyValuePair<object, object>("Iteration", 1),
new KeyValuePair<object, object>("Iteration", 2)
}
},
new ITaskData
{
TaskParams = new List<KeyValuePair<object,object>>
{
new KeyValuePair<object, object>("Location", "Stockroom"),
new KeyValuePair<object, object>("Location", "Salesfloor"),
new KeyValuePair<object, object>("Iteration", 101),
new KeyValuePair<object, object>("Iteration", 2)
}
}
};
var result = taskDataList.Where(td =>
td.TaskParams.Any(tp => ((string)tp.Key == "Location") && ((string)tp.Value == "Stockroom")) &&
td.TaskParams.Any(tp => (string)tp.Key == "Iteration" && (int)tp.Value == 1)
);
As you can see, you need to cast object to an exact type, so this approach is very error-prone, and can easily cause run-time exceptions if you key,value collection will have items with type different from what you expect.
If you need to filter by location or iteration, define them as properties inside your TaskParams class, then your query will become more clear, strongly typed and less error-prone. See the example below:
public class TaskParamsType
{
public IList<string> Locations;
public IList<int> Iterations;
}
public class ITaskDataNew
{
public TaskParamsType TaskParams { get; set; }
}
var result = taskDataList.Where(td =>
td.TaskParams.Locations.Contains("Stockroom") &&
td.TaskParams.Iterations.Contains(1)
);
Try this:
var results =
taskDataList
.Where(td => td.TaskParams != null)
.Where(td =>
td.TaskParams.Any(kvp =>
kvp.Key != null
&& kvp.Key.ToString().Equals("LOCATION", StringComparison.OrdinalIgnoreCase)
&& kvp.Value != null
&& kvp.Value.Equals("Stockroom"))
&& td.TaskParams.Any(kvp =>
kvp.Key != null
&& kvp.Key.ToString().Equals("ITERATION", StringComparison.OrdinalIgnoreCase)
&& kvp.Value != null
&& kvp.Value.Equals(1)))
.ToList();
I have tested this code against this data:
IList<ITaskData> taskDataList = new List<ITaskData>();
var taskData = new TaskData();
taskData.TaskParams.Add(new KeyValuePair<object, object>("Location", "Stockroom"));
taskData.TaskParams.Add(new KeyValuePair<object, object>("Location", "Salesfloor"));
taskData.TaskParams.Add(new KeyValuePair<object, object>("Iteration", 1));
taskData.TaskParams.Add(new KeyValuePair<object, object>("Iteration", 2));
taskDataList.Add(taskData);
Let's suppose you have the following code which returns a List<KeyValuePair<object, object>> matching the logical condition:
public class ITaskData
{
public List<KeyValuePair<object, object>> keyValuePairs { get; set; }
}
class Program
{
private static List<ITaskData> list = new List<ITaskData>();
private static void Main()
{
List<KeyValuePair<object, object>> result = new List<KeyValuePair<object, object>>();
foreach (var a in list)
foreach (var b in a.keyValuePairs)
if (b.Value.ToString().Contains("Stockroom")) result.Add(b);
// Here I make .ToString().Contains("Stockroom")
// You can add any required logics here
}
}
You can make it in LINQ:
List<KeyValuePair<object, object>> result =
(from a in list
from b in a.keyValuePairs
where b.Value.ToString().Contains("Stockroom")
select b)
.ToList();
Or in LINQ method chain:
List<KeyValuePair<object, object>> result =
(list
.SelectMany(a => a.keyValuePairs, (a, b) => new {a, b})
.Where(t => t.b.Value.ToString().Contains("Stockroom"))
.Select(t => t.b)
).ToList();
However, in my private opinion, in your case the solution with foreachs looks more elegant and readable.
Of course, this code will throw a NullReferenceException as keyValuePairs are not initialized. I don't initialize it as it is an example and you have your own ITaskData class with proper initialization.
It should be something like this:
var tasks = taskDataList.Where(
i => i.TaskParams.Any(x => x.Key == "Location" && x.Value.Contains("Stockroom")) &&
i.TaskParams.Any(x => x.Key == "Iteration" && x.Values.Contains(2)));
The above code is just to explain the logic. You need to cast the object to the right type (if you know them) or user another comparison method.

How to use LINQ to query a subset of string

Suppose I have an unordered List<String> named letters:
letters.Add("d.pdf");
letters.Add("a.pdf");
letters.Add("c.pdf");
letters.Add("b.pdf");
letters.Add("e.pdf");
letters.Add("f.pdf");
I want to order that list alphabetically and then, take the elements between b and d (including the endpoints), so this will return a new list like {"b.pdf","c.pdf","d.pdf"}.
Simple foreach loop solution:
var letters = new List<string> {"d", "a", "c", "b", "e"};
letters.Sort();
var start = "b";
var end = "d";
var r = new List<string>();
foreach (var l in letters)
{
if (l.CompareTo(start) >= 0 && l.CompareTo(end) <= 0)
{
r.Add(l);
}
}
another simple solution:
var letters = new List<string> {"d", "a", "c", "b", "e"};
letters.Sort();
var startIndex = letters.IndexOf(letters.Find(l=> l.CompareTo(start) == 0));
var endIndex = letters.IndexOf(letters.Find(l => l.CompareTo(end) == 0));
var r = letters.GetRange(startIndex, endIndex - startIndex + 1);
If you use TakeWhile, this solution should work, this is more Linq like. The main problem would be to have the starting element in the first index of collection, otherwise TakeWhile won't work.
var letters = new List<string> { "d", "a", "c", "b", "e" };
letters.Sort();
var start = "b";
var end = "d";
var startIndex = letters.IndexOf(letters.Find(l=> l.CompareTo(start) == 0));
var r = letters.GetRange(startIndex, letters.Count - startIndex)
.TakeWhile(l => l.CompareTo(start) >= 0 && l.CompareTo(end) <= 0).ToList();
I'm not sure there is something built-in that would support this easily, but with the addition of a new extension method, you can do it really easily:
void Main()
{
var letters = new List<string>();
letters.Add("d.pdf");
letters.Add("a.pdf");
letters.Add("c.pdf");
letters.Add("b.pdf");
letters.Add("e.pdf");
var results = letters
.OrderBy(x => x)
.SkipWhile(x => x != "b.pdf")
.TakeTo(x => x == "d.pdf")
.ToList();
}
static class Extensions
{
public static IEnumerable<TValue> TakeTo<TValue>(this IEnumerable<TValue> source, Func<TValue, bool> predicate)
{
bool predicateReached = false;
foreach (var value in source)
{
yield return value;
predicateReached = predicate(value);
if (predicateReached) yield break;
}
}
}
The TakeTo extension method works similar to the TakeWhile extension, except it will yield all values until and including the first value which matches the given predicate function.
This should work in your case:
letters.OrderBy(x => x).TakeWhile(x => x != "e.pdf");
If you want to generalize it you can write an extension method:
public static IEnumerable<TSource> GetRange<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> start,
Func<TSource, bool> end)
{
int counter = 0;
foreach (var item in source)
{
if (start(item) || end(item))
{
yield return item;
counter++;
if(counter == 2) yield break;
}else if (counter != 0) yield return item;
}
}
Then use it:
letters.OrderBy(x => x).GetRange(x => x == "b.pdf", x => x == "d.pdf");
Or you can do that without using LINQ, List<T> has also a GetRange method:
letters.Sort();
int startIndex = letters.IndexOf("b.pdf");
int endIndex = letters.IndexOf("d.pdf");
var result = letters.GetRange(startIndex, endIndex - startIndex +1);

Linq Conditional .Any() Select

How can I perform a conditional select on a column value, where I have a preference over which value is returned. If I can't find the top choice, I settle on the next, if available, and then if not the next, etc. As it looks right now, it would take 3 total queries. Is there a way to simplify this further?
var myResult = string.Empty;
if (myTable.Where(x => x.ColumnValue == "Three").Any())
{
myResult = "Three"; // Can also be some list.First().Select(x => x.ColumnValue) if that makes it easier;
}
else if (myTable.Where(x => x.ColumnValue == "One").Any())
{
myResult = "One";
}
else if (myTable.Where(x => x.ColumnValue == "Two").Any())
{
myResult = "Two";
}
else
{
myResult = "Four";
}
You could use a string[] for your preferences:
string[] prefs = new[]{ "One", "Two", "Three" };
string myResult = prefs.FirstOrDefault(p => myTable.Any(x => x.ColumnValue == p));
if(myResult == null) myResult = "Four";
Edit Enumerable.Join is a very efficient hash table method, it also needs only one query:
string myResult = prefs.Select((pref, index) => new { pref, index })
.Join(myTable, xPref => xPref.pref, x => x.ColumnValue, (xPref, x) => new { xPref, x })
.OrderBy(x => x.xPref.index)
.Select(x => x.x.ColumnValue)
.DefaultIfEmpty("Four")
.First();
Demo
I wrote an extension method that effectively mirrors Tim Schmelter's answer (was testing this when he posted his update. :-()
public static T PreferredFirst<T>(this IEnumerable<T> data, IEnumerable<T> queryValues, T whenNone)
{
var matched = from d in data
join v in queryValues.Select((value,idx) => new {value, idx}) on d equals v.value
orderby v.idx
select new { d, v.idx };
var found = matched.FirstOrDefault();
return found != null ? found.d : whenNone;
}
// usage:
myResult = myTable.Select(x => x.ColumnValue)
.PreferredFirst(new [] {"Three", "One", "Two"}, "Four");
I've written one that will quit a little more early:
public static T PreferredFirst<T>(this IEnumerable<T> data, IList<T> orderBy, T whenNone)
{
// probably should consider a copy of orderBy if it can vary during runtime
var minIndex = int.MaxValue;
foreach(var d in data)
{
var idx = orderBy.IndexOf(d);
if (idx == 0) return d; // best case; quit now
if (idx > 0 && idx < minIndex) minIndex = idx;
}
// return the best found or "whenNone"
return minIndex == int.MaxValue ? whenNone : orderBy[minIndex];
}
I use a weighted approach in SQL where I assign a weight to each conditional value. The solution would then be found by finding the highest or lowest weight depending on your ordering scheme.
Below would be the equivalent LINQ query. Note that in this example I am assigning a lower weight a higher priority:
void Main()
{
// Assume below list is your dataset
var myList =new List<dynamic>(new []{
new {ColumnKey=1, ColumnValue ="Two"},
new {ColumnKey=2, ColumnValue ="Nine"},
new {ColumnKey=3, ColumnValue ="One"},
new {ColumnKey=4, ColumnValue ="Eight"}});
var result = myList.Select(p => new
{
ColVal = p.ColumnValue,
OrderKey = p.ColumnValue == "Three" ? 1 :
p.ColumnValue == "One" ? 2 :
p.ColumnValue == "Two" ? 3 : 4
}).Where(i=> i.OrderKey != 4)
.OrderBy(i=>i.OrderKey)
.Select(i=> i.ColVal)
.FirstOrDefault();
Console.WriteLine(result ?? "Four");
}
How about something like this:
var results = myTable.GroupBy(x => x.ColumnValue).ToList();
if (results.Contains("Three")) {
myResult = "Three";
} else if (results.Contains("One")) {
myResult = "One";
} else if (results.Contains("Two")) {
myResult = "Two";
} else {
myResult = "Four";
}

Categories

Resources