Net 6.0
Our company uses a metric layout that takes records that look like this
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
and transforms it into this
AAA|BLANK|BLANK|BLANK
AAA|0000A|BLANK|BLANK
AAA|0000B|BLANK|BLANK
AAA|0000A|000AA|BLANK
AAA|0000A|000AB|BLANK
AAA|0000B|000BA|BLANK
AAA|0000B|000BB|BLANK
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AA|0003A
AAA|0000A|000AA|0004A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
I have a custom object
public class HierarchyStructure
{
public string Employer { get; set; }
public string Level1 { get; set; }
public string Level2 { get; set; }
public string Level3 { get; set; }
public string Level4 { get; set; }
public bool isRxlvl3 { get; set; }
public bool isExpired { get; set; }
public string lvl4SubType { get; set; }
public HierarchyStructure(string employer,
string? level1 = null,
string? level2 = null,
string? level3 = null,
string? level4 = null,
bool isRxlvl3 = false,
bool isExpired = false,
string? lvl4SubType = null)
{
this.Employer = employer;
this.Level1 = string.IsNullOrEmpty(level1) ? string.Empty : level1;
this.Level2 = string.IsNullOrEmpty(level2) ? string.Empty : level2;
this.Level3 = string.IsNullOrEmpty(level3) ? string.Empty : level3;
this.Level4 = string.IsNullOrEmpty(level4) ? string.Empty : level4;
this.isRxlvl3 = isRxlvl3;
this.isExpired = isExpired;
this.lvl4SubType = string.IsNullOrEmpty(lvl4SubType) ? string.Empty : lvl4SubType;
}
}
I am trying to populate from a static level into a formatted output list
private List<HierarchyStructure> AllLevels => _allLevels;
private List<HierarchyStructure> LocationLevels => _LocationLevels;
I developed methods for each level call, but I can't figure out how to merge them into one method that either calls itself recursively or dynamically selects the right values. I do not think it is possible to combine these due to the different comparisons and return values.
public bool GetDistinctEmployers()
{
var selectedLevels = (from levels in AllLevels
select new HierarchyStructure(levels.Employer));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel1(string emp)
{
var selectedLevels = (from levels in AllLevels
where levels.Employer == emp
select new HierarchyStructure(levels.Employer,levels.Level1));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel2(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level1 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel3(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level2 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
public bool GetDistinctLevel4(string lvl)
{
var selectedLevels = (from levels in AllLevels
where levels.Level3 == lvl
select new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4));
foreach(HierarchyStructure level in selectedLevels) LocationLevels.Add(level);
return true;
}
When I try and combine all these together, it ends up being soooo nested and I feel like there must be a better way to accomplish this. I am fairly certain this level of nesting will error my quality gate on my pipeline.
public void ProcessLevels()
{
if(GetDistinctEmployers())
{
foreach(string employer in AllLevels.Select(x => x.Employer))
{
if(GetDistinctLevel1(employer))
{
foreach(string level1 in AllLevels.Select(x => x.Level1))
{
if(GetDistinctLevel2(level1))
{
foreach(string level2 in AllLevels.Select(x => x.Level2))
{
if(GetDistinctLevel3(level2))
{
foreach(string level3 in AllLevels.Select(x => x.Level3))
{
if(GetDistinctLevel4(level3))
{
SaveData();
}
}
}
}
}
}
}
}
}
}
I am also getting another inline quality flag that my expressions in this block can be shortened using LINQ. I dont understand how my one line can be simplified.
...AllLevels.Select(x => x.Employer)
...AllLevels.Select(x => x.Level1)
... etc
A view of the data output I am trying to get.
Update: Thank you for the help offered - using the answers provided in this thread, I have coded my solution like this eliminating a lot of loops.
public void ProcessLevels()
{
var employers = AllLevels.Where(x=> !x.isExpired ).Select(x => x.Employer).Distinct();
var lvl1 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1 }).Distinct();
var lvl2 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2 }).Distinct();
var lvl3 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2, x.Level3 }).Distinct();
var lvl4 = AllLevels.Where(x=> !x.isExpired ).Select(x => new {x.Employer, x.Level1, x.Level2, x.Level3, x.Level4 }).Distinct();
foreach(var emp in employers) { LocationLevels.Add(new LevelHierarchyStructure(emp)); }
foreach(var lvl in lvl1) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1)); }
foreach(var lvl in lvl2) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2)); }
foreach(var lvl in lvl3) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2, lvl.Level3)); }
foreach(var lvl in lvl4) { LocationLevels.Add(new LevelHierarchyStructure(lvl.Employer, lvl.Level1, lvl.Level2, lvl.Level3, lvl.Level4)); }
SaveData();
}
I assume someone will flag me for posting my update, but im posting it so ppl understand what i did and see my final solution
Perhaps you need something that first expands each source row into all of the possible levels, and runs those results through a .Distinct() and an .OrderBy().
Given:
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B|000BA|0001A
AAA|0000B|000BB|0001B
The expanded data would be:
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0002A
...
AAA
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
Then a .Distinct() and .OrderBy()
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
...
AAA|0000B
AAA|0000B|000BB
AAA|0000B|000BB|0001B
...
You would need a generating function to expand each source row into an enumerated list than can be fed into a .SelectMany(), and may also need to define custom comparators to be used by the .Distinct() and a .OrderBy() functions.
Something like:
private static IEnumerable<HierarchyStructure> HierarchyGenerator(this HierarchyStructure level)
{
yield return new HierarchyStructure(levels.Employer);
yield return new HierarchyStructure(levels.Employer,levels.Level1);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3);
yield return new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4);
}
...
LocationLevels = AllLevels
.SelectMany(levels => levels.HierarchyGenerator())
.Distinct(...custom HierarchyStructure IEqualityComparer...)
.OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
.ToList();
There are other ways, such as generating distinct values are each level, unioning them all together, and then feeding them to the sort.
Something like:
var level0 = AllLevels
.Select(levels => newHierarchyStructure(levels.Employer))
.Distinct(IEqualityComparer...);
...
var level4 = AllLevels
.Select(levels => new HierarchyStructure(levels.Employer,levels.Level1, levels.Level2, levels.Level3, levels.Level4))
.Distinct(IEqualityComparer...);
LocationLevels = level0
.Union(level1)
.Union(level2)
.Union(level3)
.Union(level4)
.OrderBy(hier => hier, ...custom HierarchyStructure IComparer...)
.ToList();
There may be some performance tradeoffs in selecting where the distinct operations are applied. Using intermediate anonymous objects might also help, such as:
var level1 = AllLevels
.Select(levels => new {levels.Employer, levels.Level1})
.Distinct()
.Select(levels => new HierarchyStructure(levels.Employer,levels.Level1));
Here the .Distinct() uses the default comparator for the anonymous object, which compares each contained value.
The custom comparers can also be incorporated into the HierarchyStructure class by implementing the IComparable interface. The HierarchyGenerator() function could also be made a member function within the HierarchyStructure class.
(My apologies in advance for any syntax errors. This is untested. I'll update the above given any comments.)
This seems to be able to be solved quite easily with LINQ.
I'll start with this input:
string[] input = new[]
{
"AAA|0000A|000AA|0001A",
"AAA|0000A|000AA|0002A",
"AAA|0000A|000AB|0001B",
"AAA|0000A|000AB|0002B",
"AAA|0000B|000BA|0001A",
"AAA|0000B|000BB|0001B",
};
Now I can transform this into the output like this:
string[][] parts = input.Select(x => x.Split('|')).ToArray();
int max = parts.Max(p => p.Length);
string[][] expanded =
Enumerable
.Range(0, max)
.SelectMany(i => parts.Select(p => p.Take(i + 1).ToArray()).ToArray())
.DistinctBy(xs => String.Join("|", xs))
.OrderBy(xs => String.Join("|", xs))
.ToArray();
string[] output =
expanded
.Select(e => String.Join("|", e))
.ToArray();
That gives me:
AAA
AAA|0000A
AAA|0000A|000AA
AAA|0000A|000AA|0001A
AAA|0000A|000AA|0002A
AAA|0000A|000AB
AAA|0000A|000AB|0001B
AAA|0000A|000AB|0002B
AAA|0000B
AAA|0000B|000BA
AAA|0000B|000BA|0001A
AAA|0000B|000BB
AAA|0000B|000BB|0001B
If you just want to stop to fill a grid, just use expanded.
With this approach it doesn't matter how many levels deep the source data is.
Related
I have two lists classes
public class class1{
public Int Id { get; set; }
public Bool Flag{ get; set; }
}
public class class2{
public Int Id { get; set; }
}
Now i have List<class1> and List<class2>,
Now i have to update Flag property to true in List<class1> for only those Ids which match with the Id's present in List<class2> using lambda expression c#.Don't want to use foreach.
using lambda expression. Don't want to use foreach.
That's usually a silly requirement and a hallmark that you're not really familiar with C#, Linq or performance analysis. You have a collection whose elements you want to modify, so you should use foreach().
If you're trying out functional programming, then you should treat the list elements as immutable and project into a new collection.
The first part of your problem, looking up which list elements to modify based on a presence of one of their properties in another collection's elements' properties, is trivial:
var elementsToModify = list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id));
Now with a foreach(), this'll be simple:
foreach (var l1 in elementsToModify)
{
l1.Flag = true;
}
Or, even denser (not that less code equals more performance):
foreach (var l1 in list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id)))
{
l1.Flag = true;
}
So, there's your code. But you didn't want to use foreach(). Then you need to project into a new collection:
var newList1 = list1.Where(l1 => list2.Any(l2 => l2.Id == l1.Id))
.Select(l1 => new Class1
{
Id = l1.Id,
Flag = true,
})
.ToList();
There you have it, a List<Class1> with only flagged items. Optionally you could use this list in a foreach() to update the original list1. Oh, wait.
The below solution does not use the classical "for each", but is compiled to one under the hood. If that's not what you meant, then please explain what you are trying to achieve. Using for each in this example is a good approach. One could also use while or for loops, but is it really what's being asked here?
Object definition:
public class MyObject
{
public int Id { get; set; }
public bool Flag { get; set; }
}
List initialization:
var list = new List<MyObject>()
{
new MyObject() { Id= 1 },
new MyObject() { Id= 2 },
new MyObject() { Id= 3 },
new MyObject() { Id= 4 }
};
var list2 = new List<MyObject>()
{
new MyObject() { Id= 2 },
new MyObject() { Id= 4 }
};
Code:
list.ForEach(el => el.Flag = list2.Any(el2 => el2.Id == el.Id));
EDIT:
An example with a while loop (a bit nasty to do it this way):
int i = -1;
int numberOfElements = list.Count;
while (++i < numberOfElements)
{
list[i].Flag = list2.Any(el => el.Id == list[i].Id);
}
I guess you can write a for loop yourself...
I have a method where list cannot convert to ienumerable. How do i cast???
public ActionResult Attending()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier).Value;
var gigs = _context.Attendances.Include(a => a.Gig.Artist).Include(a => a.Gig.Genre).Where(a => a.AttendeeId == userId).ToList();
var viewModel = new GigsViewModel()
{
UpcomingGigs = gigs,
ShowActions = User.Identity.IsAuthenticated,
Heading = "Gigs I'm Attending"
};
return View("Gigs", viewModel);
}
Here is my ViewModel:
public class GigsViewModel
{
public IEnumerable<Gig> UpcomingGigs { get; set; }
public bool ShowActions { get; set; }
public string Heading { get; set; }
}
Try using below code, AsEnumerable() will do your work
var gigs = _context.Attendances.Where(a => a.AttendeeId == userId).select(a=>
new Gig()
{
property = a.property //assuming property, you can repeat the same for other properties of Gig
}).AsEnumerable();
The result of a Linq database query is typically IQueryable which is derived from IEnumerable, IQueryable, and IEnumerable.
In your case,it's linq database query so you need to call AsEnumerable();
You can also first fetch all attendees, and select the gigs.
IEnumerable<Gig> gigs = _context.Attendances.Where(a => a.AttendeeId == userId).Select(a => a.Gig).ToList();
Be aware if there can be multiple similar gigs, then perhaps you need only distinct set of gigs using Distinct:
IEnumerable<Gig> gigs = _context.Attendances.Where(a => a.AttendeeId == userId).Select(a => a.Gig).Distinct().ToList();
See more at: https://msdn.microsoft.com/en-us/library/bb534803(v=vs.110).aspx
Edited: edited the old suggestion, Distinct and Select added.
I have two lists, one of all languages and another subset of languages that the site has, the idea is to return all the languages but change the property of a boolean if the element of the subset corresponds to the list of all languages.
DTO of language:
public class DTOLanguage
{
public bool HaveLanguage { get; set; }
public int IdLanguage { get; set; }
//Other properties...
}
Method that returns all languages:
public List<DTOLanguage> GetLanguages()
{
var result = repository.RepSite.GetLanguages().Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage,
CodName = x.CodName,
Name = x.Name
}).ToList();
return result;
}
Method that returns the subset of languages:
public List<DTOLanguage> GetLanguagesById(int idSite)
{
var result = repository.RepSite.GetLanguagesById(idSite).Select(x => new DTOLanguage
{
IdLanguage = x.IdLanguage
}).ToList();
return result;
}
The GetLanguagesById is called in the DataAccess layer, so what Im thinking is that this method should receive another parameter (what GetLanguages returns) and make some fancy LINQ there.
I know that I can filter (example):
SubsetOfLanguages.Where(lg => lg.IdLanguage == AllLanguagesItem.IdLanguage)
{
AllLanguagesItem.HaveLanguage = True;
}
But Im not really sure as how it should be.
Thanks in advance.
You could use Contains extension method this way:
var languages=GetLanguages();
var subsetids=repository.RepSite.GetLanguagesById(idSite).Select(x =>x.IdLanguage);//Select just the id value
foreach(var l in languages.Where(l=>subsetids.Contains(l.IdLanguage)))
{
l.HaveLanguage = true;
}
You could do this:
var allLanguages = GetLanguages();
var subset = SubsetOfLanguages
.Where(lg => allLanguages.Any(a => lg.IdLanguage == a.IdLanguage))
.ToArray();
foreach(var item in subset)
{
item.HaveLanguage = True;
}
I have the following RavenDB Index:
public class RidesByPostcode : AbstractIndexCreationTask<Ride, RidesByPostcode.IndexEntry>
{
public class IndexEntry
{
public string PostcodeFrom { get; set; }
public string PostcodeTo { get; set; }
}
public RidesByPostcode()
{
Map = rides => from doc in rides
select new
{
doc.DistanceResult.PostcodeFrom,
doc.DistanceResult.PostcodeTo
};
StoreAllFields(FieldStorage.Yes);
}
}
I also have a list of strings representing postcodes, and I want to get all the Rides for which the PostcodeFrom is in the list of postcodes:
var postcodes = new List<string> { "postcode 1", "postcode 2" };
var rides = _database.Query<RidesByPostcode.IndexEntry, RidesByPostcode>()
.Where(x => postcodes.Contains(x.PostcodeFrom))
.OfType<Ride>()
.ToList();
But of course RavenDb says it cannot understand the .Contains expression.
How can I achieve such a query in RavenDb without having to call .ToList() before the where clause?
Ok, I found the answer: RavenDb's .In() extension method (see the "Where + In" section of the docs).
Apparently I was thinking from the outside in, instead of from the inside out :)
This is the final query:
var rides = _database.Query<RidesByPostcode.IndexEntry, RidesByPostcode>()
.Where(x => !x.IsAccepted && x.PostcodeFrom.In(postcodes))
.OfType<Ride>()
.ToList();
I'm looking for an efficient way of sorting an array of email addresses to avoid items with the same domain to be consecutive, in C#.
Email addresses inside the array are already distinct and all of them are lower case.
Example:
Given an array with the following entries:
john.doe#domain1.com
jane_doe#domain1.com
patricksmith#domain2.com
erick.brown#domain3.com
I would like to obtain something similar to the following:
john.doe#domain1.com
patricksmith#domain2.com
jane_doe#domain1.com
erick.brown#domain3.com
With the help of an extension method (stolen from https://stackoverflow.com/a/27533369/172769), you can go like this:
List<string> emails = new List<string>();
emails.Add("john.doe#domain1.com");
emails.Add("jane_doe#domain1.com");
emails.Add("patricksmith#domain2.com");
emails.Add("erick.brown#domain3.com");
var q = emails.GroupBy(m => m.Split('#')[1]).Select(g => new List<string>(g)).Interleave();
The Interleave method is defined as:
public static IEnumerable<T> Interleave<T>(this IEnumerable<IEnumerable<T>> source )
{
var queues = source.Select(x => new Queue<T>(x)).ToList();
while (queues.Any(x => x.Any())) {
foreach (var queue in queues.Where(x => x.Any())) {
yield return queue.Dequeue();
}
}
}
So basically, we create groups based on the domain part of the email adresses, project (or Select) each group into a List<string>, and then "Interleave" those lists.
I have tested against your sample data, but more thorough testing might be needed to find edge cases.
DotNetFiddle snippet
Cheers
This will distribute them semi-evenly and attempt to avoid matching domains next to each other (although in certain lists that may be impossible). This answer will use OOP and Linq.
DotNetFiddle.Net Example
using System;
using System.Linq;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
var seed = new List<string>()
{
"1#a.com",
"2#a.com",
"3#a.com",
"4#a.com",
"5#a.com",
"6#a.com",
"7#a.com",
"8#a.com",
"9#a.com",
"10#a.com",
"1#b.com",
"2#b.com",
"3#b.com",
"1#c.com",
"4#b.com",
"2#c.com",
"3#c.com",
"4#c.com"
};
var work = seed
// Create a list of EmailAddress objects
.Select(s => new EmailAddress(s)) // s.ToLowerCase() ?
// Group the list by Domain
.GroupBy(s => s.Domain)
// Create a List<EmailAddressGroup>
.Select(g => new EmailAddressGroup(g))
.ToList();
var currentDomain = string.Empty;
while(work.Count > 0)
{
// this list should not be the same domain we just used
var noDups = work.Where(w => w.Domain != currentDomain);
// if none exist we are done, or it can't be solved
if (noDups.Count() == 0)
{
break;
}
// find the first group with the most items
var workGroup = noDups.First(w => w.Count() == noDups.Max(g => g.Count()));
// get the email address and remove it from the group list
var workItem = workGroup.Remove();
// if the group is empty remove it from *work*
if (workGroup.Count() == 0)
{
work.Remove(workGroup);
Console.WriteLine("removed: " + workGroup.Domain);
}
Console.WriteLine(workItem.FullEmail);
// last domain looked at.
currentDomain = workItem.Domain;
}
Console.WriteLine("Cannot disperse email addresses affectively, left overs:");
foreach(var workGroup in work)
{
while(workGroup.Count() > 0)
{
var item = workGroup.Remove();
Console.WriteLine(item.FullEmail);
}
}
}
public class EmailAddress
{
public EmailAddress(string emailAddress)
{
// Additional Email Address Validation
var result = emailAddress.Split(new char[] {'#'}, StringSplitOptions.RemoveEmptyEntries)
.ToList();
if (result.Count() != 2)
{
new ArgumentException("emailAddress");
}
this.FullEmail = emailAddress;
this.Name = result[0];
this.Domain = result[1];
}
public string Name { get; private set; }
public string Domain { get; private set; }
public string FullEmail { get; private set; }
}
public class EmailAddressGroup
{
private List<EmailAddress> _emails;
public EmailAddressGroup(IEnumerable<EmailAddress> emails)
{
this._emails = emails.ToList();
this.Domain = emails.First().Domain;
}
public int Count()
{
return _emails.Count();
}
public string Domain { get; private set; }
public EmailAddress Remove()
{
var result = _emails.First();
_emails.Remove(result);
return result;
}
}
}
Output:
1#a.com
1#b.com
2#a.com
1#c.com
3#a.com
2#b.com
4#a.com
2#c.com
5#a.com
3#b.com
6#a.com
3#c.com
7#a.com
removed: b.com
4#b.com
8#a.com
removed: c.com
4#c.com
9#a.com
Cannot disperse email addresses affectively, left overs:
10#a.com
Something like this will spread them equally, but you will have the problems (=consecutive elements) at the end of the new list...
var list = new List<string>();
list.Add("john.doe#domain1.com");
list.Add("jane_doe#domain1.com");
list.Add("patricksmith#domain2.com");
list.Add("erick.brown#domain3.com");
var x = list.GroupBy(content => content.Split('#')[1]);
var newlist = new List<string>();
bool addedSomething=true;
int i = 0;
while (addedSomething) {
addedSomething = false;
foreach (var grp in x) {
if (grp.Count() > i) {
newlist.Add(grp.ElementAt(i));
addedSomething = true;
}
}
i++;
}
Edit: Added a high level description :)
What this code does is group each element by the domain, sort the groups by size in descending order (largest group first), project the elements of each group into a stack, and pop them off of each stack (always pop the next element off the largest stack with a different domain). If there is only a single stack left, then its contents are yielded.
This should make sure that all domains distributed as evenly as possible.
MaxBy extension method from: https://stackoverflow.com/a/31560586/969962
private IEnumerable<string> GetNonConsecutiveEmails(List<string> list)
{
var emailAddresses = list.Distinct().Select(email => new EmailAddress { Email = email, Domain = email.Split('#')[1]}).ToArray();
var groups = emailAddresses
.GroupBy(addr => addr.Domain)
.Select (group => new { Domain = group.Key, EmailAddresses = new Stack<EmailAddress>(group)})
.ToList();
EmailAddress lastEmail = null;
while(groups.Any(g => g.EmailAddresses.Any()))
{
// Try and pick from the largest stack.
var stack = groups
.Where(g => (g.EmailAddresses.Any()) && (lastEmail == null ? true : lastEmail.Domain != g.Domain))
.MaxBy(g => g.EmailAddresses.Count);
// Null check to account for only 1 stack being left.
// If so, pop the elements off the remaining stack.
lastEmail = (stack ?? groups.First(g => g.EmailAddresses.Any())).EmailAddresses.Pop();
yield return lastEmail.Email;
}
}
class EmailAddress
{
public string Domain;
public string Email;
}
public static class Extensions
{
public static T MaxBy<T,U>(this IEnumerable<T> data, Func<T,U> f) where U:IComparable
{
return data.Aggregate((i1, i2) => f(i1).CompareTo(f(i2))>0 ? i1 : i2);
}
}
What I am trying to do here is to sort them first.
Then I re-arrange from a different end. I'm sure there're more efficient ways to do this but this is one easy way to do it.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
String[] emails = { "john.doe#domain1.com", "jane_doe#domain1.com", "patricksmith#domain2.com", "erick.brown#domain3.com" };
var result = process(emails);
}
static String[] process(String[] emails)
{
String[] result = new String[emails.Length];
var comparer = new DomainComparer();
Array.Sort(emails, comparer);
for (int i = 0, j = emails.Length - 1, k = 0; i < j; i++, j--, k += 2)
{
if (i == j)
result[k] = emails[i];
else
{
result[k] = emails[i];
result[k + 1] = emails[j];
}
}
return result;
}
}
public class DomainComparer : IComparer<string>
{
public int Compare(string left, string right)
{
int at_pos = left.IndexOf('#');
var left_domain = left.Substring(at_pos, left.Length - at_pos);
at_pos = right.IndexOf('#');
var right_domain = right.Substring(at_pos, right.Length - at_pos);
return String.Compare(left_domain, right_domain);
}
}
}