How to do a comparison function in C# - c#

Say I have elements (X, Y, and Z) in a list, I have a function, that generates a percentage, of how much two objects resemble each other.
What I want to do, is run X against Y and Z using my compareElements, so:
compareElements(X,Y); // equals 55
compareElements(X,Z); // equals 60
Then Y against X and Z
compareElements(Y,X); // equals 55
compareElements(Y,Z); // equals 62
Then Z against Y and X
compareElements(Z,X); // equals 60
compareElements(Z,Y); // equals 62
Then, I return the highest value, which is 62.
Obviously, there's some repetition there, I don't need the repetition, but I'm not sure how to eliminate it.
How do I structure my LINQ query, or a function/algorithm to do this comparison on every element, without the repetition?
I'd prefer to use LINQ if I can, as I'm being passed an enumerable and the function returns before the list is actually enumerated, so we can save the cost of performing the compare, until the list is enumerated.
All I need is that highest value, of the compare functions, 62.
Note: My actual result set I'm working with averages between 3 and 10 elements in the list, that need to be ran through this compare function.

I'd be inclined to do it like this:
int count = list.Count;
var query = from index1 in Enumerable.Range(0, count)
from index2 in Enumerable.Range(index1 + 1, count - (index1 + 1))
select ComputeSimilarity(list[index1], list[index2]);
var maxSimilarity = query.Max();

I'm not sure I'm understanding you correctly, but try something like this:
public int compareElementList(List<Element> elements)
{
int result = 0;
for (int i = 0; i < elements.Count - 1; i++)
{
for (int q = i + 1; q < elements.Count; q++)
{
result = Math.Max(result, compareElements(elements[i], elements[q]));
}
}
return result;
}
This will eliminate the duplicate comparisons for you. It doesn't use LINQ, but I think it's still pretty readable.
UPDATE: Here is my version modified to handle IEnumerables. It varies from Jon Hanna's in that it doesn't create a new List, it just keeps track of two iterators.
public int compareElementEnumerable(IEnumerable<Element> elements)
{
int result = 0, i = 0, q = 1;
foreach (Element el in elements)
{
foreach (Element el2 in elements)
{
if (q > i)
{
result = Math.Max(result, compareElements(el, el2));
}
q++;
}
i++;
}
return result;
}

For the sake of readability, I would write an iterator block to generate the comparisons in a non-repetitive manner:
IEnumerable<Tuple<T, T>> GetComparisons<T>(IEnumerable<T> elements)
{
var visited = new List<T>();
foreach(T current in elements)
{
foreach(T previous in visited)
yield return new Tuple<T, T>(current, previous);
visited.Add(current);
}
}
Then you can do the following:
var highScore = GetComparisons(listOfElements)
.Select(x=>compareElements(x.Item1, x.Item2)
.Max();
(That said I prefer Smelch's suggestion for situations where there's no practical reason to use LINQ or iterators, such as having a need for composable routines.)

Don't know if it is what you are searching for, but I would try to use LINQ in this way:
var linq = from el1 in list
from el2 in list
where el1 != el2
select CompareFunction(el1, el2);
int max = linq.Max();
Comparison sample implementation:
int CompareFunction(string a, string b)
{
return a.Length - b.Length;
}
This way you compare each element against the other elements in the list (it is a sort of permutation I think) except itself, then select the comparison value and finally the highest value.

You could compile a list of the possible combinations you want to test into a List<Tuple<int, int>>
and then select the maximum
mylist.Select(i => new [] { Tuple.New(i.X, i.Y}, Tuple.New(i.X, i.Z), Tuple.New(i.Y, i.Z)})
.Max(t => compareElements(t.First, t.Second))

Pretty much the same as a couple given, but doesn't require one to assign to a list first.
List<Element> soFar = new List<Element>();
// If I expected a good few duplicate values, and if
// compareElements(x, x) isn't 100% - i.e. it's not a similarity
// check for example, then I'd use HashSet<Element> and skip
// when .Add() fails.
int result = 0;
foreach(Element el in sourceEnumeration)
{
for(int i = 0; i != soFar.Count; ++i)
{
int cmp = compareElements(el, soFar[i]);
if(cmp > result)
{
if(cmp == 100)
return 100;
cmp = result;
}
}
soFar.Add(el);
}
return result;

An unreadable LINQ implementation (may not compile, I haven't tested):
Enumerable.Range(0, listOfElements.Length).ToList().ForEach(i=>Enumerable.Range(i, listOfElements.Length-i-1).ToList().ForEach(j=>compareElements(listOfElements[i], listOfElements[j]))).Max();

Related

Filter over IEnumerable ... .Where?

I made a function to generate the odd numbers:
static IEnumerable<int> OddNumbers()
{
int n = 1;
while (true)
yield return 1 + 2 * (n++ - 1);
}
How do I go through and filter this list? I am trying to remove all the multiples of a certain number factor, I wrote this:
using (var oddNumbers = OddNumbers().GetEnumerator())
{
oddNumbers.MoveNext();
int factor = oddNumbers.Current;
yield return factor;
oddNumbers = oddNumbers.Where(x => x % factor != 0);
}
but I get told that
The type arguments for method `System.Linq.Enumerable.Where<TSource>(
this System.Collections.Generic.IEnumerable<TSource>,
System.Func<TSource,bool>)' cannot be inferred from the usage.
Try specifying the type arguments explicitly`
To my understanding there is no need to access the enumerator directly, it can be done using Linq alone as follows.
var FilteredNumbers = OddNumbers().Where(x => x % factor != 0);
You can either use Linq:
// Initial generator
static IEnumerable<int> OddNumbers() {
for (int n = 1; ; n += 2) // for loop is far better than while here
yield return n;
}
...
var result = OddNumbers()
.Where(x => x % factor ! = 0);
Or modify the generator itself:
static IEnumerable<int> OddNumbersAdvanced(int factorToExclude = int.MinValue) {
for (int n = 1; ; n += 2)
if (n % factorToExclude != 0)
yield return n;
}
...
var result = OddNumbersAdvanced(factor);
To go through use foreach loop:
foreach (int item in result) {
//TODO: put relevant code here; do not forget to break the loop
}
Since you need that to generate sequence of lucky numbers, here is some code I wrote to do this with iterators. Note that it's very inefficient to do that this way, but hopefully you are doing this just for fun or for learning
static IEnumerable<int> LuckyNumbers(IEnumerable<int> all = null, int n = 2, int step = 0) {
if (step == 0) {
all = Enumerable.Range(1, int.MaxValue); // start with all numbers
yield return 1;
step++;
}
// apply a filter for current "n" (starting with 2)
var filtered = Filtered(all, n);
// get next item from the sequence (skip items first, because this sequence represents whole lucky number sequence, starting from 1)
var current = filtered.Skip(step).First();
yield return current;
step++;
// now recursive call back into LuckyNumber
foreach (var other in LuckyNumbers(filtered, current, step)) {
yield return other;
}
}
static IEnumerable<int> Filtered(IEnumerable<int> previous, int n) {
// filter out each n-th item
return previous.Where((x, i) => (i + 1)%n != 0);
}
Use like this:
foreach (var next in LuckyNumbers().Take(10)) {
Console.WriteLine(next);
}

Create a list of sequential numbers excluding one number

Requirements:
Create a list of n sequential numbers starting at a.
Exclude number x.
This is the best I have right now, the problem being that it creates n + 1 numbers if x is not within the range.
var numbers = Enumerable
.Range(a, numberOfDataRowsToAdd + 1)
.Where(i => i != TechnicalHeaderRowIndex);
Example 1 should produce 0,1,2,3,4,5,6,7,8,9.
var a = 0;
var n = 10;
var x = 11;
Example 2 should produce 0,1,2,3,4,5,7,8,9,10.
var a = 0;
var n = 10;
var x = 6;
Here is a Fiddle that demonstrates Mark's answer.
How about
Enumerable.Range(a, n + 1)
.Where(i => i != x)
.Take(n);
My example, how it can be done without LINQ and extra loop iterations:
public static IEnumerable<int> GenerateNumbers(int a, int n, int x)
{
for (var i = 0; i < n; i++)
{
if (a == x)
{
i--;
a++;
continue;
}
yield return a++;
}
}
But if you don't want create new method for this purpose, Mark Sowul or Jakub Lortz answers are better.
The problem can be described as
Get n + 1 sequential numbers starting from a
If x is in the range, remove x, otherwise remove the maximum number from the list
Translated to C#
int numberToExclude = Math.Min(n + a, x);
var numbers = Enumerable.Range(a, n + 1).Where(i => i != numberToExclude);
It makes sense to generate only necessary values instead of generating n + 1 values and then remove x:
Enumerable.Range(a, n).Select(i => i < x ? i : i + 1);
Example 1: 0,1,2,3,4,5,6,7,8,9.
Example 2: 0,1,2,3,4,5,7,8,9,10.
You can drop the last if your enumerable count is bigger than numberOfDataRowsToAdd
Extension method:
public static IEnumerable<T> DropLast<T>(this IEnumerable<T> enumerable)
{
return enumerable.Take(enumerable.Count()-1);
}
Usage:
var numbers = Enumerable
.Range(a, numberOfDataRowsToAdd + 1)
.Where(i => i != TechnicalHeaderRowIndex);
if(numbers.Count() > numberOfDataRowsToAdd)
numbers = numbers.DropLast();
I don't see what really is the challenge - Linq shortest or fastest or just working. How about the natural (which should also be the fastest Linq based)
var numbers = a <= x && x < a + n ?
Enumerable.Range(a, x - a).Concat(Enumerable.Range(x + 1, a - x + n)) :
Enumarble.Range(a, n);

List<int> separated into thirds

Assuming I have a list of numbers, which could be any amount, realistically over 15.
I want to separate that list of numbers into three groups depending on their size, small, medium, and large for instance.
What is the best way of achieving this?
I've written out the below, is it necessary to make my own function as per below, or is there anything existing that I can utilise in .NET?
public static List<int> OrderByThree (List<int> list)
{
list.Sort();
int n = list.Count();
int small = n / 3;
int medium = (2 * n) / 3;
int large = n;
// depending if the number is lower/higher than s/m/l,
// chuck into group via series of if statements
return list;
}
Example
Say I have a list of numbers, 1-15 for instance, I want 1-5 in small, 6-10 in medium and 11-15 in large. However I won't know the amount of numbers at the start, no dramas, using list.count I was hoping to divide for my own function.
Since you have the list sorted already, you can use some LINQ to get the results. I'm assuming a right-closed interval here.
list.Sort();
int n = list.Count();
var smallGroup = list.TakeWhile(x => (x <= n / 3)).ToList();
var middleGroup = list.Skip(smallGroup.Count).TakeWhile(x => (x <= (2 * n) / 3)).ToList();
var largeGroup = list.Skip(smallGroup.Count + middleGroup.Count).ToList();
EDIT
As Steve Padmore commented, you probably will want to return a list of lists (List<List<int>>) from your method, rather than just List<int>.
return new List<List<int>> { smallGroup, middleGroup, largeGroup };
This would be a simple way of doing it:
var result = list.GroupBy (x =>
{
if(x <= small) return 1;
if(x <= medium) return 2;
return 3;
});
Or:
var result = list.GroupBy (x => x <= small ? 1 : x <= medium ? 2 : 3);
(This does not require the list to be sorted)

LINQ get x amount of elements from a list

I have a query which I get as:
var query = Data.Items
.Where(x => criteria.IsMatch(x))
.ToList<Item>();
This works fine.
However now I want to break up this list into x number of lists, for example 3. Each list will therefore contain 1/3 the amount of elements from query.
Can it be done using LINQ?
You can use PLINQ partitioners to break the results into separate enumerables.
var partitioner = Partitioner.Create<Item>(query);
var partitions = partitioner.GetPartitions(3);
You'll need to reference the System.Collections.Concurrent namespace. partitions will be a list of IEnumerable<Item> where each enumerable returns a portion of the query.
I think something like this could work, splitting the list into IGroupings.
const int numberOfGroups = 3;
var groups = query
.Select((item, i) => new { item, i })
.GroupBy(e => e.i % numberOfGroups);
You can use Skip and Take in a simple for to accomplish what you want
var groupSize = (int)Math.Ceiling(query.Count() / 3d);
var result = new List<List<Item>>();
for (var j = 0; j < 3; j++)
result.Add(query.Skip(j * groupSize).Take(groupSize).ToList());
If the order of the elements doesn't matter using an IGrouping as suggested by Daniel Imms is probably the most elegant way (add .Select(gr => gr.Select(e => e.item)) to get an IEnumerable<IEnumerable<T>>).
If however you want to preserve the order you need to know the total number of elements. Otherwise you wouldn't know when to start the next group. You can do this with LINQ but it requires two enumerations: one for counting and another for returning the data (as suggested by Esteban Elverdin).
If enumerating the query is expensive you can avoid the second enumeration by turning the query into a list and then use the GetRange method:
public static IEnumerable<List<T>> SplitList<T>(List<T> list, int numberOfRanges)
{
int sizeOfRanges = list.Count / numberOfRanges;
int remainder = list.Count % numberOfRanges;
int startIndex = 0;
for (int i = 0; i < numberOfRanges; i++)
{
int size = sizeOfRanges + (remainder > 0 ? 1 : 0);
yield return list.GetRange(startIndex, size);
if (remainder > 0)
{
remainder--;
}
startIndex += size;
}
}
static void Main()
{
List<int> list = Enumerable.Range(0, 10).ToList();
IEnumerable<List<int>> result = SplitList(list, 3);
foreach (List<int> values in result)
{
string s = string.Join(", ", values);
Console.WriteLine("{{ {0} }}", s);
}
}
The output is:
{ 0, 1, 2, 3 }
{ 4, 5, 6 }
{ 7, 8, 9 }
You can create an extension method:
public static IList<List<T>> GetChunks<T>(this IList<T> items, int numOfChunks)
{
if (items.Count < numOfChunks)
throw new ArgumentException("The number of elements is lower than the number of chunks");
int div = items.Count / numOfChunks;
int rem = items.Count % numOfChunks;
var listOfLists = new List<T>[numOfChunks];
for (int i = 0; i < numOfChunks; i++)
listOfLists[i] = new List<T>();
int currentGrp = 0;
int currRemainder = rem;
foreach (var el in items)
{
int currentElementsInGrp = listOfLists[currentGrp].Count;
if (currentElementsInGrp == div && currRemainder > 0)
{
currRemainder--;
}
else if (currentElementsInGrp >= div)
{
currentGrp++;
}
listOfLists[currentGrp].Add(el);
}
return listOfLists;
}
then use it like this :
var chunks = query.GetChunks(3);
N.B.
in case of number of elements not divisible by the number of groups, the first groups will be bigger. e.g. [0,1,2,3,4] --> [0,1] - [2,3] - [4]

Infinite IEnumerable in a foreach loop

After answering this question I put together the following C# code just for fun:
public static IEnumerable<int> FibonacciTo(int max)
{
int m1 = 0;
int m2 = 1;
int r = 1;
while (r <= max)
{
yield return r;
r = m1 + m2;
m1 = m2;
m2 = r;
}
}
foreach (int i in FibonacciTo(56).Where(n => n >= 24) )
{
Console.WriteLine(i);
}
The problem is that I don't like needing to pass a max parameter to the function. Right now, if I don't use one the code will output the correct data but then appear to hang as the IEnumerable continues to work. How can I write this so that I could just use it like this:
foreach (int i in Fibonacci().Where(n => n >= 24 && n <= 56) )
{
Console.WriteLine(i);
}
You need to use a combination of SkipWhile and TakeWhile instead.
foreach (int i in Fibonacci().SkipWhile(n => n < 24)
.TakeWhile(n => n <= 56))
{
Console.WriteLine(i);
}
These are able to end loops depending on a condition; Where streams its input (filtering appropriately) until the input runs out (in your case, never).
I don't think this is possible unless you write your own LINQ provider. In the example you gave you are using LINQ to Objects which will need to completely evaluate the IEnumerable before it can apply a filter to it.

Categories