How to group items by index? C# LINQ - c#

Suppose I have
var input = new int[] { 0, 1, 2, 3, 4, 5 };
How do I get them grouped into pairs?
var output = new int[][] { new int[] { 0, 1 }, new int[] { 2, 3 }, new int[] { 4, 5 } };
Preferably using LINQ

input
.Select((value, index) => new { PairNum = index / 2, value })
.GroupBy(pair => pair.PairNum)
.Select(grp => grp.Select(g => g.value).ToArray())
.ToArray()

Probably not applicable to you, but you could use the new Zip method in C# 4.0
var input = new int[] { 0, 1, 2, 3, 4, 5 };
IEnumerable evens = input.Where((element, index) => index % 2 == 0);
IEnumerable odds = input.Where((element, index) => index % 2 == 1);
var results = evens.Zip(odds, (e, o) => new[] { e, o }).ToArray();

var indexedNumbers = input.Select((number, index) => new { Index = index, Number = number });
var pairs =
from indexedNumber in indexedNumbers
group indexedNumber by indexedNumber.Index / 2 into indexedNumberPair
select indexedNumberPair.Select(indexedNumber => indexedNumber.Number);
var arrays = pairs.Select(pair => pair.ToArray()).ToArray();

Using ToLookup method:
input
.Select((number, index) => new { index , number})
.ToLookup(_ => _.index / 2, _ => _.number)
.Select(_ => _.ToArray())
.ToArray();
Using Zip method:
input
.Zip(input.Skip(1), (_, __) => new[] {_, __})
.Where((_, index) => index % 2 == 0)
.ToArray();

Related

C# Pattern match arrays

var x = new int[] { 1, 2 };
var y = x switch {
{ 1, 2 } => "yea",
_ => "nay"
};
fails to compile.
How can I pattern-match arrays?
You have to expand the elements of the array yourself like so
var x = new int[] { 1, 2 };
var y = (x[0], x[1]) switch {
(1, 2) => "yea",
_ => "nay"
};

C# function for finding most repeated numbers in an array

Hi I am trying to write a C# (Visual Studio) program for a function that takes integers in an array , and returns an array of integers that contains those integers which are most common in the input array.
sample in out -
[1,2,3,4,3,3,2,2,4] result = [2,3]
[1, 2, 3, 4, 5, 1, 6, 7, 1, 1] result = [1]
[1, 2, 3, 4, 5, 6, 7] result = [1, 2, 3, 4, 5, 6, 7]
I am almost there, but not getting expected results. Below is the code I wrote and I am a beginner.
namespace StringCommonElements
{
class Program
{
static void Main(string[] args)
{
// Compute frequencies for this data.
string[] values = { "bird", "cat", "bird", "dog", "bird", "man", "frog", "cat" };
// Get a list.
List<string> valuesList = new List<string>(values);
// Call our methods.
var freqs = GetFrequencies(valuesList);
DisplaySortedFrequencies(freqs);
}
static Dictionary<string, int> GetFrequencies(List<string> values)
{
var result = new Dictionary<string, int>();
foreach (string value in values)
{
if (result.TryGetValue(value, out int count))
{
// Increase existing value.
result[value] = count + 1;
}
else
{
// New value, set to 1.
result.Add(value, 1);
}
}
// Return the dictionary.
return result;
}
static void DisplaySortedFrequencies(Dictionary<string, int> frequencies)
{
// Order pairs in dictionary from high to low frequency.
var sorted = from pair in frequencies
orderby pair.Value descending
select pair;
// Display all results in order.
foreach (var pair in sorted)
{
Console.WriteLine($"{pair.Key} = {pair.Value}");
}
}
}
}
Here's how you can do that with Linq. That will group the numbers and find the count of each, then take all the ones that show up more than once. Then if the result is empty just return the original array because all the numbers are unique.
public int[] MostCommon(int[] numbers)
{
var ans = numbers
.GroupBy(x => x)
.Select(x => new {x.Key, x.Count}))
.Where(x => x.Count > 1)
.Select(x => x.Key)
.ToArray();
return ans.Length > 0 ? ans : numbers;
}
In case you were just wondering how to make your existing code work, all you have to do is return (or output) the items that have the same frequency as the one with the maximum frequency.
For example:
var maxFrequency = sorted.First().Value;
Console.WriteLine("These items all occur the most:");
foreach (var pair in sorted)
{
if (pair.Value < maxFrequency) break;
Console.WriteLine($" - {pair.Key} = {pair.Value}");
}
To get the single most occurring number, you can use this LINQ expression (this will return 3, even though 2 appears just as much):
int appearsMost = new int[] { 1, 2, 3, 4, 3, 3, 2, 2, 4 }
.GroupBy(x => x)
.Select(x => (Key: x.Key, Items: x.ToList()))
.OrderByDescending(x => x.Items.Count)
.First().Key;
The select clause with the ToList() in between is to prevent having to count() the grouped items multiple times.
The following solution should give you the numbers that appear most. (this will return 2 and 3)
int[] appearMost = new int[] { 1, 2, 3, 4, 3, 3, 2, 2, 4 }
.GroupBy(x => x)
.Select(x => (Key: x.Key, Items: x.ToList()))
.GroupBy(x => x.Items.Count)
.OrderByDescending(x => x.Key)
.First()
.Select(x => x.Key)
.ToArray();
If you want all numbers that appear more than once: (this will return 2,3,4,1)
int[] appearMoreThanOnce = new int[] { 1, 2, 3, 4, 3, 3, 2, 2, 4 }
.GroupBy(x => x)
.Select(x => (Key: x.Key, Items: x.ToList()))
.OrderByDescending(x => x.Items.Count)
.Where(x => x.Items.Count >= 1).Select(x => x.Key).ToArray();
In all cases, you can do the same by animals directly (only with small adjustments):
string[] animalsThatAppearMoreThanOnce = new string[] { "bird", "cat", "bird", "dog", "bird", "man", "frog", "cat" }
.GroupBy(x => x)
.OrderByDescending(x => x.Count())
.Where(x => x.Count() >= 1).Select(x => x.Key).ToArray();
// I added another cat, so this will return 'bird' and 'cat'.
string[] animalsThatAppearMost = new string[] { "bird", "cat", "bird", "dog", "bird", "man", "frog", "cat", "cat" }
.GroupBy(x => x)
.Select(x => (Key: x.Key, Items: x.ToList()))
.GroupBy(x => x.Items.Count)
.First()
.Select(x => x.Key)
.ToArray();
Actually, your methods work. I think it is just a matter of display. In order to emulate the examples that you posted, I would just write the DisplaySortedFrequencies this way:
static void DisplaySortedFrequencies(Dictionary<string, int> frequencies)
{
// Order pairs in dictionary from high to low frequency.
var sorted = from pair in frequencies
orderby pair.Value descending
select pair;
// Display all results in order.
int MaxNumValues = sorted.First().Value;
foreach (var pair in sorted)
{
int numValues = pair.Value;
if (numValues < MaxNumValues) //Show only the most predominant groups
break;
Console.WriteLine($"{pair.Key} = {numValues}");
}
}
In any case, if it is code that has to be performant, I would go for the solutions already posted that use Linq. I you don't want to use Linq for some reason, I would suggest to sort the items first and then count equal elements in a row instead of using a dictionary search for every element.
I have made a few changes to your code.
static void Main(string[] args)
{
// Compute frequencies for this data.
string[] values = { "bird", "cat", "bird", "dog", "bird", "man", "frog", "cat" };
var freqs = GetFrequencies(values);
DisplaySortedFrequencies(freqs);
}
static Dictionary<string, int> GetFrequencies(IEnumerable<string> values)
{
if (values == null) return new Dictionary<string, int>();
var maxCount = 1;
var result = new Dictionary<string, int>();
foreach (string value in values)
{
if (result.TryGetValue(value, out int count))
{
result[value] = count + 1;
if (maxCount < result[value])
{
maxCount = result[value];
}
}
else
{
result.Add(value, 1);
}
}
return result
.Where(item => item.Value == maxCount)
.OrderBy(item => item.Key)
.ToDictionary(item => item.Key, item => item.Value);
}
static void DisplaySortedFrequencies(Dictionary<string, int> frequencies)
{
foreach (var pair in frequencies)
{
Console.WriteLine($"{pair.Key} = {pair.Value}");
}
}

Convert a sequence of numbers into a zero based order?

Suppose I have:
var correctOrder = new[] {2, 1, 0};
var actualPositionsFound = new[] {63,62,61];
How can I easily convert actualPositionsFound to a zero based sequence?
So if I had:
var actualPositionsFound = new[] {100,50,200];
I would like to end up with :
var result = new[] {1,0,2};
Update: In an attempt to make this clearer to avoid closing, what I believe is being asked for is to translate a list of numbers into another list of numbers representing the ascending order of the other list like a sort map, 0-based.
So { 16, 19, 2, 4 } would create a map { 2, 3, 0, 1 }, being 0-based.
If there are no duplicates:
var actualPositionsFound = new[] { 100, 50, 200 };
var indices = actualPositionsFound.OrderBy(n => n)
.Select((n, i) => new { n, i })
.ToDictionary(o => o.n, o => o.i);
var result = actualPositionsFound.Select(n => indices[n]).ToList();
Is it that you are looking for?
actualPositionsFound.Select((elem, idx) => new { elem, idx })
.OrderBy(wrap => wrap.elem)
.Select((wrap, idx) => new { wrap.idx, newIdx = idx })
.OrderBy(wrap => wrap.idx)
.Select(wrap => wrap.newIdx)
.ToArray();
actualPositionsFound
.OrderBy(x => x).ToList()
.Select(x => Array.IndexOf(actualPositionsFound,x)).ToArray();
This won't handle duplicates.

name of assoc-diff in C#

What do you call this method, (is it available in .net?)
var list1 = new List<int>() { 1, 2, 2, 3, 4 };
var list2 = new List<int>() { 1, 2, 3};
var results = list1.diff(list2);
results:
{ 2, 4 }
The closest thing built in is the Except LINQ operator.
Produces the set difference of two sequences.
Though with your example it will result in:
{ 4 }
I don't believe there is a direct analogue to what you want.
You actually need a multiset implementation. Although there is no multiset out of the box in BCL, there are some ideas here and in the linked question.
Or you can actually implement one by yourself, it's not so complicated:
class Multiset<K> // maybe implement IEnumerable?
{
Dictionary<K, int> arities = new Dictionary<K, int>();
...
Multiset<K> Except(Multiset<K> other)
{
foreach (var k in arities.keys)
{
int arity = arities[k];
if (other.Contains(k))
arity -= other.Arity(k);
if (arity > 0)
result.Add(k, arity);
}
return result;
}
}
This exactly return what you want, You can refactor it in a Extension Method:
var results = list1.GroupBy(p => p).Select(p => new { item = p.Key, count = p.Count() })
.Concat(list2.GroupBy(p => p).Select(p => new { item = p.Key, count = -p.Count() }))
.GroupBy(p => p.item).Select(p => new { item = p.Key, count = p.Sum(q => q.count) })
.Where(p => p.count > 0)
.SelectMany(p => Enumerable.Repeat(p.item, p.count));
Like this: (see oded's post for a linq to msdn)
int[] numbersA = { 0, 2, 4, 5, 6, 8, 9 };
int[] numbersB = { 1, 3, 5, 7, 8 };
IEnumerable<int> aOnlyNumbers = numbersA.Except(numbersB);

Possible to group by Count in LINQ?

This might be either impossible or so obvious I keep passing over it.
I have a list of objects(let's say ints for this example):
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
I'd like to be able to group by pairs with no regard to order or any other comparison, returning a new IGrouping object.
ie,
list.GroupBy(i => someLogicToProductPairs);
There's the very real possibility I may be approaching this problem from the wrong angle, however, the goal is to group a set of objects by a constant capacity. Any help is greatly appreciated.
Do you mean like this:
List<int> list = new List<int>() { 1, 2, 3, 4, 5, 6 };
IEnumerable<IGrouping<int,int>> groups =
list
.Select((n, i) => new { Group = i / 2, Value = n })
.GroupBy(g => g.Group, g => g.Value);
foreach (IGrouping<int, int> group in groups) {
Console.WriteLine(String.Join(", ", group.Select(n=>n.ToString()).ToArray()));
}
Output
1, 2
3, 4
5, 6
you can do something like this...
List<int> integers = new List<int>() { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
var p = integers.Select((x, index) => new { Num = index / 2, Val = x })
.GroupBy(y => y.Num);
int counter = 0;
// this function returns the keys for our groups.
Func<int> keyGenerator =
() =>
{
int keyValue = counter / 2;
counter += 1;
return keyValue;
};
var groups = list.GroupBy(i => {return keyGenerator()});

Categories