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);
Related
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;
}
}
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);
}
}
I have an array of lists:
var stringLists = new List<string>[]
{
new List<string>(){ "a", "b", "c" },
new List<string>(){ "d", "b", "c" },
new List<string>(){ "a", "d", "c" }
};
I want to extract all elements that are common in at least 2 lists. So for this example, I should get all elements ["a", "b", "c", "d"]. I know how to find elements common to all but couldn't think of any way to solve this problem.
You could use something like this:
var result = stringLists.SelectMany(l => l.Distinct())
.GroupBy(e => e)
.Where(g => g.Count() >= 2)
.Select(g => g.Key);
Just for fun some iterative solutions:
var seen = new HashSet<string>();
var current = new HashSet<string>();
var result = new HashSet<string>();
foreach (var list in stringLists)
{
foreach(var element in list)
if(current.Add(element) && !seen.Add(element))
result.Add(element);
current.Clear();
}
or:
var already_seen = new Dictionary<string, bool>();
foreach(var list in stringLists)
foreach(var element in list.Distinct())
already_seen[element] = already_seen.ContainsKey(element);
var result = already_seen.Where(kvp => kvp.Value).Select(kvp => kvp.Key);
or (inspired by Tim's answer):
int tmp;
var items = new Dictionary<string,int>();
foreach(var str in stringLists.SelectMany(l => l.Distinct()))
{
items.TryGetValue(str, out tmp);
items[str] = tmp + 1;
}
var result = items.Where(kv => kv.Value >= 2).Select(kv => kv.Key);
You could use a Dictionary<string, int>, the key is the string and the value is the count:
Dictionary<string, int> itemCounts = new Dictionary<string,int>();
for(int i = 0; i < stringLists.Length; i++)
{
List<string> list = stringLists[i];
foreach(string str in list.Distinct())
{
if(itemCounts.ContainsKey(str))
itemCounts[str] += 1;
else
itemCounts.Add(str, 1);
}
}
var result = itemCounts.Where(kv => kv.Value >= 2);
I use list.Distinct() since you only want to count occurences in different lists.
As requested, here is an extension method which you can reuse with any type:
public static IEnumerable<T> GetItemsWhichOccurAtLeastIn<T>(this IEnumerable<IEnumerable<T>> seq, int minCount, IEqualityComparer<T> comparer = null)
{
if (comparer == null) comparer = EqualityComparer<T>.Default;
Dictionary<T, int> itemCounts = new Dictionary<T, int>(comparer);
foreach (IEnumerable<T> subSeq in seq)
{
foreach (T x in subSeq.Distinct(comparer))
{
if (itemCounts.ContainsKey(x))
itemCounts[x] += 1;
else
itemCounts.Add(x, 1);
}
}
foreach(var kv in itemCounts.Where(kv => kv.Value >= minCount))
yield return kv.Key;
}
Usage is simple:
string result = String.Join(",", stringLists.GetItemsWhichOccurAtLeastIn(2)); // a,b,c,d
Follow these steps:
Create a Dictionary element -> List of indices
loop over all lists
for list number i: foreach element in the list: add i to the list in the dictionary at position : dictionary[element].Add(i) (if not already present)
Count how many lists in the dictionary have two entries
You can use SelectMany to flatten the list and then pick all elemeents which occur twice or more:
var singleList = stringLists.SelectMany(p => p);
var results = singleList.Where(p => singleList.Count(q => p == q) >= 2).Distinct();
Currently, this is just something I am curious about, I don't have any code I am working on but I am wondering how this could be achieved...
Lets say for example that I have an application that tracks the results of all the football teams in the world. What I want to be able to do is to identify the longest "win" streak for any given team.
I imagine I would most likely have some sort of data table like so:
MatchDate datetime
TeamA string
TeamB string
TeamAGoals int
TeamBGoals int
So what I would want to do for example is find the longest win streak where TeamA = "My Team" and obviously this would mean TeamAGoals must be greater than TeamBGoals.
As I have said, this is all just for example. It may be better for a different DB design for something like this. But the root question is how to calculate the longest streak/run of matching results.
This is an old question now, but I just had to solve the same problem myself, and thought people might be interested in a fully LINQ implementation of Rawling's LongestStreak extension method. This uses Aggregate with a seed and result selector to run through the list.
public static int LongestStreak<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
return source.Aggregate(
new {Longest = 0, Current = 0},
(agg, element) => predicate(element) ?
new {Longest = Math.Max(agg.Longest, agg.Current + 1), Current = agg.Current + 1} :
new {agg.Longest, Current = 0},
agg => agg.Longest);
}
There's no out-of-the-box LINQ method to count streaks, so you'll need a custom LINQy method such as
public static int LongestStreak<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
int longestStreak = 0;
int currentStreak = 0;
foreach (TSource s in source)
{
if (predicate(s))
currentStreak++;
else
{
if (currentStreak > longestStreak) longestStreak = currentStreak;
currentStreak = 0;
}
}
if (currentStreak > longestStreak) longestStreak = currentStreak;
return longestStreak;
}
Then, to use this, first turn each "match result" into a pair of "team results".
var teamResults = matches.SelectMany(m => new[] {
new {
MatchDate = m.MatchDate,
Team = m.TeamA,
Won = m.TeamAGoals > m.TeamBGoals },
new {
MatchDate = m.MatchDate,
Team = m.TeamB,
Won = m.TeamBGoals > m.TeamAGoals }
});
Group these by team.
var groupedResults = teamResults.GroupBy(r => r.Team);
Then calculate the streaks.
var streaks = groupedResults.Select(g => new
{
Team = g.Key,
StreakLength = g
// unnecessary if the matches were ordered originally
.OrderBy(r => r.MatchDate)
.LongestStreak(r => r.Won)
});
If you want the longest streak only, use MoreLinq's MaxBy; if you want them all ordered, you can use OrderByDescending(s => s.StreakLength).
Alternatively, if you want to do this in one pass, and assuming matches is already ordered, using the following class
class StreakAggregator<TKey>
{
public Dictionary<TKey, int> Best = new Dictionary<TKey, int>();
public Dictionary<TKey, int> Current = new Dictionary<TKey, int>();
public StreakAggregator<TKey> UpdateWith(TKey key, bool success)
{
int c = 0;
Current.TryGetValue(key, out c);
if (success)
{
Current[key] = c + 1;
}
else
{
int b = 0;
Best.TryGetValue(key, out b);
if (c > b)
{
Best[key] = c;
}
Current[key] = 0;
}
return this;
}
public StreakAggregator<TKey> Finalise()
{
foreach (TKey k in Current.Keys.ToArray())
{
UpdateWith(k, false);
}
return this;
}
}
you can then do
var streaks = teamResults.Aggregate(
new StreakAggregator<string>(),
(a, r) => a.UpdateWith(r.Team, r.Won),
(a) => a.Finalise().Best.Select(kvp =>
new { Team = kvp.Key, StreakLength = kvp.Value }));
and OrderBy or whatever as before.
You can get all results of team with single query:
var results = from m in Matches
let homeMatch = m.TeamA == teamName
let awayMatch = m.TeamB == teamName
let hasWon = (homeMatch && m.TeamAGoals > m.TeamBGoals) ||
(awayMatch && m.TeamBGoals > m.TeamAGoals)
where homeMatch || awayMatch
orderby m.MatchDate
select hasWon;
Then just do simple calculation of longest streak:
int longestStreak = 0;
int currentStreak = 0;
foreach (var hasWon in results)
{
if (hasWon)
{
currentStreak++;
if (currentStreak > longestStreak)
longestStreak = currentStreak;
continue;
}
currentStreak = 0;
}
You can use it as is, extract to method, or create IEnumerable extension for calculating longest sequence in results.
You could make use of string.Split. Something like this:
int longestStreak =
string.Concat(results.Select(r => (r.ours > r.theirs) ? "1" : "0"))
.Split(new[] { '0' })
.Max(s => s.Length);
Or, better, create a Split extension method for IEnumerable<T> to avoid the need to go via a string, like this:
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> items, Predicate<T> p)
{
while (true)
{
items = items.SkipWhile(i => !p(i));
var trueItems = items.TakeWhile (i => p(i)).ToList();
if (trueItems.Count > 0)
{
yield return trueItems;
items = items.Skip(trueItems.Count);
}
else
{
break;
}
}
}
You can then simply do this:
int longestStreak = results.Split(r => r.ours > r.theirs).Max(g => g.Count());
How to convert a String[] to an IDictionary<String, String>?
The values at the indices 0,2,4,... shall be keys, and consequently values at the indices 1,3,5,... shall be values.
Example:
new[] { "^BI", "connectORCL", "^CR", "connectCR" }
=>
new Dictionary<String, String> {{"^BI", "connectORCL"}, {"^CR", "connectCR"}};
I'd recommend a good old for loop for clarity. But if you insist on a LINQ query, this should work:
var dictionary = Enumerable.Range(0, array.Length/2)
.ToDictionary(i => array[2*i], i => array[2*i+1])
Dictionary<string,string> ArrayToDict(string[] arr)
{
if(arr.Length%2!=0)
throw new ArgumentException("Array doesn't contain an even number of entries");
Dictionary<string,string> dict=new Dictionary<string,string>();
for(int i=0;i<arr.Length/2;i++)
{
string key=arr[2*i];
string value=arr[2*i+1];
dict.Add(key,value);
}
return dict;
}
There's really no easy way to do this in LINQ (And even if there were, it's certainly not going to be clear as to the intent). It's easily accomplished by a simple loop though:
// This code assumes you can guarantee your array to always have an even number
// of elements.
var array = new[] { "^BI", "connectORCL", "^CR", "connectCR" };
var dict = new Dictionary<string, string>();
for(int i=0; i < array.Length; i+=2)
{
dict.Add(array[i], array[i+1]);
}
Something like this maybe:
string[] keyValues = new string[20];
Dictionary<string, string> dict = new Dictionary<string, string>();
for (int i = 0; i < keyValues.Length; i+=2)
{
dict.Add(keyValues[i], keyValues[i + 1]);
}
Edit: People in the C# tag are damn fast...
If you have Rx as a dependency you can do:
strings
.BufferWithCount(2)
.ToDictionary(
buffer => buffer.First(), // key selector
buffer => buffer.Last()); // value selector
BufferWithCount(int count) takes the first count values from the input sequence and yield them as a list, then it takes the next count values and so on. I.e. from your input sequence you will get the pairs as lists: {"^BI", "connectORCL"}, {"^CR", "connectCR"}, the ToDictionary then takes the first list item as key and the last ( == second for lists of two items) as value.
However, if you don't use Rx, you can use this implementation of BufferWithCount:
static class EnumerableX
{
public static IEnumerable<IList<T>> BufferWithCount<T>(this IEnumerable<T> source, int count)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (count <= 0)
{
throw new ArgumentOutOfRangeException("count");
}
var buffer = new List<T>();
foreach (var t in source)
{
buffer.Add(t);
if (buffer.Count == count)
{
yield return buffer;
buffer = new List<T>();
}
}
if (buffer.Count > 0)
{
yield return buffer;
}
}
}
It looks like other people have already beaten me to it and/or have more efficient answers but I'm posting 2 ways:
A for loop might be the clearest way to accomplish in this case...
var words = new[] { "^BI", "connectORCL", "^CR", "connectCR" };
var final = words.Where((w, i) => i % 2 == 0)
.Select((w, i) => new[] { w, words[(i * 2) + 1] })
.ToDictionary(arr => arr[0], arr => arr[1])
;
final.Dump();
//alternate way using zip
var As = words.Where((w, i) => i % 2 == 0);
var Bs = words.Where((w, i) => i % 2 == 1);
var dictionary = new Dictionary<string, string>(As.Count());
var pairs = As.Zip(Bs, (first, second) => new[] {first, second})
.ToDictionary(arr => arr[0], arr => arr[1])
;
pairs.Dump();
FYI, this is what I ended up with using a loop and implementing it as an extension method:
internal static Boolean IsEven(this Int32 #this)
{
return #this % 2 == 0;
}
internal static IDictionary<String, String> ToDictionary(this String[] #this)
{
if (!#this.Length.IsEven())
throw new ArgumentException( "Array doesn't contain an even number of entries" );
var dictionary = new Dictionary<String, String>();
for (var i = 0; i < #this.Length; i += 2)
{
var key = #this[i];
var value = #this[i + 1];
dictionary.Add(key, value);
}
return dictionary;
}
Pure Linq
Select : Project original string value and its index.
GroupBy : Group adjacent pairs.
Convert each group into dictionary entry.
string[] arr = new string[] { "^BI", "connectORCL", "^CR", "connectCR" };
var dictionary = arr.Select((value,i) => new {Value = value,Index = i})
.GroupBy(value => value.Index / 2)
.ToDictionary(g => g.FirstOrDefault().Value,
g => g.Skip(1).FirstOrDefault().Value);