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;
}
Related
I have a complicated sorting pattern to replicate, and my solution seems a bit hamfisted.
My input is a list of numbers that can have several letters as a suffix and prefix both are only letter ('aaa', 'aab', 'ac' etc).
I need to sort numerically, then sort by suffix (if there is one) and then by prefix (if there is one).
E.g.
"a1a",
"5ac",
"1",
"12",
"2",
"11",
"5aa",
"3",
"5ab",
"a2b",
"abb11ca",
"1b",
"aba11ca"
would be sorted as
1
a1a
1b
2
a2b
3
5aa
5ab
5ac
11
aba11ca
abb11ca
12
Here is the solution that I came up with using Linq.
static void Main(string[] args)
{
var arr = new []
{"b2","a1a","5ac","1","12","2","11","5aa","3","5ab","a1","a2b","abb11ca","1b","aba11ca"
};
var ordered = arr.Select(str => {
var parts = SplitIntoPrefixNumberSuffix(str);
var number = int.Parse(parts[1]);
return new { str, parts, number };
})
.OrderBy(x => x.number).ThenBy(x => x.parts[2]).ThenBy(x => x.parts[0])
.Select(x => x.str);
Console.WriteLine("sorted array: ");
foreach (var s in ordered)
{
Console.WriteLine("{0}", s);
}
Console.ReadLine();
}
public static string[] SplitIntoPrefixNumberSuffix(string str)
{
var numChar = new[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
var numLoc = str.IndexOfAny(numChar);
var nums = "";
foreach (var c in str)
{
if (char.IsDigit(c))
nums = nums + c;
}
Console.WriteLine("numLoc: {0}; nums: {1}", numLoc, nums.Count());
var prefix = str.Substring(0, numLoc);
var suffix = str.Substring(numLoc + nums.Count());
Console.WriteLine("prefix {0}; nums {1}; suffix {2}", prefix, nums, suffix);
return new[] { prefix, nums, suffix };
}
Here is a .netfiddle of it working: https://dotnetfiddle.net/C7ZA0b.
While it works, it feels like it's not a very good solution. I'm iterating over the collection several times, and I think I should be using a custom comparable.
I've never written a Comparable before; I looked at Dot Net Pearls Alphanumeric Sorting and can follow it but not well enough to modify it to suit my needs.
Is there an IComparable I can use to do the above job? Any suggestions on a good place to learn how to write one?
So you can use regex named groups to split out the various components of the string, then order by each component:
var regex = new Regex(#"^(?<pre>\D*)(?<num>\d+)(?<suff>\D*)$");
var ordered = data.Select(d => (match: regex.Match(d), value: d))
.Where(x => x.match.Success) //throw away anything that doesn't conform
.Select(x => (
x.value,
pre: x.match.Groups["pre"].Value,
num: int.Parse(x.match.Groups["num"].Value),
suff: x.match.Groups["suff"].Value))
.OrderBy(x => x.num)
.ThenBy(x => x.suff)
.ThenBy(x => x.pre)
.Select(x => x.value);
...but ultimately this isn't that different to your solution. I can't really see how a specialized IComparer will simplify this.
If you don't have tuples available ( < C#7.0), swap for anonymous classes:
data.Select(d => new { match = regex.Match(d), value = d})
.Where(x => x.match.Success)
.Select(x => new {
x.value,
pre = x.match.Groups["pre"].Value,
num = int.Parse(x.match.Groups["num"].Value),
suff = x.match.Groups["suff"].Value})
.OrderBy(x => x.num)
.ThenBy(x => x.suff)
.ThenBy(x => x.pre)
.Select(x => x.value)
As option you can implement your own Custom Comparer
public class CustomStringComparer : IComparer<string>
{
public int Compare(string first, string second)
{
var compareByCore = CompareCore(first, second);
var compareBySuffix = CompareSuffix(first, second);
var compareByPrefix = ComparePrefix(first, second);
return compareByCore != 0 ? compareByCore
: compareBySuffix != 0 ? compareBySuffix
: compareByPrefix;
}
private int CompareCore(string a, string b)
{
var firstCoreNumber = Regex.Match(a, #"\d+").Value;
var secondCoreNumber = Regex.Match(b, #"\d+").Value;
if (!string.IsNullOrEmpty(firstCoreNumber) && !string.IsNullOrEmpty(secondCoreNumber))
{
return int.Parse(firstCoreNumber).CompareTo(int.Parse(secondCoreNumber));
}
return 0;
}
private int CompareSuffix(string a, string b)
{
var firstSuffix = Regex.Match(a, #"\D+$").Value;
var secondSuffix = Regex.Match(b, #"\D+$").Value;
return firstSuffix.CompareTo(secondSuffix);
}
private int ComparePrefix(string a, string b)
{
var firstPrefix = Regex.Match(a, #"^\D+").Value;
var secondPrefix = Regex.Match(b, #"^\D+").Value;
return firstPrefix.CompareTo(secondPrefix);
}
}
And when you call order method just send an instance of this comparer:
var arr = new[]
{ "a1a", "5ac", "1", "12", "2", "11", "5aa", "3", "5ab", "a2b", "abb11ca", "1b", "aba11ca" };
var sortedArr = arr.OrderBy(x => x, new CustomStringComparer());
foreach (var s in sortedArr)
{
Console.Write($"{s} ");
}
As an alternative to my existing answer, I wrote a ComparerBuilder<T> helper a while ago that makes some nice client code.
Now you can:
var comparer = new ComparerBuilder<string>()
.SortKey(k => int.Parse(Regex.Match(k, #"\d+").Value))
.ThenKey(k => Regex.Match(k, #"\D*$").Value)
.ThenKey(k => Regex.Match(k, #"^\D*").Value)
.Build();
var ordered = data.OrderBy(x => x, comparer);
Here is the IComparer implementation of alphanumeric by Dave Koelle.
Edit: Adding code sample.
void Main()
{
var arr = new[] {"b2","a1a","5ac","1","12","2","11","5aa","3","5ab","a1","a2b","abb11ca","1b","aba11ca" };
var items = arr.OrderBy(x => x.ToString(), new AlphanumComparator()).ToList();
Console.WriteLine("sorted array: ");
foreach (var s in items)
{
Console.WriteLine("{0}", s);
}
}
public class AlphanumComparator : IComparer<object>
{
private enum ChunkType { Alphanumeric, Numeric };
private bool InChunk(char ch, char otherCh)
{
ChunkType type = ChunkType.Alphanumeric;
if (char.IsDigit(otherCh))
{
type = ChunkType.Numeric;
}
if ((type == ChunkType.Alphanumeric && char.IsDigit(ch))
|| (type == ChunkType.Numeric && !char.IsDigit(ch)))
{
return false;
}
return true;
}
public int Compare(object x, object y)
{
String s1 = x as string;
String s2 = y as string;
if (s1 == null || s2 == null)
{
return 0;
}
int thisMarker = 0, thisNumericChunk = 0;
int thatMarker = 0, thatNumericChunk = 0;
while ((thisMarker < s1.Length) || (thatMarker < s2.Length))
{
if (thisMarker >= s1.Length)
{
return -1;
}
else if (thatMarker >= s2.Length)
{
return 1;
}
char thisCh = s1[thisMarker];
char thatCh = s2[thatMarker];
StringBuilder thisChunk = new StringBuilder();
StringBuilder thatChunk = new StringBuilder();
while ((thisMarker < s1.Length) && (thisChunk.Length == 0 || InChunk(thisCh, thisChunk[0])))
{
thisChunk.Append(thisCh);
thisMarker++;
if (thisMarker < s1.Length)
{
thisCh = s1[thisMarker];
}
}
while ((thatMarker < s2.Length) && (thatChunk.Length == 0 || InChunk(thatCh, thatChunk[0])))
{
thatChunk.Append(thatCh);
thatMarker++;
if (thatMarker < s2.Length)
{
thatCh = s2[thatMarker];
}
}
int result = 0;
// If both chunks contain numeric characters, sort them numerically
if (char.IsDigit(thisChunk[0]) && char.IsDigit(thatChunk[0]))
{
thisNumericChunk = Convert.ToInt32(thisChunk.ToString());
thatNumericChunk = Convert.ToInt32(thatChunk.ToString());
if (thisNumericChunk < thatNumericChunk)
{
result = -1;
}
if (thisNumericChunk > thatNumericChunk)
{
result = 1;
}
}
else
{
result = thisChunk.ToString().CompareTo(thatChunk.ToString());
}
if (result != 0)
{
return result;
}
}
return 0;
}
}
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) {_}));
}
I have list of domains and sub-domains like
abc.com
def.com
ijk.com
pages.abc.com
help.abc.com
contactus.def.com
help.def.com
My Requirement is to sort this list by domains, such that the final output is
abc.com
pages.abc.com
help.abc.com
def.com
contactus.def.com
ijk.com
How can i achieve this in C#? Im new to C# programming. Can anybody help?
Think you made an error sorting the example but here's a solution:
class DomainComparer : IComparer<string>
{
public int Compare(string x, string y)
{
if(x==y) return 0;
string[] _x = x.Split('.');
string[] _y = y.Split('.');
return Compare(_x, _y, 0);
}
private int Compare(string[] x, string[] y, int depth)
{
if (x.Length - depth - 1 >= 0 && y.Length - depth -1 < 0)
{
return +1;
}
if (y.Length - depth - 1 >= 0 && x.Length - depth -1 < 0)
{
return -1;
}
if (x[x.Length-depth-1].CompareTo(y[y.Length - depth-1]) == 0)
{
return Compare(x, y, depth + 1);
}
else
{
return x[x.Length-depth-1].CompareTo(y[y.Length - depth-1]);
}
}
}
Then you can call it with:
string[] domains = new[] { "abc.com", "def.com", "ijk.com", "pages.abc.com", "help.abc.com", "contactus.def.com", "help.def.com" };
Array.Sort(domains, new DomainComparer());
foreach (var item in domains)
{
Console.WriteLine(item);
}
Output:
abc.com
help.abc.com
pages.abc.com
def.com
contactus.def.com
help.def.com
ijk.com
Or if you dont have an array but a
List<string>
or an
IEnumerable<string>
you can do it with Linq:
IEnumerable<string> sorted = domains.OrderBy(x => x, new DomainComparer());
If you only need to sort by second level domain & TLD then you can do something like this.
var uriList = new string[] {"abc.com", "def.com", "ijk.com", "pages.abc.com",
"help.abc.com", "contactus.def.com", "help.def.com"};
var query = from uri in uriList.Select(item =>
new { Uri = item, UriParts = item.Split('.') })
orderby uri.UriParts[uri.UriParts.Length-2] + uri.UriParts[uri.UriParts.Length-1],
string.Join(".", uri.UriParts) select uri.Uri;
Console.WriteLine(string.Join(" ,", query));
The output will be slightly different from what you are expecting, like this.
abc.com, help.abc.com, pages.abc.com, contactus.def.com, def.com, help.def.com, ijk.com
it could be done like this:
List<string> list = new List<string>();
list.Add("abc.com");
list.Add("def.com");
list.Add("ijk.com");
list.Add("pages.abc.com");
list.Add("help.abc.com");
list.Add("contactus.def.com");
list.Add("help.def.com");
List<string> listRoot = new List<string>();
List<string> listSub = new List<string>();
foreach (string item in list)
{
string[] split = item.Split(new char[] {'.'}, StringSplitOptions.RemoveEmptyEntries);
if (split.Length == 2)
{
listRoot.Add(item);
}
else
{
listSub.Add(item);
}
}
listRoot.Sort();
Dictionary<string, List<string>> dictionary = new Dictionary<string, List<string>>();
foreach (string root in listRoot)
{
List<string> listSubIntern = new List<string>();
foreach (string item in listSub)
{
if (item.EndsWith(root, StringComparison.InvariantCultureIgnoreCase))
{
listSubIntern.Add(item);
}
}
listSubIntern.Sort();
dictionary.Add(root, listSubIntern);
}
foreach (KeyValuePair<string, List<string>> keyValuePair in dictionary)
{
Console.WriteLine(keyValuePair.Key);
foreach (string s in keyValuePair.Value)
{
Console.WriteLine("\t{0}", s);
}
}
and the output:
abc.com
help.abc.com
pages.abc.com
def.com
contactus.def.com
help.def.com
ijk.com
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();
}
}
I'm looking to combine the contents of two string arrays, into a new list that has the contents of both joined together.
string[] days = { "Mon", "Tue", "Wed" };
string[] months = { "Jan", "Feb", "Mar" };
// I want the output to be a list with the contents
// "Mon Jan", "Mon Feb", "Mon Mar", "Tue Jan", "Tue Feb" etc...
How can I do it ? For when it's only two arrays, the following works and is easy enough:
List<string> CombineWords(string[] wordsOne, string[] wordsTwo)
{
var combinedWords = new List<string>();
foreach (var wordOne in wordsOne)
{
foreach (string wordTwo in wordsTwo)
{
combinedWords.Add(wordOne + " " + wordTwo);
}
}
return combinedWords;
}
But I'd like to be able to pass varying numbers of arrays in (i.e. to have a method with the signature below) and have it still work.
List<string> CombineWords(params string[][] arraysOfWords)
{
// what needs to go here ?
}
Or some other solution would be great. If it's possible to do this simply with Linq, even better!
What you want to do is actually a cartesian product of all the arrays of words, then join the words with spaces. Eric Lippert has a simple implementation of a Linq cartesian product here. You can use it to implement CombineWords:
List<string> CombineWords(params string[][] arraysOfWords)
{
return CartesianProduct(arraysOfWords)
.Select(x => string.Join(" ", x))
.ToList();
}
To cross join on any amount of arrays of strings:
// Define other methods and classes here
List<string> CombineWords(params string[][] arraysOfWords)
{
if (arraysOfWords.Length == 0)
return new List<string>();
IEnumerable<string> result = arraysOfWords[0];
foreach( string[] words in arraysOfWords.Skip(1) )
{
var tempWords = words;
result = from r in result
from w in tempWords
select string.Concat(r, " ", w);
}
return result.ToList();
}
Code below works for any number of arrays (and uses linq to some degree):
List<string> CombineWords(params string[][] wordsToCombine)
{
if (wordsToCombine.Length == 0)
return new List<string>();
IEnumerable<string> combinedWords = wordsToCombine[0].ToList();
for (int i = 1; i < wordsToCombine.Length; ++i)
{
var temp = i;
combinedWords = (from x in combinedWords from y in wordsToCombine[temp]
select x + " " + y);
}
return combinedWords.ToList();
}
public static List<string> CombineWords(params string[][] arraysOfWords)
{
var strings = new List<string>();
if (arraysOfWords.Length == 0)
{
return strings;
}
Action<string, int> combineWordsInternal = null;
combineWordsInternal = (baseString, index) =>
{
foreach (var str in arraysOfWords[index])
{
string str2 = baseString + " " + str;
if (index + 1 < arraysOfWords.Length)
{
combineWordsInternal(str2, index + 1);
}
else
{
strings.Add(str2);
}
}
};
combineWordsInternal(string.Empty, 0);
return strings;
}
Second try... I'm not able to do it in LINQ... A little too much complex to linquize correctly :-)
I'm using a local anonymous function (and showing that it's quite complex to recurse on anonymous functions, because you have to declare them separately)
This is a non-recursive solution which buffers strings as it progresses, to reduce the number of concatenations. Therefore it should also be usable for more arrays.
It also preserves your desired order - the items in the first array will always be at the beginning of the resulting string.
var s1 = new string[] { "A", "B", "C" };
var s2 = new string[] { "1", "2", "3", "4" };
var s3 = new string[] { "-", "!", "?" };
var res = Combine(s1, s2, s3);
And the function in question:
private List<string> Combine(params string[][] arrays)
{
if (arrays.Length == 1)
{
// The trivial case - exit.
return new List<string>(arrays[0]);
}
IEnumerable<string> last = arrays[arrays.Length - 1];
// Build from the last array, progress forward
for (int i = arrays.Length - 2; i >= 0; i--)
{
var buffer = new List<string>();
var current = arrays[i];
foreach (var head in current)
{
foreach (var tail in last)
{
// Concatenate with the desired space.
buffer.Add(head + " " + tail);
}
}
last = buffer;
}
return (List<string>)last;
}
Could you try this method ?
static List<string> CombineWords(string[] wordsOne, string[] wordsTwo)
{
var combinedWords = new List<string>();
for(int x = 0; (x <= wordsOne.Length - 1); ++x)
{
for(int y = 0; (x <= wordsTwo.Length - 1); ++y)
{
combinedWords.Add(string.Format("{0} {1}", wordsOne[x], wordsTwo[y]));
}
}
return combinedWords;
}
Kris