Find subsets of a string but just which is neighbours - c#

I am trying to extract subsets but not all of them, just which is a neighbour. The simple example below;
Input : "123456789"
Result :
3.Level Subset: out3 : [123,234,345,456,567,678,789]
..
5.Level Subset : out5 :[12345,23456,34567,45678,56789]
..
8.Level Subset: out8 :[12345678,23456789]
result = [out1,..,out5,..out8]
If there is a cool solution for this and if it can be string operation it will be good.
Many thanks

You'll need to do additional error checks to see if level is longer than the length of the string etc.
public static void Main(string[] args)
{
var results = FindSubsets("123456789", 3);
Console.Read();
}
public static List<string> FindSubsets(string data, int level)
{
if (level > data.Length || level < 1)
return null;
var results = new List<string>();
for (int i = 0; i < data.Length - level + 1; i++)
{
results.Add(data.Substring(i, level));
}
return results;
}
Edit:
Added string length check against level.
Edit2:
If you want to find subsets of certain length, you can do something like the following. Create a List<int> with all the levels you want to find subsets of, then repeatedly call the function. For example, let's say you want to find subsets for levels 3, 5, and 8. Then:
var data = "123456789";
var levels = new List<int>() { 3, 5, 8 };
var results = new List<List<string>>();
foreach(var level in levels)
{
results.Add(FindSubsets(data, level));
}

public IEnumerable<string> GetSubsets(string input, int length)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentException("Invalid string");
if (length <= 0)
throw new ArgumentException("Length must be greater than 0.");
if (length > input.Length)
throw new ArgumentException("The desired set length is longer than the string");
for(int i = 0; i<=input.Length - length;i++)
{
yield return input.Substring(i, length);
}
}

Yes this can be done using Regex:
string input = "123456789";
int size = 3; // set to whatever
MatchCollection split = Regex.Matches(input, #$"\d{{size}}");
string delimited = string.Join(",", split);
Or as an extension method:
public static string Delimit(this string s, int size)
{
MatchCollection split = Regex.Matches(input, #$"\d{{size}}");
return delimited = string.Join(",", split);
}

I basically just brute forced the answer:
import java.util.ArrayList;
public class MyClass {
public static ArrayList<ArrayList<String>> getAllSubets(String input){
ArrayList<ArrayList<String>> list = new ArrayList<ArrayList<String>>();
ArrayList<String> out = new ArrayList<String>();
for(int i= input.length(); i>=0; i--){
int z = i;
for(int j = 0; j+i<input.length() ;j++){
z++;
out.add(input.substring(j, z));
}
}
list.add(out);
return list;
}
public static void main(String args[]) {
System.out.println(MyClass.getAllSubets("123456789"));
}
}
Output:
[[123456789, 12345678, 23456789, 1234567, 2345678, 3456789, 123456, 234567, 345678, 456789, 12345, 23456, 34567, 45678, 56789, 1234, 2345, 3456, 4567, 5678, 6789, 123, 234, 345, 456, 567, 678, 789, 12, 23, 34, 45, 56, 67, 78, 89, 1, 2, 3, 4, 5, 6, 7, 8, 9]]
This was a fun question to do its easy to get tripped up with the indexes.

Finding all Subsets:
var Input = "123456789";
var Result = Enumerable
.Range(1, Input.Length)
.Select(i => Enumerable
.Range(0, Input.Length - i + 1)
.Select(j => Input.Substring(j, i)))
.ToList();
To restrain to only some levels, like 3, 5, or 8, simply filter before performing the work, as is done below:
var Levels = new List<int>{ 3, 5, 8 };
var Input = "123456789";
var Result = Enumerable
.Range(1, Input.Length)
.Where(i => Levels.Contains(i)) // Filter the levels you need before constructing the subsets.
.Select(i => Enumerable
.Range(0, Input.Length - i + 1)
.Select(j => Input.Substring(j, i)))
.ToList();
Results:
123, 234, 345, 456, 567, 678, 789
12345,
23456,
34567,
45678,
56789
12345678,
23456789

Related

C# Find all values in an array closest to a given number

given:
int[] myArray = new int[]{-8, -17, 12, 8, 16, -3, 7, 3};
Find all values in array closest to 0.
Currently using another code found on the site but it only tells me 1 value.
int nearest = myArray.Select(p => new {Value = p, Difference = Math.Abs(p - searchValue)}).OrderBy(p => p.Difference).First().Value;
In the current case both -3 and 3 are closest to 0 but since -3 comes first it only outputs -3.
Is there another way to do this where it finds all values instead of just First()?
Just to add to this. If you want to do this using O(n) complexity without sorting then you can do it this way :
public List<int> GetClosestNumbers(int searchVal)
{
int[] myArray = new int[] { -8, -17, 12, 8, 16, -3, 7, 3 };
int minimumDist = int.MaxValue;
List<int> output = new List<int>();
for (int i = 0; i < myArray.Length; i++)
{
var currentDistance = Math.Abs(myArray[i] - searchVal);
if (currentDistance < minimumDist)
{
minimumDist = currentDistance;
output.Clear();
output.Add(myArray[i]);
}
else if (minimumDist == currentDistance)
{
output.Add(myArray[i]);
}
}
return output;
}
Use a grouping on the lowest absolute value
int[] result = myArray
.OrderBy(i => Math.Abs(i - searchValue))
.GroupBy(i => Math.Abs(i - searchValue))
.First()
.ToArray();

Is there an efficient way to find all ordered arrangements of elements in the set S that add up to N?

Here's what I mean. Suppose S = {1, 4} and N = 5. The ordered arrangements of elements in the set S would be like
{1}, {4}, {1,1}, {1,4}, {4,1}, {4,4}, {1,1,1}, ....
and the ones that sum up to N are
{1,4}, {4, 1}, {1,1,1,1,1}
I want an algorithm that finds those efficiently.
My "brute force" way would be like
static IEnumerable<IEnumerable<int>> OrderedArrangements(IEnumerable<int> nums, int k)
{
var singles = nums.Select(i => new int[] {i} );
var cumulative = singles;
for(int j = 2; j <= k; ++j)
{
var last = cumulative.Where(list => list.Count() == (j - 1));
var next = from x in singles
from y in last
select x.Concat(y);
cumulative = cumulative.Concat(next);
}
return cumulative;
}
and then
int sumToN = OrderedArrangements(new int[] {1, 4}, N)
.Where(x => x.Sum() == N);
but I'm wondering if there's an obvious and more efficient way to do this.
Just in case the above answer isn't clear enough, you could try straight forward recursion e.g.
...
/ \
(1) (4)
/ \ / \
(1)(4) (1)(4)
static void f(int sum, int n, String str, int[] arr){
if (n == sum){
Console.WriteLine(str);
return;
}
if (n > sum) return;
for (int i = 0; i < arr.Length; i++){
f(sum, n + arr[i], str + arr[i].ToString(), arr);
}
}
static void Main(string[] args){
int[] arr = { 1, 4 };
f(5, 0, "", arr);
}
Where sum is N in your question, n is initialized to 0, str is initialized to "" and arr is S in your question.
output:
11111
14
41
This works for me:
static IEnumerable<IEnumerable<int>> OrderedArrangements(IEnumerable<int> nums, int k)
{
return
k <= 0
? new [] { Enumerable.Empty<int>() }
: nums
.SelectMany(
n => OrderedArrangements(nums, k - n),
(n, ns) => new [] { n }.Concat(ns))
.Where(ns => ns.Sum() == k);
}
The result of OrderedArrangements(new [] { 1, 4 }, 5) is:
I ran this performance testing code:
Func<Func<IEnumerable<IEnumerable<int>>>, double> measure = f =>
{
var sw = Stopwatch.StartNew();
var result = f();
sw.Stop();
return sw.Elapsed.TotalMilliseconds;
};
var n = 200;
var a = 0.0;
var b = 0.0;
for (var i = 0; i < n; i++)
{
a += measure(() => OrderedArrangements_A(new [] { 1, 4, 9, 13 }, 50000));
b += measure(() => OrderedArrangements_B(new [] { 1, 4, 9, 13 }, 50000));
}
OrderedArrangements_A is the OP's code and OrderedArrangements_B is mine.
I got an average of 15.6ms for "A" and 0.004ms for "B". My code run about 3,895 times faster for this test.

Generate random number from two List<>

i want to generate random number from two list. i want to create a function where i pass how much random number from two list.
List<int> integers = new List<int>() { 54, 23, 76, 123, 93, 7, 3489 };
List<int> value2 = new List<int>() { 1, 3, 4, 6, 8, 17, 40 };
i want my result = List<int> result = {54,40,123,17,3,1,3489,76...etc}
When i run again the set of result will be change.
Presently i am using this function that return List
public static List<int> GenerateRandom(int count)
{
// generate count random values.
HashSet<int> candidates = new HashSet<int>();
while (candidates.Count < count)
{
// May strike a duplicate.
candidates.Add(random.Next(1,30));
}
// load them in to a list.
List<int> result = new List<int>();
result.AddRange(candidates);
// shuffle the results:
int i = result.Count;
while (i > 1)
{
i--;
int k = random.Next(i + 1);
int value = result[k];
result[k] = result[i];
result[i] = value;
}
return result;
}
i am calling the function
List<int> vals = GenerateRandom(20);
But i want that the random number from two List<> List<int> integers and List<int> value2 . so how can i do this .
You can do something like this:
var result =
integers.Concat(value2)
.OrderBy(x => random.Next())
.Take(count)
.ToList();
You could write a general-purpose function to give you a random ordering of any number of sequences, like so:
public static IReadOnlyCollection<T> InRandomOrder<T>(Random rng, params IEnumerable<T>[] lists)
{
return lists
.SelectMany(x => x)
.OrderBy(y => rng.Next())
.ToList();
}
You can then pass as many lists as you like and get the contents back in a fully randomised order:
var list1 = new[] {1, 2, 3, 4, 5};
var list2 = new[] {6, 7, 8};
var list3 = new[] {9, 0};
Random rng = new Random();
for (int i = 0; i < 10; ++i)
{
var randomisedFirst5 = InRandomOrder(rng, list1, list2, list3).Take(5);
Console.WriteLine(string.Join(", ", randomisedFirst5));
}
There's a less efficient approach you can use that avoids the need for an instance of Random, but you should only use this for short lists or where you really don't care about performance. It uses Guid.NewGuid() to generate random numbers:
public static IReadOnlyCollection<T> InRandomOrder<T>(params IEnumerable<T>[] lists)
{
return lists
.SelectMany(x => x)
.OrderBy(y => Guid.NewGuid())
.ToList();
}
Even the more efficient approach isn't the fastest. A faster way would be to use reservoir sampling to take the first N items that you want, and put them into an array which you shuffle using Knuth. That would make it a lot faster, at the expense of more complicated code - meaning you should only do it the fast way if it's really needed.
If what you want is to select a number that exists either in list A or B, randomly, you can do:
List<int> integers = new List<int>() { 54, 23, 76, 123, 93, 7, 3489 };
List<int> value2 = new List<int>() { 1, 3, 4, 6, 8, 17, 40 };
List<int> allInOne = new List<int>(integers.Concat(value2));
Random r = new Random(DateTime.Now.Millisecond);
/********************************
For demonstration purposes
********************************/
for(int i = 0; i < 5; i++)
{
var randomListIndex = r.Next(0, allInOne.Count - 1);
Console.WriteLine(allInOne[randomListIndex]);
}
Use KeyValuePair
static void Main(string[] args)
{
List<KeyValuePair<int, int>> results = GenerateRandom(100);
}
static List<int> integers = new List<int>() { 54, 23, 76, 123, 93, 7, 3489 };
static List<int> value2 = new List<int>() { 1, 3, 4, 6, 8, 17, 40 };
static Random random = new Random();
public static List<KeyValuePair<int,int>> GenerateRandom(int count)
{
List<KeyValuePair<int,int>> result = new List<KeyValuePair<int,int>>();
for(int i = 0; i < count; i++)
{
int firstValue = integers[random.Next(0, integers.Count - 1)];
int seconValue = value2[random.Next(0, value2.Count - 1)];
result.Add(new KeyValuePair<int,int>(firstValue,seconValue));
}
return result;
}​
I actually made a library a while back that handles some of this stuff : Underscore.cs
It's a nuget package so easy to install, the code to shuffle or take a sample randomly of two lists is :
var ls1 = GenerateRandom(10);
var ls2 = GenerateRandom(20);
var mixer = ls1.Concat(ls2).ToList();
//if you want all of the items shuffled use shuffle
var result = _.List.Shuffle(mixer);
//or if you want a subset randomly sorted use sample
result = _.List.Sample(mixer);

Find a series of the same number in a List

I have a List of items containing either 1 or 0, I'm looking to output the items only where there are six 1's back to back in the list. So only write to the console if the item in this list is part of a group of six.
1
1
1
1
1
1
0
1
1
1
0
In the above list, the first six items would be output but the bottom set of three 1s would not as they are not part of a group of six.
Is this a job for LINQ or RegEx?
You can concatenate all values into string, then split it by zeros. From substrings select those which have at least 6 characters:
List<int> values = new List<int> { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
var series = String.Concat(values)
.Split(new[] { '0' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.Length >= 6);
For given input data series will contain single item "111111" which you can output to console.
Classic run length encoding, O(n), lazy evaluated, stack agnostic, generic for any equatable type.
public void TestRunLength()
{
var runs = new List<int>{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 0, 4};
var finalGroup = RunLength(runs).FirstOrDefault(i => i.Count == 6 && i.First() == 1);
}
private IEnumerable<List<T>> RunLength<T>(IEnumerable<T> source) where T : IEquatable<T>
{
T current = default(T);
var requiresInit = true;
var list = new List<T>();
foreach (var i in source)
{
if (requiresInit)
{
current = i;
requiresInit = false;
}
if (i.Equals(current))
{
list.Add(i);
}
else
{
yield return list;
list = new List<T>{ i };
current = i;
}
}
if (list.Any())
{
yield return list;
}
}
And because it's lazy it works on infinite sequences (yes I know its not infinite, but it is large)!
public void TestRunLength()
{
var random = new Random();
var runs = Enumerable.Range(int.MinValue, int.MaxValue)
.Select(i => random.Next(0, 10));
var finalGroup = RunLength(runs)
.FirstOrDefault(i => i.Count == 6);
}
Probably it can be done with Regex too if you concatenate your numbers into a string. But I would prefer linq:
var bits = new List<int> {1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0};
int bitCountPerGroup = 6;
var result = bits // (1) (2)
.Select((x,idx) => bits.Skip(idx).TakeWhile(y => y == x))
.Where(g => g.Count() == bitCountPerGroup); // (3)
foreach (var set in result)
Console.WriteLine(string.Join(" ", set));
This code gets a number-set for each number by starting from the number (1) and taking the next numbers as long as they are equal (2). Then filter the groups and gets only those groups which have 6 numbers (3).
If for example your list is of an unknown size,or better,you do not know the items in it you could do this recursive example(note that i placed more zeros so it would fetch 2 sets of data,it works with yours also),and pass to the method the amout to group by:
//this is the datastructure to hold the results
static List<KeyValuePair<string, List<int>>> Set = new List<KeyValuePair<string, List<int>>>();
private static void GetData(List<int> lst, int group)
{
int count = 1;
int pivot = lst.First();
if (lst.Count < group)
{
return;
}
else
{
foreach (int i in lst.Skip(1))
{
if (i == pivot)
{
count++;
}
else if (count == group)
{
Set.Add(new KeyValuePair<string, List<int>>("Set of items " + pivot, lst.Take(count).ToList()));
GetData(lst.Skip(count).ToList(), group);
break;
}
else
{
GetData(lst.Skip(count).ToList(), group);
break;
}
}
}
}
Then in Main():
static void Main(string[] args)
{
List<int> test = new List<int> { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
GetData(test, 6);
foreach (var item in Set)
{
Console.WriteLine("\t" + item.Key);
foreach (var subitem in item.Value)
{
Console.WriteLine(subitem);
}
}
}

Splitting a list of items evenly between x number of smaller lists

I'm asking this question again, since the last time I asked it, it was falsely marked as duplicated. I am going to include some more information this time, that might make it easier to understand my need (it may very well have been my own fault for not defining the question properly).
I'm trying to split a list of a generic type into 4 lists. For simplicity and understanding, I will use a list of integers in this example, but that shouldn't make a difference.
I have done a lot of searching, found multiple answers like "Split List into Sublists with LINQ", using batch methods to split, I have tried MoreLinq's Batch methods and so on. Those suggestions work fine for what they should, but they do not work the way I need them to.
If I have a list containing the following elements (integers ranging 1-25):
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
Then what I need to do is make 4 lists with a variable number of elements in them, where the elements increment in the same list, instead of jumping to the next list with the next element.
[ 1, 2, 3, 4, 5, 6, 7]
[ 8, 9, 10, 11, 12, 13, 14]
[15, 16, 17, 18, 19, 20, 21]
[20, 21, 22, 23, 24, 25]
When using the solutions in either of the questions linked, with 4 "parts" as the parameter, I get lists like this (this is the example where the element jumps to the next list instead of just the next element of the list):
[1, 5, 9, 13, 17, 21, 25],
[2, 6, 10, 14, 18, 22, 26],
[3, 7, 11, 15, 19, 23, 27],
[4, 8, 12, 16, 20, 24]
or this (does the same as MoreLinq's Batch method)
[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12],
[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24],
[25, 26, 27],
So the first solution splits the list into 4 lists, but puts the elements in the wrong order. The second solution splits the lists in the right order, but not in the right length. In the last solution, he gets X amount of lists with 4 elements in each, where I need to have 4 lists with X elements in each.
You can use following extension method to split list on required number of sub-lists, and include additional items in first sub-list:
public static IEnumerable<List<T>> Split<T>(this List<T> source, int count)
{
int rangeSize = source.Count / count;
int firstRangeSize = rangeSize + source.Count % count;
int index = 0;
yield return source.GetRange(index, firstRangeSize);
index += firstRangeSize;
while (index < source.Count)
{
yield return source.GetRange(index, rangeSize);
index += rangeSize;
}
}
With given input
var list = Enumerable.Range(1, 25).ToList();
var result = list.Split(4);
Result is
[
[ 1, 2, 3, 4, 5, 6, 7 ],
[ 8, 9, 10, 11, 12, 13 ],
[ 14, 15, 16, 17, 18, 19 ],
[ 20, 21, 22, 23, 24, 25 ]
]
UPDATE: This solution adds additional item to each range
public static IEnumerable<List<T>> Split<T>(this List<T> source, int count)
{
int rangeSize = source.Count / count;
int additionalItems = source.Count % count;
int index = 0;
while (index < source.Count)
{
int currentRangeSize = rangeSize + ((additionalItems > 0) ? 1 : 0);
yield return source.GetRange(index, currentRangeSize);
index += currentRangeSize;
additionalItems--;
}
}
Here is another solution based on IEnumerable<T>. It has the following characteristics:
Always yields batchCount items, if the source enumerable is smaller than the batch size, it will yield empty lists.
Favors larger lists at the front (e.g. when the batchCount is 2 and the size is 3, the length of the results will be [2,1].
Iterates multiple times of the IEnumerable. This means AsEnumerable should be called somewhere if something like an Entity Framework query is executed here.
The first example is optimized for List<T>
public static class BatchOperations
{
public static IEnumerable<List<T>> Batch<T>(this List<T> items, int batchCount)
{
int totalSize = items.Count;
int remain = totalSize % batchCount;
int skip = 0;
for (int i = 0; i < batchCount; i++)
{
int size = totalSize / batchCount + (i <= remain ? 1 : 0);
if (skip + size > items.Count) yield return new List<T>(0);
else yield return items.GetRange(skip, size);
skip += size;
}
}
public static IEnumerable<IEnumerable<T>> Batch<T>(this IEnumerable<T> items, int batchCount)
{
int totalSize = items.Count();
int remain = totalSize%batchCount;
int skip = 0;
for (int i = 0; i < batchCount; i++)
{
int size = totalSize/batchCount + (i <= remain ? 1 : 0);
yield return items.Skip(skip).Take(size);
skip += size;
}
}
}
Sergey's answer is clearly the best for this, but for completeness here's a solution you could use if you did not want to make copies of the sublists for some reason (perhaps because you just had IEnumerable<T> as input):
using System;
using System.Collections.Generic;
using System.Linq;
namespace ConsoleApp1
{
public static class EnumerableExt
{
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> input, int blockCount, int count)
{
int blockSize = count/blockCount;
int currentBlockSize = blockSize + count%blockSize;
var enumerator = input.GetEnumerator();
while (enumerator.MoveNext())
{
yield return nextPartition(enumerator, currentBlockSize);
currentBlockSize = blockSize;
}
}
private static IEnumerable<T> nextPartition<T>(IEnumerator<T> enumerator, int blockSize)
{
do
{
yield return enumerator.Current;
}
while (--blockSize > 0 && enumerator.MoveNext());
}
}
class Program
{
private void run()
{
var list = Enumerable.Range(1, 25).ToList();
var sublists = list.Partition(4, list.Count);
foreach (var sublist in sublists)
Console.WriteLine(string.Join(" ", sublist.Select(element => element.ToString())));
}
static void Main()
{
new Program().run();
}
}
}
I guess this would run much more slowly than using Lists, but it would use much less memory.
const int groupSize = 4;
var items = new []{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25};
var currentGroupIndex=-1;
var step1 = items.Select(a =>{
if (++currentGroupIndex >= groupSize)
currentGroupIndex = 0;
return new {Group = currentGroupIndex, Value = a};
}).ToArray();
var step2 = step1.GroupBy(a => a.Group).Select(a => a.ToArray()).ToArray();
var group1 = step2[0].Select(a => a.Value).ToArray();
var group2 = step2[1].Select(a => a.Value).ToArray();
var group3 = step2[2].Select(a => a.Value).ToArray();
var group4 = step2[3].Select(a => a.Value).ToArray();
What this does is it first introduces an counter (currentGroupIndex) which starts at zero and will be incremented for each element in the list. The index gets reset to zero when the group size has reached.
the variable step1 now contains items continaing a Group and a Value property.
The Group value is then used in the GroupBy statement.
Take & Skip could be very helpful here i think but personally i like using Func to make these choices, makes the method more flexible.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Splitter
{
class Program
{
static void Main(string[] args)
{
List<int> numbers = Enumerable.Range(1, 25).ToList();
int groupCount = 4;
var lists = numbers.Groupem(groupCount, (e, i) =>
{
// In what group do i wanna have this element.
int divider = numbers.Count / groupCount;
int overflow = numbers.Count % divider;
int index = (i - overflow) / divider;
return index < 0 ? 0 : index;
});
Console.WriteLine("numbers: {0}", numbers.ShowContent());
Console.WriteLine("Parts");
foreach (IEnumerable<int> list in lists)
{
Console.WriteLine("{0}", list.ShowContent());
}
}
}
public static class EnumerableExtensions
{
private static List<T>[] CreateGroups<T>(int size)
{
List<T>[] groups = new List<T>[size];
for (int i = 0; i < groups.Length; i++)
{
groups[i] = new List<T>();
}
return groups;
}
public static void Each<T>(this IEnumerable<T> source, Action<T, int> action)
{
var i = 0;
foreach (var e in source) action(e, i++);
}
public static IEnumerable<IEnumerable<T>> Groupem<T>(this IEnumerable<T> source, int groupCount, Func<T, int, int> groupPicker, bool failOnOutOfBounds = true)
{
if (groupCount <= 0) throw new ArgumentOutOfRangeException("groupCount", "groupCount must be a integer greater than zero.");
List<T>[] groups = CreateGroups<T>(groupCount);
source.Each((element, index) =>
{
int groupIndex = groupPicker(element, index);
if (groupIndex < 0 || groupIndex >= groups.Length)
{
// When allowing some elements to fall out, set failOnOutOfBounds to false
if (failOnOutOfBounds)
{
throw new Exception("Some better exception than this");
}
}
else
{
groups[groupIndex].Add(element);
}
});
return groups;
}
public static string ShowContent<T>(this IEnumerable<T> list)
{
return "[" + string.Join(", ", list) + "]";
}
}
}
How about this, includes parameter checking, works with an emtpy set.
Completes in two passes, should be fast, I haven't tested.
public static IList<Ilist<T>> Segment<T>(
this IEnumerable<T> source,
int segments)
{
if (segments < 1)
{
throw new ArgumentOutOfRangeException("segments");
}
var list = source.ToList();
var result = new IList<T>[segments];
// In case the source is empty.
if (!list.Any())
{
for (var i = 0; i < segments; i++)
{
result[i] = new T[0];
}
return result;
}
int remainder;
var segmentSize = Math.DivRem(list.Count, segments, out remainder);
var postion = 0;
var segment = 0;
while (segment < segments)
{
var count = segmentSize;
if (remainder > 0)
{
remainder--;
count++;
}
result[segment] = list.GetRange(position, count);
segment++;
position += count;
}
return result;
}
Here is an optimized and lightweight O(N) extension method solution
public static void Bifurcate<T>(this IEnumerable<T> _list, int amountOfListsOutputted, IList<IList<T>> outLists)
{
var list = _list;
var index = 0;
outLists = new List<IList<T>>(amountOfListsOutputted);
for (int i = 0; i < amountOfListsOutputted; i++)
{
outLists.Add(new List<T>());
}
foreach (var item in list)
{
outLists[index % amountOfListsOutputted].Add(item);
++index;
}
}
Simply use it like this:
public static void Main()
{
var list = new List<int>(1000);
//Split into 2
list.Bifurcate(2, out var twoLists);
var splitOne = twoLists[0];
var splitTwo = twoLists[1];
// splitOne.Count == 500
// splitTwo.Count == 500
//Split into 3
list.Bifurcate(3, out var threeLists);
var _splitOne = twoLists[0];
var _splitTwo = twoLists[1];
var _splitThree = twoLists[2];
// _splitOne.Count == 334
// _splitTwo.Count = 333
// _splitThree.Count == 333
}

Categories

Resources