How to combine lists with respect to precedence - c#

I have a List of strings like this :
List<string> andOrList = new List<string>();
andOrList.Add("AND");
andOrList.Add("OR");
andOrList.Add("AND");
And I have 4 lists to combine :
List<int> list1 = new List<int>(new int[] { 19, 23, 29 });
List<int> list2 = new List<int>(new int[] { 1, 4, 29 });
List<int> list3 = new List<int>(new int[] { 1, 5, 23 });
List<int> list4 = new List<int>(new int[] { 2, 4, 19 });
I want to make a new list from these 4 lists using ANDs and ORs from andOrList. Since AND has higher precedence than OR first I will apply ANDs so I will have these :
var tempList1 = list1.Intersect(list2).ToList();
var tempList2 = list3.Intersect(list4).ToList();
And finally combine these two templists because there is an OR :
var resulList = tempList1.Union(tempList2);
As you can see it's possible to do this by hand when there is defined number of lists and defined number of ANDs and ORs. But I couldn't figure out how to do it programmatically when there are n number of Lists to combine and n-1 number of ANDs and ORs. Can you help me with that? Thanks.

I suggest splitting execution into two stages:
1. Performs all `AND`s
2. Perform all `OR`s
E.g.
a & b & c | d | e & f & g | h == // put the right order
(a & b & c) | (d) | (e & f & g) | (h) == // perform ANDs
a_b_c | d | e_f_g | h == // perform ORs
final result
in your case
{19, 23, 29} & {1, 4, 29} | {1, 5, 23} & {2, 4, 19} == // put the right order
({19, 23, 29} & {1, 4, 29}) | ({1, 5, 23} & {2, 4, 19}) == // perform ANDs
{29} | {} == // perform ORs
{29}
Implementation
private static IEnumerable<T> CombinatorOrAnd<T>(IEnumerable<IEnumerable<T>> sources,
IEnumerable<string> actions) {
List<IEnumerable<T>> orList = new List<IEnumerable<T>>();
// First, do all ANDs
bool isFirst = true;
IEnumerable<T> temp = null;
using (var en = actions.GetEnumerator()) {
foreach (var argument in sources) {
if (isFirst) {
temp = argument;
isFirst = false;
continue;
}
en.MoveNext();
if (en.Current == "AND")
temp = temp.Intersect(argument);
else {
orList.Add(temp);
temp = argument;
}
}
}
orList.Add(temp);
// Finally, perform all ORs
return orList.Aggregate((s, a) => s.Union(a));
}
Test
List<int> list1 = new List<int>(new int[] { 19, 23, 29 });
List<int> list2 = new List<int>(new int[] { 1, 4, 29 });
List<int> list3 = new List<int>(new int[] { 1, 5, 23 });
List<int> list4 = new List<int>(new int[] { 2, 4, 19 });
List<string> andOrList = new List<string>();
andOrList.Add("AND");
andOrList.Add("OR");
andOrList.Add("AND");
var result = CombinatorOrAnd(new List<int>[] { list1, list2, list3, list4}, andOrList);
Console.Write(string.Join(", ", result.OrderBy(item => item)));
Outcome
29

Apologies for the belated answer, but I had this open in the background. The idea was pretty much the same: do the ANDs first, but to do this by mutating (a copy of) the input list.
public static IEnumerable<int> ProcessAndOr(List<string> andOrList, params List<int>[] Input)
{
var lst = new List<IEnumerable<int>>(Input);
for(int i = andOrList.Count -1 ; i >= 0 ; i--)
if(andOrList[i] == "AND")
{
lst[i] = lst[i].Intersect(lst[++i]);
lst.RemoveAt(i--);
}
return lst.SelectMany(l=>l).Distinct();
}
The example could be called with var resultList = ProcessAndOr(andOrList, list1,list2,list3,list4); and produces 29
PS, the reverse order isn't really necessary but is done to be able to use a single variable for iteration.

Related

Remove the end of a list based on the start of another list

I have two lists:
var list1 = new List<int> { 0, 1, 2 };
var list2 = new List<int> { 1, 2, 3 };
I want to be able to check if the ending chunk of list1 is present at the start of list2. After that I want to delete one of the chunks from any of the lists, merging both into a third list (sequentially, list1 + list2).
var list3 = list1.Something(list2);
//Returns 0,1,2,3 instead of 0,1,2,1,2,3
There's another problem, one list can be smaller than the other, such as:
0,1,2,3 <-- 2,3,4 = 0,1,2,3,4
5,6 <-- 6,7,8 = 5,6,7,8
And of course, both lists can be different:
0,1,2 <-- 5,6,7 = 0,1,2,5,6,7
[empty] <-- 1,2 = 1,2
Is there any method provided by .Net Framework that allows me to do that?
If not, could you help me create one?
The end and start can only "kill" each other if they are sequentially equal.
Example, if list1 ends in 1,2 and list2 starts with 2,1 they are not equal.
So, Distinct() is not helpful.
My use case:
private List<int> Cut(this List<int> first, List<int> second)
{
//Code
return new List<int>();
}
internal List<int> MergeKeyList()
{
var keyList = new List<int>() {0, 1, 2};
var newList = new List<int>() {1, 2, 3};
return keyList.InsertRange(keyList.Count, keyList.Cut(newList));
}
Would be much more efficient with for loops .. but whatever:
keyList.TakeWhile((_, i) => !keyList.Skip(i).SequenceEqual(newList.Take(keyList.Count - i)))
.Concat(newList)
Try this:
void Main()
{
var keyList = new List<int>() {0, 1, 2};
var newList = new List<int>() {1, 2, 3};
var result = keyList.Cut(newList);
}
public static class Ex
{
public static List<int> Cut(this List<int> first, List<int> second)
{
var skip =
second
.Select((x, n) => new { x, n })
.Where(xn => xn.x == first.Last())
.Where(xn =>
first
.Skip(first.Count - xn.n - 1)
.SequenceEqual(second.Take(xn.n + 1)))
.Reverse()
.Select(xn => xn.n + 1)
.FirstOrDefault();
return first.Concat(second.Skip(skip)).ToList();
}
}
result becomes:
Also:
{ 0, 1, 2 } & { 1, 2, 1, 2, 3 } => { 0, 1, 2, 1, 2, 3 }
{ 0, 1, 2, 1 } & { 1, 2, 1, 2, 3 } => { 0, 1, 2, 1, 2, 3 }

Get common elements with index from two list C#

I have Two lists of type list<int> and i know we can find the common elements between two lists. But is there any way to get common elements and corresponding indexes of common elements in Intersected list or i need to go across each elements find the indexes.
LINQ has operations to project a sequence using indexes, but this isn't built into the query expression syntax, so you have to use "regular" extension method calls to start with. After that it's fairly easy, although probably just as simple not using LINQ, to be honest:
var pairs1 = list1.Select((value, index) => new { value, index });
var pairs2 = list2.Select((value, index) => new { value, index });
var matches = from pair1 in pairs1
join pair2 in pairs2 on pair1.value equals pair2.value
select new
{
Value = pair1.value,
Index1 = pair1.index,
Index2 = pair2.index
};
(You could use from pair2 in pairs2 where pair1.value == pair2.value if you'd prefer...)
Or non-LINQ (using Tuple<,,> for simplicity; other options are feasible):
var results = new List<Tuple<int, int, int>>();
for (int index1 = 0; index1 < list1.Count; index1++)
{
for (int index2 = 0; index2 < list2.Count; index2++)
{
if (list1[index1] == list2[index2])
{
results.Add(Tuple.Of(list1[index1], index1, index2);
}
}
}
Note that unlike a regular intersection operation, both of these can give you multiple results for the same value - because there can be multiple index pairs. For example, with lists of { 1, 2 } and {2, 2, 0}, you'd have tuples of (value=2,index1=1,index2=0), (value=2,index1=1,index2=1).
try below code
List<int> lstA = new List<int>() { 10, 2, 7, 9, 13, 21, 17 };
List<int> lstB = new List<int>() { 2, 10, 7, 21, 13, 9, 17 };
var lstA_Temp = lstA.Select((value, index) => new { index, value }).ToList();
var lstB_Temp = lstB.Select((value, index) => new { index, value }).ToList();
List<int> result = (from A in lstA_Temp from B in lstB_Temp
where A.index == B.index where A.value == B.value
select A.value).ToList();
you can also do this thing without linq see below logic
List<int> lstA = new List<int>() { 10, 2, 7, 9, 13, 21, 17 };
List<int> lstB = new List<int>() { 2, 10, 7, 21, 13, 9, 17 };
List<int> lstResult = new List<int>();
for (int i = 0; i < lstA.Count; i++)
{
if (lstA[i] == lstB[i])
lstResult.Add(lstA[i]);
}

Split array into array of arrays

There's an array:
var arr = new int[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
Is there a simple way to split it into arrays of the same values?
var arrs = new int[][] {
new int[] { 1, 1 },
new int[] { 2 },
new int[] { 6, 6 },
new int[] { 7 },
new int[] { 1, 1 },
new int[] { 0 } };
I would prefer a linq solution but couldn't find it at the first time.
I would write an extension method for this:
public static class SOExtensions
{
public static IEnumerable<IEnumerable<T>> GroupSequenceWhile<T>(this IEnumerable<T> seq, Func<T, T, bool> condition)
{
List<T> list = new List<T>();
using (var en = seq.GetEnumerator())
{
if (en.MoveNext())
{
var prev = en.Current;
list.Add(en.Current);
while (en.MoveNext())
{
if (condition(prev, en.Current))
{
list.Add(en.Current);
}
else
{
yield return list;
list = new List<T>();
list.Add(en.Current);
}
prev = en.Current;
}
if (list.Any())
yield return list;
}
}
}
}
and use it as
var arr = new int[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
var result = arr.GroupSequenceWhile((x, y) => x == y).ToList();
var grouped = arr.GroupBy(x => x).Select(x => x.ToArray())
Didn't notice you were after neighbouring groups initially, the following should work for that
var arr = new[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
var groups = new List<int[]>();
for (int i = 0; i < arr.Length; i++)
{
var neighours = arr.Skip(i).TakeWhile(x => arr[i] == x).ToArray();
groups.Add(neighours);
i += neighours.Length-1;
}
Live example
This will do the trick:
var arrs = arr.Select((x, index) =>
{
var ar = arr.Skip(index)
.TakeWhile(a => a == x)
.ToArray();
return ar;
}).Where((x, index) => index == 0 || arr[index - 1] != arr[index]).ToArray();
Basically this will generate an array for each sequence item with a length of 1 or greater and will only choose the arrays which correspond to an item in the original sequence which is either the first element or an element that differs from its predecessor.
You can try this:
int index = 0;
var result = arr.Select(number =>
{
var ar = arr.Skip(index)
.TakeWhile(a => a == number)
.ToArray();
index += ar.Length;
return ar;
}).Where(x => x.Any()).ToArray();
An extension method like the answer by #L.B but a little bit more functional oriented:
public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> source, Func<T, T, bool> func)
{
var firstElement = source.FirstOrDefault();
return firstElement == null ? Enumerable.Empty<IEnumerable<T>>() : source.Skip(1).Aggregate(new
{
current = Tuple.Create(firstElement, ImmutableList<T>.Empty.Add(firstElement)),
results = ImmutableList<ImmutableList<T>>.Empty,
}, (acc, x) =>
func(acc.current.Item1, x)
? new { current = Tuple.Create(x, acc.current.Item2.Add(x)), results = acc.results }
: new { current = Tuple.Create(x, ImmutableList<T>.Empty.Add(x)), results = acc.results.Add(acc.current.Item2) },
x => x.results.Add(x.current.Item2).Select(r => r));
}
Note that the extension method uses the Microsoft Immutable Collections library. The library can be downloaded through NuGet.
Usage:
var arr = new int[] { 1, 1, 2, 6, 6, 7, 1, 1, 0 };
var result = arr.GroupWhile((prev, current) => prev == current);
var printFormattedResult = result.Select((x, i) => Tuple.Create(i, string.Join(",", x)));
foreach (var array in printFormattedResult)
Console.WriteLine("Array {0} = {1}", array.Item1, array.Item2);
Output:
Array 0 = 1,1
Array 1 = 2
Array 2 = 6,6
Array 3 = 7
Array 4 = 1,1
Array 5 = 0
Benchmark
Just for the sake of fun, I tried to benchmark the answers.
I used the following code:
var rnd = new Random();
var arr = Enumerable.Range(0, 100000).Select(x => rnd.Next(10)).ToArray();
var time = Stopwatch.StartNew();
var result = <answer>.ToArray();
Console.WriteLine(t.ElapsedMilliseconds);
And got the following results:
-------------------------------------
| Solution Time(ms) Complexity |
------------------------------------|
| L.B | 3ms | O(n) |
|-----------------------------------|
|´ebb | 41ms | O(n) |
|-----------------------------------|
| James | 137ms | O(n^2) |
|-----------------------------------|
| Robert S. | 155ms | O(n^2) |
|-----------------------------------|
| Selman22 | 155ms | O(n^2) |
-------------------------------------
The slight time overhead from my solution (the 41ms) is due to using immutable collections. Adding an item to ex. List<T> would modify the List<T> object. - Adding an item to ImmutableList<T> clones the current elements in it, and adds them to a new ImmutableList<T> along with the new item (which results in a slight overhead).

How to find complex permutations based on and/or rules of lists of numbers, C#/Linq

The current problem is that the code works, but it gets exponentially slower as more combinations are passed in. (The calculation takes > 5 seconds after 15 combinations are passed in.) I need to be able to pass in up to 100 combinations and still get a result back that takes less than 2 seconds.
I'm betting that a Linq query could solve this?
What I want to achieve:
{1, 2, 3} + {1, 5, 26, 40} = 12 combinations:
[1,1]
[1,5]
[1,26]
[1,40]
[2,1]
[2,5]
[2,26]
[2,40]
[3,1]
[3,5]
[3,26]
[3,40]
However, this example above only includes 2 combination sets. I should be able to pass in any number of combination sets.
The closest thing that looks like it is similar to what I want as an end result, due to being fast and efficient, is a linq query that handles most or all of the logic within it. Example: Getting all possible combinations from a list of numbers
public IEnumerable<IEnumerable<T>> GetPowerSet<T>(List<T> list)
{
return from m in Enumerable.Range(0, 1 << list.Count)
select
from i in Enumerable.Range(0, list.Count)
where (m & (1 << i)) != 0
select list[i];
}
Example of working code:
[Test]
public void StackOverflowExample_Simple()
{
var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 1, 5, 26, 40 };
var myListsOfNumberCombinations = new List<List<int>>() { list1, list2 };
var results = GetAllPossibleCombinations(myListsOfNumberCombinations);
Assert.AreEqual(12, results.Count());
StringBuilder sb = new StringBuilder();
foreach (var result in results)
{
foreach (var number in result.OrderBy(x => x))
{
sb.Append(number + ",");
}
sb.Append("|");
}
string finalResult = sb.ToString().Replace(",|", "|");
Assert.AreEqual(finalResult, "1,1|1,5|1,26|1,40|1,2|2,5|2,26|2,40|1,3|3,5|3,26|3,40|");
}
[Test]
public void StackOverflowExample_TakesALongTime()
{
var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 4, 5 };
var list3 = new List<int>() { 1, 6 };
var list4 = new List<int>() { 2, 5 };
var list5 = new List<int>() { 1, 3, 55, 56 };
var list6 = new List<int>() { 3, 4, 7, 8, 9 };
var myListsOfNumberCombinations = new List<List<int>>() { list1, list2, list3, list4, list5, list1, list1, list1, list3, list4, list4, list5, list6, list6, list2 };
DateTime startTime = DateTime.Now;
var results = GetAllPossibleCombinations(myListsOfNumberCombinations);
Assert.AreEqual(4147200, results.Count());
var duration = DateTime.Now.Subtract(startTime).TotalSeconds;
//duration = about 4 or 5 seconds
Assert.Less(duration, 10); //easy place to put a breakpoint
}
public IEnumerable<IEnumerable<int>> GetAllPossibleCombinations(List<List<int>> combinationSets)
{
List<List<int>> returnList = new List<List<int>>();
_RecursiveGetMoreCombinations(
ref returnList,
new List<int>(),
combinationSets,
0);
return returnList;
}
private void _RecursiveGetMoreCombinations(
ref List<List<int>> returnList,
List<int> appendedList,
List<List<int>> combinationSets,
int index)
{
var combinationSet = combinationSets[index];
foreach (var number in combinationSet)
{
List<int> newList = appendedList.AsEnumerable().ToList();
newList.Add(number);
if (combinationSets.Count() == index + 1)
{
returnList.Add(newList);
}
else
{
_RecursiveGetMoreCombinations(
ref returnList,
newList,
combinationSets,
index + 1);
}
}
}
Can you not just do permutations of the first and third sets (the OR sets) and then place '45' (the AND set), or whatever the static numbers are, in between those numbers?
You don't need to include 4 and 5 (in this example) in the permutation logic if they are always going to be present.

get common elements in lists in C#

I have two sorted lists as below:
var list1 = new List<int>() { 1, 1, 1, 2, 3 };
var list2 = new List<int>() { 1, 1, 2, 2, 4 };
I want the output to be: {1, 1, 2}
How to do this in C#?
Is there a way using Linq?
Use Intersect:
var commonElements = list1.Intersect(list2).ToList();
The extra 1 means you can't use Intersect because it returns a set.
Here's some code that does what you need:
var list1 = new List<int>() { 1, 1, 1, 2, 3 };
var list2 = new List<int>() { 1, 1, 2, 2, 4 };
var grouped1 =
from n in list1
group n by n
into g
select new {g.Key, Count = g.Count()};
var grouped2 =
from n in list2
group n by n
into g
select new {g.Key, Count = g.Count()};
var joined =
from b in grouped2
join a in grouped1 on b.Key equals a.Key
select new {b.Key, Count = Math.Min(b.Count, a.Count)};
var result = joined.SelectMany(a => Enumerable.Repeat(a.Key, a.Count));
CollectionAssert.AreEquivalent(new[] {1, 1, 2}, result);
This works nicely:
var list1 = new List<int>() { 1, 1, 1, 2, 3 };
var list2 = new List<int>() { 1, 1, 2, 2, 4 };
var lookup1 = list1.ToLookup(x => x);
var lookup2 = list2.ToLookup(x => x);
var results = lookup1.SelectMany(l1s => lookup2[l1s.Key].Zip(l1s, (l2, l1) => l1));
While both #Austin Salonen's solution and #Enigmativity's solution work for any given lists, neither take advantage of OP's condition that the lists are sorted.
Given that both lists will be ordered we can do a search in O(n + m) time where n and m are the length of each list. Not entirely sure what the previous solutions big o performance is, but it's definitely slower then O(n + m).
Basically we just walk both lists, moving one or both enumerators based on a comparison check.
var results = new List<int>();
var e1 = list1.GetEnumerator();
var e2 = list2.GetEnumerator();
var hasNext = e1.MoveNext() && e2.MoveNext();
while (hasNext) {
var value1 = e1.Current;
var value2 = e2.Current;
if (value1 == value2) {
results.Add(value1);
hasNext = e1.MoveNext() && e2.MoveNext();
} else if (value1 < value2) {
hasNext = e1.MoveNext();
} else if (value1 > value2) {
hasNext = e2.MoveNext();
}
}
That's it! results will be an empty list if no matches are found.
Note this assumes both lists are in ascending order. If it's descending, just flip the < and > operators.
I am late in answering this question, this might help future visitors.
List<int> p = new List<int> { 1, 1, 1, 2, 3 };
List<int> q = new List<int> { 1, 1, 2, 2, 4 };
List<int> x = new List<int>();
for (int i = 0; i < p.Count; i++ )
{
if (p[i] == q[i])
{
x.Add(p[i]);
}
}

Categories

Resources