creating array of bad names to check and replace in c# - 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.

Related

Group multiple rows containing index and create list of custom objects for each index

I have got a List of strings (read from a file) in this order and format and need to convert into List of class.
1.0.1.0.1, Type: DateTime, Value: 06/03/2013 11:06:10
1.0.1.0.2, Type: DateTime, Value: 06/03/2014 11:06:10
1.0.1.0.3, Type: DateTime, Value: 06/03/2015 11:06:10
1.0.1.0.4, Type: DateTime, Value: 06/03/2016 11:06:10
1.0.1.0.5, Type: DateTime, Value: 06/03/2017 11:06:10
1.0.1.1.1, Type: Integer, Value: 1
1.0.1.1.2, Type: Integer, Value: 2
1.0.1.1.3, Type: Integer, Value: 3
1.0.0.1.4, Type: Integer, Value: 4
1.0.1.1.5, Type: Integer, Value: 5
1.0.1.2.1, Type: String, Value: Hello
1.0.1.2.2, Type: String, Value: Hello1
1.0.1.2.3, Type: String, Value: Hello2
1.0.1.2.4, Type: String, Value: Hello3
1.0.1.2.5, Type: String, Value: Hello4
Here is my class
public class MyData
{
public DateTime DateTime {get;set;}
public int Index {get;set;}
public string Value {get;set;}
}
Now What I wanted is to convert it into a list of C# class
Something like this...
List<MyData> myDataList = new List<MyData>();
MyData data1 = new MyData();
data1.DateTime = "06/03/2013 11:06:10";
data1.Index = 1;
data1.Value = "Hello";
myDataList.Add(data1);
MyData data2 = new MyData();
data2.DateTime = "06/03/2014 11:06:10";
data2.Index = 2;
data2.Value = "Hello1";
myDataList.Add(data2);
and so on..
This is what I have tried so far.
List<List<string>> allLists = lines
.Select(str => new { str, token = str.Split('.') })
.Where(x => x.token.Length >= 4)
.GroupBy(x => string.Concat(x.token.Take(4)))
.Select(g => g.Select(x => x.str).ToList())
.ToList();
Do I really need to iterate or can I modify My LINQ to get me desired output ?
Here is my iteration.
foreach (var list in allLists)
{
MyData data = new MyData();
var splittedstring = list[0].Split(',').ToList();
if (splittedstring.Count == 3)
{
var valueData = splittedstring [2];
var indexof = valueData.IndexOf(':');
var value = valueData.Substring(indexof + 1);
// But Over here, how will get DateTime and Index ?
data.Value = value;
}
}
First, fix your GroupBy: string.Concat(x.token.Take(4)) may create uncertainties when dot-separated numbers are ambiguous. For example, 1.23.4.5 and 12.3.4.5 would both produce "12345" string. Use string.Join with some non-numeric separator instead:
.GroupBy(x => string.Join("|", x.token.Take(4)))
Now for the main part of your question an easy fix would be to add a static method that parses the list of three strings, and use it in your LINQ query:
List<MyData> dataList = lines
.Select(str => new { str, token = str.Split('.') })
.Where(x => x.token.Length >= 4)
.GroupBy(x => string.Concat(x.token.Take(4)))
.Select(g => g.Select(x => x.str).ToList())
.Where(list => list.Count == 3)
.Select(MyDataFromList)
.ToList();
...
private static MyData MyDataFromList(List<string> parts) {
if (parts.Count != 3) {
throw new ArgumentException(nameof(parts));
}
var byType = parts
.Select(ToTypeAndValue)
.ToDictionary(t => t.Item1, t => t.Item2)
return new MyData {
DateTime = DateTime.Parse(byType["DateTime"])
, Index = int.Parse(byType["Integer"])
, Value = byType["String"]
};
}
private static Tuple<string,string> ToTypeAndValue(string s) {
var tokens = s.Split(',');
if (tokens.Length != 3) return null;
var typeParts = tokens[1].Split(':');
if (typeParts.Length != 2 || typeParts[0] != "Type") return null;
var valueParts = tokens[2].Split(':');
if (valueParts.Length != 2 || valueParts[0] != "Value") return null;
return Tuple.Create(typeParts[1].Trim(), typeParts[2].Trim());
}
Note that the above code makes an assumption that the three types are unique (hence the use of Dictionary<string,string>). This is required, because the structure of your data provides no other way to tie the values to fields of MyData.
You can do this using regular expressions. It would look like:
public List<MyData> GetData(string str){
var regexDate = new Regex(#"\d\.\d\.\d\.\d\.(?<id>\d).*DateTime.*Value:\s*(?<val>.*)");
var regexInteger = new Regex(#"\d\.\d\.\d\.\d\.(?<id>\d).*Integer.*Value:\s*(?<val>.*)");
var regexString = new Regex(#"\d\.\d\.\d\.\d\.(?<id>\d).*String.*Value:\s*(?<val>.*)");
var dict = new Dictionary<int, MyData>();
foreach (Match myMatch in regexDate.Matches(str))
{
if (!myMatch.Success) continue;
var index = int.Parse(myMatch.Groups["id"].Value);
dict[index] = new MyData()
{
Index = index,
DateTime = DateTime.ParseExact(myMatch.Groups["val"].Value, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture)
};
}
foreach (Match myMatch in regexInteger.Matches(str))
{
if (!myMatch.Success) continue;
var index = int.Parse(myMatch.Groups["id"].Value);
dict[index].Index = Int32.Parse(myMatch.Groups["val"].Value);
}
foreach (Match myMatch in regexString.Matches(str))
{
if (!myMatch.Success) continue;
var index = int.Parse(myMatch.Groups["id"].Value);
dict[index].Value = myMatch.Groups["val"].Value;
}
return dict.Values
}
Here is my solution to your problem. I have already tested it, you can test it to here: Raw To Custom List
string text = rawData;
//Raw Data Is the exact data you read from textfile without modifications.
List<MyData> myDataList = new List<MyData>();
string[] eElco = text.Split( new[] { Environment.NewLine }, StringSplitOptions.None );
var tmem = eElco.Count();
var eachP = tmem / 3;
List<string> unDefVal = new List<string>();
foreach (string rw in eElco)
{
String onlyVal = rw.Split(new[] { "Value: " } , StringSplitOptions.None)[1];
unDefVal.Add(onlyVal);
}
for (int i = 0; i < eachP; i++)
{
int ind = Int32.Parse(unDefVal[i + eachP]);
DateTime oDate = DateTime.ParseExact(unDefVal[i], "dd/MM/yyyy hh:mm:ss",System.Globalization.CultureInfo.InvariantCulture);
MyData data1 = new MyData();
data1.DateTime = oDate;
data1.Index = ind;
data1.Value = unDefVal[i + eachP + eachP];
myDataList.Add(data1);
Console.WriteLine("Val1 = {0}, Val2 = {1}, Val3 = {2}",
myDataList[i].Index,
myDataList[i].DateTime,
myDataList[i].Value);
}
Here is my solution, using Regex. It could be improved by providing a conditional regex match based on the matched type named group(string), but I think the concept is clearer this way, and the regex easier to work with. As it stands, the date format is not validated to be as OP wrote them, they are assumed to be as OP wrote them.
This solution is tolerant to some extra spaces and parameters containing commas, but intolerant to inexact matches, i.e. extra fields added or removed in the rows in the future, etc.
The idea is to first parse the rows to a more "friendly" format, and then group the friendly format by index and return the MyData rows by iterating each group (by index).
Regex r = new Regex(#"^(?<fieldName>(\d\.)+(?<index>\d*)), *Type: *(?<dataType>.*), *Value: (?<dataValue>.*)$");
public class MyData
{
public DateTime DateTime { get; set; }
public int Index { get; set; }
public string Value { get; set; }
}
class LogRow
{
public int Index { get; set; }
public string Type { get; set; }
public string Value { get; set; }
}
//In a parser I would rather not be too defensive, I let exceptions bubble up
IEnumerable<LogRow> ParseRows(IEnumerable<string> lines)
{
foreach (var line in lines)
{
var match = r.Matches(line).AsQueryable().Cast<Match>().Single();
yield return new LogRow()
{
Index = int.Parse(match.Groups["index"].Value),
Type = match.Groups["dataType"].Value,
Value = match.Groups["dataValue"].Value
};
}
}
IEnumerable<MyData> RowsToData(IEnumerable<LogRow> rows)
{
var byIndex = rows.GroupBy(b => b.Index).OrderBy(b=> b.Key);
//assume that rows exist for all MyData fields for a given index
foreach (var group in byIndex)
{
var rawRow = group.ToDictionary(g => g.Type, g => g);
var date = DateTime.ParseExact(rawRow["DateTime"].Value, "dd/MM/yyyy HH:mm:ss", CultureInfo.InvariantCulture);
yield return new MyData() { Index = group.Key, DateTime = date, Value = rawRow["String"].Value };
}
}
Usage:
var myDataList = RowsToData(ParseRows(File.ReadAllLines("input.txt"))).ToList();
I'd just go for the manual approach... and since that list of integers at the start contains indices for the objects and for the properties, it'd only be logical to use these instead of the type strings.
Using a Dictionary, you can use that object-index to make a new object at the moment you find any of its properties, and store it using that index. And whenever you encounter another properties for the same index, you retrieve the object and fill in that property on it.
public static List<MyData> getObj(String[] lines)
{
Dictionary<Int32, MyData> myDataDict = new Dictionary<Int32, MyData>();
const String valueStart = "Value: ";
foreach (String line in lines)
{
String[] split = line.Split(',');
// Too many fail cases; I just ignore any line that stops matching at any point.
if (split.Length < 3)
continue;
String[] numData = split[0].Trim().Split('.');
if (numData.Length < 5)
continue;
// Using the 4th number as property identifier. Could also use the
// type string, but switch/case on a numeric value is more elegant.
Int32 prop;
if (!Int32.TryParse(numData[3], out prop))
continue;
// Object index, used to reference the objects in the Dictionary.
Int32 index;
if (!Int32.TryParse(numData[4], out index))
continue;
String typeDef = split[1].Trim();
String val = split[2].TrimStart();
if (!val.StartsWith(valueStart))
continue;
val = val.Substring(valueStart.Length);
MyData data;
if (myDataDict.ContainsKey(index))
data = myDataDict[index];
else
{
data = new MyData();
myDataDict.Add(index, data);
}
switch (prop)
{
case 0:
if (!"Type: DateTime".Equals(typeDef))
continue;
DateTime dateVal;
// Don't know if this date format is correct; adapt as needed.
if (!DateTime.TryParseExact(val, "dd/MM/yyyy HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.None, out dateVal))
continue;
data.DateTime = dateVal;
break;
case 1:
if (!"Type: Integer".Equals(typeDef))
continue;
Int32 numVal;
if (!Int32.TryParse(val, out numVal))
continue;
data.Index = numVal;
break;
case 2:
if (!"Type: String".Equals(typeDef)) continue;
data.Value = val;
break;
}
}
return new List<MyData>(myDataDict.Values);
}

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.

Using Regex Array with String Array

I'm trying to make a program where a user can input an array of serial numbers and have each corresponding product show up.
Suppose I know that Product A always starts with "C02", Product B always ends in "X02", and Product C always contains "A1700". Then if the user input was "C02HGV32,N93XA1700D,J3429X02", it would return "C02HGV32: Product A; N93XA1700D: Product C; J3429X02: Product B".
How would I get an array of Regex expressions to compare against the array of strings? Here's what I have:
using System.Text.RegularExpressions;
public class ReturnProduct{
public Regex[] compareAgainst = new Regex[3]{#"[C02]*",#"*[X02]",#"*[A1700]*"}; //Clearly not the right way, but not sure how else to do it
...
public string getTheProduct(string input){
string[] compareString = input.Split(",");
for (int a = 0; a < compareString.Length; a++){
for (int b = 0; b < compareAgainst.Length; b++){
//Do something Regex-y with compareString[a] and compareAgainst[b]
}
}
If the requirements of these codes are so simple you can use String.Contains, String.StartsWith and String.EndsWith. You can create a Dictionary to hold product names and functions to check if a given string has the pattern for a product.
var dict = new Dictionary<string, Predicate<string>>
{
["Product A"] = s => s.StartsWith("C02"),
["Product B"] = s => s.EndsWith("X02"),
["Product C"] = s => s.Contains("A1700")
};
string GetProductName(string serialNum)
{
foreach(var keyVal in dict)
{
if(keyVal.Value(serialNum))
return keyVal.Key;
}
return "No product name found";
}
List<(string, string)> GetProductNames(string str)
{
var productCodes = str.Split(',');
var productNames = new List<(string, string)>(); // list of tuples (string, string)
foreach(var serialNum in productCodes)
{
productNames.Add((serialNum, GetProductName(serialNum)));
}
return productNames;
}
Usage:
var userString = "C02HGV32,N93XA1700D,J3429X02";
List<(string serialNum, string name)> productNames = GetProductNames(userString);
foreach(var tuple in productNames)
{
Console.WriteLine($"{tuple.serialNum} : {tuple.name}");
}
If you specifically want to use Regex, you can use the following patterns:
var regexDict = new Dictionary<string, Regex>
{
["Product A"] = new Regex("^C02"), //'^' means beginning of string
["Product B"] = new Regex("X02$"), //'$' means end of string
["Product C"] = new Regex("A1700") //given string anywhere
};
string GetProductName(string serialNum)
{
foreach(var keyVal in regexDict)
{
if(keyVal.Value.IsMatch(serialNum))
return keyVal.Key;
}
return "No product name found";
}
List<(string, string)> GetProductNames(string str)
{
var productCodes = str.Split(',');
var productNames = new List<string>();
foreach(var serialNum in productCodes)
{
productNames.Add((serialNum, GetProductName(serialNum)));
}
return productNames;
}
Define a class for your products:
public class Product
{
public string Name { get; set; }
public Regex Expr { get; set; }
}
then create an array with all your regexes:
var regexes = new[]
{
new Product
{
Name = "Product A",
Expr = new Regex("^C02")
},
new Product
{
Name = "Product B",
Expr = new Regex("X02$")
},
new Product
{
Name = "Product C",
Expr = new Regex("A1700")
}
};
now you can use LINQ query:
var input = "C02HGV32,N93XA1700D,J3429X02";
var result = string.Join("; ",
input.Split(',')
.Select(s => new {regexes.FirstOrDefault(p => p.Expr.IsMatch(s))?.Name, Value = s})
.Select(x => $"{x.Value}: {x.Name}"));
result would be
C02HGV32: Product A; N93XA1700D: Product C; J3429X02: Product B
Regex syntax:
"^C02.*" - Starts with C02 followed by any number of characters including 0 characters.
"^.*X02" - Starts with any number of characters including 0 characters and ends with X02.
"^.A1700.*" - Starts and ends with any number of characters, and contains A1700 somewhere.
public static void GetTheProduct(string input, List<Regex> regList)
{
List<string> compareString = input.Split(new char[] { ',' }).ToList();
foreach (string item in compareString)
{
if (regList[0].Match(item).Success)
Console.WriteLine("{0} : {1}", item, "Product A");
else if (regList[1].Match(item).Success)
Console.WriteLine("{0} : {1}", item, "Product B");
else if (regList[2].Match(item).Success)
Console.WriteLine("{0} : {1}", item, "Product C");
}
}
static void Main(string[] args)
{
List<Regex> regexList = new List<Regex>() { new Regex("^C02.*"), new Regex("^.*X02"), new Regex("^.*A1700.*") };
GetTheProduct("C02HGV32,N93XA1700D,J3429X02", regexList);
Console.ReadLine();
}
You could also generalize the method and avoid hardcoding Product names.
Like so:
public static void GetTheProduct(string input, Dictionary<string, Regex> regDictionary)
{
List<string> compareString = input.Split(new char[] { ',' }).ToList();
foreach (string item in compareString)
{
string key = regDictionary.First(x => x.Value.IsMatch(item)).Key;
Console.WriteLine("{0} : {1}", item, key);
}
}
static void Main(string[] args)
{
Dictionary<string, Regex> regDictionary = new Dictionary<string, Regex>();
regDictionary.Add("Product A", new Regex("^C02.*"));
regDictionary.Add("Product B", new Regex("^.*X02"));
regDictionary.Add("Product C", new Regex("^.*A1700.*"));
GetTheProduct("C02HGV32,N93XA1700D,J3429X02", regDictionary);
Console.ReadLine();
}

Counting words using LinkedList

I have a class WordCount which has string wordDic and int count. Next, I have a List.
I have ANOTHER List which has lots of words inside it. I am trying to use List to count the occurrences of each word inside List.
Below is where I am stuck.
class WordCount
{
string wordDic;
int count;
}
List<WordCount> usd = new List<WordCount>();
foreach (string word in wordsList)
{
if (usd.wordDic.Contains(new WordCount {wordDic=word, count=0 }))
usd.count[value] = usd.counts[value] + 1;
else
usd.Add(new WordCount() {wordDic=word, count=1});
}
I don't know how to properly implement this in code but I am trying to search my List to see if the word in wordsList already exists and if it does, add 1 to count but if it doesn't then insert it inside usd with count of 1.
Note: *I have to use Lists to do this. I am not allowed to use anything else like hash tables...*
This is the answer before you edited to only use lists...btw, what is driving that requirement?
List<string> words = new List<string> {...};
// For case-insensitive you can instantiate with
// new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
Dictionary<string, int> counts = new Dictionary<string, int>();
foreach (string word in words)
{
if (counts.ContainsKey(word))
{
counts[word] += 1;
}
else
{
counts[word] = 1;
}
}
If you can only use lists, Can you use List<KeyValuePair<string,int>> counts which is the same thing as a dictionary (although I'm not sure it would guarantee uniqueness). The solution would be very similar. If you can only use lists the following will work.
List<string> words = new List<string>{...};
List<string> foundWord = new List<string>();
List<int> countWord = new List<int>();
foreach (string word in words)
{
if (foundWord.Contains(word))
{
countWord[foundWord.IndexOf(word)] += 1;
}
else
{
foundWord.Add(word);
countWord.Add(1);
}
}
Using your WordCount class
List<string> words = new List<string>{...};
List<WordCount> foundWord = new List<WordCount>();
foreach (string word in words)
{
WordCount match = foundWord.SingleOrDefault(w => w.wordDic == word);
if (match!= null)
{
match.count += 1;
}
else
{
foundWord.Add(new WordCount { wordDic = word, count = 1 });
}
}
You can use Linq to do this.
static void Main(string[] args)
{
List<string> wordsList = new List<string>()
{
"Cat",
"Dog",
"Cat",
"Hat"
};
List<WordCount> usd = wordsList.GroupBy(x => x)
.Select(x => new WordCount() { wordDic = x.Key, count = x.Count() })
.ToList();
}
Use linq: Assuming your list of words :
string[] words = { "blueberry", "chimpanzee", "abacus", "banana", "abacus","apple", "cheese" };
You can do:
var count =
from word in words
group word.ToUpper() by word.ToUpper() into g
where g.Count() > 0
select new { g.Key, Count = g.Count() };
(or in your case, select new WordCount()... it'll depend on how you have your constructor set up)...
the result will look like:
First, all of your class member is private, thus, they could not be accessed somewhere out of your class. Let's assume you're using them in WordCount class too.
Second, your count member is an int. Therefore, follow statement will not work:
usd.count[value] = usd.counts[value] + 1;
And I think you've made a mistype between counts and count.
To solve your problem, find the counter responding your word. If it exists, increase count value, otherwise, create the new one.
foreach (string word in wordsList) {
WordCount counter = usd.Find(c => c.wordDic == word);
if (counter != null) // Counter exists
counter.count++;
else
usd.Add(new WordCount() { wordDic=word, count = 1 }); // Create new one
}
You should use a Dictionary as its faster when using the "Contains" method.
Just replace your list with this
Dictionary usd = new Dictionary();
foreach (string word in wordsList)
{
if (usd.ContainsKey(word.ToLower()))
usd.count[word.ToLower()].count++;
else
usd.Add(word.ToLower(), new WordCount() {wordDic=word, count=1});
}

Linq write new list from old list sublist, change said list, write back to old list

I'm rather new to Linq. I'm having trouble coding this.
I have a list with many different sublists.
oldList[0] some type
oldList[1] another different type
oldList[2] the type I want
oldList[3] more types
I want to select all the parameters from a specific type and write them to a temp list.
If that temp list is empty, I want to assign some values (values don't actually matter).
After changing the values, I want to write temp list back into oldList.
Please advise. This is a huge learning experience for me.
public void myFunction(list)
{
//list contains at least 5 sublists of various type
//check if the type I want is null
IEnumerable<TypeIWant> possiblyEmptyList = list.OfType<TypeIWant>(); //find the type I want from the list and save it
if (possiblyEmptyList == null) //this doesn't work and possiblyEmptyList.Count <= 1 doesn't work
{
//convert residence address to forwarding address
IEnumerable<ReplacementType> replacementList = list.OfType<ReplacementType>();
forwardingAddress = replacementList.Select(x => new TypeIWant /* this statement functions exactly the way I want it to */
{
Address1 = x.Address1,
Address2 = x.Address2,
AddressType = x.AddressType,
City = x.City,
CountryId = x.CountryId,
CountyRegion = x.CountyRegion,
Email = x.Email,
ConfirmEmail = x.ConfirmEmail,
Fax = x.Fax,
Telephone = x.Telephone,
State = x.State,
PostalCode = x.PostalCode
});
//write forwarding address back to list
//don't know how to do this
}
LINQ purpose is querying. It doesn't allow you to replace some items in collection with other items. Use simple loop instead:
IEnumerable<TypeIWant> possiblyEmptyList = list.OfType<TypeIWant>();
if (!possiblyEmptyList.Any())
{
for (int i = 0; i < list.Count; i++)
{
ReplacementType item = list[i] as ReplacementType;
if (item == null)
continue;
list[i] = ConvertToTypeIWant(item);
}
}
And conversion (which is better to do with something like automapper):
private TypeIWant ConvertToTypeIWant(ReplacementType x)
{
return new TypeIWant
{
Address1 = x.Address1,
Address2 = x.Address2,
AddressType = x.AddressType,
City = x.City,
CountryId = x.CountryId,
CountyRegion = x.CountyRegion,
Email = x.Email,
ConfirmEmail = x.ConfirmEmail,
Fax = x.Fax,
Telephone = x.Telephone,
State = x.State,
PostalCode = x.PostalCode
};
}
Not LINQ but an example.
class Program
{
static void Main(string[] args)
{
// Vars
var list = new List<List<string>>();
var a = new List<string>();
var b = new List<string>();
var c = new List<string> { "one", "two", "three" };
var d = new List<string>();
// Add Lists
list.Add(a);
list.Add(b);
list.Add(c);
list.Add(d);
// Loop through list
foreach (var x in list)
{
if (x.Count < 1)
{
var tempList = new List<string>();
tempList.Add("some value");
x.Clear();
x.AddRange(tempList);
}
}
// Print
int count = 0;
foreach (var l in list)
{
count++;
Console.Write("(List " + count + ") ");
foreach (var s in l)
{
Console.Write(s + " ");
}
Console.WriteLine("");
}
}
}
(List 1) some value
(List 2) some value
(List 3) one two three
(List 4) some value

Categories