Home > OS >  How to group multiple sets of values according to variable required amounts (C#)
How to group multiple sets of values according to variable required amounts (C#)

Time:10-31

Faced with a bit of a problem here that I think I may have stared at for too long, and I'm hoping somebody can point me in the right direction. I've been attempting some array manipulation that I feel like I've come close with a few approaches, but haven't gotten there yet.

What I'm after is this: Given an array of values (for this example I'll use simple values 'A', 'B', 'C', 'D'), and a stated number of minimum required occurrences of each value, the ability to group the collections of these values of the required size. For example:

// when given this list of required amounts
var config = new[] {
    { Value: 'A', AmountRequired: 1 },
    { Value: 'B', AmountRequired: 2 },
    { Value: 'C', AmountRequired: 3 },
    { Value: 'D', AmountRequired: 4 }
};

// and this array of values (matches requirements exactly)
var values = new[] { 'A', 'B', 'B', 'C', 'C', 'C', 'D', 'D', 'D', 'D' };

// the logic would return
var results = [
    ['A'],
    ['B', 'B'],
    ['C', 'C', 'C'],
    ['D', 'D', 'D', 'D'],
];


// this array of values (with one extra of each value) would also return the same result
// because there aren't enough added values for a second combined group
var values = new[] { 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C', 'D', 'D', 'D', 'D', 'D' };

The use-case that's easiest to describe the behaviour is probably like a videogame crafting menu where you would see something like "You need 1 A, 2 B's, 3 C's and 4 D's to be able to craft this item", and so all you're interested in is how many complete sets of those values you have, any spares can be ignored.

I've attempted this using Linq, and I had it working when the required amount was the same for all fields, which obviously isn't going to meet all use cases. I wasn't planning on posting my code attempt here as I was hoping to get a fresh perspective on the problem, but I can make it available if it will help.

Thanks in advance,

Mark

CodePudding user response:

This answer is based on the assumption that the result can simply be a count of complete sets.


I would suggest starting by creating an overview of the contents of your source array: How many times is each character present? A good tool for such an overview would be a dictionary.

First, the values in the source array need to be grouped. Then, a dictionary can be created based on these groups. The key for each KeyValuePair would the the char, and the value would be the occurrence count for that char:

var countPerChar = values
    .GroupBy(_ => _)
    .ToDictionary(
        charGroup => charGroup.Key,
        charGroup => charGroup.Count());

If your source array is this:

var values = new[] { 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C', 'C', 'D', 'D', 'D', 'D', 'D' };

, then countPerChar will look something like this:

Then, I would create a dictionary to store the count of complete sets per char. This dictionary should contain as many entries as the config (and all keys in config should be present).

For each entry in config, I would try to get the value of the target char from countPerChar.

If the target char exists in countPerChar, the associated char count can be divided by the required amount to obtain the complete set of the target char.

If the target char does not exist in countPerChar, then the complete set of the target char is 0.

var completeSetsPerChar = new Dictionary<char, int>();

foreach (var entry in config.Where(c => c.AmountRequired > 0))
{
    countPerChar.TryGetValue(entry.Value, out int charCount);
    
    completeSetsPerChar[entry.Value] = charCount / entry.AmountRequired;
}

Now, to get the count of complete sets, you would simply take the minimum of all the complete sets per char:

var completeSets = completeSetsPerChar.Values.Min();

Example fiddle here.

CodePudding user response:

I have also a solution that will tell u if a set is complete or not:

            // when given this list of required amounts
            var config = new[] {
                 new { Value= 'A', AmountRequired= 1 },
                new { Value = 'B', AmountRequired= 2 },
                new { Value = 'C', AmountRequired = 3 },
                new { Value= 'D', AmountRequired= 4 }
            };

            // and this array of values (matches requirements exactly)
            var values = new[] { 'A', 'B', 'B', 'C', 'C', 'C', 'D', 'D', 'D', 'D' };

            var res =
                       from cfg in config
                       select (new { cfg.Value, setComplete = (values.Count(x => x == cfg.Value) >= cfg.AmountRequired) });

            res.ToList().ForEach(x => Console.WriteLine(x));

CodePudding user response:

I would personally use a Dictionary to store the config. Also i think Linq might be a good way to shorten code, but when i am working with arrays and not lists i like to use regular foreach loops and new fields while using linq for things like .Distinct() and .Count().

Ill start with copying what you have, but making a dict.

Dictionary<char, int> config = new() {
           {'A', 1},
           {'B', 2},
           {'C', 3},
           {'D', 4},
        };

        var values = new[] { 'A', 'B', 'B', 'C', 'C', 'C', 'D', 'D', 'D', 'D' };

then ill determine the max amount of times the values array meets the config criterea

int Maximum = int.MaxValue;
int NewMax;
foreach (char value in values.Distinct())
{
    if (config.ContainsKey(value))
    { 
        if (Maximum > (NewMax = values.Count(x => x == value) / config[value]))
            Maximum = NewMax;
    }
    else
    {
        //Inset which warning you would like to display
        //if a value in values is not in the config dictionary
    }
}

Now that we know how many times we can meet the requirements ill make an array of char arrays and fill it.

char[][] results = Array.Empty<char[]>();

foreach (char value in values.Distinct())
{
    char[] NewArr = new string(value, config[value] * Maximum).ToCharArray();
    _ = results.Append(NewArr);

    //print
    foreach (char i in new string(value, config[value] * Maximum).ToCharArray())
        Console.Write(  $"[{i}],");
    Console.WriteLine();
}
  • Related