String concatenation C# with List of Class - c#

this may be very fundamental C# question as I only study C# myself there are things that I do not have the logic to start.
I have a class CustomerSite with string Customer {get;set} and string Site {get;set;}
I create a list List<CustomerSite> listCustomerSite= new List<CustomerSite>();
Assume, I have a list with the following data
SAMSUNG CHINA
SAMSUNG AMERICA
SAMSUNG AFRICA
LG CHINA
APPLE AMERICA
APPLE CHINA
I would like to have 1 concatenated string
string Result = "APPLE (AMERICA, CHINA), LG (CHINA), SAMSUNG (AFRICA, AMERICA, CHINA)"
How could I do that?
My idea is to use a dictionary to keep a list of distinct Customers and adding the site to the string but I still have no clue how to deal with sorting (AFRICA --> AMERICA --> CHINA)
Dictionary<string, int> dictCustomer = new Dictionary<string, int>();
foreach (var i in listCustomerSite)
{
if (!dictCustomer.ContainsKey(i.Customer))
{
dictCustomer.Add(i.Customer, 0);
Result = Result + "," + "i.Customer" + "( i.Site) ";
}
else
{
Result.Replace(")", "," + i.Site + ")");
}
}

You can utilize LINQ and String.Join to group your collection by customer, convert each grouping to a formatted string with sorted comma-separated sites, and then combine them to a single string:
var customersWithLocations = listCustomerSite
.GroupBy(cs => cs.Customer)
.Select(g => $"{g.Key} ({String.Join(", ", g.Select(cs => cs.Site).OrderBy(s => s))})")
.ToArray();
string result = String.Join(", ", customersWithLocations);

A one line LINQ statment can do it all:
var list = listCustomerSite
.GroupBy(c => c.Customer)
.Select(g => $"{g.Key} ({string.Join(", ", g.Select(c => c.Site))})")
.ToList();

This should produce the desired output, sorted as required:
public class CustomerSite
{
public CustomerSite(string customer, string site)
{
Customer = customer;
Site = site;
}
public string Customer { get; set; }
public string Site { get; set; }
}
class Program
{
static void Main(string[] args)
{
var listCustomerSite = new List<CustomerSite>()
{
new CustomerSite("SAMSUNG", "CHINA"),
new CustomerSite("SAMSUNG", "AMERICA"),
new CustomerSite("SAMSUNG", "AFRICA"),
new CustomerSite("LG", "CHINA"),
new CustomerSite("APPLE", "AMERICA"),
new CustomerSite("APPLE", "CHINA")
};
var list = from cs in listCustomerSite
group cs by cs.Customer into g
orderby g.Key
select $"{g.Key} ({string.Join(", ", g.OrderBy(c => c.Site).Select(c => c.Site))})";
Console.WriteLine(string.Join(", ", list));
Console.ReadKey();
}
}
Output:
APPLE (AMERICA, CHINA), LG (CHINA), SAMSUNG (AFRICA, AMERICA, CHINA)
Hope this helps!

The idea of using a dictionary is good; however, I would operate in two steps:
Gather the data into sorted collections.
Concatenate the customers and sites.
I'm not going to use LINQ, as it will be more instructive and show the fundamental way of doing things (since you are learning C#).
I am using a SortedDictionary<TKey, TValue> as it automatically sorts the entries by the key (the customers in our case).
var dictCustomer = new SortedDictionary<string, SortedSet<string>>();
The idea is to use the customer name as key and a sorted set of sites as value. A SortedSet<T> not only keeps the entries sorted, but also eliminates duplicates (which is not required here).
foreach (CustomerSite customerSite in listCustomerSite) {
if (dictCustomer.TryGetValue(customerSite.Customer, out var siteSet)) {
siteSet.Add(customerSite.Site);
} else {
siteSet = new SortedSet<string> { customerSite.Site };
dictCustomer.Add(customerSite.Customer, siteSet);
}
}
Now, let us build the result string:
var sb = new StringBuilder();
foreach (var keyValuePair in dictCustomer) {
if (sb.Length > 0) {
sb.Append(", ");
}
sb.Append(keyValuePair.Key).Append(" ("); // Appends the customer and " (".
bool nextSite = false;
foreach (string site in keyValuePair.Value) {
if (nextSite) {
sb.Append(", ");
}
sb.Append(site);
nextSite = true;
}
sb.Append(")");
}
string result = sb.ToString();
Note that a StringBuilder is more efficient than concatenating strings directly, since a StringBuilder uses a buffer internally which grows only as needed, while string concatenation creates a new string object at each step and involves copying an ever-growing string over and over.

Related

convert the strings in my list to a list c#

i'm quite new to this, i already did a little c# in unity, but never really in VS.
I have a txt file that looks approximatively like this :
monday;8;server1,server2,server3
tuesday;9;server3,server4
wedneday;8;server1,server2,server4
i splitted this into 3 list, one with the day (monday)... one with the hour (8)... and one with the servers(server1,server2,server3), but i would like to convert this server string into unique lists,
for example i would like a list1 to contain all servers to of monday (server1,server2,server3), a list2 to contain all servers of tuesday (server3,server4).
These servers names are splitted with a comma, and i would like to split every string into a
unique list
i know i was not very clear, please ask for any specifications
List<string> plages_horaires_ouverture = File.ReadAllLines(#"file.txt").ToList();
foreach (var fileLine in plages_horaires_ouverture)
{
var splitLignes = fileLine.Split(new[] { ";" }, StringSplitOptions.None);
listeJoursOuverture.Add(splitLignes[0]);
listeHeuresOuverture.Add(splitLignes[1]);
listeServeursOuverture.Add(splitLignes[2]);
}
this code splits the txt file into 3 separate lists containing strings, and now i would like to convert every element (string) of the list "listeServeursOuverture" (sorry for the french name) into a unique list. Every element in this list looks like this "server1,server2,server4" or "server2,server3" and is separated with a comma
Any ideas? I tried many things but none worked, this question is certainly stupid, and im sorry for that
Leno
Use List.AddRange():
List<String> servers = new List<String>();
servers.AddRange(splitLignes[2].Split(new[] { "," }, StringSplitOptions.None));
I'd use a dictionary here:
List<string> plages_horaires_ouverture = File.ReadAllLines(#"file.txt").ToList();
Dictionary<KeyValuePair<string, string>,List<string>> servers = new Dictionary<KeyValuePair<string, string>,List<string>>
foreach (var fileLine in plages_horaires_ouverture)
{
var splitLignes = fileLine.Split(new[] { ";" }, StringSplitOptions.None);
KeyValuePair<string, string> key = new KeyValuePair<string, string>(splitLignes[0], splitLignes[1]);
List<string> serversForDay = splitLignes[2].Split(new[] { "," }, StringSplitOptions.None);
servers.Add(key, serversForDay);
}
Instead of spreading your data into different Lists, I'd have one List (or at least some kind of DataStructure. You may later find a Dictionary helpful, maybe).
In that List, I'd have instances of a Model class like for example this one:
public class MyModel
{
public string Day {get; set;}
public int Hour {get; set;}
public List<string> Servers {get; set;} = new List<string>();
}
Then you can look up the Server Lists by Day as you want if I read the question correctly.
Populating may go something like this:
foreach (var fileLine in plages_horaires_ouverture)
{
var splitLignes = fileLine.Split(new[] { ";" }, StringSplitOptions.None);
var model = new MyModel{
Day = splitLignes[0],
Hour = int.Parse(splitLignes[1].Trim())
};
model.Servers.AddRange(splitLignes[2].Split(new[] { "," }, StringSplitOptions.None));
listeOverture.Add(model);
// don't need these anymore.
// listeJoursOuverture.Add(splitLignes[0]);
// listeHeuresOuverture.Add(splitLignes[1]);
// listeServeursOuverture.Add(splitLignes[2]);
}
You can edit the names and the types as needed. Make a class object to sort the data as you like, then make a method that returns a list of that class. This is more clear for you when you need to deal with your code later.
public List<DataModel> GetListFromData()
{
List<string> plages_horaires_ouverture = File.ReadAllLines( #"file.txt" ).ToList();
List<DataModel> list = new List<Dd>();
foreach ( var fileLine in plages_horaires_ouverture )
{
var splitLignes = fileLine.Split( ";" , StringSplitOptions.None );
List<string> d = splitLignes[0].Split( "," ).ToList();
List<string> h =splitLignes[1].Split( "," ).ToList();
List<string> s =splitLignes[2].Split( "," ).ToList();
list.Add( new DataModel( d , h[0] , s ) );
}
return list;
}
class DataModel
{
public DataModel( List<string> days,string hour , List<string> servers )
{
Hour = hour;
Days = days;
Servers = servers;
}
public string Hour { get; set; }
public List<string> Days { get; set; }
public List<string> Servers { get; set; }
}

creating array of bad names to check and replace in c#

I'm looking to create a method that loops through an list and replaces with matched values with a new value. I have something working below but it really doesnt follow the DRY principal and looks ugly.
How could I create a dictionary of value pairs that would hold my data of values to match and replace?
var match = acreData.data;
foreach(var i in match)
{
if (i.county_name == "DE KALB")
{
i.county_name = "DEKALB";
}
if (i.county_name == "DU PAGE")
{
i.county_name = "DUPAGE";
}
}
In your question, you can try to use linq and Replace to make it.
var match = acreData.data.ToList();
match.ForEach(x =>
x.county_name = x.county_name.Replace(" ", "")
);
or you can try to create a mapper table to let your data mapper with your value. as #user2864740 say.
Dictionary<string, string> dict = new Dictionary<string, string>();
dict.Add("DE KALB", "DEKALB");
dict.Add("DU PAGE", "DUPAGE");
var match = acreData.data;
string val = string.Empty;
foreach (var i in match)
{
if (dict.TryGetValue(i.county_name, out val))
i.county_name = val;
}
If this were my problem and it is possible a county could have more than one common misspelling I would create a class to hold the correct name and the common misspellings. The you could easily determine if the misspelling exists and correct if. Something like this:
public class County
{
public string CountyName { get; set; }
public List<string> CommonMisspellings { get; set; }
public County()
{
CommonMisspellings = new List<string>();
}
}
Usage:
//most likely populate from db
var counties = new List<County>();
var dekalb = new County { CountyName = "DEKALB" };
dekalb.CommonMisspellings.Add("DE KALB");
dekalb.CommonMisspellings.Add("DE_KALB");
var test = "DE KALB";
if (counties.Any(c => c.CommonMisspellings.Contains(test)))
{
test = counties.First(c => c.CommonMisspellings.Contains(test)).CountyName;
}
If you are simply replacing all words in a list containing space without space, then can use below:
var newList = match.ConvertAll(word => word.Replace(" ", ""));
ConvertAll returns a new list.
Also, I suggest not to use variable names like i, j, k etc..but use temp etc.
Sample code below:
var oldList = new List<string> {"DE KALB", "DE PAGE"};
var newList = oldList.ConvertAll(word => word.Replace(" ", ""));
We can try removing all the characters but letters and apostroph (Cote d'Ivoire has it)
...
i.country_name = String.Concat(i.country_name
.Where(c => char.IsLetter(c) || c == '\''));
...
I made a comment under answer of #Kevin and it seems it needs further explanation. Sequential searching in list does not scale well and unfortunately for Kevin, that is not my opinion, asymptotic computational complexity is math. While searching in dictionary is more or less O(1), searching in list is O(n). To show a practical impact for solution with 100 countries with 100 misspellings each, lets make a test
public class Country
{
public string CountryName { get; set; }
public List<string> CommonMisspellings { get; set; }
public Country()
{
CommonMisspellings = new List<string>();
}
}
static void Main()
{
var counties = new List<Country>();
Dictionary<string, string> dict = new Dictionary<string, string>();
Random rnd = new Random();
List<string> allCountryNames = new List<string>();
List<string> allMissNames = new List<string>();
for (int state = 0; state < 100; ++state)
{
string countryName = state.ToString() + rnd.NextDouble();
allCountryNames.Add(countryName);
var country = new Country { CountryName = countryName };
counties.Add(country);
for (int miss = 0; miss < 100; ++miss)
{
string missname = countryName + miss;
allMissNames.Add(missname);
country.CommonMisspellings.Add(missname);
dict.Add(missname, countryName);
}
}
List<string> testNames = new List<string>();
for (int i = 0; i < 100000; ++i)
{
if (rnd.Next(20) == 1)
{
testNames.Add(allMissNames[rnd.Next(allMissNames.Count)]);
}
else
{
testNames.Add(allCountryNames[rnd.Next(allCountryNames.Count)]);
}
}
System.Diagnostics.Stopwatch st = new System.Diagnostics.Stopwatch();
st.Start();
List<string> repairs = new List<string>();
foreach (var test in testNames)
{
if (counties.Any(c => c.CommonMisspellings.Contains(test)))
{
repairs.Add(counties.First(c => c.CommonMisspellings.Contains(test)).CountryName);
}
}
st.Stop();
Console.WriteLine("List approach: " + st.ElapsedMilliseconds.ToString() + "ms");
st = new System.Diagnostics.Stopwatch();
st.Start();
List<string> repairsDict = new List<string>();
foreach (var test in testNames)
{
if (dict.TryGetValue(test, out var val))
{
repairsDict.Add(val);
}
}
st.Stop();
Console.WriteLine("Dict approach: " + st.ElapsedMilliseconds.ToString() + "ms");
Console.WriteLine("Repaired count: " + repairs.Count
+ ", check " + (repairs.SequenceEqual(repairsDict) ? "OK" : "ERROR"));
Console.ReadLine();
}
And the result is
List approach: 7264ms
Dict approach: 4ms
Repaired count: 4968, check OK
List approach is about 1800x slower, actually more the thousand times slower in this case. The results are as expected. If that is a problem is another question, it depends on concrete usage pattern in concrete application and is out of scope of this post.

Pick Random string from List<string> with exclusions and non-repetitive pick

im trying to write a program that would let a user:
Load a set of string.
Loop through the set, and pick another string from the same set.
Avoid a picked string from being picked again.
Have specific strings not be able to pick specified strings.
Example is table below:
And below table is a sample scenario:
How am i supposed to do this the easy way?
i have below code, but it is taking like forever to generate a valid set since it restarts everything if there are nothing left to pick, while not eliminating the possibility.
private List<Participant> _participants;
AllOverAgain:
var pickedParticipants = new List<Participant>();
var participantPicks = new List<ParticipantPick>();
foreach(var participant in _participants)
{
var pickedParticipantNames = from rp in participantPicks select rp.PickedParticipant;
var picks = (from p in _participants where p.Name != participant.Name & !Utilities.IsInList(p.Name, pickedParticipantNames) select p).ToList();
var pick = picks[new Random().Next(0, picks.Count())];
if(pick == null)
{
UpdateStatus($"No Available Picks left for {participant.Name}, Restarting...");
goto AllOverAgain;
}
var exclusions = participant.Exclusions.Split(',').Select(p => p.Trim()).ToList();
if(exclusions.Contains(pick.Name))
{
UpdateStatus($"No Available Picks left for {participant.Name}, Restarting...");
goto AllOverAgain;
}
participantPicks.Add(new ParticipantPick(participant.Name, pick.Name, participant.Number));
}
return participantPicks; // Returns the final output result
The Participant Class consists of these Properties:
public string Name { get; set; }
public string Number { get; set; }
public string Exclusions { get; set; }
The ParticipantPick Class consists of these Properties:
public string Participant { get; set; }
public string PickedParticipant { get; set; }
public string Number { get; set; }
One way you can solve this is by using a dictionary, using a composite key of a tuple and the matching value of a datatype bool.
Dictionary<Tuple<string, string>, bool>
The composite key Tuple<sring,string> will contain every permutation of participants and match them to their appropriate bool value.
For example, the dictionary filled with values such as:
Dictionary<Tuple<"Judith","James">, true>
...would be indicating that Judith picking James is valid.
So lets create a dictionary with every single possible combination of participants, and set the value of them to true for them being valid at the start of the program.
This can be accomplished by a cartesian join using an array with itself.
Dictionary<Tuple<string, string>, bool> dictionary = participants.SelectMany(left => participants, (left, right) => new Tuple<string, string>(left, right)).ToDictionary(item=> item, item=>true);
After getting every permutation of possible picks and setting them to true, we can go through the "not allowed to pick" lists and change the dictionary value for that composite key to false.
dictionary[new Tuple<string, string>(personNotAllowing, notAllowedPerson)] = false;
You can remove a participant from picking itself by using a loop in the following way:
for(int abc=0;abc<participants.Length;abc++)
{
//remove clone set
Tuple<string, string> clonePair = Tuple.Create(participants[abc], participants[abc]);
dictionary.Remove(clonePair);
}
Or by simply changing the value of the clone pair to false.
for(int abc=0;abc<participants.Length;abc++)
{
dictionary[Tuple.Create(participants[abc],participants[abc])] = false;
}
In this example program, I create a string[] of participants, and a string[] for the respective list of people they do not allow. I then perform a cartesian join, the participants array with itself. This leads to every permutation, with an initial true boolean value.
I change the dictionary where the participants are not allowed to false, and display the example dictionary.
Afterward, I create 10 instances of random participants who are picking other random participants and test if it would be valid.
Every time a participant picks another participant, I check that composite key to see if it has a value of true.
If it does result in a valid pick, then every combination of the resulting participant who was picked gets set to false.
for(int j=0; j<participants.Length;j++)
{
//Make the partner never be able to be picked again
Tuple<string, string> currentPair2 = Tuple.Create(partner, participants[j]);
try
{
dictionary[currentPair2] = false;
}
catch
{
}
}
This concept is better illustrated with running the code.
The demo:
static void Main(string[] args)
{
//Create participants set
string[] participants = {"James","John","Tyrone","Rebecca","Tiffany","Judith"};
//Create not allowed lists
string[] jamesNotAllowedList = {"Tiffany", "Tyrone"};
string[] johnNotAllowedList = {};
string[] tyroneNotAllowedList = {};
string[] rebeccaNotAllowedList ={"James", "Tiffany"};
string[] judithNotAllowedList = {};
//Create list of not allowed lists
string[][] notAllowedLists = { jamesNotAllowedList, johnNotAllowedList, tyroneNotAllowedList, rebeccaNotAllowedList, judithNotAllowedList};
//Create dictionary<Tuple<string,string>, bool> from participants array by using cartesian join on itself
Dictionary<Tuple<string, string>, bool> dictionary = participants.SelectMany(left => participants, (left, right) => new Tuple<string, string>(left, right)).ToDictionary(item=> item, item=>true);
//Loop through each person who owns a notAllowedList
for (int list = 0; list < notAllowedLists.Length; list++)
{
//Loop through each name on the not allowed list
for (int person = 0; person<notAllowedLists[list].Length; person++)
{
string personNotAllowing = participants[list];
string notAllowedPerson = notAllowedLists[list][person];
//Change the boolean value matched to the composite key
dictionary[new Tuple<string, string>(personNotAllowing, notAllowedPerson)] = false;
Console.WriteLine(personNotAllowing + " did not allow " + notAllowedPerson);
}
}
//Then since a participant cant pick itself
for(int abc=0;abc<participants.Length;abc++)
{
//remove clone set
Tuple<string, string> clonePair = Tuple.Create(participants[abc], participants[abc]);
dictionary.Remove(clonePair);
}
//Display whats going on with this Dictionary<Tuple<string,string>, bool>
Console.WriteLine("--------Allowed?--Dictionary------------\n");
Console.WriteLine(string.Join(" \n", dictionary));
Console.WriteLine("----------------------------------------\n\n");
//Create Random Object
Random rand = new Random();
//Now that the data is organized in a dictionary..
//..Let's have random participants pick random participants
//For this demonstration lets try it 10 times
for (int i=0;i<20;i++)
{
//Create a new random participant
int rNum = rand.Next(participants.Length);
string randomParticipant = participants[rNum];
//Random participant picks a random participant
string partner = participants[rand.Next(participants.Length)];
//Create composite key for the current pair
Tuple<string, string> currentPair = Tuple.Create(partner,randomParticipant);
//Check if it's a valid choice
try
{
if (dictionary[currentPair])
{
Console.WriteLine(randomParticipant + " tries to pick " + partner);
Console.WriteLine("Valid.\n");
//add to dictionary
for(int j=0; j<participants.Length;j++)
{
//Make the partner never be able to be picked again
Tuple<string, string> currentPair2 = Tuple.Create(partner, participants[j]);
try
{
dictionary[currentPair2] = false;
}
catch
{
}
}
}
else
{
Console.WriteLine(randomParticipant + " tries to pick " + partner);
Console.WriteLine(">>>>>>>>Invalid.\n");
}
}
catch
{
//otherwise exception happens because the random participant
//And its partner participant are the same person
//You can also handle the random participant picking itself differently
//In this catch block
//Make sure the loop continues as many times as necessary
//by acting like this instance never existed
i = i - 1;
}
}
Console.ReadLine();
}
This code will always give you output that adheres to your criteria:
public static class Program
{
public static void Main(string[] args)
{
var gathering = new Gathering();
gathering.MakeSelections();
foreach (var item in gathering.participants)
{
Console.WriteLine(item.name + ":" + item.selectedParticipant);
}
}
public class Participant
{
public string name;
public List<string> exclusions;
public string selectedParticipant;
}
public class Gathering
{
public List<Participant> participants;
public List<string> availableParticipants;
public List<string> usedNames;
public Dictionary<string, string> result;
public Gathering()
{
//initialize participants
participants = new List<Participant>();
participants.Add(new Participant
{
name = "James",
exclusions = new List<string> { "Tiffany", "Tyrone" }
});
participants.Add(new Participant
{
name = "John",
exclusions = new List<string> { }
});
participants.Add(new Participant
{
name = "Judith",
exclusions = new List<string> { }
});
participants.Add(new Participant
{
name = "Rebecca",
exclusions = new List<string> { "James", "Tiffany" }
});
participants.Add(new Participant
{
name = "Tiffany",
exclusions = new List<string> { }
});
participants.Add(new Participant
{
name = "Tyrone",
exclusions = new List<string> { }
});
//prevent participants from selecting themselves
foreach (Participant p in participants)
{
p.exclusions.Add(p.name);
}
//create list of all the names (all available participants at the beginning)
availableParticipants = participants.Select(x => x.name).ToList();
}
public void MakeSelections()
{
Participant currentParticipant;
Random randy = new Random();
//Sort Participants by the length of their exclusion lists, in descending order.
participants.Sort((p1, p2) => p2.exclusions.Count.CompareTo(p1.exclusions.Count));
//Get the first participant in the list which hasn't selected someone yet
currentParticipant = participants.FirstOrDefault(p => p.selectedParticipant == null);
while (currentParticipant != null)
{
//of the available participants, create a list to choose from for the current participant
List<string> listToChooseFrom = availableParticipants.Where(x => !currentParticipant.exclusions.Contains(x)).ToList();
//select a random participant from the list of eligible ones to be matched with the current participant
string assignee = listToChooseFrom[randy.Next(listToChooseFrom.Count)];
currentParticipant.selectedParticipant = assignee;
//remove the selected participant from the list of available participants
availableParticipants.RemoveAt(availableParticipants.IndexOf(assignee));
//remove the selected participant from everyone's exclusion lists
foreach (Participant p in participants)
if (p.exclusions.Contains(assignee))
p.exclusions.RemoveAt(p.exclusions.IndexOf(assignee));
//Resort Participants by the length of their exclusion lists, in descending order.
participants.Sort((p1, p2) => p2.exclusions.Count.CompareTo(p1.exclusions.Count));
//Get the first participant in the list which hasn't selected someone yet
currentParticipant = participants.FirstOrDefault(p => p.selectedParticipant == null);
}
//finally, sort by alphabetical order
participants.Sort((p1, p2) => p1.name.CompareTo(p2.name));
}
}
}
In the simpler version, the items can just be shuffled:
string[] source = { "A", "B", "C", "D", "E", "F" };
string[] picked = source.ToArray(); // copy
var rand = new Random();
for (int i = source.Length - 1, r; i > 0; --i)
{
var pick = picked[r = rand.Next(i)]; // pick random item less than the current one
picked[r] = picked[i]; // and swap with the current one
picked[i] = pick;
Console.WriteLine(i + " swapped with " + r);
}
Console.WriteLine("\nsource: " + string.Join(", ", source) +
"\npicked: " + string.Join(", ", picked));
sample result:
5 swapped with 4
4 swapped with 2
3 swapped with 0
2 swapped with 1
1 swapped with 0
source: A, B, C, D, E, F
picked: F, D, B, A, C, E
or, the source can be optionally shuffled, and each person can pick the person that is next in the list.

How to do the sequential ordering?

I have looked into this Q/A , though it is working too some extent but not as expected. I want it to happen sequentially.How to do that?
Thanks in advance.
You can use Enumerable.Zip to combine the agents and accounts together (after repeating the list of agents to match or exceed the number of accounts). Then GroupBy agent.
var repeatCount = lstAccounts.Count / lstAgents.Count + 1;
var agents = Enumerable.Repeat(lstAgents, repeatCount).SelectMany(x => x);
// agents = { "Agent1", "Agent2", "Agent3", "Agent1", "Agent2", "Agent3" }
// lstAccounts = { "1001" , "1002" , "1003" , "1004" , "1005" }
var result = agents
.Zip(lstAccounts, (agent, account) => new { Agent = agent, Account = account })
.GroupBy(x => x.Agent)
.Select(g => new { Agent = g.Key, Accounts = g.Select(x => x.Account).ToList() })
.ToList();
It might not be the fastest way to do it, but it's short and readable.
Edit
Another way (probably nicer) to achieve the same result is to start by mapping each account to an index of agent using index % lstAgents.Count.
var result = lstAccounts
.Select((acc, index) => new { AgentIndex = index % lstAgents.Count, Account = acc })
.GroupBy(x => x.AgentIndex)
.Select(g => new { Agent = lstAgents[g.Key], Accounts = g.Select(x => x.Account).ToList() })
.ToList();
The algorithm is very similar to the one proposed by varocarbas, but expressed in a functional (not imperative) way.
I think that conventional loops are the best approach here: easy-to-build, clear and very scalable-/modifiable-friendly. For example:
Dictionary<string, List<string>> results = new Dictionary<string, List<string>>();
int i = -1;
while (i < lstAccounts.Count - 1)
{
for (int i2 = 0; i2 < lstAgents.Count; i2++)
{
i = i + 1;
string curAccount = lstAccounts[i];
string curAgent = lstAgents[i2];
if (!results.ContainsKey(curAgent)) results.Add(curAgent, new List<string>());
results[curAgent].Add(curAccount);
if (i >= lstAccounts.Count - 1) break;
}
}
Additionally, note that this approach is quite fast. As a reference: around 4-5 times faster (results after a simplistic test with one of the provided inputs and a Stopwatch) than the alternative proposed by Jakub in his answer.
You can try this approach with linq extention. Split extension method will split the accounts list into "n" parts (number of agents) so that you can assign each part to agents.
class Program
{
static void Main(string[] args)
{
List<string> lstAgents = new List<string>() { "Agent1", "Agent2","Agent3" };
List<string> lstAccounts = new List<string>() { "1001", "1002" ,"1003", "1004", "1005" };
var op = lstAccounts.Split(lstAgents.Count);
int i = 0;
foreach (var accounts in op)
{
//Get agent
Console.WriteLine("Account(s) for Agent: ", lstAgents[i]);
foreach (var acc in accounts)
{
Console.WriteLine(acc);
}
Console.WriteLine(Environment.NewLine);
i++;
}
Console.ReadKey();
}
}
static class LinqExtensions
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> list, int parts)
{
int i = 0;
var splits = from item in list
group item by i++ % parts into part
select part.AsEnumerable();
return splits;
}
}

Query Expansion logic in C#

I have to write logic for expanding queries for solr search engine. I am using
Dictionary<string, string> dicSynon = new Dictionary<string, string>();.
Each time I will be getiing strings like "2 ln ca". In my dictionary I am having synonyms for ln and ca as lane and california. Now I need to pass solr all the combination of strings. Like this
2 ln ca
2 lane ca
2 lane california
2 ln california
Please help me to build the logic....
This is a exercise in using combinatorics and Linqs SelectMany:
First you have to writeyourself some function to give you a sequence of synonyms given a word (including the word, so "2" will result in ("2")) - let's call this 'Synonmys' - using a dictionary it can look like this:
private Dictionary<string, IEnumerable<string>> synonyms = new Dictionary<string, IEnumerable<string>>();
public IEnumerable<string> GetSynonmys(string word)
{
return synonyms.ContainsKey(word) ? synonyms[word] : new[]{word};
}
(you have to fill the Dictionary yourself...)
Having this your task is rather easy - just think on it. You have to combine every synonym of a word with all the combinations you get from doing the task on the rest of the words - that's exactly where you can use SelectMany (I only paste the rest seperated by a space - maybe you should refactor a bit) - the Algorithm yourself is your standard recursive combinations-algorithm - you will see this a lot if you know this kind of problem:
public string[] GetWords(string text)
{
return text.Split(new[]{' '}); // add more seperators if you need
}
public IEnumerable<string> GetCombinations(string[] words, int lookAt = 0)
{
if (lookAt >= words.Length) return new[]{""};
var currentWord = words[lookAt];
var synonymsForCurrentWord = GetSynonmys(currentWord);
var combinationsForRest = GetCombinations(words, lookAt + 1);
return synonymsForCurrentWord.SelectMany(synonym => combinationsForRest.Select(rest => synonym + " " + rest));
}
Here is a complete example:
class Program
{
static void Main(string[] args)
{
var test = new Synonmys(Tuple.Create("ca", "ca"),
Tuple.Create("ca", "california"),
Tuple.Create("ln", "ln"),
Tuple.Create("ln", "lane"));
foreach (var comb in test.GetCombinations("2 ln ca"))
Console.WriteLine("Combination: " + comb);
}
}
class Synonmys
{
private Dictionary<string, IEnumerable<string>> synonyms;
public Synonmys(params Tuple<string, string>[] syns )
{
synonyms = syns.GroupBy(s => s.Item1).ToDictionary(g => g.Key, g => g.Select(kvp => kvp.Item2));
}
private IEnumerable<string> GetSynonmys(string word)
{
return synonyms.ContainsKey(word) ? synonyms[word] : new[]{word};
}
private string[] GetWords(string text)
{
return text.Split(new[]{' '}); // add more seperators if you need
}
private IEnumerable<string> GetCombinations(string[] words, int lookAt = 0)
{
if (lookAt >= words.Length) return new[]{""};
var currentWord = words[lookAt];
var synonymsForCurrentWord = GetSynonmys(currentWord);
var combinationsForRest = GetCombinations(words, lookAt + 1);
return synonymsForCurrentWord.SelectMany(synonym => combinationsForRest.Select(rest => synonym + " " + rest));
}
public IEnumerable<string> GetCombinations(string text)
{
return GetCombinations(GetWords(text));
}
}
Feel free to comment if something is not crystal-clear here ;)

Categories