Searching Country by Country code - c#

I am working on a search method, which will be called with Ajax, and updates a Webgrid in Mvc4.
The search will go through a list of Project objects, which contains some fields.
One of the fields is Country. And right now my code only checks if the input string contains the search string:
private bool StringStartWith(string input, string searchstring)
{
bool startwith = false;
var inputlist = new List<string>(input.ToLower().Split(' ').Distinct());
var searchList = new List<string>(searchstring.ToLower().Split(' '));
var count = (from inp in inputlist from sear in searchList where inp.StartsWith(sear) select inp).Count();
if (count == searchList.Count)
startwith = true;
return startwith;
}
But I also want to be able to search by country code. So if I write "DK", it should tell that it is equal to Denmark.
I hope I can get some help for it.
Thanks.
//UPDATE!!
iTURTEV answer helped me to make my method work as it should. I just had to update my method as shown here:
private bool InputStartWithSearch(string input, string searchstring)
{
if(searchstring[searchstring.Length-1].Equals(' '))
searchstring = searchstring.Substring(0,searchstring.Length-2);
bool startwith = false;
var inputlist = new List<string>(input.ToLower().Split(' ').Distinct());
var searchList = new List<string>(searchstring.ToLower().Split(' '));
if (searchstring.Length == 2)
{
var countryCode = new RegionInfo(searchstring.ToUpper()).EnglishName;
if (inputlist.Any(country => country.ToLower().Equals(countryCode.ToLower())))
{
return true;
}
}
var count = (from inp in inputlist from sear in searchList where inp.StartsWith(sear) select inp).Count();
if (count == searchList.Count)
startwith = true;
return startwith;
}
Thanks a lot.

May be you can use RegionInfo:
// returns Bulgaria
new RegionInfo("BG").EnglishName;

Assuming:
public class Country {
public int Id { get; set; }
public string Name { get; set; }
public string IsoCode { get; set; }
}
Then:
return x.Countries.Where(q =>
q.Name != null && q.Name.ToLowerInvariant().Contains(text) ||
q.IsoCode != null && q.IsoCode.ToLowerInvariant().Contains(text));
This will return every Country having text on its name or code. It's important to check for nulls unless you're using [Required] data annotation, if you don't want this to be case insensitive you could remove the .ToLowerInvariant().

Related

Simplest method to prove that the contents of two lists (containing objects) are equal

I am having a bit of a frustrating time finding a simple method to compare and prove that the contents of two lists are equal. I have looked at a number of solutions on stackoverflow but I have not been successful. Some of the solutions look like they will require a large amount of work to implement and do something that on the face of it to my mind should be simpler, but perhaps I am too simple to realize that this cannot be done simply :)
I have created a fiddle with some detail that can be viewed here: https://dotnetfiddle.net/cvQr5d
Alternatively please find the full example below, I am having trouble with the object comparison method (variable finalResult) as it's returning false and if the content were being compared I would expect the value to be true:
using System;
using System.Collections.Generic;
using System.Linq;
public class ResponseExample
{
public Guid Id { get; set; } = Guid.Parse("00000000-0000-0000-0000-000000000000");
public int Value { get; set; } = 0;
public string Initials { get; set; } = "J";
public string FirstName { get; set; } = "Joe";
public string Surname { get; set; } = "Blogs";
public string CellPhone { get; set; } = "0923232199";
public bool EmailVerified { get; set; } = false;
public bool CellPhoneVerified { get; set; } = true;
}
public class Program
{
public static void Main()
{
var responseOne = new ResponseExample();
var responseTwo = new ResponseExample();
var responseThree = new ResponseExample();
var responseFour = new ResponseExample();
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseThree);
objectListTwo.Add(responseFour);
bool result = objectListOne.Count == objectListTwo.Count();
Console.WriteLine($"Count: {result}");
bool finalResult = ScrambledEquals<ResponseExample>(objectListOne, objectListTwo);
Console.WriteLine($"Object compare: {finalResult}");
}
//https://stackoverflow.com/a/3670089/3324415
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
var cnt = new Dictionary<T,
int>();
foreach (T s in list1)
{
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T s in list2)
{
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
}
As people in comments have pointed out this will not work as comparing a complex type by default compares whether the reference is the same. Field by field comparison will not work without implementing equality methods (and then you would need to overload GetHashCode and so on). See https://learn.microsoft.com/en-us/dotnet/api/system.object.equals?view=net-5.0
However, if you can use c# 9, which is what you have in the fiddle you can define the type as a record instead of class. Records have built in field by field comparison. See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/records#characteristics-of-records
So public class ResponseExample would become public record ResponseExample and your code works as you expect.
Use Enumerable.All<TSource>(IEnumerable<TSource>, Func<TSource,Boolean>) Method which Determines whether all elements of a sequence satisfy a condition.
Once you have initilized your two List
list1.All(x=>list2.Contains(x))
This works by ensuring that all elements in list2 are containted in list1 otherwise returns false
Your method as is will compare if the 2 lists contain the same objects. So it is returning false as there are 4 different objects. If you create your list like this, using the same objects, it will return true:
List<ResponseExample> objectListOne = new List<ResponseExample>();
objectListOne.Add(responseOne);
objectListOne.Add(responseTwo);
List<ResponseExample> objectListTwo = new List<ResponseExample>();
objectListTwo.Add(responseTwo);
objectListTwo.Add(responseOne);
To get a true value when the contents of the objects are the same you could serialize the objects into a json string like this:
public static bool ScrambledEquals<T>(IEnumerable<T> list1, IEnumerable<T> list2)
{
JavaScriptSerializer json = new JavaScriptSerializer();
var cnt = new Dictionary<string,
int>();
foreach (T _s in list1)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]++;
}
else
{
cnt.Add(s, 1);
}
}
foreach (T _s in list2)
{
string s = json.Serialize(_s);
if (cnt.ContainsKey(s))
{
cnt[s]--;
}
else
{
return false;
}
}
return cnt.Values.All(c => c == 0);
}
If the performance is not a big deal, you can use Newtonsoft.Json. We will be able to compare different types of objects as well as run a deep equals check.
First install the package:
Install-Package Newtonsoft.Json
Here is the code snip:
public static bool DeepEqualsUsingJson<T>(IList<T> l1, IList<T> l2)
{
if (ReferenceEquals(l1, l2))
return true;
if (ReferenceEquals(l2, null))
return false;
if (l1.Count != l2.Count)
return false;
var l1JObject = l1.Select(i => JObject.FromObject(i)).ToList();
var l2JObject = l2.Select(i => JObject.FromObject(i)).ToList();
foreach (var o1 in l1JObject)
{
var index = l2JObject.FindIndex(o2 => JToken.DeepEquals(o1, o2));
if (index == -1)
return false;
l2JObject.RemoveAt(index);
}
return l2JObject.Count == 0;
}

Using Linq to determine if there is more than 2 distinct items in a class

I have a class that contains a list of another class which has a property that I want to check if it has more than one distinct value.
e.g
public class BasketModel
{
public BasketModel()
{
BasketOrderLines = new List<BasketOrderLine>();
}
.
.
.
public class BasketOrderLine
{
public int OrderLineId { get; set; }
public string ImageUrl { get; set; }
public string ProductType { get; set; }
.
.
Given a basket model object I want to find out if there are more than one distinct value in the ProductType.
e.g If all Product Types are "A" then that would be false, if 3 products are of type "A" and one is of type "B" then this would be true.
Cheers
Macca
Your title: "more than two distinct", your question body: "more than one distinct"
If the title is a typo:
bool notDistinctTypes = theBasket.BasketOrderLine
.Select(o => o.ProductType)
.Distinct()
.Skip(1)
.Any();
This doesn't need to enumerate all items to find out if there is more than one ProductType.
// Does this basket contains three or more types
public bool HasSeveralTypes(BasketModel basket)
{
if (basket == null)
return false;
int differentTypes = basket.BasketOrderLines
.Select(l => l.ProductType)
.Distinct()
.Count();
return (differentTypes > 2);
}
Something like this :
Public bool CheckDistinct (){
var res = basketOrderLines.Select(o => o.ProductType).Distinct ().Count ();
return res > 1;
}
There are a few ways to do this, here's one:
public class BasketModel
{
public BasketModel()
{
BasketOrderLines = new List<BasketOrderLine>();
}
public bool HasMulitpleDistinctProducts
{
get
{
if (!BasketOrderLines.Any())
{
return true; // or false?
}
return BasketOrderLines.Select(b => b.ProductType).Distinct().Count() > 1;
}
}
}
Here is a type extension you can call directly from your list. The pros of this code is to be adaptable to any type implementing IEquals and not only string + kick to use from your code.
The code :
public static class Tools
{
public static bool fDistinctProductType(this List<BasketOrderLine> lstToAnalyse)
{
BasketOrderLine ProductTypeA = lstToAnalyse.FirstOrDefault();
if (ProductTypeA == null) // It's null when lstToAnalyse is empty
return false;
BasketOrderLine ProductTypeB = lstToAnalyse.Where(b => b.ProductType.Equals(ProductTypeA.ProductType)).FirstOrDefault();
if (ProductTypeB == null) // It's null when it doesn't exists a distinct ProductType
return false;
return true;
}
}
How to call:
List<BasketOrderLine> lst = new List<BasketOrderLine>();
// Add element to list
if (lst.fDistinctProductType())
{
// DO Something
}

Check which elements are on one list comparing to another list LINQ

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;
}

Sorting a List<> using linq in C#

I would like to do "sorting" under Datagridview ColumnHeaderMouseClick.
Accending or Decending should need to be automatic, selected column value automatic.
I gone through many websites and tried some options but I could not be able to achieve my goal.
private void lst_Install_Item_Main_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
try
{
DataGridViewColumn newColumn = lst_Install_Item_Main.Columns[e.ColumnIndex];
List<test> temp = (List<test>)lst_Install_Item_Main.DataSource;
//var newList = temp.OrderBy(m => m.feet).ToList();
//var newList = temp.AsQueryable().OrderBy(m => m.feet).ToList();
temp.Sort((m1, m2) => m1.feet.CompareTo(m2.feet));
lst_Install_Item_Main.DataSource = temp;
lst_Install_Item_Main.Refresh();
}
catch (Exception ex)
{
MessageBox.Show("There was an error bringing sorting \n" + ex.Message, "Testing", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
Above code "sorting" the list on "feet" column but
I would like to pass the columnname which user clicked, is that possible ?
I have input like (1', 21', 123', 5', 10')
Above code sorting the list like >> (1', 10', 123', 21', 5')
But I want output like >> (1', 5', 10', 21', 123')
Is that possible to achieve ?
How to achieve Ascending or Descending here
(I mean while I clicked first time it will do Ascending and while click on same column second time it should need to do Descending)
Your suggestionshelp are greatly appreciated.
If you don't have negative values, then to sort as you want you need either to parse values as numeric or simply pad them:
temp.Sort((m1, m2) => m1.feet.PadLeft(2).CompareTo(m2.feet.PadLeft(2)));
When comparing strings "1", "5" and "10" this will be comparing " 1", " 5" and "10" instead (notice empty space, which is less than 0 character), making them sorted in the right order.
Make sure to chose number big enough to cover padding to the longest number.
You need to sort the values of feet as integers. To do this, you first need to remove the feet-symbol (') and afterwards parse the value to an int (temporarily, the value is still stored as a string).
This should do the trick:
temp.Sort((m1, m2) => int.Parse(m1.feet.Replace("'", "")).CompareTo(int.Parse(m2.feet.Replace("'", ""))));
Also, I would recommend that you don't store the feet-symbol in the value, and instead use some kind of formatting to include it, when showing the values in the Grid. That way you can avoid these kinds of conversions and comparisons, each time you need to use the values.
You have to convert your strings to integer-values as strings are differently ordered (lexicographically 10 comes before 2). Furtheremore as your input contains '-characters you have to delete them first using String.Trim('\'').
temp.Sort((m1, m2) => Convert.ToInt32(m1.feet.Trim('\'')).CompareTo(Convert.ToInt(m2.feet.Trim('\''))));
Alternativly you may also use Linq-OrderBy:
temp = temp.OrderBy(x => Convert.ToInt32(x.feet.Trim('\''))).ToList();
And OrderByDescending if in descending-order.
UPDATED for question # 3, added trigger for sort order
I would suggest you to use ICoparer in such way
public class TestComparer : IComparer<test>
{
bool isAscending;
public TestComparer(bool isAscendingOrder)
{
isAscending = isAscendingOrder;
}
int IComparer<test>.Compare(test a, test b)
{
int c1 = IntFromStr(a.feet);
int c2 = IntFromStr(b.feet);
int res;
res = (c1 > c2) ? 1 : (c1 < c2) ? -1 : 0;
return isAscending ? res : -res;
}
int IntFromStr(string s)
{
int result;
return (int.TryParse(s.Replace("'", ""), out result)) ? result : int.MaxValue;
}
}
this comparer will move not valid items to the end of the sorted list, also you will be able to change your sort behaviour easily, you will call it like below
List < test > lst = new List<test>();
// It will be your property to Trigger value each time you click
// (sortOrderTrigger = !sortOrderTrigger)
bool sortOrderTrigger = true;
lst.Add(new test { feet = "1'" });
lst.Add(new test { feet = "21'" });
lst.Add(new test { feet = "123'" });
lst.Add(new test { feet = "5'" });
lst.Add(new test { feet = "10'" });
lst.Add(new test { feet = "15'" });
lst.Add(new test { feet = "jj'" });
lst.Add(new test { feet = "ff'" });
lst.Sort(new TestComparer(sortOrderTrigger));
Thanks for all your help and Direction.
I have fixed my requirements as like below, Hope this help to someone like me :)
1. Created list which contains the same columns as the columns dataproperty.
public class sortList
{
public string Layer { get; set; }
public string mlenth { get; set; }
public string diameter { get; set; }
public string subtypecd { get; set; }
public string coatingtype { get; set; }
public string year { get; set; }
public string gisid { get; set; }
public string taxdistrict { get; set; }
public string lengthsource { get; set; }
public string shapelen { get; set; }
public string feet { get; set; }
public sortList()
{
this.Layer = ListSortDirection.Ascending.ToString();
this.mlenth = ListSortDirection.Ascending.ToString();
this.feet = ListSortDirection.Ascending.ToString();
this.diameter = ListSortDirection.Ascending.ToString();
this.subtypecd = ListSortDirection.Ascending.ToString();
this.coatingtype = ListSortDirection.Ascending.ToString();
this.year = ListSortDirection.Ascending.ToString();
this.gisid = ListSortDirection.Ascending.ToString();
this.taxdistrict = ListSortDirection.Ascending.ToString();
this.lengthsource = ListSortDirection.Ascending.ToString();
this.shapelen = ListSortDirection.Ascending.ToString();
}
}
2. Written code on ColumnHeaderMouseClick event and order function
private void lst_Install_Item_Main_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
{
try
{
//Get Column which clicked
DataGridViewColumn newColumn = lst_Install_Item_Main.Columns[e.ColumnIndex];
//Get Values from DataGrid
List<test> temp = (List<test>)lst_Install_Item_Main.DataSource;
//Get the sorting order for that column
string orderby = GetOrder(newColumn.DataPropertyName);
if (orderby == ListSortDirection.Ascending.ToString()) //Ascending
{
if (newColumn.DataPropertyName == "feet") //Feet column sort with double value and required trim
{
temp = temp.OrderBy(x => Convert.ToDouble(x.feet.Trim('\''))).ToList();
}
else if (newColumn.DataPropertyName == "shapelen") //Shapelen column sort with double value and required trim
{
temp = temp.OrderBy(x => Convert.ToDouble(x.shapelen.Trim('\''))).ToList();
}
else ///other columns having string value only.
{
temp = temp.OrderBy(x => x.GetType().GetProperty(newColumn.DataPropertyName).GetValue(x, null)).ToList();
}
}
else // Descending
{
if (newColumn.DataPropertyName == "feet") //Feet column sort with double value and required trim
{
temp = temp.OrderByDescending(x => Convert.ToDouble(x.feet.Trim('\''))).ToList();
}
else if (newColumn.DataPropertyName == "shapelen") //Shapelen column sort with double value and required trim
{
temp = temp.OrderByDescending(x => Convert.ToDouble(x.shapelen.Trim('\''))).ToList();
}
else //other columns having string value only.
{
temp = temp.OrderByDescending(y => y.GetType().GetProperty(newColumn.DataPropertyName).GetValue(y, null)).ToList();
}
}
lst_Install_Item_Main.DataSource = temp;
lst_Install_Item_Main.Refresh();
}
catch (Exception ex)
{
MessageBox.Show("There was an error while sorting \n" + ex.Message, "Closeout Calculator", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private string GetOrder(string columnName)
{
//Store the each coulmn(Ascending/Descending) values to make it dynamic
string ord = sortOrder[0].GetType().GetProperty(columnName).GetValue(sortOrder[0], null).ToString();
if (ord == ListSortDirection.Ascending.ToString())
{
sortOrder[0].GetType().GetProperty(columnName).SetValue(sortOrder[0], ListSortDirection.Descending.ToString(), null);
}
else
{ sortOrder[0].GetType().GetProperty(columnName).SetValue(sortOrder[0], ListSortDirection.Ascending.ToString(), null); }
return ord;
}
3. sortoder list initialize and obj declared at Constructor.
Private List<sortList> sortOrder = new List<sortList>();
sortOrder.Add(new sortList());

Interleave an array of email addresses avoiding items with same domain to be consecutive

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);
}
}
}

Categories