C
C#2y ago
malkav

❔ Using two lists, compare, and render based on smaller list [Answered]

So I've got two List items, and I need to render one list, and make "green" based on the value in the second list. Example:
List<string> renderItems = new();
List<MatchingItem> compareItems = new();

public class MatchingItem
{
public string Name {get;set;}
public bool Matches {get;set;}
}
List<string> renderItems = new();
List<MatchingItem> compareItems = new();

public class MatchingItem
{
public string Name {get;set;}
public bool Matches {get;set;}
}
Say I have 7 items in the list renderItems and I've got 47 in the list compareItems of which maybe three items have the field Matches set to true. So I need to render the 7 items of renderItems with a text-color of grey, but for those three items that have both the field Name equal to the string in renderItems and the field Matches set to true, I want the text-color set to green. Now I've been trying with foreach in foreach and such, but I'm getting a bit confused and lost. Please halp Here's what I've tried:
public void AttemptMethod()
{
foreach (var item in renderItems)
{
foreach (var comp in compareItems)
{
if (comp.Name == item && comp.Matches)
{
// render green?
}
else
{
// render grey??
}
}
}
}
public void AttemptMethod()
{
foreach (var item in renderItems)
{
foreach (var comp in compareItems)
{
if (comp.Name == item && comp.Matches)
{
// render green?
}
else
{
// render grey??
}
}
}
}
14 Replies
undisputed world champions
i would split this in two parts: 1. create a HashSet<string> from compareItems, that only contains the Names of MatchingItems with Match == true (in your example it would only contain three items) 2. iterate over renderItems and check if the previously created HashSet<string> contains the current item
Anton
Anton2y ago
your solution should work with a few modifications. you have to fall back to rendering grey though, it shouldn't be in the loop, because then you would render it grey once for each time it didn't match. so you either need a flag to keep track of whether you've matched in the loop, break in the case where you render green, setting it to true, and then after the loop only render grey if it's not set. Alternatively, you could use linq to not have that flag. so the foreach body gets to become:
var matchedFilters = compareItems.Where(c => c.Name == item && c.Matches);
if (matchedFilters.Any())
// render green
else
// render grey
var matchedFilters = compareItems.Where(c => c.Name == item && c.Matches);
if (matchedFilters.Any())
// render green
else
// render grey
if you happen to need the compare item that matched, the best built-in way is to use an enumerator, of FirstOrDefault if the item is of a reference type
var matchedFilters = compareItems.Where(c => c.Name == item && c.Matches);
using var e = matchedFilters.GetEnumerator();
if (e.MoveNext())
// render green, e.Current is the compare item
else
// render grey
var matchedFilters = compareItems.Where(c => c.Name == item && c.Matches);
using var e = matchedFilters.GetEnumerator();
if (e.MoveNext())
// render green, e.Current is the compare item
else
// render grey
malkav
malkav2y ago
To be fair, what I am experiencing now from my two lists, is that when I have 7 in renderList, and 500 or so in my compareItems for some reason I get 657% equality, which is not correct 😅 So I might be doing something wrong with my linq here.. I'll post a deeper example of what I have now:
_matchingTags.AddRange(
User
.Profile
.Experiences
.SelectMany(experience
=> experience
.Methods
.Distinct()
.SelectMany(method
=> Assignment
.Tags
.Select(tag
=> new MatchingTags { Tag = method, Matches =
tag.IndexOf(method, StringComparison.CurrentCultureIgnoreCase)>= 0
|| method.IndexOf(tag, StringComparison.CurrentCultureIgnoreCase)>= 0
})
))
);
_matchAmount = _matchingTags.Count(x => x.Matches) == 0
? 0
: _matchingTags.Count(x => x.Matches) * 100 / Assignment.Tags.Count;
_matchingTags.AddRange(
User
.Profile
.Experiences
.SelectMany(experience
=> experience
.Methods
.Distinct()
.SelectMany(method
=> Assignment
.Tags
.Select(tag
=> new MatchingTags { Tag = method, Matches =
tag.IndexOf(method, StringComparison.CurrentCultureIgnoreCase)>= 0
|| method.IndexOf(tag, StringComparison.CurrentCultureIgnoreCase)>= 0
})
))
);
_matchAmount = _matchingTags.Count(x => x.Matches) == 0
? 0
: _matchingTags.Count(x => x.Matches) * 100 / Assignment.Tags.Count;
Explanation of code: User.Profile.Exprience.Methods are the "skills" a user has in certain work experiences, like [".NET", "Blazor", "Yaml", "etc..."] and Assignment.Tags are the skills required for any assignment. Now what I wanted with this bit of code, is to calculate the user's matching methods/tags in an assignment. So if an assignment has 7 tags, and the user has buttloads of methods in twenty experiences or something, and they contain those 7 methods, then it should be 100% if any less, the percentage of matching items should show From there I have the render thing that I asked earlier, which I think I can make now with your linq thing
Anton
Anton2y ago
imma help you in a bit
malkav
malkav2y ago
thanks 😅 I've already started a new project with Unit Tests and everything to sort this out 😅
Anton
Anton2y ago
This looks like it should work. Note the Distinct calls - it's what you were missing. They remove duplicate methods among experiences and then duplicate matched tags.
using System;
using System.Linq;

partial class Data
{
public User User { get; set; }
public Assignment Assignment { get; set; }
}

class User
{
public Profile Profile { get; set; }
}

class Profile
{
public Experience[] Experiences { get; set; }
}

class Experience
{
public string[] Methods;
}

class Assignment
{
public string[] Tags;
}

partial class Data
{
private static bool AreMethodAndTagEqual(string method, string tag)
{
string a, b;
if (tag.Length > method.Length)
{
a = tag;
b = method;
}
else
{
a = method;
b = tag;
}
return a.IndexOf(b, StringComparison.CurrentCultureIgnoreCase) >= 0;
}

float GetMatchingTagsPercentage()
{
// Eliminate duplicates, done somewhere prior.
// Note: the below query could be quicker if you were to cache the hash sets
// that Distinct creates.
Assignment.Tags = Assignment.Tags.Distinct().ToArray();
foreach (var experience in User.Profile.Experiences)
experience.Methods = experience.Methods.Distinct().ToArray();

var matchingTags = User.Profile.Experiences
.SelectMany(experience => experience.Methods)
.Distinct()
.SelectMany(method => Assignment.Tags
.Where(tag => AreMethodAndTagEqual(method, tag)))
.Distinct();
return (float) matchingTags.Count() / Assignment.Tags.Length;
}
}
using System;
using System.Linq;

partial class Data
{
public User User { get; set; }
public Assignment Assignment { get; set; }
}

class User
{
public Profile Profile { get; set; }
}

class Profile
{
public Experience[] Experiences { get; set; }
}

class Experience
{
public string[] Methods;
}

class Assignment
{
public string[] Tags;
}

partial class Data
{
private static bool AreMethodAndTagEqual(string method, string tag)
{
string a, b;
if (tag.Length > method.Length)
{
a = tag;
b = method;
}
else
{
a = method;
b = tag;
}
return a.IndexOf(b, StringComparison.CurrentCultureIgnoreCase) >= 0;
}

float GetMatchingTagsPercentage()
{
// Eliminate duplicates, done somewhere prior.
// Note: the below query could be quicker if you were to cache the hash sets
// that Distinct creates.
Assignment.Tags = Assignment.Tags.Distinct().ToArray();
foreach (var experience in User.Profile.Experiences)
experience.Methods = experience.Methods.Distinct().ToArray();

var matchingTags = User.Profile.Experiences
.SelectMany(experience => experience.Methods)
.Distinct()
.SelectMany(method => Assignment.Tags
.Where(tag => AreMethodAndTagEqual(method, tag)))
.Distinct();
return (float) matchingTags.Count() / Assignment.Tags.Length;
}
}
The note on the hash sets tho I'm not quite sure if that's compatible with the way you compare tags You can make a dictionary ignore casing, but you can't make it use IndexOf IndexOf >= 0 can be replaced with Contains btw It might be best to tokenize the methods in the first place if you can help it. Then you could make this work with hash sets only
malkav
malkav2y ago
Sorry, I just now returned from lunch. Let me read through your example, because I've been trying to work with ISet<string>.IntersectWith() before lunch, and I got stuck on the "IgnoreCase" part here, let me show you what I had so far in my Unit Tests
[Fact]
public void LongMethods_AndLongTags_AsIntersection()
{
// Arrange
string[] allMethods = Constants.LongExperiences.SelectMany(exp => exp.Methods.Select(method => method))
.Distinct().ToArray();
string[] allTags = Constants.LongTags.Select(tag => tag).Distinct().ToArray();
HashSet<string> methodSet = new HashSet<string>(collection: allMethods, comparer: StringComparer.CurrentCultureIgnoreCase);

ISet<string> tagSet = new HashSet<string>(allTags, comparer: StringComparer.CurrentCultureIgnoreCase);

// Act
methodSet.IntersectWith(tagSet);

// Assert
methodSet.Count.Should().Be(9);
}
[Fact]
public void LongMethods_AndLongTags_AsIntersection()
{
// Arrange
string[] allMethods = Constants.LongExperiences.SelectMany(exp => exp.Methods.Select(method => method))
.Distinct().ToArray();
string[] allTags = Constants.LongTags.Select(tag => tag).Distinct().ToArray();
HashSet<string> methodSet = new HashSet<string>(collection: allMethods, comparer: StringComparer.CurrentCultureIgnoreCase);

ISet<string> tagSet = new HashSet<string>(allTags, comparer: StringComparer.CurrentCultureIgnoreCase);

// Act
methodSet.IntersectWith(tagSet);

// Assert
methodSet.Count.Should().Be(9);
}
I haven't finished yet. Here's my Constants thing: https://hastebin.com/wekenaboxa.csharp The distinct thing I was already using for duplicates sorry quick edit to my code: (Should().Be(9))
[Fact]
public void LongMethods_AndLongTags_AsIntersection()
{
// Arrange
string[] allMethods = Constants.LongExperiences
.SelectMany(exp => exp.Methods.Select(method => method.Trim()))
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToArray();
string[] allTags = Constants.LongTags
.Select(tag => tag.Trim())
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToArray();

ISet<string> methodSet = new HashSet<string>(allMethods, comparer: StringComparer.CurrentCultureIgnoreCase);
ISet<string> tagSet = new HashSet<string>(allTags, comparer: StringComparer.CurrentCultureIgnoreCase);

// Act
methodSet.IntersectWith(tagSet);

// Assert
methodSet.Count.Should().Be(9);
}
[Fact]
public void LongMethods_AndLongTags_AsIntersection()
{
// Arrange
string[] allMethods = Constants.LongExperiences
.SelectMany(exp => exp.Methods.Select(method => method.Trim()))
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToArray();
string[] allTags = Constants.LongTags
.Select(tag => tag.Trim())
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToArray();

ISet<string> methodSet = new HashSet<string>(allMethods, comparer: StringComparer.CurrentCultureIgnoreCase);
ISet<string> tagSet = new HashSet<string>(allTags, comparer: StringComparer.CurrentCultureIgnoreCase);

// Act
methodSet.IntersectWith(tagSet);

// Assert
methodSet.Count.Should().Be(9);
}
This is what I have currently, it works but it's not there yet, because for some reason I'm still at 7 instead of 9. Or I miscounted... so I'll recount manually just to be sure found the solution!
var allMethods = Constants.LongExperiences
.SelectMany(exp => exp.Methods.Select(method => method.Trim()))
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToHashSet(comparer: StringComparer.CurrentCultureIgnoreCase);
var allTags = Constants.LongTags
.Select(tag => tag.Trim())
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToHashSet(comparer: StringComparer.CurrentCultureIgnoreCase);

// Act
allMethods.IntersectWith(allTags);

// Assert
allMethods.Count.Should().Be(7);
var allMethods = Constants.LongExperiences
.SelectMany(exp => exp.Methods.Select(method => method.Trim()))
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToHashSet(comparer: StringComparer.CurrentCultureIgnoreCase);
var allTags = Constants.LongTags
.Select(tag => tag.Trim())
.Distinct(comparer: StringComparer.CurrentCultureIgnoreCase)
.ToHashSet(comparer: StringComparer.CurrentCultureIgnoreCase);

// Act
allMethods.IntersectWith(allTags);

// Assert
allMethods.Count.Should().Be(7);
This works perfect
malkav
malkav2y ago
Accord
Accord2y ago
✅ This post has been marked as answered!
Anton
Anton2y ago
this isn't the same behavior tho you're not tokenizing the methods
malkav
malkav2y ago
True enough, but it works wonders, and it ignores casing
Anton
Anton2y ago
ah yes also remove the distinct call distinct internally uses a hash set so you end up creating two hash sets per each variable hash set is already a set
malkav
malkav2y ago
Thanks, I’ll have a look. Am on my way to an event atm, so will do when I get home
Accord
Accord16mo ago
Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity. Was this issue resolved? If so, run /close - otherwise I will mark this as stale and this post will be archived until there is new activity.
Want results from more Discord servers?
Add your server
More Posts