Find closest value in a list in C# with linq? - c#

I've a list like this:
public List<Dictionary<int, int>> blanks { get; set; }
This keep some index values:
In addition I have also a variable named X. X can take any value. I want to find closest 'Key' value to X. For example:
If X is 1300, I want to take blanks index: 2 and Key: 1200.
How can I do this via linq? Or, is there any other solution?
Thanks in advance.
EDIT: What if it is not a Dictionary. What if it is a List like this:
List<List<int[]>> lastList = new List<List<int[]>>();
This time, I want to take first List's indexes and second List's index. For example, if X is 800, I want to take 0 and 0 (for index 0) and also take 1 and 1 (for index 1) How can I do this time?

var diffs = blanks.SelectMany((item, index) => item.Select(entry => new
{
ListIndex = index, // Index of the parent dictionary in the list
Key = entry.Key, // Key
Diff = Math.Abs(entry.Key - X) // Diff between key and X
}));
var closestDiff = diffs.Aggregate((agg, item) => (item.Diff < agg.Diff) ? item : agg);
Dictionary<int, int> closestKeyDict = blanks[closestKey.ListIndex];
int closestKey = closestDiff.Key;
int closestKeyValue = closestKeyDict[closestKey];
The SelectMany clause flattens all the dictionaries entries into a collection of { ListIndex, DictionaryKey, Difference } instances.
This flattened collection is then aggregated to retrieve the item with the minimum difference.
To answer your second questsion:
var diffs = blanks.SelectMany((list, listIndex) => list.
SelectMany((array, arrayIndex) => array.
Select((item, itemIndex) => new
{
ListIndex = listIndex,
ArrayIndex = arrayIndex,
ItemIndex = itemIndex,
Diff = Math.Abs(item - X)
})));
var closestDiff = diffs.Aggregate((agg, item) => (item.Diff < agg.Diff) ? item : agg);
Now in closestDiff you'll find the indices of the closes item (List index, array index and array item index)

This might not be the most optimized way but it should just work,
List<Dictionary<int, int>> blanks = new List<Dictionary<int, int>>
{
new Dictionary<int, int>{{100,200}},
new Dictionary<int, int>{{500,200}},
new Dictionary<int, int>{{700,200}},
new Dictionary<int, int>{{1200,200}},
new Dictionary<int, int>{{300,200}},
new Dictionary<int, int>{{200,200}},
new Dictionary<int, int>{{800,200}},
};
int x = 1300;
IEnumerable<int> keys = blanks.SelectMany(ints => ints.Keys);
var diff = keys.Select(i => Math.Abs(i - x)).ToList();
var index = diff.IndexOf(diff.Min());
var value = blanks[index].Keys.First();

Related

C# Searching String from list and add its Integer if there is more than one result

this is my list
var list1 = new List<KeyValuePair<string, int>>();
list1.Add(new KeyValuePair<string, int>("Narzo", 8));
list1.Add(new KeyValuePair<string, int>("Stinger", 5));
list1.Add(new KeyValuePair<string, int>("Stinger", 2));
I want to search a string and display the string together with the integer.
If the string has duplicate value then it will add the integer of the duplicate/s
Sample
INPUT:
Search "Stinger"
OUTPUT:
Stinger 5
Stinger 2
Stinger 7
If you just want to sum the values of the duplicate-keys and output them, use LINQ:
var duplicates = list1.GroupBy(kv => kv.Key)
.Where(g => g.Count() > 1)
.Select(g => $"{g.Key} {g.Sum(kv => kv.Value)}");
foreach(var duplicate in duplicates)
Console.WriteLine(duplicate); // Stinger 7
var searchFor = "Stinger";
Console.WriteLine($"{searchFor} {list1.Where(w => w.Key == searchFor).Sum(x=>x.Value)}");
Seems like a simple loop plus a variable to keep track of the count and sum would work:
Pseudo-code
string search = "Stinger";
int count = 0;
int sum = 0;
foreach(var kvp in list1)
{
if the item matches the search
{
increment the count
add the value to the sum
print the item
}
if more than one item found
{
print the search string and sum of values
}
}

Sorting array by frequency of elements in C#

Here is what i have so far
int[] numbers = { 3,5,4,3,8,8,5,3,2,1,9,5 };
int[] n = new int[12];
int[] k;
foreach (int number in numbers)
{
n[number]++;
}
Array.Sort(n);
Array.Reverse(n);
foreach (int value in n)
{
Console.WriteLine(value);
}
I know i am missing the part where i sort the frequency of the elements after i counted them and i just cant get my head around it. I'd appreciate some help, Thanks!
What's the problem with your solution ?
Whereas you correctly keep the frequencies of the numbers in the table called n in your code, which hereby I would call it frequencies, then you Sort this array. This action breaks your solution, since each frequency is associated with the corresponding index of its location in the array.
E.g. If an instance of this array is this [8,2,1,7,6]. When you call the Sort method on this array, this would have as a result the array to be sorted and the order of the elements of the array would be this [1,2,7,6,8]. Before calling sort, the first element of the array was indicating that the number 0 (the index of the first element is 0) has been found 8 times in our numbers. After sort, the first element is 1, which means now that the frequency of the number 0 is 1, which is apparently wrong.
If you want to keep it your way, then you could try something like this:
int[] numbers = { 1,2,2,9,1,2,5,5,5,5,2 };
int[] frequencies = new int[12];
int k = 3;
foreach (int number in numbers)
{
frequencies[number]++;
}
var mostFrequentNumbers = frequencies.Select((frequency, index) => new
{
Number = index,
Frequency = frequency
})
.OrderByDescending(item => item.Frequency)
.Select(item => item.Number)
.Take(k);
foreach (int mostFrequentNumber in mostFrequentNumbers)
{
Console.WriteLine(mostFrequentNumber);
}
Are there any other approaches ?
An easy way to do this is to use a data structure like a Dictionary, in which you would keep as keys the numbers and as the corresponding values the corresponding frequencies.
Then you can order by descending values the above data structure an keep the k most frequent numbers.
int[] numbers = { 1,2,2,9,1,2,5,5,5,5,2 };
int k = 3;
Dictionary<int, int> numberFrequencies = new Dictionary<int, int>();
foreach (int number in numbers)
{
if(numberFrequencies.ContainsKey(number))
{
numberFrequencies[number] += 1;
}
else
{
numberFrequencies.Add(number, 1);
}
}
var mostFrequentNumbers = numberFrequencies.OrderByDescending(numberFrequency => numberFrequency.Value)
.Take(k)
.Select(numberFrequency => numberFrequency.Key);
foreach (int mostFrequentNumber in mostFrequentNumbers)
{
Console.WriteLine(mostFrequentNumber);
}
You can also achieve the same thing by only using LINQ:
int[] numbers = { 1,2,2,9,1,2,5,5,5,5,2 };
int k = 3;
var mostFrequentNumbers = numbers.GroupBy(number => number)
.ToDictionary(gr => gr.Key, gr => gr.Count())
.OrderByDescending(keyValue => keyValue.Value)
.Take(k)
.Select(numberFrequency => numberFrequency.Key);
foreach (int mostFrequentNumber in mostFrequentNumbers)
{
Console.WriteLine(mostFrequentNumber);
}
You can just use Linq extensions:
using System.Linq;
using System.Collections.Generic;
...
private static IEnumerable<int> Solve(int[] numbers, int k) {
return numbers
.GroupBy(x => x)
.OrderByDescending(g => g.Count())
.Select(g => g.Key)
.Take(k);
}
Then you can call:
var numbers = new []{1,2,2,9,1,2,5,5,5,5,2};
var k = 3;
var result = Solve(numbers, k);
foreach (int n in result)
Console.WriteLine(n);
To be very terse:
var frequents = numbers.GroupBy(t => t)
.Where(grp => grp.Count() > 1)
.Select(t => t.Key)
.OrderByDescending(t => t)
.Take(k)
.ToList();

Get duplicate values from dictionary

I have dictionary and I need get duplicate values.
For example:
Dictionary<int, List<string>> dictionary = new Dictionary<int, List<string>>();
List<string> list1 = new List<string> { "John", "Smith" };
List<string> list2 = new List<string> { "John", "Smith" };
List<string> list3 = new List<string> { "Mike", "Johnson" };
dictionary.Add(1, list1);
dictionary.Add(2, list2);
dictionary.Add(3, list3);
I need find all duplicate from dictionary and return max keys(collection of key) of each duplicate values. From my test dictionary I need return list with only one key = 2
Maybe I chose the wrong data structure. I would like to receive optimal algorithm
With your current structure, you're in a bit of trouble because you don't necessarily have an easy way to compare two List<string> to see if they are equal.
One way to work around this is to create a custom List<string> comparer that implements IEqualityComparer<List<string>>. However, since you have a list of strings, we also need to reorder both to ensure that we are comparing each value in the correct order. This affects the cost of your algorithm. On the other hand, if you are happy with the order of the values inside of the lists, that works just fine as well and you can avoid that cost.
public class StringListComparer : IEqualityComparer<List<string>>
{
public bool Equals(List<string> x, List<string> y)
{
return CompareLists(x, y);
}
public int GetHashCode(List<string> obj)
{
return base.GetHashCode();
}
private static bool CompareLists(List<string> x, List<string> y)
{
if (x.Count != y.Count)
return false;
// we HAVE to ensure that lists are in same order
// for a proper comparison
x = x.OrderBy(v => v).ToList();
y = y.OrderBy(v => v).ToList();
for (var i = 0; i < x.Count(); i++)
{
if (x[i] != y[i])
return false;
}
return true;
}
}
Once we have our comparer, we can use it to pull out keys from subsequent duplicates, leaving the first key (per your requirement).
public List<int> GetDuplicateKeys(Dictionary<int, List<string>> dictionary)
{
return dictionary
.OrderBy (x => x.Key)
.GroupBy(x => x.Value, new StringListComparer())
.Where (x => x.Count () > 1)
.Aggregate (
new List<int>(),
(destination, dict) =>
{
var first = dict.FirstOrDefault();
foreach (var kvp in dict)
{
if (!kvp.Equals(first))
destination.Add(kvp.Key);
}
return destination;
}
).ToList();
}
The following test outputs keys 2 and 4.
Dictionary<int, List<string>> dictionary = new Dictionary<int, List<string>>();
dictionary.Add(1, new List<string> { "John", "Smith" });
dictionary.Add(2, new List<string> { "John", "Smith" });
dictionary.Add(3, new List<string> { "Mike", "Johnson"});
dictionary.Add(4, new List<string> { "John", "Smith" });
var result = GetDuplicateKeys(dictionary);
You could create a list of KeyValuePair<int, List<string>>, that contains the ordered lists, with the outer list sorted. Then you could find duplicates very quickly. You'd need a list comparer that can compare ordered lists.
class MyListComparer: Comparer<List<string>>
{
public override int Compare(List<string> x, List<string> y)
{
for (var ix = 0; ix < x.Count && ix < y.Count; ++ix)
{
var rslt = x[ix].CompareTo(y[ix]);
if (rslt != 0)
{
return rslt;
}
}
// exhausted one of the lists.
// Compare the lengths.
return x.Count.CompareTo(y.Count);
}
}
var comparer = new MyListComparer();
var sortedList = dictionary.Select(kvp =>
new KeyValuePair<int, List<string>>(kvp.Key, kvp.Value.OrderBy(v => v))
.OrderBy(kvp => kvp.Value, comparer)
.ThenBy(kvp => kvp.Key);
Note the ThenBy, which ensures that if two lists are equal, the one with the smaller key will appear first. This is necessary because, although OrderBy does a stable sort, there's no guarantee that enumerating the dictionary returned items in order by key.
// the lists are now sorted by value. So `"Smith, John"` will appear before `"Smith, William"`.
// We can go through the list sequentially to output duplicates.
var previousList = new List<string>();
foreach (var kvp in sortedList)
{
if (kvp.Value.SequenceEqual(previousList))
{
// this list is a duplicate
// Lookup the list using the key.
var dup = dictionary[kvp.Key];
// Do whatever you need to do with the dup
}
else
{
previousList = kvp.Value;
}
}
This sorts each list only once. It does use more memory, because it duplicates the dictionary in that list of KeyValuePair<int, List<string>>, but for larger data sets it should be much faster than sorting each list multiple times and comparing it against every other list.
Caveats: The code above assumes that none of the lists are null, and none of them are empty. If a list in the dictionary can be null or empty, then you'll have to add some special case code. But the general approach would be the same.

VBA/Excel RANK in C#

I am working on creating calculations from a spreadsheet into C#, and I was wondering if C# has a similar method to Rank in Excel?
Rank in Excel
Returns the rank of a number in a list of numbers. The rank of a
number is its size relative to other values in a list. (If you were to
sort the list, the rank of the number would be its position.)
Syntax
RANK(number,ref,order)
Number is the number whose rank you want to find.
Ref is an array of, or a reference to, a list of numbers.
Nonnumeric values in ref are ignored.
Order is a number specifying how to rank number.
If order is 0 (zero) or omitted, Microsoft Excel ranks number as if
ref were a list sorted in descending order. If order is any nonzero
value, Microsoft Excel ranks number as if ref were a list sorted in
ascending order.
The same can be achieved through code, but I just wanted to check if there was anything I was missing first.
You can, sort of.
SortedList<int, object> list = new SortedList<int, object>();
// fill with unique ints, and then look for one
int rank = list.Keys.IndexOf(i);
Rank will be an ascending, zero-based position.
You could pretty it up by writing an extension method:
public static class Extensions
{
public static int Rank(this int[] array, int find)
{
SortedList<int, object> list = new SortedList<int, object>();
for (int i = 0; i < array.Length; i++)
{
list.Add(array[i], null);
}
if (list.ContainsKey(find))
{
return list.Keys.IndexOf(find);
}
else
{
return -1;
}
}
}
And use it like:
int[] ints = new int[] { 2, 7, 6, 3, 9, 12 };
int rank = ints.Rank(2);
...but I'm not convinced its the most sensible thing to do.
To get the equivalent of RANK you'll need to get the minimum index of each item when you group:
var ranks = list.OrderBy(x => x)
.Select((x, i) => new {x, i = i+1}) // get 1-based index of each item
.GroupBy(xi => xi.x) // group by the item
.Select(g => new {rank = g.Min(xi => xi.i), items = g}) // rank = min index of group
.SelectMany(g => g.items, (g, gg) => new {g.rank, gg.i}) ; // select rank and item
or if you'rs grouping by the property of a class:
var ranks = list.OrderBy(x => x.{some property})
.Select((x, i) => new {x, i = i+1}) // get 1-based index of each item
.GroupBy(xi => xi.x.{some property}) // group by the item's property
.Select(g => new {rank = g.Min(xi => xi.i), items = g}) // rank = min index of group
.SelectMany(g => g.items, (g, gg) => new {g.rank, gg.i}) ; // select rank and item
This works for me so far (and it is simpler)
public static int Rank<T>(T value, IEnumerable<T> data)
{
return data.OrderByDescending(x => x).ToList().IndexOf(value) + 1;
}
I used T so it can take all numeric types (int/double/decimal).
The usage is similar to Excel
int[] data = new[] { 3, 2, 2, 3, 4 };
int rank = Rank(3, data); // returns 2
I hope I didn't miss anything

How to get count similar word in list?

I have C# list with lot of similar name i want to count all individual similar word.
Example
Suppose list has these values
one,one,one,two,two,four,four,four
then i want to calculate like this
one 3
two 2
four 3
how can i calculate value like this from list.
I would split the string on comma, loop through all the results and add each word to a hashtable or dictionary with a value of one. If the word (key) is already present, then increment the value.
string[] values = "one,one,one,two,two,four,four,four".Split(',');
var counts = new Dictionary<string, int>();
foreach (string value in values) {
if (counts.ContainsKey(value))
counts[value] = counts[value] + 1;
else
counts.Add(value, 1);
}
Or, if you prefer, here is a LINQ solution
var counts = values.GroupBy<string, string, int>(k => k, e => 1)
.Select(f => new KeyValuePair<string, int>(f.Key, f.Sum()))
.ToDictionary(k => k.Key, e => e.Value);
Here is a solution based on Linq:
string s = "one,one,one,two,two,four,four,four";
List<string> list = s.Split(',').ToList();
Dictionary<string, int> dictionary = list.GroupBy(x => x)
.ToDictionary(x => x.Key, x => x.Count());
foreach (var kvp in dictionary)
Console.WriteLine("{0}: {1}", kvp.Key, kvp.Value);
Output:
one: 3
two: 2
four: 3
This solutions doesn't take advantage of the fact that the common values are consecutive. If this is always the case, a slightly faster solution could be written, but this is fine for short lists, or if the items can come in any order.
Dictionaty<string, int> listCount = new Dictionaty<string, int>();
for (int i = 0; i < yourList.Count; i++)
{
if(listCount.ContainsKey(yourList[i]))
listCount[yourList[i].Trim()] = listCount[yourList[i].Trim()] + 1;
else
listCount[yourList[i].Trim()] = 1;
}
For List, you could do the following (untested):
List<string> list = new List<string>()
{
"One",
"One",
"Two",
// etc
}
Dictionary<string, int> d = new Dictionary<string, int>();
foreach (string s in list)
{
if (d.ContainsKey(s))
d.Add(s, 1);
else
d[s]++;
}
The preferred (and cleaner) method is to do this using GroupBy and Count with Linq, but I don't have the type to type out the syntax at the moment.
Good luck!

Categories

Resources