Find unique permutations for object - c#

I have a list of products that have an ID and a Quantity, and I need to find a list of combinations of products that will fill a certain quantity.
E.g.
ProductID | Quantity
1 | 5
2 | 5
3 | 8
4 | 15
If I require a quantity of 15 then I want to get a list with the following combinations:
Products: {1, 2, 3}, {1, 3, 2}, {1, 2, 4}, {1, 3, 4}, {1, 4}
{2, 1, 3}, {2, 1, 4}, {2, 3, 1}, {2, 3, 4}, {2, 4}
{3, 1, 2}, {3, 1, 4}, {3, 2, 1}, {3, 2, 4}, {3, 4}
{4}
It's almost a permutation, but it's filtered out entries that sum up to more than what is required. I need to stop taking further items, if at any point, the current total sum of values goes beyond 15. Doing this way, if I had all permutations then I would have 24 results, but I only have 16.
E.g. if I take product 4 then I don't need to combine it with anything to make 15. Similarly, if I take product 1 then take product 4, I don't need to pick up anymore item since the sum is already beyond 15 (5 + 15 = 20).
I was able to get the code working by getting all Permutations (e.g. here) and then filtering that down to the ones I care about, however once you start getting a large number of products (e.g. 30) then you end up with 4.3 Billion combinations which causes out of memory exceptions.
How can I create only the required permutations in C#?

looks like only two rule:
1. elements picked are distinct.
2. sum of picked elements' qty must greater then goal, not just only equal to goal.
My example add some interface for sorting. Every kind of combination that can reach goal are listed. But I trying to list in unique form for reading. You can to oringinal expand job within each combination.
PS. for order purpose I add IComparable, not very important.
class Product: IComparable
{
public int ID { get; set; }
public uint Qty { get; set; }
public int CompareTo(object obj)
{
if (obj is Product)
return this.ID.CompareTo(((Product)obj).ID);
else
return -1;
}
public override string ToString()
{
return string.Format("Product: {0}", this.ID);
}
}
class Combination : List<Product>, IComparable
{
public int Goal { get; private set; }
public bool IsCompleted
{
get
{
return this.Sum(product => product.Qty) >= Goal;
}
}
public Combination(int goal)
{
Goal = goal;
}
public Combination(int goal, params Product[] firstProducts)
: this(goal)
{
AddRange(firstProducts);
}
public Combination(Combination inheritFrom)
: base(inheritFrom)
{
Goal = inheritFrom.Goal;
}
public Combination(Combination inheritFrom, Product firstProduct)
: this(inheritFrom)
{
Add(firstProduct);
}
public int CompareTo(object obj)
{
if (obj is Combination)
{
var destCombination = (Combination)obj;
var checkIndex = 0;
while (true)
{
if (destCombination.Count - 1 < checkIndex && this.Count - 1 < checkIndex)
return 0;
else if (destCombination.Count - 1 < checkIndex)
return -1;
else if (this.Count - 1 < checkIndex)
return 1;
else
{
var result = this[checkIndex].CompareTo(destCombination[checkIndex]);
if (result == 0)
checkIndex++;
else
return result;
}
}
}
else
return this.CompareTo(obj);
}
public override int GetHashCode()
{
unchecked
{
return this.Select((item, idx) => item.ID * (10 ^ idx)).Sum();
}
}
public override bool Equals(object obj)
{
if (obj is Combination)
return ((Combination)obj).GetHashCode() == this.GetHashCode();
else
return base.Equals(obj);
}
}
the testing part provide product list and the goal.
public static void Test()
{
var goal = 25;
var products = new[]
{
new Product() { ID = 1, Qty = 5 },
new Product() { ID = 2, Qty = 5 },
new Product() { ID = 3, Qty = 8 },
new Product() { ID = 4, Qty = 15 },
new Product() { ID = 5, Qty = 17 },
new Product() { ID = 6, Qty = 1 },
new Product() { ID = 7, Qty = 4 },
new Product() { ID = 8, Qty = 6 },
};
var orderedProducts = products.OrderBy(prod => prod.ID);
//one un-completed combination, can bring back muliple combination..
//that include completed or next-staged-uncompleted combinations
Func<Combination, IEnumerable<Combination>> job = null;
job = (set) =>
{
if (set.IsCompleted)
return new[] { set }.ToList();
else
{
return orderedProducts
.Where(product => set.Contains(product) == false && product.ID >= set.Last().ID)
.Select(product => new Combination(set, product))
.SelectMany(combination => job(combination));
}
};
var allPossibility = orderedProducts
.Select(product => new Combination(goal, product))
.SelectMany(combination => job(combination))
.Where(combination => combination.IsCompleted)
.Select(combination => new Combination(goal, combination.OrderBy(product => product.ID).ToArray()))
.OrderBy(item => item)
.ToList();
foreach (var completedCombination in allPossibility)
{
Console.WriteLine(string.Join<int>(", ", completedCombination.Select(prod => prod.ID).ToArray()));
}
Console.ReadKey();
}

This probably isn't the most efficient answer, but it does give the right answer:
void Main()
{
List<Product> products = new List<Product> { new Product { ProductID = 1, Quantity = 5 },
new Product { ProductID = 2, Quantity = 5 },
new Product { ProductID = 3, Quantity = 8 },
new Product { ProductID = 4, Quantity = 15 },
};
decimal requiredQuantity = 15;
if (requiredQuantity < products.Sum(p => p.Quantity))
{
var output = Permutations(products, requiredQuantity);
output.Dump();
}
else
{
products.Dump();
}
}
// Define other methods and classes here
private List<Queue<Product>> Permutations(List<Product> list, decimal requiredValue, Stack<Product> currentList = null)
{
if (currentList == null)
{
currentList = new Stack<Product>();
}
List<Queue<Product>> returnList = new List<System.Collections.Generic.Queue<Product>>();
foreach (Product product in list.Except(currentList))
{
currentList.Push(product);
decimal currentTotal = currentList.Sum(p => p.Quantity);
if (currentTotal >= requiredValue)
{
//Stop Looking. You're Done! Copy the contents out of the stack into a queue to process later. Reverse it so First into the stack is First in the Queue
returnList.Add(new Queue<Product>(currentList.Reverse()));
}
else
{
//Keep looking, the answer is out there
var result = Permutations(list, requiredValue, currentList);
returnList.AddRange(result);
}
currentList.Pop();
}
return returnList;
}
struct Product
{
public int ProductID;
public int Quantity;
}

I'll discuss the solution in terms of Python because I don't have C# installed on this Mac, but C# has iterators, so what I'm talking about will work.
First of all, as you discovered, you DO NOT want to return the whole list. It consumes a tremendous amount of memory. Instead return an iterator as in https://msdn.microsoft.com/en-us/library/65zzykke(v=vs.100).aspx that will return each element of your list in turn.
Secondly, you can build iterators out of iterators. The first one is the one that does subsets where the last element pushes you to your threshold and beyond:
def minimal_subset_iter (product, threshold):
# Sort smallest to the front so that we skip no combinations that
# will lead to our threshold in any order.
ids = list(sorted(product.keys(), key=lambda key: (product[key], key)))
# Figure out the size of the trailing sums.
remaining_sum = []
total_sum = sum(product.values())
for i in range(len(ids)):
remaining_sum.append(
total_sum - sum(product[ids[j]] for j in range(i)))
remaining_sum.append(0)
# We modify this in place and yield it over and over again.
# DO NOT modify it in the return, make a copy of it there.
chosen_set = []
def _choose (current_sum, i):
if threshold <= current_sum:
yield chosen_set
elif threshold <= current_sum + remaining_sum[i]:
# Can I finish without this element?
for x in _choose(current_sum, i+1):
yield x
# All of the ways to finish with this element.
chosen_set.append(ids[i])
current_sum = current_sum + product[ids[i]]
for x in _choose(current_sum, i+1):
yield x
# Cleanup!
chosen_set.pop()
return _choose(0, 0)
for x in minimal_subset_iter({1: 5, 2: 5, 3: 8, 4: 15}, 15):
print(x)
And now you need an iterator that turns a minimal subset into all permutations of that subset where the last element pushes you to the threshold.
I won't write that one since the principle is straightforward. And besides you have to translate it into a different language. But the idea is to pull out each possibility for that last element that reaches the end, run permutations of the rest, and append the last element back before yielding it.
This algorithm will be very efficient on memory (it basically keeps the dictionary and the current permutation) and also quite efficient on performance (it has a lot of lists to create, but will waste little time creating ones that it doesn't need). It does, however, take some time to wrap your head around this way of working.

Related

Return min/max number in nested list

I'm trying to find the min/max number within a nested list using Linq.
An example class would be:
public class ListA
{
public HashSet<HashSet<int>> nestedList;
public ListA()
{
nestedList = new HashSet<HashSet<int>>()
{
new HashSet<int> { 1, 3, 5, 7 },
new HashSet<int> { 2, 12, 7, 19 },
new HashSet<int> { 6, 9 , 3, 14 }
};
}
public int MaxNumber
{
// return the highest number in list
}
}
In the example above, the minimum number would be 1 and the max number 19.
I'm struggling to get anything to get valid syntax. Anyone help?
SelectMany and Max will likely produce the result you desire.
Also consider using DefaultIfEmpty (with whatever default makes sense for your context) - this will ensure that Max doesn't throw an exception if nestedList is empty.
public int MaxNumber
{
get { return nestedList.SelectMany(z => z).DefaultIfEmpty(0).Max(); }
}
nestedList.SelectMany(item => item).Max();
and
nestedList.SelectMany(item => item).Min();

c# How to set the maximum number when do the find all possible combinations of numbers to reach a given sum

Im new to c# then my supervisor ask me to find all possible combination of given set of numbers and I must set the maximum for the combinations. The combinations I already get but for set the maximum number can't be done.The maximum number is for the combinations. From my image it have 5,4 and 3 row that is all the possible combinations. but I just want to set only output that have 3 row only will be display. I have tried many way but still can't get it. Sorry for my bad english.
Here are the code.
class Program
{
static void Main(string[] args)
{
string input;
decimal goal;
decimal element;
int max = 2;
do
{
Console.WriteLine("Please enter the target:");
input = Console.ReadLine();
}
while (!decimal.TryParse(input, out goal));
Console.WriteLine("Please enter the numbers (separat`enter code here`ed by spaces)");
input = Console.ReadLine();
string[] elementsText = input.Split(' ');
List<decimal> elementsList = new List<decimal>();
foreach (string elementText in elementsText)
{
if (decimal.TryParse(elementText, out element))
{
elementsList.Add(element);
}
}
Solver solver = new Solver();
List<List<decimal>> results = solver.Solve(goal, elementsList.ToArray());
//foreach (List<decimal> result in results)
//{
// foreach (decimal value in result)
// {
// Console.Write("{0}\t", value);
// }
// Console.WriteLine();
//}
for (int i = 0; i <= results.Count; i++)
{
int x = results.SelectMany(list => list).Distinct().Count();
if (x <= max)
{
for (int j = 0; j <= max; j++)
{
Console.Write("{0}\t", results[i][j]);
}
Console.WriteLine();
}
}
Console.ReadLine();
}
}
here is the ouput
From the comments in the question and other answers, it seems to me that the OP already knows how to calculate all the combinations whose sum is a target number (that's probably what the Solver in the question does). What I think he wants is to get the combination with the least amount of numbers.
I have a couple of solutions, since I'm not really sure what you want:
1) If you want all the combinations with the least of amount of numbers, do this:
public static void Main()
{
// Here I have hard-coded all the combinations,
// but in real life you would calculate them.
// Probably using your `Solver` or any of the other answers in this page.
var combinations = new List<List<decimal>>{
new List<decimal>{ 1, 2, 3, 4, 5 },
new List<decimal>{ 1, 2, 5, 7 },
new List<decimal>{ 1, 3, 4, 7 },
new List<decimal>{ 1, 3, 5, 6 },
new List<decimal>{ 2, 3, 4, 6 },
new List<decimal>{ 2, 6, 7 },
new List<decimal>{ 3, 5, 7 },
new List<decimal>{ 4, 5, 6 }
};
// Filter the list above to keep only the lists
// that have the least amount of numbers.
var filteredCombinations = LeastNumbers(combinations);
foreach (var combination in filteredCombinations)
{
Console.WriteLine(string.Join("\t", combination));
}
}
public static List<List<decimal>> LeastNumbers(List<List<decimal>> combinations)
{
// First get the count for each combination, then get the minimum of those.
int smallestLength = combinations.Select(l => l.Count).Min();
// Second, only keep those combinations which have a count equals to the value calculated above.
return combinations.Where(l => l.Count == smallestLength).ToList();
}
Output:
2 6 7
3 5 7
4 5 6
2) If you only want one of the combinations with the least amount of numbers, do this instead:
public static void Main()
{
// Here I have hard-coded all the combinations,
// but in real life you would calculate them.
// Probably using your `Solver` or any of the answers in this page.
var combinations = new List<List<decimal>>{
new List<decimal>{ 1, 2, 3, 4, 5 },
new List<decimal>{ 1, 2, 5, 7 },
new List<decimal>{ 1, 3, 4, 7 },
new List<decimal>{ 1, 3, 5, 6 },
new List<decimal>{ 2, 3, 4, 6 },
new List<decimal>{ 2, 6, 7 },
new List<decimal>{ 3, 5, 7 },
new List<decimal>{ 4, 5, 6 }
};
// Filter the list above to keep only the first list
// that has the least amount of numbers.
var filteredCombination = LeastNumbers(combinations);
Console.WriteLine(string.Join("\t", filteredCombination));
}
public static List<decimal> LeastNumbers(List<List<decimal>> combinations)
{
// First get the count for each combination,
// then get the minimum of those.
int smallestLength = combinations.Select(l => l.Count).Min();
// Second, get only one of the combinations that have a count
// equals to the value calculated above.
return combinations.First(l => l.Count == smallestLength);
}
Output:
2 6 7
3) The OP also mentioned a max value of 3. So, if you know that number before-hand, you can do this:
public static void Main()
{
// Here I have hard-coded all the combinations,
// but in real life you would calculate them.
// Probably using your `Solver` or any of the answers in this page.
var combinations = new List<List<decimal>>{
new List<decimal>{ 1, 2, 3, 4, 5 },
new List<decimal>{ 1, 2, 5, 7 },
new List<decimal>{ 1, 3, 4, 7 },
new List<decimal>{ 1, 3, 5, 6 },
new List<decimal>{ 2, 3, 4, 6 },
new List<decimal>{ 2, 6, 7 },
new List<decimal>{ 3, 5, 7 },
new List<decimal>{ 4, 5, 6 }
};
// This must be known before hand.
// That's why I think my first solution is more usefull.
int max = 3;
// Filter the list above to keep only the lists
// that have a count less or equal to a predetermined maximum.
var filteredCombinations = FilterByMaxLength(combinations, max);
foreach (var combination in filteredCombinations)
{
Console.WriteLine(string.Join("\t", combination));
}
}
public static List<List<decimal>> FilterByMaxLength(List<List<decimal>> combinations, int max)
{
return combinations.Where(l => l.Count <= max).ToList();
}
2 6 7
3 5 7
4 5 6
Note: In a real scenario, you would also want to do some checking in those functions, like checking for null or empty lists.
Here is my try, you can tweak this depending on what you want:
using System.Collections.Generic;
using System.Linq;
private static void GetMaxPermutation(int max)
{
var numbers = new[] { 1, 2, 4, 6, 7 };
var results = new List<IEnumerable<int>>();
for (int i = 1; i <= numbers.Length; i++)
{
results.AddRange(GetPermutations(numbers, i));
}
Console.WriteLine("Result: " + string.Join(" ", results.Select(x => new { Target = x, Sum = x.Sum() }).Where(x => x.Sum <= max).OrderByDescending(x => x.Sum).FirstOrDefault().Target));
}
private static IEnumerable<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> items, int count)
{
int i = 0;
foreach (var item in items)
{
if (count == 1)
yield return new T[] { item };
else
{
foreach (var result in GetPermutations(items.Skip(i + 1), count - 1))
yield return new T[] { item }.Concat(result);
}
++i;
}
}
I got this permutations method from here
Hard to find what you're trying to do, is something like this
List<string> numbers = new List<string>(){"1","2","3","4","5"};
List<string> possibleCombination = GetCombination(numbers, new List<string>(), "");
Console.Write(string.Join(", ",possibleCombination.Distinct().OrderBy(itm => itm)));
The method
static List<string> GetCombination(List<string> list, List<string> combinations, string sumNum, bool addNumberToResult = false)
{
if (list.Count == 0) {
return combinations;
}
string tmp;
for (int i = 0; i <= list.Count - 1; i++) {
tmp = string.Concat(sumNum , list[i]);
if(addNumberToResult){
combinations.Add(tmp);
}
List<string> tmp_list = new List<string>(list);
tmp_list.RemoveAt(i);
GetCombination(tmp_list,combinations,tmp, true);
}
return combinations;
}
C# Fiddle can help you?

Find a series of the same number in a List

I have a List of items containing either 1 or 0, I'm looking to output the items only where there are six 1's back to back in the list. So only write to the console if the item in this list is part of a group of six.
1
1
1
1
1
1
0
1
1
1
0
In the above list, the first six items would be output but the bottom set of three 1s would not as they are not part of a group of six.
Is this a job for LINQ or RegEx?
You can concatenate all values into string, then split it by zeros. From substrings select those which have at least 6 characters:
List<int> values = new List<int> { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
var series = String.Concat(values)
.Split(new[] { '0' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.Length >= 6);
For given input data series will contain single item "111111" which you can output to console.
Classic run length encoding, O(n), lazy evaluated, stack agnostic, generic for any equatable type.
public void TestRunLength()
{
var runs = new List<int>{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 0, 4};
var finalGroup = RunLength(runs).FirstOrDefault(i => i.Count == 6 && i.First() == 1);
}
private IEnumerable<List<T>> RunLength<T>(IEnumerable<T> source) where T : IEquatable<T>
{
T current = default(T);
var requiresInit = true;
var list = new List<T>();
foreach (var i in source)
{
if (requiresInit)
{
current = i;
requiresInit = false;
}
if (i.Equals(current))
{
list.Add(i);
}
else
{
yield return list;
list = new List<T>{ i };
current = i;
}
}
if (list.Any())
{
yield return list;
}
}
And because it's lazy it works on infinite sequences (yes I know its not infinite, but it is large)!
public void TestRunLength()
{
var random = new Random();
var runs = Enumerable.Range(int.MinValue, int.MaxValue)
.Select(i => random.Next(0, 10));
var finalGroup = RunLength(runs)
.FirstOrDefault(i => i.Count == 6);
}
Probably it can be done with Regex too if you concatenate your numbers into a string. But I would prefer linq:
var bits = new List<int> {1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0};
int bitCountPerGroup = 6;
var result = bits // (1) (2)
.Select((x,idx) => bits.Skip(idx).TakeWhile(y => y == x))
.Where(g => g.Count() == bitCountPerGroup); // (3)
foreach (var set in result)
Console.WriteLine(string.Join(" ", set));
This code gets a number-set for each number by starting from the number (1) and taking the next numbers as long as they are equal (2). Then filter the groups and gets only those groups which have 6 numbers (3).
If for example your list is of an unknown size,or better,you do not know the items in it you could do this recursive example(note that i placed more zeros so it would fetch 2 sets of data,it works with yours also),and pass to the method the amout to group by:
//this is the datastructure to hold the results
static List<KeyValuePair<string, List<int>>> Set = new List<KeyValuePair<string, List<int>>>();
private static void GetData(List<int> lst, int group)
{
int count = 1;
int pivot = lst.First();
if (lst.Count < group)
{
return;
}
else
{
foreach (int i in lst.Skip(1))
{
if (i == pivot)
{
count++;
}
else if (count == group)
{
Set.Add(new KeyValuePair<string, List<int>>("Set of items " + pivot, lst.Take(count).ToList()));
GetData(lst.Skip(count).ToList(), group);
break;
}
else
{
GetData(lst.Skip(count).ToList(), group);
break;
}
}
}
}
Then in Main():
static void Main(string[] args)
{
List<int> test = new List<int> { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
GetData(test, 6);
foreach (var item in Set)
{
Console.WriteLine("\t" + item.Key);
foreach (var subitem in item.Value)
{
Console.WriteLine(subitem);
}
}
}

How to get all combinations of several List<int> [duplicate]

This question already has answers here:
Combination of List<List<int>>
(9 answers)
Closed 9 years ago.
Differs from sugested solution above in that a list item can only appear once for each row.
This is for a booking system for my spa. Different employees can perform different treatments.
I have a List<List<int>>. These are therapists that can perform the treatment that is booked.
Each list (booking) contain a number of integers like this (these are therapists that can perform the booking):
{1, 3, 6}, //Booking 1
{1, 2, 6}, //Booking 2
{1}, //Booking 3
{2,3} //Booking 4
I'd like to see all possible combinations where the number can only appear in one Place. For the above list the two possible ombinations would be:
6,2,1,3 or
3,6,1,2
That is for the first combination:
Booking 1: Therapist 6
Booking 2: Therapist 2
Booking 3: Therapist 1
Booking 4: Therapist 3
Hope this makes the question a Little bit clearer.
Solve by recursion:
static IEnumerable<List<int>> GetCombinations(IEnumerable<List<int>> lists, IEnumerable<int> selected)
{
if (lists.Any())
{
var remainingLists = lists.Skip(1);
foreach (var item in lists.First().Where(x => !selected.Contains(x)))
foreach (var combo in GetCombinations(remainingLists, selected.Concat(new int[] { item })))
yield return combo;
}
else
{
yield return selected.ToList();
}
}
static void Main(string[] args)
{
List<List<int>> lists = new List<List<int>>
{
new List<int> { 1, 3, 6 },
new List<int> { 1, 2, 6 },
new List<int> { 1 },
new List<int> { 2, 3 }
};
var combos = GetCombinations(lists, new List<int>()).Distinct();
foreach (var combo in combos)
Console.WriteLine("{ " + string.Join(", ", combo.Select(x => x.ToString())) + " }");
return;
}
Output:
{ 3, 6, 1, 2 }
{ 6, 2, 1, 3 }
This solution is far from efficient:
private static void Main()
{
List<List<int>> list = new List<List<int>>
{
new List<int>() {1, 3, 6}, //Booking 1
new List<int>() {1, 2, 6}, //Booking 2
new List<int>() {1}, //Booking 3
new List<int>() {2, 3}
};
List<int[]> solutions = new List<int[]>();
int[] solution = new int[list.Count];
Solve(list, solutions, solution);
}
private static void Solve(List<List<int>> list, List<int[]> solutions, int[] solution)
{
if (solution.All(i => i != 0) && !solutions.Any(s => s.SequenceEqual(solution)))
solutions.Add(solution);
for (int i = 0; i < list.Count; i++)
{
if (solution[i] != 0)
continue; // a caller up the hierarchy set this index to be a number
for (int j = 0; j < list[i].Count; j++)
{
if (solution.Contains(list[i][j]))
continue;
var solutionCopy = solution.ToArray();
solutionCopy[i] = list[i][j];
Solve(list, solutions, solutionCopy);
}
}
}
It sounds like this can be solved more efficiently with Dynamic programming, but it's been a while since I took the relevant course.
A simple way to look at this problem would be to choose from all combinations of the list of values, where every value in the combination is unique.
First figure out what all the combinations of values are.
public static IEnumerable<IList<T>> Combinations<T>(IEnumerable<IList<T>> collections)
{
if (collections.Count() == 1)
{
foreach (var item in collections.Single())
yield return new List<T> { item };
}
else if (collections.Count() > 1)
{
foreach (var item in collections.First())
foreach (var tail in Combinations(collections.Skip(1)))
yield return new[] { item }.Concat(tail).ToList();
}
}
Then you need a way to determine if all the values are unique. A simple way to figure that out would be to check if the count of distinct values equals the count of all values.
public static bool AllUnique<T>(IEnumerable<T> collection)
{
return collection.Distinct().Count() == collection.Count();
}
Once you have all that, put it all together.
var collections = new[]
{
new List<int> { 1, 3, 6 },
new List<int> { 1, 2, 6 },
new List<int> { 1 },
new List<int> { 2, 3 },
};
var results =
from combination in Combinations(collections)
where AllUnique(combination)
select combination;
// results:
// 3,6,1,2
// 6,2,1,3

LINQ for diffing sets

I have the following arrays:
var original= new int[] { 2, 1, 3 };
var target = new int[] { 1, 3, 4 };
enum Operation {Added,Removed}
I would like to execute a LINQ query that would return the following:
{{2,Removed},{4,Added}}
Limitation: I would like LINQ to perform this very efficiently and avoid and O(n^2) style algorithms.
Perhaps a LINQ solution is not the best option in this case.
This will produce a dictionary with the result that you want.
Dictionary<int, Operation> difference = new Dictionary<int,Operation>();
foreach (int value in original) {
difference.Add(value, Operation.Removed);
}
foreach (int value in target) {
if (difference.ContainsKey(value)) {
difference.Remove(value);
} else {
difference.Add(value, Operation.Added);
}
}
To keep the size of the dictionary down, perhaps it's possible to loop the enumerations in parallell. I'll have a look at that...
Edit:
Here it is:
Dictionary<int, Operation> difference = new Dictionary<int,Operation>();
IEnumerator<int> o = ((IEnumerable<int>)original).GetEnumerator();
IEnumerator<int> t = ((IEnumerable<int>)target).GetEnumerator();
bool oActive=true, tActive=true;
while (oActive || tActive) {
if (oActive && (oActive = o.MoveNext())) {
if (difference.ContainsKey(o.Current)) {
difference.Remove(o.Current);
} else {
difference.Add(o.Current, Operation.Removed);
}
}
if (tActive && (tActive = t.MoveNext())) {
if (difference.ContainsKey(t.Current)) {
difference.Remove(t.Current);
} else {
difference.Add(t.Current, Operation.Added);
}
}
}
Edit2:
I did some performance testing. The first version runs 10%-20% faster, both with sorted lists and randomly ordered lists.
I made lists with numbers from 1 to 100000, randomly skipping 10% of the numbers. On my machine the first version of the code matches the lists in about 16 ms.
enum Operation { Added, Removed, }
static void Main(string[] args)
{
var original = new int[] { 2, 1, 3 };
var target = new int[] { 1, 3, 4 };
var result = original.Except(target)
.Select(i => new { Value = i, Operation = Operation.Removed, })
.Concat(
target.Except(original)
.Select(i => new { Value = i, Operation = Operation.Added, })
);
foreach (var item in result)
Console.WriteLine("{0}, {1}", item.Value, item.Operation);
}
I don't think you can do this with LINQ using only a single pass given the stock LINQ extension methods but but might be able to code a custom extension method that will. Your trade off will likely be the loss of deferred execution. It would be interesting to compare the relative performance of both.
You are out of luck. If, as you stated in the comments, the lists are not sorted you can't compute the difference you seek in a single forward pass. Consider:
{ 1, 2, 3, 4, 5, 6, 7, ...
{ 1, 2, 3, 6, 7, 8, 9, ...
At the point where the first difference in encountered (4 vs. 6) it's impossible for you to determine if you are looking at the removal of 4 & 5 (as would be the case if both lists were monotonically increasing, or the insertion of 6, 7, 8, & 9 as would be the case if the lists continued like so:
{ 1, 2, 3, 4, 5, 6, 7, 8, 9,...
{ 1, 2, 3, 6, 7, 8, 9, 4, 5, 6, 7, 8, 9,...
This will achieve the result in a single pass, however I'm not sure of the complexity of the GroupBy operation.
var original= new int[] { 1, 2, 3 };
var target = new int[] { 1, 3, 4 };
var output = original.Select( i => new { I = i, L = "o" } )
.Concat( target.Select( i => new { I = i, L = "t" } ) )
.GroupBy( i => i.I ).Where( i => i.Count() == 1 )
.Select( i => new { I = i.Key, S = (i.ElementAt( 0 ).L == "o" ? Operation.Removed : Operation.Added) } );

Categories

Resources