Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

LINQ (Or pseudocode) to group items by proximity

Is anybody able to enlighten me on how to use LINQ (or something more appropriate if necessary) to create a list of lists of integers that are grouped by the the proximity from each other.

Basically, I want to create groups where the numbers are within 5 of any other number.

So, given:

3 27 53 79 113 129 134 140 141 142 145 174 191 214 284 284

Produce the following list:

3 
27
53
79
113
129 134 
140 141 142 145
174
194
214
284 284

Thanks!

like image 406
StevieJ81 Avatar asked Jul 22 '11 02:07

StevieJ81


2 Answers

LINQ is not very good at things like rolling sums etc. A simple foreach loop is better here:

static IEnumerable<IEnumerable<int>> GroupByProximity(
    this IEnumerable<int> source, int threshold)
{
    var g = new List<int>();
    foreach (var x in source)
    {
        if ((g.Count != 0) && (x > g[0] + threshold))
        {
            yield return g;
            g = new List<int>();
        }
        g.Add(x);
    }
    yield return g;
}

Example:

var source = new int[]
{
    3, 27, 53, 79, 113, 129, 134, 140, 141, 142, 145, 174, 191, 214, 284, 284
};

foreach (var g in source.GroupByProximity(5))
{
    Console.WriteLine(string.Join(", ", g));
}

Output:

3
27
53
79
113
129, 134
140, 141, 142, 145
174
191
214
284, 284
like image 182
dtb Avatar answered Sep 23 '22 22:09

dtb


class Program
{
    static void Main(string[] args)
    {
        foreach (IEnumerable<int> grp in nums.GroupsOfN(5))
            Console.WriteLine(String.Join(", ", grp));

        Console.ReadKey();
    }

    static List<int> nums = new List<int>()
    {
        3,
        27,
        53,
        79,
        113,
        129,
        134,
        140,
        141,
        142,
        145,
        174,
        191,
        214,
        284,
        284
    };


}

public static class Extensions
{
    public static IEnumerable<IEnumerable<int>> GroupsOfN(this IEnumerable<int> source, int n)
    {
        var sortedNums = source.OrderBy(s => s).ToList();

        List<int> items = new List<int>();
        for (int i = 0; i < sortedNums.Count; i++)
        {
            int thisNumber = sortedNums[i];
            items.Add(thisNumber);

            if (i + 1 >= sortedNums.Count)
            {
                yield return items;
            }
            else
            {
                int nextNumber = sortedNums[i + 1];

                if (nextNumber - thisNumber > n)
                {
                    yield return items.ToList();
                    items.Clear();
                }
            }
        }
    }
}
like image 45
cordialgerm Avatar answered Sep 23 '22 22:09

cordialgerm