How to achieve desired results in multidimensional array? C# - c#

I am trying to achieve the output by using multidimensional array which I can get by using KeyValuePair.
Input:
var foodPair = new Dictionary<string, string>
{
{"Pizza", "Italian"},
{"Curry", "Indian"},
{"Masala", "Indian"}
};
var teamPreference = new Dictionary<string, string>
{
{"Jose", "Italian" },
{"John", "Indian" },
{"Sarah", "Thai" },
{"Mary", "*" }
};
* means give everything
If selected food type is not available than give nothing. i.e Thai
Output:
Jose, Pizza
John, Curry
John, Masala
Mary, Pizza
Mary, Curry
Mary, Masala
Working Result by using KeyValuePair<string, string>:
https://dotnetfiddle.net/hNdlfy
I want to achieve same result by using string[,] but I don't know how to insert in dimensional array. Basically I am trying to learn how multidimensional arrays works by doing this kind of example.

You cannot "insert" in any array (single-dimensional or multidimensional). But you can create array with enough space.
var foodPair = new Dictionary<string, string> { { "Pizza", "Italian" }, { "Curry", "Indian" }, { "Masala", "Indian" } };
var teamPreference = new Dictionary<string, string> { { "Jose", "Italian" }, { "John", "Indian" }, { "Sarah", "Thai" }, { "Mary", "*" } };
var results = new List<KeyValuePair<string, string>>();
var results2 = new string[10, 2];
int rowIndex = 0;
foreach (var teamMember in teamPreference)
{
switch (teamMember.Key)
{
case "Jose":
var key = foodPair.FirstOrDefault(x => x.Value == "Italian").Key;
results.Add(new KeyValuePair<string, string>(teamMember.Key, key));
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = key;
rowIndex++;
break;
case "John":
var getAll = foodPair.Where(x => x.Value == "Indian").ToList();
if (getAll.Any())
{
results.AddRange(getAll.Select(a => new KeyValuePair<string, string>(teamMember.Key, a.Key)));
}
foreach (var item in getAll)
{
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = item.Key;
rowIndex++;
}
break;
case "Sarah":
var c = foodPair.FirstOrDefault(x => x.Value == "Thai").Key;
if (!string.IsNullOrEmpty(c))
{
results.Add(new KeyValuePair<string, string>(teamMember.Key, c));
}
if (!string.IsNullOrEmpty(c))
{
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = c;
rowIndex++;
}
break;
case "Mary":
if (teamMember.Value == "*")
{
var everything = foodPair.Keys.ToList();
if (everything.Any())
{
results.AddRange(everything.Select(food => new KeyValuePair<string, string>(teamMember.Key, food)));
}
foreach (var item in everything)
{
results2[rowIndex, 0] = teamMember.Key;
results2[rowIndex, 1] = item;
rowIndex++;
}
}
break;
}
}
if (results.Any())
{
foreach (var result in results)
{
Console.WriteLine("{0}, {1}", result.Key, result.Value);
}
}
Console.WriteLine("Using multidimensional array");
for (int row = 0; row < rowIndex; row++)
{
Console.WriteLine("{0}, {1}", results2[row, 0], results2[row, 1]);
}
But multidimensional array is not suitable data structure for this task. (You can see code with array looks terrible). Data structure that you need is called tuple and the definition for results can look like var results3 = new List<Tuple<string, string>>();
Additional note - you not need using .Any() with foreach statement. foreach is not executed for empty collections.
Good luck with learning C#!

I used Extension method to achieve the desired result.
public static string[,] FoodPreferenceWithDimensionalArray()
{
var foodPair = new Dictionary<string, string>
{
{"Pizza", "Italian"},
{"Curry", "Indian"},
{"Masala", "Indian"}
};
var teamPreference = new Dictionary<string, string>
{
{"Jose", "Italian" },
{"John", "Indian" },
{"Sarah", "Thai" },
{"Mary", "*" }
};
var results = new List<KeyValuePair<string, string>>();
foreach (var teamMember in teamPreference)
{
switch (teamMember.Key)
{
case "Jose":
var italianDish = foodPair.FirstOrDefault(x => x.Value == "Italian").Key;
results.Add(new KeyValuePair<string, string>(teamMember.Key, italianDish));
break;
case "John":
var indianDish = foodPair.Where(x => x.Value == "Indian");
if (indianDish.Any())
{
results.AddRange(indianDish.Select(dish => new KeyValuePair<string, string>(teamMember.Key, dish.Key)));
}
break;
case "Sarah":
var thaiDish = foodPair.FirstOrDefault(x => x.Value == "Thai").Key;
if (!string.IsNullOrEmpty(thaiDish))
{
results.Add(new KeyValuePair<string, string>(teamMember.Key, thaiDish));
}
break;
case "Mary":
if (teamMember.Value == "*")
{
var everything = foodPair.Keys.ToList();
if (everything.Any())
{
results.AddRange(everything.Select(food => new KeyValuePair<string, string>(teamMember.Key, food)));
}
}
break;
}
}
var resultIn2DArray = results.To2DArray();
return resultIn2DArray;
}
// Created extension to convert List to String[,]
public static class Extension
{
public static string[,] To2DArray<T>(this List<T> list)
{
if (list.Count == 0)
{
throw new ArgumentException("The list must have non-zero dimensions.");
}
var result = new string[list.Count, list.Count];
for (var i = 0; i < list.Count; i++)
{
// This is set to 0 since I know the output but will work on this to make it dynamic.
result[0, i] = list[i].ToString();
}
return result;
}
}
Thank you all for your input.

Related

C# find anagram from a string array of candidates

My task is to implement a method that could return a correct sublist of anagrams.
Now so far I am having problems with figuring out how can I collect the anagrams in candidates that match word and return it.
This is my code for now:
public class Anagram
{
public string word;
public Anagram(string sourceWord)
{
if (sourceWord is null)
{
throw new ArgumentNullException(nameof(sourceWord));
}
if (sourceWord.Length == 0)
{
throw new ArgumentException(null);
}
this.word = sourceWord;
}
public string[] FindAnagrams(string[] candidates)
{
if (candidates is null)
{
throw new ArgumentNullException(nameof(candidates));
}
char[] char1 = this.word.ToLower().ToCharArray();
Array.Sort(char1);
string newWord1 = new string(char1);
string newWord2;
string[] result = new string[candidates.Length];
for (int i = 0; i < candidates.Length; i++)
{
char[] char2 = candidates[i].ToLower().ToCharArray();
Array.Sort(char2);
newWord2 = char2.ToString();
if (newWord1 == newWord2)
{
result[i] = candidates[i];
}
}
return result;
}
}
Should I do a second for loop in the if statement or something else.
And by the way how I am doing with my class constructor, It's the first time I am trying to use it and at the end I don't think I called the sourceWord variable correctly..
This is one of my test scenarios I would need to pass later on:
[TestCase("master", new[] { "stream", "pigeon", "maters" }, ExpectedResult = new[] { "stream", "maters" })]
[TestCase("listen", new[] { "enlists", "google", "inlets", "banana" }, ExpectedResult = new[] { "inlets" })]
[TestCase("allergy", new[] { "gallery", "ballerina", "regally", "clergy", "largely", "leading" }, ExpectedResult = new[] { "gallery", "regally", "largely" })]
public string[] FindAnagrams_Detects_Anagrams(string word, string[] candidates)
{
var sut = new Anagram(word);
return sut.FindAnagrams(candidates);
}
Unfortunatly, can't use LINQ on this task.
If two words are anagrams, they have the same numbers of same letters:
art ~ rat ~ tar
we can sort letters within each word and group words by such keys:
...
aaabnn: banana
aemrst: maters, stream
...
Code:
using System.Linq;
...
// Given list of (candidates) word return anagrams
public static IEnumerable<string[]> FindAnagrams(IEnumerable<string> candidates) {
if (null == candidates)
throw new ArgumentNullException(nameof(candidates));
return candidates
.GroupBy(word => string.Concat(word.OrderBy(c => c)))
.Where(group => group.Count() > 1)
.Select(group => group.OrderBy(word => word).ToArray());
}
Demo:
string[] demo = new string[] {
"stream", "pigeon", "maters",
"enlists", "google", "inlets", "banana",
"gallery", "ballerina", "regally", "clergy", "largely", "leading",
"art", "tar", "rat"
};
string report = string.Join(Environment.NewLine, FindAnagrams(demo)
.Select(group => string.Join(", ", group)));
Console.Write(report);
Outcome:
maters, stream
gallery, largely, regally
art, rat, tar
Edit: Same idea for FindAnagrams_Detects_Anagrams:
public string[] FindAnagrams_Detects_Anagrams(string word, string[] candidates) {
if (word == null || candidates == null)
return new string[0];
string[] wordArr =
string key = string.Concat(word.OrderBy(c => c));
return candidates
.Where(w => w != null)
.Where(w => key == string.Concat(w.OrderBy(c => c)))
.ToArray();
}
You can get rid of Linq if you want:
All anagrams:
public static IEnumerable<string[]> FindAnagrams(IEnumerable<string> candidates) {
if (null == candidates)
throw new ArgumentNullException(nameof(candidates));
Dictionary<string, List<string>> groups =
new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
foreach (var word in candidates) {
char[] keyArray = word.ToCharArray();
Array.Sort(keyArray);
string key = string.Concat(keyArray);
if (groups.TryGetValue(key, out var list))
list.Add(word);
else
groups.Add(key, new List<string>() { word});
}
foreach (var pair in groups) {
if (pair.Value.Count > 1) {
string[] result = new string[pair.Value.Count];
for (int i = 0; i < pair.Value.Count; ++i)
result[i] = pair.Value[i];
yield return result;
}
}
}
Detect anagrams:
public string[] FindAnagrams_Detects_Anagrams(string word, string[] candidates) {
if (word == null || candidates == null)
return new string[0];
char[] keyArray = word.ToCharArray();
Array.Sort(keyArray);
string key = string.Concat(keyArray);
List<string> list = new List<string>();
foreach (string w in candidates) {
char[] wArray = w.ToCharArray();
Array.Sort(wArray);
string wKey = string.Concat(wArray);
if (string.Equals(wKey, key, StringComparison.OrdinalIgnoreCase))
list.Add(w);
}
string[] result = new string[list.Count];
for (int i = 0; i < list.Count; ++i)
result[i] = list[i];
return result;
}

C# Add lines with same Date.Time in .csv file

I'm currently making a program that tracks certain things (basic INT Values and the Date when they were saved).
My goal is to add up the INT values with the same Date.
20.11.2018 00:00:00; 1;1;1;1;1
20.11.2018 00:00:00; 1;1;1;1;1
22.11.2018 00:00:00; 1;1;1;1;1
Should basically look like this
20.11.2018 00:00:00; 2;2;2;2;2
22.11.2018 00:00:00; 1;1;1;1;1
The Saving Data and even the adding the 2 "Lines" together is working perfectly fine.
The problem is that When I add the Lines together, the 2 Lines with the 1 obviously don't get deleted.
This is the Method that Compares the Date and adds the lines together:
public static Dictionary<DateTime, int[]> CompareDateMethod(Dictionary<DateTime, int[]> oDateTimeAndIntDictionary,string[][] ReadData)
{
Dictionary<DateTime, int[]> oPrintRealData = new Dictionary<DateTime, int[]>();
Dictionary<DateTime, int[]> oAddRealData = new Dictionary<DateTime, int[]>();
for (int i = 0 ; i < ReadData.Length; i++)
{
DateTime dtDateValue;
if (DateTime.TryParse(ReadData[i][0], out dtDateValue))
{
int[] iValuesToAdd = ConvertArrayToInt(ReadData[i]);
if (dtDateValue.Date == DateTime.Now.Date)
{
for (int j = 0; j < iValuesToAdd.Length; j++)
{
oDateTimeAndIntDictionary[dtDateValue.Date][j] += iValuesToAdd[j];
}
}
else if (dtDateValue.Date != DateTime.Now.Date)
{
goto Endloop;
}
}
}
Endloop:
return oDateTimeAndIntDictionary;
This is the method that Writes the Data into the .CSV file
Dictionary<DateTime, int[]> oDateTimeAndIntDictionary = new Dictionary<DateTime, int[]>();
string[][] OldData= AddVariables.ReadOldData();
int[] iNewDataArray = new int[] { iVariable1, iVariable2, iVariable3, iVariable4, iVariable5};
oDateTimeAndIntDictionary.Add(DateTime.Now.Date, iNewDataArray);
using (System.IO.FileStream fileStream = new System.IO.FileStream(#"C: \Users\---\Csvsave\SaveDatei.csv", System.IO.FileMode.Append, System.IO.FileAccess.Write))
using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(fileStream))
{
foreach (KeyValuePair<DateTime, int[]> kvp in AddVariables.CompareDateMethod(oDateTimeAndIntDictionary, OldData))
{
streamWriter.WriteLine("{0}; {1}", kvp.Key, string.Join(";", kvp.Value));
}
}
I tried so hard to come up with something but nothing worked (I tried deleting lines from the .csv which seems really hard, I tried reading the file in backwards which didnt work etc.)
If someone can give me some pointers I would really appreciate it.
I think the problem with the original code is that it's a bit confused about what happens when. I've restructured it so that things happen in a logical order (and updated it a bit, simplifying variable names, etc). There's one function for combining rows with the same date, which is separate from the CSV writing code (which hasn't changed)
static void Main(string[] args)
{
var oldData = ReadOldData();
// Do the work
var results = SumValuesForSameDate(oldData);
// Write the file
using (System.IO.FileStream fileStream = new System.IO.FileStream(#"C: \Users\---\Csvsave\SaveDatei.csv", System.IO.FileMode.Append, System.IO.FileAccess.Write))
using (System.IO.StreamWriter streamWriter = new System.IO.StreamWriter(fileStream))
{
foreach (KeyValuePair<DateTime, int[]> kvp in results)
{
streamWriter.WriteLine("{0}; {1}", kvp.Key, string.Join(";", kvp.Value));
}
}
}
public static Dictionary<DateTime, int[]> SumValuesForSameDate(string[][] readData)
{
var oDateTimeAndIntDictionary = new Dictionary<DateTime, int[]>();
var currentDate = DateTime.MinValue;
foreach (var row in readData)
{
DateTime dateValue;
if(!DateTime.TryParse(row[0], out dateValue)) continue;
dateValue = dateValue.Date;
var intValues = ConvertArrayToInt(row);
if (dateValue == currentDate)
{
for (var j = 0; j < intValues.Length; j++)
{
oDateTimeAndIntDictionary[dateValue][j] += intValues[j];
}
}
else
{
oDateTimeAndIntDictionary.Add(dateValue, intValues);
currentDate = dateValue;
}
}
return oDateTimeAndIntDictionary;
}
static int[] ConvertArrayToInt(string[] strings)
{
var output = new int[strings.Length - 1];
for (var i = 1; i < strings.Length; i++)
{
output[i - 1] = int.Parse(strings[i]);
}
return output;
}
static string[][] ReadOldData()
{
// Fake data
var data = new string[][]
{
new string[] { "20.11.2018 00:00:00", "1", "1", "1", "1", "1" },
new string[] { "20.11.2018 00:00:00", "1", "1", "1", "1", "1" },
new string[] { "22.11.2018 00:00:00", "1", "1", "1", "1", "1" },
};
return data;
}
}
For overwriting the previous CSV just use System.IO.FileMode.Create instead of Append. This will overwrite any previous data.
You need to overwrite the csv anyways to get rid of the written row.
So instead of returning oDateTimeAndIntDictionary from CompareDateMethod method, return ReadData and overwrite the parsed values of ReadData.
Something like this,
public static Dictionary<DateTime, int[]> CompareDateMethod(Dictionary<DateTime, int[]> oDateTimeAndIntDictionary,string[][] ReadData)
{
Dictionary<DateTime, int[]> oPrintRealData = new Dictionary<DateTime, int[]>();
Dictionary<DateTime, int[]> oAddRealData = new Dictionary<DateTime, int[]>();
for (int i = 0 ; i < ReadData.Length; i++)
{
DateTime dtDateValue;
if (DateTime.TryParse(oDateTimeAndIntDictionary[0][0], out dtDateValue))
{
int[] iValuesToAdd = ConvertArrayToInt(ReadData[i]);
if (dtDateValue.Date == DateTime.Now.Date)
{
for (int j = 0; j < iValuesToAdd.Length; j++)
{
//Add the ReadData values here and store at ReadData[i][j]
}
}
else if (dtDateValue.Date != DateTime.Now.Date)
{
goto Endloop;
}
}
}
Endloop:
return ReadData;
}
Hope this helps...
I readed your comment about not using linq and 3rd part lib too late.
But let me show you what you are missing.
Here it's a little Linq + CSVHelper
First Lest define your data, and define how to map them in the CSV
public sealed class data
{
public DateTime TimeStamp { get; set; }
public List<int> Numbers { get; set; }
}
public sealed class dataMapping : ClassMap<data>
{
public dataMapping()
{
Map(m => m.TimeStamp).Index(0);
Map(m => m.Numbers)
.ConvertUsing(
row =>
new List<int> {
row.GetField<int>(1),
row.GetField<int>(2),
row.GetField<int>(3)
}
);
}
}
And now this is a short demo:
class CsvExemple
{
string inputPath = "datas.csv";
string outputPath = "datasOut.csv";
List<data> datas;
public void Demo()
{
//no duplicate row in orginal input
InitialiseFile();
LoadExistingData();
//add some new row and some dupe
NewDatasArrived();
//save to an other Path, to Compare.
SaveDatas();
}
private void SaveDatas()
{
using (TextWriter writer = new StreamWriter(outputPath))
using (var csvWriter = new CsvWriter(writer))
{
csvWriter.Configuration.RegisterClassMap<dataMapping>();
csvWriter.Configuration.Delimiter = ";";
csvWriter.Configuration.HasHeaderRecord = false;
csvWriter.WriteRecords(datas);
}
}
static List<int> SuperZip(params List<int>[] sourceLists)
{
for (var i = 1; i < sourceLists.Length; i++)
{
sourceLists[0] = sourceLists[0].Zip(sourceLists[i], (a, b) => a + b).ToList();
}
return sourceLists[0];
}
private void NewDatasArrived()
{
var now = DateTime.Today;
// New rows
var outOfInitialDataRange = Enumerable.Range(11, 15)
.Select(x => new data { TimeStamp = now.AddDays(-x), Numbers = new List<int> { x, x, x } });
// Duplicate rows
var inOfInitialDataRange = Enumerable.Range(3, 7)
.Select(x => new data { TimeStamp = now.AddDays(-x), Numbers = new List<int> { x, x, x } });
//add all of them them together
datas.AddRange(outOfInitialDataRange);
datas.AddRange(inOfInitialDataRange);
// all this could have been one Line
var grouped = datas.GroupBy(x => x.TimeStamp);
var temp = grouped.Select(g => new { TimeStamp = g.Key, ManyNumbers = g.Select(x => x.Numbers).ToArray() });
// We can combine element of 2 list using Zip. ListA.Zip(ListB, (a, b) => a + b)
datas = temp.Select(x => new data { TimeStamp = x.TimeStamp, Numbers = SuperZip(x.ManyNumbers) }).ToList();
}
private void LoadExistingData()
{
if (File.Exists(inputPath))
{
using (TextReader reader = new StreamReader(inputPath))
using (var csvReader = new CsvReader(reader))
{
csvReader.Configuration.RegisterClassMap<dataMapping>();
csvReader.Configuration.HasHeaderRecord = false;
csvReader.Configuration.Delimiter = ";";
datas = csvReader.GetRecords<data>().ToList();
}
}
else
{
datas = new List<data>();
}
}
private void InitialiseFile()
{
if (File.Exists(inputPath))
{
return;
}
var now = DateTime.Today;
var ExistingData = Enumerable.Range(0, 10)
.Select(x => new data { TimeStamp = now.AddDays(-x), Numbers = new List<int> { x, x, x } });
using (TextWriter writer = new StreamWriter(inputPath))
using (var csvWriter = new CsvWriter(writer))
{
csvWriter.Configuration.RegisterClassMap<dataMapping>();
csvWriter.Configuration.Delimiter = ";";
csvWriter.Configuration.HasHeaderRecord = false;
csvWriter.WriteRecords(ExistingData);
}
}
}

Add Key in Dictionary dynamically

Below is the code where key is being hard-coded in Dictionary
var datalist = new List<IDictionary<string, string>>();
for (var i = 0; i < dt.Rows.Count; ++i)
{
var data = new Dictionary<string, string>()
{
{ "ID", Convert.ToString(dt.Rows[i]["ID"]) },
{ "STATUS", Convert.ToString(dt.Rows[i]["Name"]) },
{ "TYPE", Convert.ToString(dt.Rows[i]["TYPE"]) }
};
datalist.Add(data);
}
Now, instead of hard-coding the keys like ID, STATUS, etc, I want to add it from my string array containing the values below
string[] arrNames = ConfigurationManager.AppSettings["NameKey"].Split(',');
How can I traverse arrNamesto add keys in Dictionary and then add in List?
Iterate through the collection of names:
var datalist = new List<IDictionary<string, string>>();
string[] arrNames = ConfigurationManager.AppSettings["NameKey"].Split(',');
for (var i = 0; i < dt.Rows.Count; ++i)
{
var data = new Dictionary<string, string>();
foreach (var name in arrNames)
{
data[name] = Convert.ToString(dt.Rows[i][name]);
}
datalist.Add(data);
}
your code should look something like this
var datalist = new List<IDictionary<string, string>>();
string[] arrNames = Convert.ToString(ConfigurationManager.AppSettings["NameKey"]).Split(',');
if (arrNames.Length == 3)
{
for (var i = 0; i < dt.Rows.Count; ++i)
{
var data = new Dictionary<string, string>()
{
{ arrNames[0], Convert.ToString(dt.Rows[i][arrNames[0]]) },
{ arrNames[1], Convert.ToString(dt.Rows[i][arrNames[1]]) },
{ arrNames[2], Convert.ToString(dt.Rows[i][arrNames[2]]) }
};
datalist.Add(data);
}
}
You can use linq method ToDictionary. Try this code:
string[] arrNames = // new[] {"ID", "STATUS", "TYPE"};
var datalist = new List<IDictionary<string, string>>();
for (var i = 0; i < dt.Rows.Count; ++i)
datalist.Add(
arrNames
.Select(key =>
new
{
key,
value = Convert.ToString(dt.Rows[i][key])
}
)
.ToDictionary(x => x.key, x => x.value)
);
If you prefer LINQ-y and concise you could try something like:
var names = ConfigurationManager.AppSettings["NameKey"].Split(',');
var list = dt.AsEnumerable()
.Select(r => names.ToDictionary(n => n, n => r[n]))
.ToList();
Here I'm assuming dt is a DataTable.
If you have at least the same number of items in your arrNames array than columns you want to read and of course with this order, then you can hardcore the indexes.
var datalist = new List<IDictionary<string, string>>();
for (var i = 0; i < dt.Rows.Count; ++i)
{
var data = new Dictionary<string, string>()
{
{ arrNames[0], Convert.ToString(dt.Rows[i]["ID"]) },
{ arrNames[1], Convert.ToString(dt.Rows[i]["Name"]) },
{ arrNames[2], Convert.ToString(dt.Rows[i]["TYPE"]) }
};
datalist.Add(data);
}

Generate all Combinations from Multiple (n) Lists

EDIT: I am completely redoing my questions as I have figured out the simplest way of asking it. Thanks to the commenters so far that got me thinking about the root problem.
public List<string> GetAllPossibleCombos(List<List<string>> strings)
{
List<string> PossibleCombos = new List<string>();
//????
{
string combo = string.Empty;
// ????
{
combo += ????
}
PossibleCombos.Add(combo);
}
return PossibleCombos;
}
I need to figure out how to recursively go through each List<string> and combine 1 string from each list into a combo string. Don't worry too much about formatting the string as the "live" code uses a custom object instead. Also, feel free to assume that every list will contain at least 1 string and that there are no null values.
Here is a simple non-recursive solution that just concatenates the elements of each combination:
public static List<string> GetAllPossibleCombos(List<List<string>> strings)
{
IEnumerable<string> combos = new [] { "" };
foreach (var inner in strings)
combos = from c in combos
from i in inner
select c + i;
return combos.ToList();
}
static void Main(string[] args)
{
var x = GetAllPossibleCombos(
new List<List<string>>{
new List<string> { "a", "b", "c" },
new List<string> { "x", "y" },
new List<string> { "1", "2", "3", "4" }});
}
You could generalize this to return an IEnumerable<IEnumerable<string>>, which allows the caller to apply any operation they like for transforming each combination into a string (such as the string.Join below). The combinations are enumerated using deferred execution.
public static IEnumerable<IEnumerable<string>> GetAllPossibleCombos(
IEnumerable<IEnumerable<string>> strings)
{
IEnumerable<IEnumerable<string>> combos = new string[][] { new string[0] };
foreach (var inner in strings)
combos = from c in combos
from i in inner
select c.Append(i);
return combos;
}
public static IEnumerable<TSource> Append<TSource>(
this IEnumerable<TSource> source, TSource item)
{
foreach (TSource element in source)
yield return element;
yield return item;
}
static void Main(string[] args)
{
var combos = GetAllPossibleCombos(
new List<List<string>>{
new List<string> { "a", "b", "c" },
new List<string> { "x", "y" },
new List<string> { "1", "2", "3", "4" }});
var result = combos.Select(c => string.Join(",", c)).ToList();
}
Hope this helps.
class NListBuilder
{
Dictionary<int, List<string>> tags = new Dictionary<int, List<string>>();
public NListBuilder()
{
tags.Add(1, new List<string>() { "A", "B", "C" });
tags.Add(2, new List<string>() { "+", "-", "*" });
tags.Add(3, new List<string>() { "1", "2", "3" });
}
public List<string> AllCombos
{
get
{
return GetCombos(tags);
}
}
List<string> GetCombos(IEnumerable<KeyValuePair<int, List<string>>> remainingTags)
{
if (remainingTags.Count() == 1)
{
return remainingTags.First().Value;
}
else
{
var current = remainingTags.First();
List<string> outputs = new List<string>();
List<string> combos = GetCombos(remainingTags.Where(tag => tag.Key != current.Key));
foreach (var tagPart in current.Value)
{
foreach (var combo in combos)
{
outputs.Add(tagPart + combo);
}
}
return outputs;
}
}
}
In case it helps anyone, here is the method syntax version for Douglas's GetAllPossibleCombos method.
public static List<string> GetAllPossibleCombos(List<List<string>> strings)
{
IEnumerable<string> combos = new[] { "" };
foreach (var inner in strings)
{
combos = combos.SelectMany(r => inner.Select(x => r + x));
}
return combos.ToList();
}
Here is a generic version that works with all object types:
public static List<List<T>> GetAllPossibleCombos<T>(List<List<T>> objects)
{
IEnumerable<List<T>> combos = new List<List<T>>() { new List<T>() };
foreach (var inner in objects)
{
combos = combos.SelectMany(r => inner
.Select(x => {
var n = r.DeepClone();
if (x != null)
{
n.Add(x);
}
return n;
}).ToList());
}
// Remove combinations were all items are empty
return combos.Where(c => c.Count > 0).ToList();
}
If you provide null values it will also give you empty combination. For example:
var list1 = new List<string>() { "1A", "1B", null };
var list2 = new List<string>() { "2A", "2B", null };
var output = GetAllPossibleCombos(allLists);
Will contain:
[["1A"], ["1B"], ["2A"], ["2B"], ["1A", "2A"], ["1A", "2B"], ["1B", "2A"], ["1B," "2B"]]
Rather than just:
var list1 = new List<string>() { "1A", "1B" };
var list2 = new List<string>() { "2A", "2B" };
var output = GetAllPossibleCombos(allLists);
[["1A", "2A"], ["1A", "2B"], ["1B", "2A"], ["1B," "2B"]]
Note: DeepClone is an extension method used for copying the list. This can be done in many ways
public static T DeepClone<T>(this T source)
{
// Don't serialize a null object, simply return the default for that object
if (Object.ReferenceEquals(source, null))
{
return default(T);
}
var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace };
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(source), deserializeSettings);
}
Here is an answer that would work for any generic type, comes with a function to convert base-10 to base-n as well.
public static class IntExt
{
const string Symbols = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
public static string ToString(this int value, int toBase)
{
switch (toBase)
{
case 2:
case 8:
case 10:
case 16:
return Convert.ToString(value, toBase);
case 64:
return Convert.ToBase64String(BitConverter.GetBytes(value));
default:
if (toBase < 2 || toBase > Symbols.Length)
throw new ArgumentOutOfRangeException(nameof(toBase));
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value));
int resultLength = 1 + (int)Math.Max(Math.Log(value, toBase), 0);
char[] results = new char[resultLength];
int num = value;
int index = resultLength - 1;
do
{
results[index--] = Symbols[num % toBase];
num /= toBase;
}
while (num != 0);
return new string(results);
}
}
}
public class UnitTest1
{
public static T[][] GetJoinCombinations<T>(T[][] arrs)
{
int maxLength = 0;
int total = 1;
for (int i = 0; i < arrs.Length; i++)
{
T[] arr = arrs[i];
maxLength = Math.Max(maxLength, arr.Length);
total *= arr.Length;
}
T[][] results = new T[total][];
int n = 0;
int count = (int)Math.Pow(maxLength, arrs.Length);
for (int i = 0; i < count; i++)
{
T[] combo = new T[arrs.Length];
string indices = i.ToString(maxLength).PadLeft(arrs.Length, '0');
bool skip = false;
for (int j = 0; j < indices.Length; j++)
{
T[] arr = arrs[j];
int index = int.Parse(indices[j].ToString());
if (index >= arr.Length)
{
skip = true;
break;
}
combo[j] = arr[index];
}
if (!skip)
results[n++] = combo;
}
return results;
}
[Fact]
public void Test1()
{
string[][] results = GetJoinCombinations(new string[][]
{
new string[] { "1", "2", "3" },
new string[] { "A", "B", "C" },
new string[] { "+", "-", "*", "/" },
});
}
}
public static IEnumerable<int[]> GetAllPossibleCombos(List<int[]> ints)
=> _getAllPossibleCombos(ints, 0, new List<int>());
private static IEnumerable<int[]> _getAllPossibleCombos(IReadOnlyList<int[]> ints, int index, IReadOnlyCollection<int> current)
{
return index == ints.Count - 1
? ints[index]
.Select(_ => new List<int>(current) {_}.ToArray())
: ints[index]
.SelectMany(_ => _getAllPossibleCombos(ints, index + 1, new List<int>(current) {_}));
}

Combine similar character in string in C#

I have a list of lists that contain integers (this list can be any length and can contain any number of integers:
{{1,2}, {3,4}, {2,4}, {9,10}, {9,12,13,14}}
What I want to do next is combine the lists where any integer matches any integer from any other list, in this case:
result = {{1,2,3,4}, {9,10,12,13,14}}
I have tried many different approaches but am stuck for an elegant solution.
If you just mean "combine when there's an intersection", then maybe something like below, with output:
{1,2,3,4}
{9,10,12}
noting that it also passes the test in your edit, with output:
{1,2,3,4}
{9,10,12,13,14}
Code:
static class Program {
static void Main()
{
var sets = new SetCombiner<int> {
{1,2},{3,4},{2,4},{9,10},{9,12}
};
sets.Combine();
foreach (var set in sets)
{
// edited for unity: original implementation
// Console.WriteLine("{" +
// string.Join(",", set.OrderBy(x => x)) + "}");
StringBuilder sb = new StringBuilder();
foreach(int i in set.OrderBy(x => x)) {
if(sb.Length != 0) sb.Append(',');
sb.Append(i);
}
Console.WriteLine("{" + sb + "}");
}
}
}
class SetCombiner<T> : List<HashSet<T>>
{
public void Add(params T[] values)
{
Add(new HashSet<T>(values));
}
public void Combine()
{
int priorCount;
do
{
priorCount = this.Count;
for (int i = Count - 1; i >= 0; i--)
{
if (i >= Count) continue; // watch we haven't removed
int formed = i;
for (int j = formed - 1; j >= 0; j--)
{
if (this[formed].Any(this[j].Contains))
{ // an intersection exists; merge and remove
this[j].UnionWith(this[formed]);
this.RemoveAt(formed);
formed = j;
}
}
}
} while (priorCount != this.Count); // making progress
}
}
Build custom comparer:
public class CusComparer : IComparer<int[]>
{
public int Compare(int[] x, int[] y)
{
x = x.OrderBy(i => i).ToArray();
y = y.OrderBy(i => i).ToArray();
for (int i = 0; i < Math.Min(x.Length, y.Length); i++ )
{
if (x[i] < y[i]) return -1;
if (x[i] > y[i]) return 1;
}
if (x.Length < y.Length) return -1;
if (x.Length > y.Length) return 1;
return 0;
}
}
Then, order by custom comparer first:
List<int[]> input = new List<int[]>()
{
new[] { 3, 4 }, new[] { 1, 2 }, new[] { 2, 4 },
new[] { 9, 10 }, new[] { 9, 12 }
};
var orderedInput = input.OrderBy(x => x, new CusComparer()).ToList();
Use Intersect.Any() to check:
List<int[]> output = new List<int[]>();
int[] temp = orderedInput[0];
foreach (var arr in orderedInput.Skip(1))
{
if (temp.Intersect(arr).Any())
temp = temp.Union(arr).ToArray();
else
{
output.Add(temp);
temp = arr;
}
}
output.Add(temp);
Here's a simple, flexible solution using LINQ's Aggregate:
void Main()
{
var ints = new []{new []{1,2},new []{3,4},new []{2,4},new []{9,10},new []{9,12}};
var grouped = ints.Aggregate(new List<HashSet<int>>(), Step);
foreach(var bucket in grouped)
Console.WriteLine(String.Join(",", bucket.OrderBy(b => b)));
}
static List<HashSet<T>> Step<T>(List<HashSet<T>> all, IEnumerable<T> current)
{
var bucket = new HashSet<T>();
foreach (var c in current)
bucket.Add(c);
foreach (var i in all.Where(b => b.Overlaps(bucket)).ToArray())
{
bucket.UnionWith(i);
all.Remove(i);
}
all.Add(bucket);
return all;
}
We maintain a list of resulting sets (1). For each source set, remove resulting sets that intersect it (2), and add a new resulting set (3) that is the union of the removed sets and the source set (4):
class Program {
static IEnumerable<IEnumerable<T>> CombineSets<T>(
IEnumerable<IEnumerable<T>> sets,
IEqualityComparer<T> eq
) {
var result_sets = new LinkedList<HashSet<T>>(); // 1
foreach (var set in sets) {
var result_set = new HashSet<T>(eq); // 3
foreach (var element in set) {
result_set.Add(element); // 4
var node = result_sets.First;
while (node != null) {
var next = node.Next;
if (node.Value.Contains(element)) { // 2
result_set.UnionWith(node.Value); // 4
result_sets.Remove(node); // 2
}
node = next;
}
}
result_sets.AddLast(result_set); // 3
}
return result_sets;
}
static IEnumerable<IEnumerable<T>> CombineSets<T>(
IEnumerable<IEnumerable<T>> src
) {
return CombineSets(src, EqualityComparer<T>.Default);
}
static void Main(string[] args) {
var sets = new[] {
new[] { 1, 2 },
new[] { 3, 4 },
new[] { 2, 4 },
new[] { 9, 10 },
new[] { 9, 12, 13, 14 }
};
foreach (var result in CombineSets(sets))
Console.WriteLine(
"{{{0}}}",
string.Join(",", result.OrderBy(x => x))
);
}
}
This prints:
{1,2,3,4}
{9,10,12,13,14}
Ok i LINQed this up! Hope this is what you wanted... crazy one ;)
void Main()
{
var matches = new List<List<ComparissonItem>> { /*Your Items*/ };
var overall =
from match in matches
let matchesOne =
(from searchItem in matches
where searchItem.Any(item => match.Any(val => val.Matches(item) && !val.Equals(item)))
select searchItem)
where matchesOne.Any()
select
matchesOne.Union(new List<List<ComparissonItem>> { match })
.SelectMany(item => item);
var result = overall.Select(item => item.ToHashSet());
}
static class Extensions
{
public static HashSet<T> ToHashSet<T>(this IEnumerable<T> enumerable)
{
return new HashSet<T>(enumerable);
}
}
class ComparissonItem
{
public int Value { get; set; }
public bool Matches(ComparissonItem item)
{
/* Your matching logic*/
}
public override bool Equals(object obj)
{
var other = obj as ComparissonItem;
return other == null ? false : this.Value == other.Value;
}
public override int GetHashCode()
{
return this.Value.GetHashCode();
}
}

Categories

Resources