Order alphanumeric string numerically, and then by prefix/suffix - c#

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

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

How to correctly divide List<string>?

I have List<string> {"", "1,5,4", "h", "5,8", "1"}. I need to divide into 3 List<int>. This is my code:
var parseString = condition.Trim().Split(separator).ToList();
var numberSections = new List<string>();
var numberRow = new List<string>();
var numberCell = new List<string>();
foreach (var str in parseString) {
if (int.TryParse(str.Substring(0, 1), out i) && numberSections.Count == 0) {
numberSections.Add(str);
parseString.Remove(str);
}
if (int.TryParse(str.Substring(0, 1), out i) && numberRow.Count == 0) {
numberRow.Add(str);
parseString.Remove(str);
}
if (int.TryParse(str.Substring(0, 1), out i) && numberCell.Count == 0) {
numberCell.Add(str);
parseString.Remove(str);
}
}
But it do not working. How I can do it?
Here is a LINQ version for it
var result = list.Select(x => x.Split(",".ToCharArray(),
StringSplitOptions.RemoveEmptyEntries)) // now we have List<List<string>>
.Select(x => x.Select(y =>
{
int value;
var isInt = int.TryParse(y, out value);
return isInt ? value : (int?)null;
})) // convert each element of inner list to null or its int values
// we have a List<List<int?>>
.Where(x => x.Any() && x.All(y => y.HasValue)) // only select lists which contains only integers
.ToList();

Meshing IEnumerable<T> Lists Together

I am looking for the most efficient way to mesh all the rows in IEnumerable<T> A before each row in IEnumerable<T> B.
For example:
A = {A, B}
B = {1, 2, 3}
After meshing:
B = {A, 1, B, 2, A, 3}
There is no easy solution, but something like this might work. The modulus operator is very important here, to repeat the results from the list with less items.
public static List<object> Mesh<T1, T2>(IEnumerable<T1> s1, IEnumerable<T2> s2)
{
T1[] array1 = s1.ToArray();
T2[] array2 = s2.ToArray();
int length1 = array1.Length;
int length2 = array2.Length;
int maxLength = Math.Max(length1, length2);
List<object> result = new List<object>();
for (int i = 0; i < maxLength; i++)
{
result.Add(array1[i % length1]);
result.Add(array2[i % length2]);
}
return result.
}
That solution uses IEnumerable parameters directly without initializing them and also returns an IEnumerable.
public IEnumerable<object> Mesh<S,T>(IEnumerable<S> items1, IEnumerable<T> items2){
bool items1Empty;
bool items2Empty;
bool items1Finished = items1Empty = ValidateParameter(items1, "items1");
bool items2Finished = items2Empty = ValidateParameter(items2, "items2");
using(var items1Enumerator = items1.GetEnumerator()){
using(var items2Enumerator = items2.GetEnumerator()){
while(true){
MoveNext(items1Enumerator, ref items1Finished);
MoveNext(items2Enumerator, ref items2Finished);
if(items1Finished && items2Finished)
break;
if(!items1Empty)
yield return items1Enumerator.Current;
if(!items2Empty)
yield return items2Enumerator.Current;
}
}
}
}
private bool ValidateParameter<T>(IEnumerable<T> parameter, string parameterName){
if(parameter == null)
throw new ArgumentNullException(parameterName);
return !parameter.Any();
}
private void MoveNext(IEnumerator enumerator, ref bool finished){
if(!enumerator.MoveNext()){
enumerator.Reset();
enumerator.MoveNext();
finished = true;
}
}
Try this, my first guess
for(int countA = 0, countB = 0; countA < A.Length; countA ++, countB++)
{
if(countB >= B.Length)
{
countB = 0;
}
newList.Add(A[countA]);
newList.Add(B[countB]);
}
I want to take a party too! Since I like LINQ very much, let's consider the following LINQ solution (sure it is not that efficient, but for fun):
public static void Main()
{
var a = new[] {1, 2, 3};
var b = new[] {'a', 'b'};
var joined = a.Join(
b,
x => Array.IndexOf(a, x) % b.Length,
y => Array.IndexOf(b, y) % a.Length,
(x, y) => new object[]{ x, y }
);
var flat = joined.SelectMany(x => x);
Console.Write(string.Join(", ", flat));
}
Another approach
public static IEnumerable<T> Mesh<T>(this IEnumerable<T> source, params IEnumerable<T>[] others)
{
var enumerators = new[] { source.GetEnumerator() }.Concat(others.Select(o => o.GetEnumerator())).ToList();
var finishes = new bool[enumerators.Count];
var allFinished = false;
while (!allFinished)
{
allFinsihed = true;
for (var i = 0; i < enumerators.Count; i++)
{
IEnumerator<T> enumerator = enumerators[i];
if (!enumerator.MoveNext())
{
finishes[i] = true;
enumerator.Reset();
enumerator.MoveNext(); // Not sure, if we need it here, Reset says: BEFORE the first element
}
yield return enumerator.Current;
if (!finishes[i])
{
allFinished = false;
}
}
}
}
You could write you own extension to allow greater flexibility.
Note: that this answer doesn't allocate a lot of unnecessary memory and enumerates all sequences exactly once.
I've included parameter checking.
public static IEnumerable<T> Mesh<T>(
this IEnumerable<T> source,
params IEnumerable<T>[] others)
{
if (others.LongLength == 0L)
{
foreach (var t in source)
{
yield return t;
}
yield break;
}
var nullCheck = Array.FindIndex(others, e => e == null);
if (nullCheck >= 0)
{
throw new ArgumentNullException(string.Format(
"Parameter {0} is null, this is not supported.",
++nullCheck),
(Exception)null);
}
var enumerators = new[] { source.GetEnumerator() }
.Concat(others.Select(o => o.GetEnumerator())).ToList();
try
{
var finishes = new bool[enumerators.Count];
var allFinished = false;
while (!allFinished)
{
allFinsihed = true;
for (var i = 0; i < enumerators.Count; i++)
{
if (finishes[i])
{
continue;
}
if (enumerators[i].MoveNext())
{
yield return enumerators[i].Current;
allFinished = false;
continue;
}
finishes[i] = true;
}
}
}
finally
{
foreach (var enumerator in enumerators)
{
enumerator.Dispose();
}
}
}
This would allow you to "Mesh" any number of jagged sequences as long has the items have the same type. e.g.
var a = new[] { 'a', 'b', 'c' };
var b = new[] { '1', '2', '3' };
var c = new[] { 'x', 'y' };
var d = new[] { '7', '8', '9', '0' };
var meshed = a.Mesh(b, c, d);
would yield
{ 'a', '1', 'x', '7', 'b', '2', 'y', '8', 'c', '3', '9', '0' }
If you wanted to mix types you could do
var a = new[] { 'a', 'b', 'c' };
var b = new[] { 1, 2, 3 };
var meshed = a.Cast<object>().Mesh(b.Cast<object>);
yielding something like,
{ ('a'), (1), ('b'), (2), ('c'), (3) }

How Sort A List With C# For A Specific Part Of Every Item In List

please see the list below :
string[] Separator = new string[] { "__" };
string[] lines_acc = File.ReadAllLines(accfilePath);
List<string> list_lines_acc = new List<string>(lines_acc);
List<string> list_lines_silver_count = new List<string>();
FileStream fs_ = null;
if (!File.Exists(silver_countfilePath))
{
using (fs_ = File.Create(silver_countfilePath))
{
foreach (string line_acc in list_lines_acc)
{
string[] line_acc_ar = line_acc.Split(Separator, StringSplitOptions.None);
string line_acc_new = line_acc_ar[0] + "__" + line_acc_ar[1] + "__" + line_acc_ar[3] + "__" + line_acc_ar[4] + "__" + "0";
list_lines_silver_count.Add(line_acc_new);
}
File.WriteAllLines(silver_countfilePath, list_lines_silver_count);
}
}
else
{
string[] lines_silver_count = File.ReadAllLines(silver_countfilePath);
list_lines_silver_count = new List<string>(lines_silver_count);
}
i want to sort list_lines_silver_count by line_acc_ar[4] part!
that part is a string like -> 325423 -> mean i can convert it to an integer.
how can i do that job?
One way is to implement the comparer and provide it as an argument to the Sort function:
public class SilverCountLineComparer : IComparer<string>
{
public int Compare(string x, string y)
{
string xPart = x.Split(new char[] {'_'}, StringSplitOptions.RemoveEmptyEntries)[3];
string yPart = y.Split(new char[] {'_'}, StringSplitOptions.RemoveEmptyEntries)[3];
int xNum = Int32.Parse(xPart);
int yNum = Int32.Parse(yPart);
return xNum.CompareTo(yNum);
}
}
And to sort call it like this:
list_lines_silver_count.Sort(new SilverCountLineComparer());
Change the foreach:
foreach (var line_acc_ar in list_lines_acc
.Select(s => s.Split(Separator, StringSplitOptions.None)
.OrderBy(a => a[4])) {
}
Further refactorings could make the code more elegant, but I think this piece of LINQ should do the job.
You can try this
sorted using a Comparison generic delegate representing the CompareStringByInteger method
public static int CompareStringByInteger(string x, string y)
{
if (x == null)
{
if (y == null)
return 0;
else
return -1;
}
else
{
try{
return Convert.ToInt32(x).CompareTo(Convert.ToInt32(y));
}catch{
return x.CompareTo(y);
}
}
}
and apply it in sort method.
list_lines_silver_count.Sort(CompareStringByInteger);
You can use Linq:
int number = 0;
string[] lines_silver_count = File
.ReadLines(silver_countfilePath)
.Select(l => new {
Line = l,
Parts = l.Split(Separator, StringSplitOptions.None)
})
.Where(x => x.Parts.Length > 4
&& int.TryParse(x.Parts[4], out number))
.OrderBy(x => number)
.Select(x => x.Line)
.ToArray();
public class MyCompare : IComparer<string>
{
public int Compare(string x, string y)
{
//get line_acc_ar[4] part from strings x and y
string[] Separator = new string[] { "__" };
string partX = x.Split(Separator, StringSplitOptions.None)[3];
string partY = y.Split(Separator, StringSplitOptions.None)[3];
int intPartX = int.Parse(partX );
int inrPartY = int.Parse(partY );
return intPartX.CompareTo(inrPartY)
}
}
list_lines_silver_count.OrderBy(a => a, new MyCompare());

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