Identify subsets of root data in a list of trees - c#

I have following structure:
Node
{
List<String> rootData;
List<Node> Children;
}
and a collection as
List<Node> lstOfTrees
the first Structure holds some words on rootData, (List of node is not really important here) and the collection lstOfTrees contains the the trees.
Problem is:
In lstOfTrees, there are multiple trees. Some of the trees have subset of rootData of other trees (possibly, not necessarily). I want to keep the tree having super-set of other rootData(s) in lstOfTrees (subset should be ignored).
example:
assuming, lstOfTrees contain the trees as
1: {rootData: A, B, C, D}
2: {rootData: E, F, G}
3: {rootData: G, H}
4: {rootData: J, A, C}
5: {rootData: D, Z}
the final answer I need, should be in a new list containing:
1: {rootData: A, B, C, D}
2: {rootData: E, F, G}
Can this be done using LINQ and TPL (or the more effecient way) ? I want it to be efficient and correct.
EDIT:
should the following code work correctly in all cases or am I missing something??
lstOfTrees.Add(new node());
lstOfTrees[0].rootData = new List<string> {"A", "B", "C", "D"};
lstOfTrees.Add(new node());
lstOfTrees[1].rootData = new List<string> {"E", "F", "G"};
lstOfTrees.Add(new node());
lstOfTrees[2].rootData = new List<string> {"G", "H"};
lstOfTrees.Add(new node());
lstOfTrees[3].rootData = new List<string> {"J", "A", "C"};
lstOfTrees.Add(new node());
lstOfTrees[4].rootData = new List<string> {"D", "Z"};
Dictionary<int,node> dictOfTrees_indexToNode = Enumerable.Range(0, lstOfTrees.Count).ToDictionary(x=>x,x => lstOfTrees[x]);
List<int> notToInclude = new List<int>();
for (int i = 0; i < lstOfTrees.Count; i++)
{
for (int j = 0; j < lstOfTrees.Count; j++)
{
if (j != i)
{
if (!lstOfTrees[j].Equals(lstOfTrees[i]))
{
if (lstOfTrees[j].rootData.Join(lstOfTrees[i].rootData, root => root, innerRoot => innerRoot,
(root, innerRoot) => 1).Any())
{
bool test = (lstOfTrees[j].rootData.Count > lstOfTrees[i].rootData.Count);
notToInclude.Add(test ? i : j);
}
}
}
}
}
List<node> finalList = new List<node>();
finalList.AddRange(lstOfTrees.Except(notToInclude.Select(s=>dictOfTrees_indexToNode[s])));
Also, Can I improve from this?

I've simplified the case a little bit for testing to just searching through the list of list of strings, which should be the same thing that you're doing after a small middle step:
var list = lstOfTrees.Select(x => new HashSet<string>(x.rootData)).ToList();
Also, it's quite possible that it would be better to use sets here, at least I don't see any duplicates in the example data, and that's the second change.
Using sets here is quite important, so if data can - in fact - be duplicated in the lists, then the whole solution would have to change.
Here's the result:
var list = new List<List<string>> {
new List<string> {"A", "B", "C", "D"},
new List<string> {"E", "F", "G"},
new List<string> {"G", "H"},
new List<string> {"J", "A", "C"},
new List<string> {"D", "Z"}};
var sets = list.Select(x => new HashSet<string>(x)).ToList();
var result = sets.Select(x => sets.Where(y => x.Overlaps(y)) // You are looking not for 'subsets', but overlapping sets
.OrderByDescending(y => y.Count)
.FirstOrDefault())
.Distinct();
This returns IEnumerable<HashSet<string>>:
{"A", "B", "C", "D"}, {"E", "F", "G"}
Tested in LINQPad :)

Related

Is there a LINQ operator to do this?

I would like to know if there's a LINQ operator to do this:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var combined = new [] { one, two };
var result = Operator(combined);
Console.WriteLine(result.Should().BeEquivalentTo(new [] { "A", "A", "B", "B", "C", "C", null, "D" }));
If short, it should act like if every sequence was a row in a matrix. And after that, it should:
Transpose the matrix (rotate it)
It should push every "cell" in the matrix, returning the corresponding item, or default if the cell is empty.
I mean, graphically:
A, B, C
A, B, C, D
<Transpose>
A, A
B, B
C, C
D
result => A, A, B, B, C, C, D, null
NOTICE
Operator should work on IEnumerable<IEnumerable<T>>
As you can see, the Operator I'm interested in, uses combined, so it accepts should IEnumerable<IEnumerable<T>> (like SelectMany).
It's a bit hard keeping up with your changing specs. Originally, it was a pair of string arrays. I changed that to be a pair of arrays of T in my answer.
Then you wrote in a comment that "oh, no, I meant N sequences". Finally, after reading that, I noticed that you'd updated your question to ask about N collections expressed as IEnumerable<T>.
In the mean time, I pointed out that my original answer would work well for N arrays with minimal change. So, here goes:
For N Arrays
I use the params keyword to remove the need for your combined variable. The params keyword will compose some or all of the parameters of a method into an array.
Here's a method that can take N arrays:
public static IEnumerable<T> KnitArrays<T>(params T[][] arrays)
{
var maxLen = (from array in arrays select array.Length).Max();
for (var i = 0; i < maxLen; i++)
{
foreach( var array in arrays)
{
yield return array.Length > i ? array[i] : default(T);
}
}
}
It's pretty much the same logic as the original answer. The test code looks the same as well:
var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var three = new[] { "A3", "B3" };
var knittedArray = KnitArrays(one, two, three);
List<string> result = knittedArray.ToList();
WriteCollectionContents(result);
Where WriteCollectionContents spits out the contents of the collection. In this case:
"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,
For N Lists and/or Arrays
It turns out that the same basic code can work with IList<T>, i.e., for both List<T> and T[]:
public static IEnumerable<T> KnitILists<T>(params IList<T>[] ilists)
{
var maxLen = (from ilist in ilists select ilist.Count).Max();
for (var i = 0; i < maxLen; i++)
{
foreach (var ilist in ilists)
{
yield return ilist.Count > i ? ilist[i] : default(T);
}
}
}
The test code for this also looks pretty similar - though note the mix or arrays and lists:
var one = new[] { "A1", "B1", "C1" };
var two = new[] { "A2", "B2", "C2", "D2" };
var list3 = new List<string> { "A3", "B3" };
var knittedLists = KnitILists(one, two, list3);
result = knittedLists.ToList();
WriteCollectionContents(result);
With exactly the same result:
"A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", null, null, "D2", null,
The reason it works with IList<T> is that that interface has a Count property and an Item indexer. If you go to ICollection<T> the Count property stays, but you lose the Item indexer.
Once you get to IEnumerable<T>, both the Count property and the Item indexer are gone. The only thing you can do with an IEnumerable is to iterate through it. As a result, the logic needs to be very different.
I might get around to coming up with a solution. However, it will likely look very similar to #gertarnold's answer.
I'm looking foreword to your upcoming comment about how you really meant for this to work with multi-dimensional arrays as well.
Original answer follows
How about something like this:
public static IEnumerable<T> KnitArrays<T>(T[] first, T[] second)
{
var maxLen = Math.Max(first.Length, second.Length);
for (var i = 0; i < maxLen; i++)
{
yield return first.Length > i ? first[i] : default(T);
yield return second.Length > i ? second[i] : default(T);
}
}
Testing this with:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var knittedArray = KnitArrays(one, two);
List<string> result = knittedArray.ToList();
yields a list that looks like what you are asking. Note that I just return a non-materialized IEnumerable since you were asking about LINQ.
To make the result independent of the number of arrays the function should loop trough all arrays and keep returning until all enumerations are exhausted:
public static IEnumerable<IEnumerable<T>> Transpose<T>(this IEnumerable<IEnumerable<T>> source)
{
var enumerators = source.Select(e => e.GetEnumerator()).ToArray();
try
{
var next = false;
do
{
var results = enumerators.Select(enr =>
{
if (enr.MoveNext())
{
return enr.Current;
}
return default;
}).ToList();
next = results.Any(e => !Equals(default, e));
if (next)
{
yield return results;
}
}
while (next);
}
finally
{
Array.ForEach(enumerators, e => e.Dispose());
}
}
Now you can use any number of arrays:
var one = new[] { "A", "B", "C" };
var two = new[] { "A", "B", "C", "D" };
var three = new[] { "U", "V","W", "X", "Y", "Z" };
var combined = new[] { one, three, two };
var result = combined.Transpose().SelectMany(e => e).ToList();
This will result in
"A","U","A","B","V","B","C","W","C",null,"X","D",null,"Y",null,null,"Z",null
Filtering the null values is trivial.
(Courtesy this answer for the basic idea, but only working for arrays of equal length).
try this
var result = Enumerable.Range(0, Math.Max(one.Count(), two.Count()))
.SelectMany(n => new[] { one.ElementAtOrDefault(n), two.ElementAtOrDefault(n) });
result
["A","A","B","B","C","C",null,"D"]

How to get multiple combination of items from multiple list in c#

I have three list
List<string> firstList = new List<string> { "A", "B" };
List<string> secondList = new List<string> { "C", "D", "E" };
List<string> thirdList = new List<string> { "F", "G" };
And i want multiple combination among all of above three list like
ACF
ACG
ADF
ADG
...
I tried SelectMany and Zip but did't work.
Note: An help would be appreciate if I get my desired output by using lambda expression.
You can do this by using Join like
public class Program
{
static void Main(string[] args)
{
List<string> firstList = new List<string> { "A", "B" };
List<string> secondList = new List<string> { "C", "D", "E" };
List<string> thirdList = new List<string> { "F", "G" };
List<string> result = firstList
.Join(secondList, x => true, y => true, (m, n) => m + n)
.Join(thirdList, a => true, b => true, (a, b) => a + b)
.ToList();
result.ForEach(x => Console.WriteLine(x));
Console.ReadLine();
}
}
Output:
you need 3 loops:
List<string> combinations = new List<string>();
for(int i=0; i < firstList.Length; i++)
for(int j=0;j < secondList.Length; j++)
for(int k=0;k < thirdList.Length; k++)
combinations.Add(firstList[i]+secondList[j]+thirdList[k]);

How to split into sublists using LINQ? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Split List into Sublists with LINQ
I'm looking for some way to split an enumerable into three enumerables using LINQ, such that each successive item in the input is in the next sublist in in the sequence. So input
{"a", "b", "c", "d", "e", "f", "g", "h"}
would result in
{"a", "d", "g"}, {"b", "e", "h"}, {"c", "f"}
I've done it this way but I'm sure there must be a way to express this more elegantly using LINQ.
var input = new List<string> {"a", "b", "c", "d", "e", "f", "g", "h"};
var list = new List<string>[3];
for (int i = 0; i < list.Length; i++)
list[i] = new List<string>();
int column = 0;
foreach (string letter in input)
{
list[column++].Add(letter);
if (column > 2) column = 0;
}
This is what you are looking for:
(Splits by columns) Modified based on the previous posts
The key difference is in the group by, using mod instead of division.
Also I made it generic so it gives you back the proper type (as opposed to "object typed" code). You can just use type inference with generics.
public static IEnumerable<IEnumerable<T>> SplitColumn<T>( IEnumerable<T> source ) {
return source
.Select( ( x, i ) => new { Index = i, Value = x } )
.GroupBy( x => x.Index % 3 )
.Select( x => x.Select( v => v.Value ).ToList() )
.ToList();
}

C# : How to get running combination from two List<String> based on a master list

Dear all , this is something like my previous question How to get moving combination from two List<String> in C#?
I'm having a masterlist and two childlist like below
List<String> MasterList = new List<string> { "A", "B", "C", "D", "E" };
List<String> ListOne = new List<string> { "A", "B", "C" };
List<String> ListTwo = new List<String> { "B", "D" };
I just need to get the running combination from the above list for that i'm using like(previous question's answer(Thanks Danny Chen))
List<String> Result = new List<string>();
Result = ListOne.SelectMany((a, indexA) => ListTwo
.Where((b, indexB) => ListTwo
.Contains(a) ? !b.Equals(a) && indexB > indexA :
!b.Equals(a)).Select(b => string.Format("{0}-{1}", a, b))).ToList();
so the Result list will contain
"A-B"
"A-D"
"B-D"
"C-B"
"C-D"
Now my problem is the sorting issue
In the above result the fourth entry is C-B but it should be B-C. Because in the MasterList the C is after B.
How to do this in my existing linq .
Please help me to do this.
Not really clear on the exact requirement here, so does the MasterList dictate which of the two items should appear first? What about the order of the X1-X2 list? i.e. should B-C appear before B-D because C appears before D in the MasterList?
Anyway, here's something that produces the result you've asked for so far:
List<String> MasterList = new List<string> { "A", "B", "C", "D", "E" };
List<String> ListOne = new List<string> { "A", "B", "C" };
List<String> ListTwo = new List<String> { "B", "D" };
ListOne.SelectMany(i =>
ListTwo.Where(i2 => i != i2)
.Select(i2 =>
{
if (MasterList.IndexOf(i) < MasterList.IndexOf(i2))
return string.Format("{0}-{1}", i, i2);
else
return string.Format("{0}-{1}", i2, i);
}
));
outputs:
A-B
A-D
B-D
B-C
C-D

LINQ expression for shortest common prefix

Can anyone help me with a nice LINQ expression for transforming a list of strings in another list containing only the shortest distinct common prefixes for the strings? The delimiter for prefixes is ..
Example: ["A", "A.B.D", "A", "A.B","E","F.E", "F","B.C"]
Goes to: ["A", "E", "F", "B.C"]
Removed:
"A.B.D" and "A.B" because the prefix "A" is already in the list
"A" because is duplicate
"F.E" because "F" already in list
Thanks!
Here you go:
from set in
(from item in list select item.Split('.')).GroupBy(x => x[0])
select
set.First()
.TakeWhile((part, index) => set.All(x => x.Length > index && x[index].Equals(part)))
.Aggregate((x, y) => String.Format("{0}.{1}", x, y));
By way of explanation:
First, we split all the strings by '.' and group by their first token.
Then, we look at the first element of each grouping, and we take parts from it while every element of that group continues to match (TakeWhile).
Then, we take all those parts and recompose them with the Aggregate(String.Format).
var items = new[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = items
.OrderBy(s => s.Length)
.Distinct()
.ToLookup(s => s.Substring(0, 1))
.Select(g => g.First());
Order the items by their length, call distinct to remove duplicates, convert to groupings based on the first character, and select the first item in each group.
Yields:
"A", "E", "F", "B.C"
Edit: You probably don't even need Distinct as your selecting the first item in each group anyway, so it's really redundant.
EDIT: thanks to the comments for pointing out a bug in my earlier approach.
To get around that shortcoming this query should work:
var list = new List<string> { "A.B.D", "A", "A.B","E","F.E", "F","B.C", "B.C.D" };
var result = list.OrderBy(s => s)
.GroupBy(s => s[0])
.Select(g => g.First());
foreach (var s in result)
{
Console.WriteLine(s);
}
Incorrect approach:
The following query will group each string by the first character. Next, if the group count has more than one item the key is selected, otherwise the single item is selected.
var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = list.GroupBy(s => s[0])
.Select(g => g.Count() > 1 ? g.Key.ToString() : g.Single());
foreach (var s in result)
{
Console.WriteLine(s);
}
Nailed it - assuming that if the source list contains "Q.X" & "Q.Y" then the result should contain "Q".
var source = new []
{
"A", "A.B.D", "A",
"A.B", "E", "F.E",
"F", "B.C",
"Q.X", "Q.Y",
"D.A.A", "D.A.B",
};
Func<string, int> startsWithCount =
s => source.Where(x => x.StartsWith(s)).Count();
var results =
(from x in source.Distinct()
let xx = x.Split('.')
let splits = Enumerable
.Range(1, xx.Length)
.Select(n => String.Join(".", xx.Take(n)))
let first = startsWithCount(splits.First())
select splits
.Where(s => startsWithCount(s) == first)
.Last()
).Distinct();
// results == ["A", "E", "F", "B.C", "Q", "D.A"]
string[] source = {"A", "A.B", "A.B.D", "B.C", "B.C.D", "B.D", "E", "F", "F.E"};
var result =
source.Distinct()
.Select(str => str.Split('.'))
.GroupBy(arr => arr[0])
.Select(g =>
{
return string.Join(".",
g.Aggregate((arr1, arr2) =>
{
return arr1.TakeWhile((str, index) => index < arr2.Length
&& str.Equals(arr2[index]))
.ToArray();
}));
});
Steps:
(1) Remove duplicated elements by Distinct()
(2) Split each element to an array, also get ready to be grouped
(3) Group those arrays by the first string in the array
(4) For each group, create one common prefix by aggregating all arrays in the group. The logic for aggregating is that for two arrays arr1 and arr2, take the elements in arr1 until (1)out of bounds (2) corresponding element in arr2 is different
Note: I add two return statements in the code, to make it look cleaner. It can be shorter if remove return and its {} brackets.
How about:
var possible = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var shortest = possible.Distinct().Where(x => possible.Distinct().Where(y => !y.Equals(x) && x.StartsWith(y)).Count() == 0).ToList();
It checks the list against itself excluding items that are equal and any items that starts with any of the other items. I'm not sure about the effeciency though :)
I think it might be hard to solve with one single nice looking linq expression so I wrote a recursive function using linq that solves the problem:
class Program
{
static void Main(string[] args)
{
var input = new string[] { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C", "B.C.D", "B.E" };
var output = FilterFunc(input);
foreach (var str in output)
Console.WriteLine(str);
Console.ReadLine();
}
static string[] FilterFunc(string[] input)
{
if (input.Length <= 1)
return input;
else
{
var firstElem = input[0];
var indexNr = firstElem.Length;
var maxFilteredElems = 0;
for (int i = firstElem.Length; i > 0; i--)
{
var numberOfFilteredElems = input.Where(x => x.StartsWith(firstElem.Substring(0, i))).Count();
if (numberOfFilteredElems > maxFilteredElems)
{
maxFilteredElems = numberOfFilteredElems;
indexNr = i;
}
}
var prefix = firstElem.Substring(0, indexNr);
var recursiveResult = FilterFunc(input.Where(x => !x.StartsWith(prefix)).ToArray());
var result = recursiveResult.ToList();
prefix = prefix.EndsWith(".") ? prefix.Substring(0, prefix.Length - 1) : prefix;
result.Insert(0, prefix);
return result.ToArray();
}
}
}
The code could probably be more effective and more organized but don't have time for that now. I think the other solutions are wrong so far, so that's why you get my longer one. I think you need to solve it recursively to be sure to get the shortest list.
My attempt, loop through items removing anything prefixed with another item.
static void Run()
{
var list = new string[] {"A", "A.B.D", "A",
"A.B", "E", "F.E",
"F", "B.C",
"Q.X", "Q.Y",
"D.A.A", "D.A.B"
};
int size = 0;
var prefixList = new string[list.Length];
Array.Copy(list, prefixList, list.Length);
for (int i = 0; i < list.Length; i++)
prefixList
= prefixList
.Where(c => !c.StartsWith(list[i]) || c == list[i])
.Distinct()
.ToArray();
foreach (string s in prefixList)
Console.WriteLine(s);
Console.ReadLine();
}
var list = new[] { "A.B.D", "A", "E", "A.B", "F", "F.E", "B.C.D", "B.C" };
var result = from s in list
group s by s.Split('.').First() into g
select LongestCommonPrefix(g);
foreach (var s in result)
{
Console.WriteLine(s);
}
Output:
A
E
F
B.C
Method to find longest common prefix from here (replace / with .).
My understanding of the question says a list containing both "B.C" and "B.E" but no "B" would get both "B.C" and "B.E".
string[] items = { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
char delimiter = '.';
var result = (from item in items.Distinct()
where !items.Any(other => item.StartsWith(other + delimiter))
select item).ToArray();
foreach (var item in result)
{
Console.WriteLine(item);
}
output
A
E
F
B.C
also works with multi-character prefixes
string[] items =
{
"Alpha",
"Alpha.Beta.Delta",
"Alpha",
"Alpha.Beta",
"Echo",
"Foxtrot.Echo",
"Foxtrot",
"Baker.Charlie"
};
gets
Alpha
Echo
Foxtrot
Baker.Charlie
If I strictly stick to the definition that dave provided, the answer is easier than it seems:
remove duplicates => distinct
remove any item that starts with any other item in the list
so we get:
from item in items.Distinct()
where !items.Any(other => other != item && item.StartsWith(other + '.'))
select item;
For the B.C and B.D question, this works as specified: Neither one includes the other, so none of the removing conditions mentioned by dave is triggered.
I admit that there might be more exciting anwers, but I'm afraid that's just not in the question ;)
Update: added delimiter to where clause in order to account for multi-char words. thanks svick!
var list = new List<string> { "A", "A.B.D", "A", "A.B", "E", "F.E", "F", "B.C" };
var result = (list.Select(a => a.Split('.').First())).Distinct();

Categories

Resources