How to use IEnumerable.GroupBy comparing multiple properties between elements? - c#

How do I group "adjacent" Sites:
Given data:
List<Site> sites = new List<Site> {
new Site { RouteId="A", StartMilepost=0.00m, EndMilepost=1.00m },
new Site { RouteId="A", StartMilepost=1.00m, EndMilepost=2.00m },
new Site { RouteId="A", StartMilepost=5.00m, EndMilepost=7.00m },
new Site { RouteId="B", StartMilepost=3.00m, EndMilepost=5.00m },
new Site { RouteId="B", StartMilepost=11.00m, EndMilepost=13.00m },
new Site { RouteId="B", StartMilepost=13.00m, EndMilepost=14.00m },
};
I want result:
[
[
Site { RouteId="A", StartMilepost=0.00m, EndMilepost=1.00m },
Site { RouteId="A", StartMilepost=1.00m, EndMilepost=2.00m }
],
[
Site { RouteId="A", StartMilepost=5.00m, EndMilepost=7.00m }
],
[
Site { RouteId="B", StartMilepost=3.00m, EndMilepost=5.00m }
],
[
Site { RouteId="B", StartMilepost=11.00m, EndMilepost=13.00m },
Site { RouteId="B", StartMilepost=13.00m, EndMilepost=14.00m }
]
]
I tried using GroupBy with a custom comparer function checking routeIds match and first site's end milepost is equal to the next sites start milepost. My HashKey function just checks out routeId so all sites within a route will get binned together but I think the comparer makes an assumption like if A = B, and B = C, then A = C, so C won't get grouped with A,B,C since in my adjacency case, A will not equal C.

First, let Site class be (for debugging / demonstration)
public class Site {
public Site() { }
public string RouteId;
public Decimal StartMilepost;
public Decimal EndMilepost;
public override string ToString() => $"{RouteId} {StartMilepost}..{EndMilepost}";
}
Well, as you can see we have to break the rules: equality must be transitive, i.e. whenever
A equals B
B equals C
then
A equals C
It's not the case in your example. However, if we sort the sites by StartMilepost we, technically, can implement IEqualityComparer<Site> like this:
public class MySiteEqualityComparer : IEqualityComparer<Site> {
public bool Equals(Site x, Site y) {
if (ReferenceEquals(x, y))
return true;
else if (null == x || null == y)
return false;
else if (x.RouteId != y.RouteId)
return false;
else if (x.StartMilepost <= y.StartMilepost && x.EndMilepost >= y.StartMilepost)
return true;
else if (y.StartMilepost <= x.StartMilepost && y.EndMilepost >= x.StartMilepost)
return true;
return false;
}
public int GetHashCode(Site obj) {
return obj == null
? 0
: obj.RouteId == null
? 0
: obj.RouteId.GetHashCode();
}
}
then GroupBy as usual; please, note that OrderBy is required, since order of comparision matters here. Suppose we have
A = {RouteId="X", StartMilepost=0.00m, EndMilepost=1.00m}
B = {RouteId="X", StartMilepost=1.00m, EndMilepost=2.00m}
C = {RouteId="X", StartMilepost=2.00m, EndMilepost=3.00m}
Here A == B, B == C (so in case of A, B, C all items will be in the same group) but A != C (and thus in A, C, B will end up with 3 groups)
Code:
List<Site> sites = new List<Site> {
new Site { RouteId="A", StartMilepost=0.00m, EndMilepost=1.00m },
new Site { RouteId="A", StartMilepost=1.00m, EndMilepost=2.00m },
new Site { RouteId="A", StartMilepost=5.00m, EndMilepost=7.00m },
new Site { RouteId="B", StartMilepost=3.00m, EndMilepost=5.00m },
new Site { RouteId="B", StartMilepost=11.00m, EndMilepost=13.00m },
new Site { RouteId="B", StartMilepost=13.00m, EndMilepost=14.00m },
};
var result = sites
.GroupBy(item => item.RouteId)
.Select(group => group
// Required Here, since MySiteEqualityComparer breaks the rules
.OrderBy(item => item.StartMilepost)
.GroupBy(item => item, new MySiteEqualityComparer())
.ToArray())
.ToArray();
// Let's have a look
var report = string.Join(Environment.NewLine, result
.Select(group => string.Join(Environment.NewLine,
group.Select(g => string.Join("; ", g)))));
Console.Write(report);
Outcome:
A 0.00..1.00; A 1.00..2.00
A 5.00..7.00
B 3.00..5.00
B 11.00..13.00; B 13.00..14.00

Here are a couple of implementations where order of Site's does not matter. You can use the LINQ Aggregate function:
return sites.GroupBy(x => x.RouteId)
.SelectMany(x =>
{
var groupedSites = new List<List<Site>>();
var aggs = x.Aggregate(new List<Site>(), (contiguous, next) =>
{
if (contiguous.Count == 0 || contiguous.Any(y => y.EndMilepost == next.StartMilepost))
{
contiguous.Add(next);
}
else if (groupedSites.Any(y => y.Any(z => z.EndMilepost == next.StartMilepost)))
{
var groupMatchIndex = groupedSites.FindIndex(y => y.Any(z => z.EndMilepost == next.StartMilepost));
var el = groupedSites.ElementAt(groupMatchIndex);
el.Add(next);
groupedSites[groupMatchIndex] = el;
}
else
{
groupedSites.Add(contiguous);
contiguous = new List<Site>();
contiguous.Add(next);
}
return contiguous;
}, final => { groupedSites.Add(final); return final; });
return groupedSites;
});
Alternatively, just with foreach:
return sites.GroupBy(x => x.RouteId)
.SelectMany(x =>
{
var groupedSites = new List<List<Site>>();
var aggList = new List<Site>();
foreach (var item in x)
{
if (aggList.Count == 0 || aggList.Any(y => y.EndMilepost == item.StartMilepost))
{
aggList.Add(item);
continue;
}
var groupMatchIndex = groupedSites.FindIndex(y => y.Any(z => z.EndMilepost == item.StartMilepost));
if (groupMatchIndex > -1)
{
var el = groupedSites.ElementAt(groupMatchIndex);
el.Add(item);
groupedSites[groupMatchIndex] = el;
continue;
}
groupedSites.Add(aggList);
aggList = new List<Site>();
aggList.Add(item);
}
groupedSites.Add(aggList);
return groupedSites;
});

I was surprised that GroupBy doesn't have overload with Func<..., bool> for in-place grouping without hassle of implementing custom class.
So I've created one:
public static IEnumerable<IEnumerable<T>> GroupBy<T>(this IEnumerable<T> source, Func<T, T, bool> func)
{
var items = new List<T>();
foreach (var item in source)
{
if (items.Count != 0)
if (!func(items[0], item))
{
yield return items;
items = new List<T>();
}
items.Add(item);
}
if (items.Count != 0)
yield return items;
}
The usage:
var result = sites.GroupBy((x, y) => x.RouteId == y.RouteId &&
x.StartMilepost <= y.EndMilepost && x.EndMilepost >= y.StartMilepost).ToList();
This should produce wanted result.
Few words about implementation. In above extension method you have to supply delegate which should return true if x and y should be grouped. The method is dumb and will simply compare adjacent items in same order as they come. Your input is ordered, but you may have to use OrderBy/ThenBy before using it with something else.

Here is an extension method for grouping lists of the specific class (Site). It is implemented with an inner iterator function GetGroup that produces one group with adjacent sites. This function is called in a while loop to produce all groups.
public static IEnumerable<IEnumerable<Site>> GroupAdjacent(
this IEnumerable<Site> source)
{
var ordered = source
.OrderBy(item => item.RouteId)
.ThenBy(item => item.StartMilepost);
IEnumerator<Site> enumerator;
bool finished = false;
Site current = null;
using (enumerator = ordered.GetEnumerator())
{
while (!finished)
{
yield return GetGroup();
}
}
IEnumerable<Site> GetGroup()
{
if (current != null) yield return current;
while (enumerator.MoveNext())
{
var previous = current;
current = enumerator.Current;
if (previous != null)
{
if (current.RouteId != previous.RouteId) yield break;
if (current.StartMilepost != previous.EndMilepost) yield break;
}
yield return current;
}
finished = true;
}
}
Usage example:
var allGroups = sites.GroupAdjacent();
foreach (var group in allGroups)
{
foreach (var item in group)
{
Console.WriteLine(item);
}
Console.WriteLine();
}
Output:
A 0,00..1,00
A 1,00..2,00
A 5,00..7,00
B 3,00..5,00
B 11,00..13,00
B 13,00..14,00

Related

Icomparer c# List

I have a list of image name like this {"1.jpg", "10.jpg", "2.jpg"}.
I would like to sort like this {"1.jpg", "2.jpg", "10.jpg"}.
I created this comparer. That means if x or y == "DSC_10.jpg", so if list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...} don't sort and keep the list.
var comparer = new CompareImageName();
imageUrls.Sort(comparer);
return imageUrls;
public class CompareImageName : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null) return 0;
var l = x.Split('/');
var l1 = y.Split('/');
int a, b;
var rs = int.TryParse(l[l.Length - 1].Split('.')[0], out a);
var rs2 = int.TryParse(l1[l1.Length - 1].Split('.')[0], out b);
if (!rs || !rs2) return 0;
if (a == b || a == 0 && b == 0) return 0;
return a > b ? 1 : -1;
}
}
This sort correctly with name {"1.jpg", "10.jpg", "2.jpg"}, but incorrectly if list is {"DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg", ...}.
I read in MSDN:
What wrong with my code?
I think you're better off doing a bit of Regex for this. Try this solution:
public class CompareImageName : IComparer<string>
{
public int Compare(string x, string y)
{
if (x == null || y == null) return 0;
var regex = new Regex(#"/(((?<prefix>\w*)_)|)((?<number>\d+))\.jpg$");
var mx = regex.Match(x);
var my = regex.Match(y);
var r = mx.Groups["prefix"].Value.CompareTo(my.Groups["prefix"].Value);
if (r == 0)
{
r = int.Parse(mx.Groups["number"].Value).CompareTo(int.Parse(my.Groups["number"].Value));
}
return r;
}
}
Apart from the Regex string itself this is easier to follow the logic.
Here is your solution check this example, following class will do the comparison
public class NumericCompare : IComparer<string>
{
public int Compare(string x, string y)
{
int input1,input2;
input1=int.Parse(x.Substring(x.IndexOf('_')+1).Split('.')[0]);
input2= int.Parse(y.Substring(y.IndexOf('_')+1).Split('.')[0]);
return Comparer<int>.Default.Compare(input1,input2);
}
}
You can make use of this class like the following:
var imageUrls = new List<string>() { "DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg" };
var comparer = new NumericCompare();
imageUrls.Sort(comparer);
Console.WriteLine(String.Join("\n",imageUrls));
Try this with simple OrderBy
var SortedList = imageUrls.OrderBy(
x=>int.Parse(
x.Substring(x.IndexOf('_')+1).Split('.')[0])
).ToList();
Basically what you want to do is sort by the numeric part within the string. You are almost there. You just have to handle the part when you split a case like this DSC_2.jpg using a . then the first part is not all digits. So you need to get digits and then compare those. Here is the code. Please note I have made the assumption you will have backslash and if that is not the case then please handle it:
public int Compare(string x, string y)
{
if (x == null || y == null) return 0;
var nameX = x.Substring(x.LastIndexOf('/'));
var nameY = y.Substring(y.LastIndexOf('/'));
var nameXParts = nameX.Split('.');
var nameYParts = nameY.Split('.');
int a, b;
var rs = int.TryParse(nameXParts[0], out a);
var rs2 = int.TryParse(nameYParts[0], out b);
var nameXDigits = string.Empty;
if (!rs)
{
for (int i = 0; i < nameXParts[0].Length; i++)
{
if (Char.IsDigit(nameXParts[0][i]))
nameXDigits += nameXParts[0][i];
}
}
var nameYDigits = string.Empty;
if (!rs2)
{
for (int i = 0; i < nameYParts[0].Length; i++)
{
if (Char.IsDigit(nameYParts[0][i]))
nameYDigits += nameYParts[0][i];
}
}
int.TryParse(nameXDigits, out a);
int.TryParse(nameYDigits, out b);
if (a == b || a == 0 && b == 0) return 0;
return a > b ? 1 : -1;
}
Don't use imageUrls.Sort(comparer); on List because it doesn't accept 0 value as keeping the order of elements.
Reason:
The Sort performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.
Link: https://msdn.microsoft.com/en-gb/library/w56d4y5z.aspx
Solution: Let's try to use OrderBy with your compare
var imageUrls1 = new List<string>() { "1.jpg", "10.jpg", "2.jpg" };
var imageUrls2 = new List<string>() { "DSC_1.jpg", "DSC_10.jpg", "DSC_2.jpg" };
var comparer = new CompareImageName();
//Sort normally
imageUrls1 = imageUrls1.OrderBy(p=>p, comparer).ToList();
//Keep the order as your expectation
imageUrls2 = imageUrls2.OrderBy(p=>p, comparer).ToList();
Maybe you can try doing this in a function instead of writing a comparator. I can't think of a good way to implement this logic as a comparator since there are different rules based on the contents (don't sort if the file name is not numeric).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace sortinglists
{
public class MainProgram
{
public static void Main()
{
var imageUrlsNumbers = new List<string>();
imageUrlsNumbers.Add("c:/a/b/1.jpg");
imageUrlsNumbers.Add("c:/a/b/10.jpg");
imageUrlsNumbers.Add("c:/a/b/2.jpg");
CustomSort(ref imageUrlsNumbers);
foreach (var imageUrl in imageUrlsNumbers)
{
Console.WriteLine(imageUrl);
}
var imageUrlsText = new List<string>();
imageUrlsText.Add("c:/a/b/DSC_1.jpg");
imageUrlsText.Add("c:/a/b/DSC_10.jpg");
imageUrlsText.Add("c:/a/b/DSC_2.jpg");
CustomSort(ref imageUrlsText);
foreach (var imageUrl in imageUrlsText)
{
Console.WriteLine(imageUrl);
}
}
public static void CustomSort(ref List<string> imageUrls)
{
if (imageUrls
.Select(s => s.Substring(s.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
.Select(t => t.Substring(0, t.IndexOf(".", StringComparison.OrdinalIgnoreCase)))
.Where(u => new Regex("[A-Za-z_]").Match(u).Success)
.Any())
{
imageUrls = imageUrls
.Select(x => x.Substring(x.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
.ToList();
}
else
{
imageUrls = imageUrls
.Select(v => v.Substring(v.LastIndexOf("/", StringComparison.OrdinalIgnoreCase) + 1))
.OrderBy(w => Convert.ToInt32(w.Substring(0, w.LastIndexOf(".", StringComparison.OrdinalIgnoreCase))))
.ToList();
}
}
}
}
The output for imageUrlsNumbers after sorting is:
1.jpg
2.jpg
10.jpg
And the output for imageUrlsText after sorting is:
DSC_1.jpg
DSC_10.jpg
DSC_2.jpg

Merging lists with a for loop

I'm working on an algorithm which can generate 2 types of recommendations, restaurants and dishes. All of this works fine, but I wanted to merge these 2 types of recommendations in a single list, which is where I encountered some issues. From my previous question I concluded that I needed a wrapper class, which I have set up like this:
public class RecommenderItem
{
public Guid Id { get; set; }
public object Entity { get; set; }
}
Now I want to alternate the 2 types of recommendations so the list would look like this:
[Restaurant][Dish][Restaurant][Dish][Restaurant][Dish] //Etc...
Note that these recommendations are completely separate. They are generated purely based on the user's preference, and they have no correlation in between them. My product owner wants to show these recommendations on the home page of our app like this.
These lists are different in length, so if I have added all items from a list, I wanted to just add the remaining objects from the other list. A possible scenario of this could look like this:
/*Other objects before this...*/[Dish][Restaurant][Dish][Dish][Dish] //Etc...
Here did the list of restaurant objects run out and I just wanted to add the remaining dish recommendations at the end of the list.
I have gotten this far, but I'm unsure how I would catch an IndexOutOfBounds exception and add the rest of the remaining objects at the end.
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations,
List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = 0;
//Check which list is longer and use that count
if (restaurantRecommendations.Count > dishRecommendations.Count)
count = dishRecommendations.Count;
else
count = restaurantRecommendations.Count;
for (int i = 0; i < count; i++)
{
//I'm fully aware this isn't the most optimal way of doing this,
//but I'm only looking at functionality here, optimizing performance comes later.
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
return output;
}
Does anyone have an idea how I could do this? Could I just catch an IndexOutOfBounds exception and use .AddRange() for the remaining objects? I'm not sure how I could check which list was out of bounds.
Let me know if I should elaborate more and thanks in advance!
Edit: -removed because it wasn't fair.-
This is a fairly succinct way of doing this.
While not Linq, it works in the spirit of the way Linq works by deferring doing any work until the resulting sequence is enumerated:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
using (var r = restaurants.GetEnumerator())
using (var d = dishes.GetEnumerator())
{
while (true)
{
bool rAvailable = r.MoveNext();
bool dAvailable = d.MoveNext();
if (rAvailable)
yield return new RecommenderItem { Id = r.Current.Id, Entity = r.Current };
if (dAvailable)
yield return new RecommenderItem { Id = d.Current.Id, Entity = d.Current };
if (!rAvailable && !dAvailable)
break;
}
}
}
If you happen to be using the MoreLinq NuGet package that includes the ZipLongest extension method, you can use the following simplified implementation instead:
public static IEnumerable<RecommenderItem> Merge(IEnumerable<Restaurant> restaurants, IEnumerable<Dish> dishes)
{
foreach (var item in restaurants.ZipLongest(dishes, (r, d) => new { r, d }))
{
if (item.r != null)
yield return new RecommenderItem { Id = item.r.Id, Entity = item.r };
if (item.d != null)
yield return new RecommenderItem { Id = item.d.Id, Entity = item.d };
}
}
Addendum
As #InBetween posted in his answer, you can put the interleaving logic into an extension method. Here's my version; it's substantially the same, except I've added a small optimisation to avoid calling .MoveNext() when its not necessary:
public static class EnumerableExt
{
public static IEnumerable<T> Interleave<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
using (var ae = a.GetEnumerator())
using (var be = b.GetEnumerator())
{
bool aAvailable = true;
bool bAvailable = true;
while (aAvailable || bAvailable)
{
aAvailable = aAvailable && ae.MoveNext();
bAvailable = bAvailable && be.MoveNext();
if (aAvailable)
yield return ae.Current;
if (bAvailable)
yield return be.Current;
}
}
}
}
Once you have that, I realised that you don't need to write an implict operator. Instead, you can just convert the two sequences to the resultant type before calling Interleave() like so:
var restaurantsAsRecommenderItems =
restaurantRecommendations
.Select(r => new RecommenderItem {Id = r.Id, Entity = r});
var dishesAsRecommenderItems =
dishRecommendations
.Select(d => new RecommenderItem {Id = d.Id, Entity = d});
var result =
restaurantsAsRecommenderItems
.Interleave(dishesAsRecommenderItems)
.ToList();
My recommendation would be to just make simple implicit operator :
public static implicit operator RecommenderItem(Restaurant restaurant) {
return new RecommenderItem { Id = restaurant.Id, Entity = restaurant };
}
Then you have possibility to convert these types easily like :
Restaurant rest = //...
RecommenderItem rItem = rest; // here the implicit operator is called
After doing this you can just use one for loop :
int count = Math.Max(restaurantRecommendations.Count, dishRecommendations.Count);
for ( int i = 0; i < count; i++ ) {
if ( i < restRecommendations.Count )
output.Add(restRecommendations[i]);
if ( i < dishRecommendations.Count )
output.Add(dishRecommendations[i]);
}
This will make your work much more easier.
Well, there are probably more elegant LINQ solutions but you have already most, it's also a very efficient approach:
public List<RecommenderItem> GetMergedRecommendationLists(List<Restaurant> restaurantRecommendations, List<Dish> dishRecommendations)
{
//Setting up the output list.
List<RecommenderItem> output = new List<RecommenderItem>();
int count = Math.Min(restaurantRecommendations.Count, dishRecommendations.Count);
for (int i = 0; i < count; i++)
{
var restRecommendation = restaurantRecommendations[i];
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
int remainingRestaurant = restaurantRecommendations.Count - count;
int remainingDishes = dishRecommendations.Count - count;
if (remainingRestaurant > 0)
{
for (int i = count; i < restaurantRecommendations.Count; i++)
{
var restRecommendation = restaurantRecommendations[i];
output.Add(new RecommenderItem()
{
Id = restRecommendation.Id,
Entity = restRecommendation
});
}
}
else if (remainingDishes > 0)
{
for (int i = count; i < dishRecommendations.Count; i++)
{
var dishRecommendation = dishRecommendations[i];
output.Add(new RecommenderItem()
{
Id = dishRecommendation.Id,
Entity = dishRecommendation
});
}
}
return output;
}
A simple way of doing it would be:
public static IEnumerable<T> Merge<T>(this IEnumerable<T> first, IEnumerable<T> second)
{
using (var firstEnumerator = first.GetEnumerator())
using (var secondEnumerator = second.GetEnumerator())
{
while (firstEnumerator.MoveNext())
{
yield return firstEnumerator.Current;
if (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
while (secondEnumerator.MoveNext())
{
yield return secondEnumerator.Current;
}
}
}
After having created two arrays of restaurants and dishes of the same type RecommenderItem, you can use the Zip method like :
var restaurants = restaurantRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var dishes = dishRecommendations.Select(x => new RecommenderItem {
Id = x.Id,
Entity = x
}).ToArray();
var output = restaurants.Zip(dishes, (r, d) => new[] { r, d })
.SelectMany(r => r).Concat(dishes.Skip(restaurants.Length))
.Concat(restaurants.Skip(dishes.Length));
Restaraunt and Dish would have to share a base type:
restaurantRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
dishRecommendations.Select(item => new RecommenderItem()
{
Id = item.Id,
Entity = item
});
Once that's the case you could use something like this slightly modified version of Zip (from System.Linq):
private static IEnumerable<T> ZipThrough<T>(IEnumerable<T> first, IEnumerable<T> second)
{
if (first == null) throw new ArgumentNullException(nameof(first));
if (second == null) throw new ArgumentNullException(nameof(second));
using (var e1 = first.GetEnumerator())
{
using (var e2 = second.GetEnumerator())
{
while (true)
if (e1.MoveNext())
{
yield return e1.Current;
if (e2.MoveNext()) yield return e2.Current;
}
else if (e2.MoveNext())
{
yield return e2.Current;
}
else
{
break;
}
}
}
}

Split List of objects based on object type

I have list of objects as below - "Text1", "Text2", new UndefinedInfo("Undfined1"), new UndefinedInfo("Undfined2"), new UndefinedInfo("Undefined3"), "Text3"
I need output as below:
Output
// List>
0 – Text1, Text2
1 – Undefined1, Undefined2, Undefined3
2 – Text3
I managed to write below function which is bad but somehow working fine. Is there any way to achieve the same using LINQ extensions - TakeWhile or SkipWhile or using yield. Any help highly appreciated:
public static List<List<object>> PartitionByTypes(List<object> values)
{
List<List<object>> partitionedList = new List<List<object>>();
int j;
for (int i = 0; i < values.Count; i++)
{
j = i;
List<object> subList = new List<object>();
Type t = values[j].GetType();
do
{
subList.Add(values[j]);
j++;
if (j == values.Count)
{
break;
}
} while (values[j].GetType() == t);
partitionedList.Add(subList);
i = j - 1;
}
return partitionedList;
}
The following will do what you want using yield. So the return will be IEnumerable<List<object>> instead.
public static IEnumerable<List<object>> PartitionByTypes(List<object> values)
{
Type prevType = null;
List<object> cache = new List<object>();
foreach (var value in values)
{
if(prevType != null && value.GetType() != prevType)
{
yield return cache;
cache = new List<object>();
}
cache.Add(value);
prevType = value.GetType();
}
if(cache.Count > 0)
yield return cache;
}
Alternatively you could use the following Linq query
public static IEnumerable<List<object>> PartitionByTypes(List<object> values)
{
int count = 0;
return values.Select((o, i) => new
{
Object = o,
Group = (i == 0 || o.GetType() == values[i - 1].GetType()) ? count : ++count
})
.GroupBy(x => x.Group)
.Select(g => g.Select(x => x.Object).ToList());
}
Well you can do this with LINQ and TakeWhile, but I doubt that will be any better than plain foreach. For example:
public static List<List<object>> LinqPartitionByTypes(List<object> values)
{
var batches = new List<List<object>>();
while (true)
{
object prev = null;
var batch = values.Skip(batches.Sum(c => c.Count)).TakeWhile(c => {
var result = prev == null || prev.GetType() == c.GetType();
prev = c;
return result;
}).ToList();
if (batch.Count == 0)
break;
batches.Add(batch);
}
return batches;
}
However I'd better do something more simple and clean:
public static List<List<object>> PartitionByTypes(List<object> values) {
var batches = new List<List<object>>();
object prev = null;
var batch = new List<object>();
foreach (var value in values) {
if (prev != null && prev.GetType() != value.GetType()) {
batches.Add(batch);
batch = new List<object>();
}
batch.Add(value);
prev = value;
}
if (batch.Count > 0)
batches.Add(batch);
return batches;
}
static Type GetObjectTypeOrNull(object o)
{
return o == null ? null : o.GetType();
}
static IEnumerable<List<object>> PartitionByTypes(List<object> values)
{
if (values == null) throw new ArgumentNullException("values");
if (values.Count == 0) yield break;
Type currentType = GetObjectTypeOrNull(values);
List<object> buffer = new List<object>();
foreach (object value in values)
{
Type valueType = GetObjectTypeOrNull(value);
if (valueType != currentType)
{
currentType = valueType;
yield return buffer;
buffer = new List<object>();
}
currentType = valueType;
buffer.Add(value);
}
if (buffer.Count > 0)
{
yield return buffer;
}
}
Example:
List<object> input = new List<object> { "Text1", "Text2", new UndefinedInfo("Undefined1"), new UndefinedInfo("Undefined2"), new UndefinedInfo("Undefined3"), "Text3" };
PartitionByTypes(input).ToList();
Result:
{ List<object>(2) { "Text1", "Text2" }, List<object>(3) { [Undefined1], [Undefined2], [Undefined3] }, List<object>(1) { "Text3" }
Another example - with nulls:
List<object> input = new List<object> { null, "Text1", "Text2", null, null, new UndefinedInfo("Undefined1"), null, null, new UndefinedInfo("Undefined2"), new UndefinedInfo("Undefined3"), "Text3" };
PartitionByTypes(input).ToList();
Result:
List<object>(1) { null }, List<object>(2) { "Text1", "Text2" }, List<object>(2) { null, null }, List<object>(1) { [Undefined1] }, List<object>(2) { null, null }, List<object>(2) { [Undefined2], [Undefined3] }, List<object>(1) { "Text3" }

Query for values

Declarations and entries in two_Dict dictionary is created as given:
Dictionary<string, List<string>>two_Dict = new Dictionary<string, List<string>>();
List<string> list;
if (!two_Dict.TryGetValue(d.ToString(), out list))
{
two_Dict.Add( d.ToString(), list = new List<string>());
list.Add(possibility_cell_list[0]);
list.Add(possibility_cell_list[1]);
}
Sample entries in two_Dict:
two_Dict["5"] Count = 2 [0]: "A2" [1]: "D2"
two_Dict["6"] Count = 2 [0]: "A2" [1]: "D2"
I am looking to form a linq query to get the keys which have the same list entries in the dictionary two_Dict. Any help will be appreciated.
You can use a fairly simple expression with linq:
var keys = from kvp1 in two_dict
where two_dict.Any(kvp2 => kvp2.Key != kvp1.Key
&& kvp2.Value.SequenceEqual(kvp1.Value))
select kvp1.Key;
However, this does not give the best performance as it will search through the entire dictionary n times, where n is the number of entries in the dictionary.
You can get slightly better performance if you only look at the items that have already been looked at so far. This way, on average you only go through half of the dictionary n times, so it's theoretically twice as fast. Unfortunately, I don't think there is a good way to do this purely using linq.
public static IEnumerable GetDuplicates(IDictionary<string, List<string>> dict)
{
var previousItems = new List<KeyValuePair<string, List<string>>>(dict.Count);
var matchedItems = new List<bool>();
foreach (var kvp in dict)
{
var match = previousItems.Select((kvp2, i) => Tuple.Create(kvp2.Key, kvp2.Value, i)).FirstOrDefault(t => kvp.Value.SequenceEqual(t.Item2));
if (match != null)
{
var index = match.Item3;
if (!matchedItems[index])
{
yield return match.Item1;
matchedItems[index] = true;
}
yield return kvp.Key;
}
else
{
previousItems.Add(kvp);
matchedItems.Add(false);
}
}
}
You would call the function like this:
var keys = GetDuplicates(two_dict);
All linq, no duplicate result but also, be careful, no null checks:
var res = from k1 in dict.Keys
from k2 in dict.Keys
where string.Compare(k1, k2) == -1 &&
dict[k1].Count == dict[k2].Count && !dict[k1].Any(x => !dict[k2].Contains(x)) && !dict[k2].Any(x => !dict[k1].Contains(x))
select new { k1, k2 };
How about this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Dictionary<string, List<string>> two_Dict = new Dictionary<string, List<string>>();
var keyIndex = two_Dict.Keys.AsEnumerable()
.Select((x, i) => new { index = i, value = x })
.ToList();
var result = (from key1 in keyIndex
from key2 in keyIndex
where key1.index > key2.index
where AreEqual(two_Dict[key1.value],two_Dict[key2.value])
select new {key1 = key1.value, key2 = key2.value}).ToList();
}
static bool AreEqual<T>(List<T> x, List<T> y)
{
// same list or both are null
if (x == y)
{
return true;
}
// one is null (but not the other)
if (x == null || y == null)
{
return false;
}
// count differs; they are not equal
if (x.Count != y.Count)
{
return false;
}
x.Sort();
y.Sort();
for (int i = 0; i < x.Count; i++)
{
if (!x[i].Equals(y[i]))
{
return false;
}
}
return true;
}
}
}
​

C# (String.StartsWith && !String.EndsWith && !String.Contains) using a List

I am a newbie in C#, and I am having problems.
I have 2 List, 2 strings and a getCombinations(string) method that
returns all combinations of a string as List;
How do i validate if a subjectStrings element does not
StartWith && !EndsWith && !Contains (or !StartWith && !EndsWith && Contains, etc.)
for every combinations of startswithString, endswithString and containsString?
Here is my code in StartWith && !EndsWith
(if you want to see it running: http://ideone.com/y8JZkK)
using System;
using System.Collections.Generic;
using System.Linq;
public class Test
{
public static void Main()
{
List<string> validatedStrings = new List<string>();
List<string> subjectStrings = new List<string>()
{
"con", "cot", "eon", "net", "not", "one", "ten", "toe", "ton",
"cent", "cone", "conn", "cote", "neon", "none", "note", "once", "tone",
"cento", "conte", "nonce", "nonet", "oncet", "tenon", "tonne",
"nocent","concent", "connect"
}; //got a more longer wordlist
string startswithString = "co";
string endswithString = "et";
foreach(var z in subjectStrings)
{
bool valid = false;
foreach(var a in getCombinations(startswithString))
{
foreach(var b in getCombinations(endswithString))
{
if(z.StartsWith(a) && !z.EndsWith(b))
{
valid = true;
break;
}
}
if(valid)
{
break;
}
}
if(valid)
{
validatedStrings.Add(z);
}
}
foreach(var a in validatedStrings)
{
Console.WriteLine(a);
}
Console.WriteLine("\nDone");
}
static List<string> getCombinations(string s)
{
//Code that calculates combinations
return Permutations.Permutate(s);
}
}
public class Permutations
{
private static List<List<string>> allCombinations;
private static void CalculateCombinations(string word, List<string> temp)
{
if (temp.Count == word.Length)
{
List<string> clone = temp.ToList();
if (clone.Distinct().Count() == clone.Count)
{
allCombinations.Add(clone);
}
return;
}
for (int i = 0; i < word.Length; i++)
{
temp.Add(word[i].ToString());
CalculateCombinations(word, temp);
temp.RemoveAt(temp.Count - 1);
}
}
public static List<string> Permutate(string str)
{
allCombinations = new List<List<string>>();
CalculateCombinations(str, new List<string>());
List<string> combinations = new List<string>();
foreach(var a in allCombinations)
{
string c = "";
foreach(var b in a)
{
c+=b;
}
combinations.Add(c);
}
return combinations;
}
}
Output:
con
cot
cone
conn
cote <<<
conte <<<
concent
connect
Done
if(z.StartsWith(a) && !z.EndsWith(b))
var b can be "et" and "te", but cote and conte endswith "te",
why it is still added in my validated strings?
Thanks in advance.
z.StartsWith(a) && !z.EndsWith(b)
check below combination
z ="cote"
a ="co"
b ="te"
so z start with "co" and z not end with "te", your condition pass and cote will add to list
i would try as below
var sw =getCombinations(startswithString);
var ew = getCombinations(endswithString);
var result = subjectStrings.Where(z=>
sw.Any(x=>z.StartsWith(x) &&
!ew.Any(y=>z.EndsWith(y))))
.ToList();
DEMO
output :
con
cot
cone
conn
concent
connect
foreach(var b in getCombinations(endswithString))
{
if(z.StartsWith(a) && !z.EndsWith(b))
{
valid = true;
break;
}
}
here you are setting valid to true as soon as there is match for !z.EndsWith(b) and you are not traversing the whole list of permutations available.
since "cote" doesn't end with "et" it is a match and valid is set to true and the code breaks.
So that's why "cote" is added to your list of valid strings. So is the case with "conte".
What you want to do is :
List<string> startsWithCombination = getCombinations("co");
List<string> endsWithCombination = getCombinations("et");
foreach (var z in subjectStrings)
{
bool isStartMatchFound = startsWithCombination.Any(b => z.StartsWith(b));
if (isStartMatchFound)
{
bool isEndMatchFound = endsWithCombination.Any(b => z.EndsWith(b));
if (!isEndMatchFound)
{
validatedStrings.Add(z);
}
}
}

Categories