-3

I have and object called Person that has props

string Name
string Amount 

The amount is an integer but held as a string.

Each group is formed when the Amount is within n of each other.

i.e. if n=3

Jeff 20
Jack 19
Ben 16
Kyle 12

would be 3 groups
Jeff 20
Jack 19
----------
Ben 16
----------
Kyle 12
----------

I tried this but No grouping happens...

var ranges = new[] {3};
var grouped = orderByResult.GroupBy(x => ranges.FirstOrDefault(r => r > Convert.ToInt32(x.Amount)));
  • 1
    umm, curious question, what does the criteria to group them into 3? or just take whatever elements and make group of three? (if yes, you can see this [QA](https://stackoverflow.com/a/23921688/4648586)) – Bagus Tesa Sep 17 '19 at 01:10
  • 1
    what have you tried already? what if you have element which is within 3 of two other groups? let's say a range 1-10 - how should they be grouped? – Adassko Sep 17 '19 at 01:10
  • 2
    Possible duplicate of [Group by variable integer range using Linq](https://stackoverflow.com/questions/1375997/group-by-variable-integer-range-using-linq) – Circle Hsiao Sep 17 '19 at 01:10
  • 3 has no meaning just an example. Simple want to group by a single range –  Sep 17 '19 at 01:13
  • `var ranges = new[] {3};` initializes `ranges` as an `int` array with a single number `3` in it (why not just use an `int`?). Then you try to find all the items where `x.Amount` is less than `3`? – Rufus L Sep 17 '19 at 01:26
  • 1
    16 is within 3 of 19. Why it is not in the same group? – mjwills Sep 17 '19 at 02:11

2 Answers2

1

You can use the answer from this link https://stackoverflow.com/a/1376305/2770274

You just need to write your own code to generate ceilings. It can look for example like this:

IEnumerable<int> GetRangeCeilings(IEnumerable<Item> input)
{
    const int maximumAllowedDistance = 1;
    var amounts = input.Select(x => int.Parse(x.Amount)).OrderBy(x => x).ToArray();
    for (var i = 1; i < amounts.Length; i ++)
    {
        if (amounts[i - 1] < amounts[i] - maximumAllowedDistance)
        {
            yield return amounts[i - 1];
        }
    }
    yield return int.MaxValue;
}

now with the previous answer:

var items = new Item[] {
    new Item { Name = "Jeff", Amount = "20" },
    new Item { Name = "Jack", Amount = "19" },
    new Item { Name = "Ben", Amount = "16" },
    new Item { Name = "Kyle", Amount = "12" }       
};

var ceilings = GetRangeCeilings(items).ToArray();
var groupings = items.GroupBy(item => ceilings.First(ceiling => ceiling >= int.Parse(item.Amount)));
Adassko
  • 5,201
  • 20
  • 37
1

The logic for generating these groups could be interpreted as follows:

  1. Sort the list
  2. Go through the list one by one and compute the gap between the current item and the previous item
  3. When the gap is less than 3, continue the group; otherwise start a new group

This can be coded like this:

static IEnumerable<IEnumerable<Item>> GroupUp(IEnumerable<Item> input)
{
    var sorted = input.OrderBy( item => int.Parse(item.Amount) ).GetEnumerator();
    int lastValue = int.MinValue;
    var list = new List<Item>();
    while (sorted.MoveNext())
    {
        var current = sorted.Current;
        var currentAmount = int.Parse(current.Amount);
        var gap = currentAmount - lastValue;
        if (gap < 3)
        {
            list.Add(current);
        }
        else
        {
            if (list.Count != 0) yield return list;
            list = new List<Item>();
            list.Add(current);
        }
        lastValue = currentAmount;
    }
    if (list.Count != 0) yield return list;
}

See my working example on DotNetFiddle.

John Wu
  • 50,556
  • 8
  • 44
  • 80