Disclaimer: A very similar question was already asked in a Python context here. This is about C#.
I have an enumeration containing integers such as:
[1, 2, 3, 4, 7, 8, 10, 11, 12, 13, 14]
I'd like to obtain a string putting out the ranges of consecutive integers:
1-4, 7-8, 10-14
I came up with:
public static void Main()
{
System.Diagnostics.Debug.WriteLine(FindConsecutiveNumbers(new int[] { 1,2, 7,8,9, 12, 15, 20,21 }));
}
private static string FindConsecutiveNumbers(IEnumerable<int> numbers)
{
var sb = new StringBuilder();
int? start = null;
int? lastNumber = null;
const string s = ", ";
const string c = "-";
var numbersPlusIntMax = numbers.ToList();
numbersPlusIntMax.Add(int.MaxValue);
foreach (var number in numbersPlusIntMax)
{
var isConsecutive = lastNumber != null && lastNumber + 1 == number;
if (!isConsecutive)
{
if (start != null)
{
if (sb.Length > 0) { sb.Append(s); }
if (start == lastNumber)
{
sb.Append(start); ;
}
else
{
sb.Append(start + c + lastNumber); ;
}
}
start = number;
}
lastNumber = number;
}
return sb.ToString();
}
This algorithm works for ordered input. Is there a built-in/LINQ/shorter C# way of doing this?
int[] numbers = { 1, 2, 3, 4, 7, 8, 10, 11, 12, 13, 14 };
return string.Join(", ",
numbers
.Select((n, i) => new { value = n, group = n - i })
.GroupBy(o => o.group)
.Select(g => g.First().value + "-" + g.Last().value)
);
I suggest decomposition: Let's split initial routine into logics:
private static IEnumerable<(int left, int right)> Consecutive(IEnumerable<int> numbers) {
int start = -1;
int stop = -2;
foreach (var item in numbers) // numbers.OrderBy(x => x) to sort numbers
if (item == stop + 1)
stop = item;
else {
if (stop >= start)
yield return (start, stop);
start = item;
stop = item;
}
if (stop >= start)
yield return (start, stop);
}
and representation
private static string FindConsecutiveNumbers(IEnumerable<int> numbers) =>
string.Join(", ", Consecutive(numbers)
.Select(item => item.left == item.right
? $"{item.left}"
: $"{item.left}-{item.right}"));
Then business as usual:
public static void Main()
{
// 1-2, 7-9, 12, 15, 20-21
System.Diagnostics.Debug.WriteLine(FindConsecutiveNumbers(new int[] {
1, 2,
7, 8, 9,
12,
15,
20, 21 }
));
}
If we could get the numbers in a List instead of an enumeration then we do not need to access all the numbers. The previous answers are have a time complexity of O(n) where n is the size of the array. Whereas if we have a list, this problem could be solved in O(klogn) where k is the number of groups.
Basically you could use a slightly modified binary search to find the end point of a group which could be done in O(logn) And then from end+1, you could do the same for the next group.
Related
Given the pair of 2 strings "2-4,6-8" I want to separate these 2 pairs and find all numbers between those range.
So first pair 2-4 should return me 2, 3, 4
Second pair 6-8 should return 6, 7, 8
I tried below code
var splittedString = ln.Split(",");
var firstPair = splittedString[0];
var secondPair = splittedString[1];
var splittedFirstPair = firstPair.Split("-");
IEnumerable<int> firsPairRange = Enumerable.Range(
Convert.ToInt32(splittedFirstPair[0]),
Convert.ToInt32(splittedFirstPair[1]));
var splittedSecondPair = secondPair.Split("-");
IEnumerable<int> secondPairRange = Enumerable.Range(
Convert.ToInt32(splittedSecondPair[0]),
Convert.ToInt32(splittedSecondPair[1]));
But the variable firsPairRange gives me output 2,3,4,5 and the variable secondPairRange gives me output 6,7,8,9,10,11,12,13
I don't understand why and how to fix it?
Instead of Enumerable.Range(start,end), the second parameter needs to be the count of elements including the last element
Enumerable.Range(start,end-start+1)
string ln = "2-4,6-8";
var splittedString = ln.Split(',').Select(x => x.Split('-').Select(int.Parse).ToArray());
int[] first = splittedString.ElementAt(0);
int[] second = splittedString.ElementAt(1);
var firstPairRange = Enumerable.Range(first[0], first[1] - first[0] + 1);
var secondPairRange = Enumerable.Range(second[0], second[1] - second[0] + 1);
Enumerable.Range has two parameters:
the start
the count(!)
So this gives you already enough information to fix it. Your start is 2 and the count is 4 because of 2-4 but you want 4-2+1=3 as count:
IEnumerable<int> firstPairRange = Enumerable.Empty<int>();
if (splittedFirstPair.Length == 2
&& int.TryParse(splittedFirstPair[0], out int first)
&& int.TryParse(splittedFirstPair[1], out int second)
&& second >= first)
{
int count = second - first + 1;
firstPairRange = Enumerable.Range(first, count);
}
In general case (negative numbers, single numbers) when we allow strings like this
-9,2-4,6-8,-5,7,-3-5,-8--2
we can put it as
private static IEnumerable<IEnumerable<int>> Ranges(string text) {
foreach (var range in text.Split(',')) {
var match = Regex.Match(range,
#"^\s*(?<left>-?[0-9]+)\s*-\s*(?<right>-?[0-9]+)\s*$");
if (match.Success) {
int left = int.Parse(match.Groups["left"].Value);
int right = int.Parse(match.Groups["right"].Value);
yield return Enumerable.Range(left, right - left + 1);
}
else
yield return new[] { int.Parse(range) };
}
}
Demo:
string text = "-9,2-4,6-8,-5,7,-3-5,-8--2";
var result = string.Join(Environment.NewLine, Ranges(text)
.Select(range => string.Join(", ", range)));
Console.Write(result);
Output:
-9
2, 3, 4
6, 7, 8
-5
7
-3, -2, -1, 0, 1, 2, 3, 4, 5
-8, -7, -6, -5, -4, -3, -2
You are providing wrong values to Enumerable.Range() function. Its syntax is Enumerable.Range(int start, int count). In start, you have to give first integer in the sequence and in count, you have to give number of integers to be generated in sequence. If you correct your count, right sequence will be generated. Replace your lines with the following lines:
IEnumerable<int> firsPairRange = Enumerable.Range(Convert.ToInt32(splittedFirstPair[0]), Convert.ToInt32(splittedFirstPair[1]) - Convert.ToInt32(splittedFirstPair[0]) + 1);
IEnumerable<int> secondPairRange = Enumerable.Range(Convert.ToInt32(splittedSecondPair[0]), Convert.ToInt32(splittedSecondPair[1]) - Convert.ToInt32(splittedSecondPair[0]) + 1);
12,13,14,15,16,19,19,19,19
to
12,19,13,19,14,19,15,19,16
Hey all. Can anyone point me to clues/samples on how to distribute the first array of Int32 values, where a bunch of 19 values were appended, to the second where the 19 values are fairly evenly interspersed in the array?
I am not looking for a random shuffling, as in this example #19 could still appear consecutively if there was randomization. I want to make sure that #19 is placed in between the other numbers in a predictable pattern.
The use-case for this is something like teams taking turns presenting a topic: teams 12-16 each present once and then team #19 shows up but should not show their topic four times in a row, they should show their topic in between the other teams.
Later, if twelve values of 7 are added to the array, then they will also have to be evenly distributed into the sequence as well, the array would be 21 elements but the same rule that neither #19 or #7 should have consecutive showings.
I thought there might be something in Math.NET library that would do this, but I did not find anything. Using C# on .NET Framework 4.7.
Thanks.
Details on the following method that evenly (mostly) distributes the duplicates in your list. Duplicates can be anywhere in your list, they will be distributed.
Create a dictionary of all numbers and keep track of the number of times they appear in the list
Use a new list without any duplicates. For Each number that has duplicates, spread it over the size of this new list. Each time the distribution is even.
public static List<int> EvenlyDistribute(List<int> list)
{
List<int> original = list;
Dictionary<int, int> dict = new Dictionary<int, int>();
list.ForEach(x => dict[x] = dict.Keys.Contains(x) ? dict[x] + 1 : 1);
list = list.Where(x => dict[x] == 1).ToList();
foreach (int key in dict.Where(x => x.Value > 1).Select(x => x.Key))
{
int iterations = original.Where(x => x == key).Count();
for (int i = 0; i < iterations; i++)
list.Insert((int)Math.Ceiling((decimal)((list.Count + iterations) / iterations)) * i, key);
}
return list;
}
Usage in main:
List<int> test = new List<int>() {11,11,11,13,14,15,16,17,18,19,19,19,19};
List<int> newList = EvenlyDistribute(test);
Output
19,11,13,19,14,11,19,15,16,19,11,17,18
Here's how to do this.
var existing = new[] { 12, 13, 14, 15, 16 };
var additional = new [] { 19, 19, 19, 19 };
var lookup =
additional
.Select((x, n) => new { x, n })
.ToLookup(xn => xn.n * existing.Length / additional.Length, xn => xn.x);
var inserted =
existing
.SelectMany((x, n) => lookup[n].StartWith(x))
.ToArray();
This gives me results like 12, 19, 13, 19, 14, 19, 15, 19, 16.
The only thing that this won't do is insert a value in the first position, but otherwise it does evenly distribute the values.
In case random distribution is enough the following code is sufficient:
static void MixArray<T>(T[] array)
{
Random random = new Random();
int n = array.Length;
while (n > 1)
{
n--;
int k = random.Next(n + 1);
T value = array[k];
array[k] = array[n];
array[n] = value;
}
}
For instance:
int[] input = new int[]{12,13,14,15,16,19,19,19,19};
MixArray<int>(input);
In case you require precise evenly distribution while retaining the order of the elements, to following code will do the job:
public static T[] EvenlyDistribute<T>(T[] existing, T[] additional)
{
if (additional.Length == 0)
return existing;
if (additional.Length > existing.Length)
{
//switch arrays
T[] temp = additional;
additional = existing;
existing = temp;
}
T[] result = new T[existing.Length + additional.Length];
List<int> distribution = new List<int>(additional.Length);
double ratio = (double)(result.Length-1) / (additional.Length);
double correction = -1;
if (additional.Length == 1)
{
ratio = (double)result.Length / 2;
correction = 0;
}
double sum = 0;
for (int i = 0; i < additional.Length; i++)
{
sum += ratio;
distribution.Add(Math.Max(0, (int)(sum+correction)));
}
int existing_added = 0;
int additional_added = 0;
for (int i = 0; i < result.Length; i++)
{
if (additional_added == additional.Length)
result[i] = existing[existing_added++];
else
if (existing_added == existing.Length)
result[i] = additional[additional_added++];
else
{
if (distribution[additional_added] <= i)
result[i] = additional[additional_added++];
else
result[i] = existing[existing_added++];
}
}
return result;
}
For instance:
int[] existing = new int[] { 12, 13, 14, 15, 16};
int[] additional = new int[] { 101, 102, 103, 104};
int[] result = EvenlyDistribute<int>(existing, additional);
//result = 12, 101, 13, 102, 14, 103, 15, 104, 16
I have an observation list and want to find consecutive alternating up and down values for a given count. For example when observation list is { 1, 3, 4, 2, 7, 5, 6, 8, 1, 2, 4} and given count for check is 4, method should return a list<list>> as {{3,4,2,7}, {2,7,5,6}}.
I already create method as below, but want to do this with linq or more efficent way.Could anybody help?
List<List<decimal>> GetinvalidObservationMatrix(List<decimal> observationList, int checkedCount)
{
List<List<decimal>> invalidObservationMatrix = new List<List<decimal>>();
while (observationList.Count >= checkedCount)
{
List<decimal> currentObservationList = observationList.Take(checkedCount).ToList();
bool isGreater = false;
bool isPreviousGreater = false;
for (int i = 1; i < checkedCount - 1; i++)
{
isPreviousGreater = isGreater;
if (currentObservationList[i] == currentObservationList[i - 1] || currentObservationList[i] == currentObservationList[i + 1])
{
break;
}
if (currentObservationList[i] > currentObservationList[i - 1])
{
isGreater = true;
}
else
{
isGreater = false;
}
if (i != 1 && isGreater == isPreviousGreater)
{
break;
}
if (isGreater)
{
if (currentObservationList[i + 1] >= currentObservationList[i])
{
break;
}
}
else
{
if (currentObservationList[i + 1] <= currentObservationList[i])
{
break;
}
}
if (i == checkedCount - 2)
{
invalidObservationMatrix.Add(currentObservationList);
}
}
observationList = observationList.Skip(1).ToList();
}
return invalidObservationMatrix;
}
Accepting that this does not actually answer the original question, this code will find all the alternating sequences.
void FindAlternativeSequences()
{
var observations = new List<int>() { 1, 3, 4, 2, 7, 5, 6, 8, 1, 2, 4 };
const int threshold = 4;
var consecutivePairs = observations.Skip(1).Zip(observations, (a, b) => Math.Sign(a - b)).Zip(GetAlternator(), (x, y) => x * y);
var runs = FindEqualRuns(consecutivePairs).Select(t => new Tuple<int, int>(t.Item1, t.Item2 + 1)).ToList();
}
public IEnumerable<int> GetAlternator()
{
int value = -1;
int sanity = Int32.MaxValue;
while (--sanity > 0)
{
value *= -1;
yield return value;
}
yield break;
}
public IEnumerable<Tuple<int,int>> FindEqualRuns(IEnumerable<int> enumerable)
{
int previousValue = 0;
int index = 0;
int startIndex = 0;
bool foundAnElement = false;
foreach ( var value in enumerable )
{
if (index == 0) previousValue = value;
foundAnElement = true;
if (!value.Equals(previousValue))
{
// This is a difference, return the previous run
yield return new Tuple<int, int>(startIndex, index - startIndex);
startIndex = index;
previousValue = value;
}
index++;
}
if (foundAnElement)
{
yield return new Tuple<int, int>(startIndex, index - startIndex);
}
yield break;
}
What it does
The code finds all alternating sequences of any length, starting with either an up-step or a down-step. It then populates the runs variable with tuples where Item1 is the index of the first element in the sequence, and Item2 is the length of the alternating sequence.
This runs variable can then be used to ask a wide range of questions about the data, such as deriving the answer to the original question. The runs output by itself does not return the alternating sequences, but identifies where and how long they are.
How it works
The bulk of the of the work is done in one line:
var consecutivePairs = observations.Skip(1).Zip(observations, (a, b) => Math.Sign(a - b)).Zip(GetAlternator(), (x, y) => x * y);
This zips together the observations along with observations-skip-1 to give pairs of consecutive values. These pairs are then subtracted and run through the Math.Sign() function to give +1, -1 or 0 for whether the first is greater, the second is greater, or if they are the same. This output has runs of +1 or -1 for increasing or decreasing sequences. It's also one element shorter than observations.
observations => { 1, 3, 4, 2, 7, 5, 6, 8, 1, 2, 4 }
Output from 1st .Zip() => { 1, 1, -1, 1, -1, 1, 1, -1, 1, 1 }
This result is then zipped and multiplied by an alternating +1, -1, +1, -1, ... sequence. This results in runs of +1 or -1 indicating consecutive alternating up-steps and down-steps.
observations => { 1, 3, 4, 2, 7, 5, 6, 8, 1, 2, 4 }
Output from 1st .Zip() => { 1, 1, -1, 1, -1, 1, 1, -1, 1, 1 }
Output from 2nd .Zip() => { 1, -1, -1, -1, -1, -1, 1, 1, 1, -1 }
The line var runs = FindEqualRuns(consecutivePairs).Select(t => new Tuple<int, int>(t.Item1, t.Item2 + 1)).ToList(); then uses another function to find all the consecutive runs or either +1, -1, or 0 in that set, giving the startIndex and count. Because this gives us runs against the pairs enumeration (which is one element shorter than the original observations), we do need to add 1 to each count. For example, if it finds a run of 3 elements in the pairs enumeration, this represents a run of 4 elements in observations.
The output in runs gives the detail on the start and length of all the alternating sequences in the data.
runs => { {0, 2}, {1, 6}, {6, 4}, {9, 2} }
So there is:
a sequence of length 2, starting at the 0th index => { 1, 3 }
a sequence of length 6, starting at the 1st index => { 3, 4, 2, 7, 5, 6 }
a sequence of length 4, starting at the 6th index => { 6, 8, 1, 2 }
a sequence of length 2, starting at the 9th index => { 2, 4 }
Hope this helps
Hello I want to take 6 items from a list with 5 items at all. And I want to start taking the items at a given position. My result should be saved in another list.
For example:
List_1 = 1, 2, 3, 4, 5
6 items needed
start at position 2 {= 3}
List_result = 3, 4, 5, 1, 2, 3
List_1 = 7, 13, 6, 9, 17
2 items needed
start at position 4 {= 17}
List_result = 17, 7
I already tried to loop through the list with for and foreach, but could not find a real solution. Any help is greatly appreciated!
Something like this will do the trick. I wrote it up quickly, so I'm sure you can make it nicer
private IEnumerable<int> DoSomething(IEnumerable<int> set, int start, int num) {
var taken = 0;
var curSet = set.Skip(start);
while (taken < num) {
foreach(var current in curSet)
{
if (taken == num)
yield break;
yield return current;
taken++;
}
curSet = set;
}
}
Use like this:
DoSomething(new int[] { 1,2,3,4,5}, 2, 6);
Yields:
3,4,5,1,2,3
using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
var data = new List<int>(5){1,2,3,4,5};
var result = new List<int>(5);
for(int i=0;i<5;i++)
{
result.Add(data[(i+2)%data.Count]);
}
for(int i=0;i<result.Count;i++)
{
Console.WriteLine(string.Format("{0}\n",result[i]));
}
}
}
You could use this extension:
public static IEnumerable<T> TakeSpinning<T>(this IEnumerable<T> source, int take, int position = 0)
{
// skip check for invalid input like negative take or position
int skip = position;
int taken = 0;
while (taken < take)
{
foreach (T element in source)
{
if (skip > 0)
{
skip--;
continue;
}
yield return element;
if (++taken == take) break;
}
}
}
Your samples:
var List_1 = new List<int> { 1, 2, 3, 4, 5 };
var List_Result = List_1.TakeSpinning(6, 2).ToList(); // 3,4,5,1,2,3
var List_2 = new List<int> { 7, 13, 6, 9, 17 };
var List_Result2 = List_2.TakeSpinning(2, 4).ToList(); // 17,7
simple,
public static IEnumerable<T> TakeLoop<T>(
this IEnumerable<T> source,
int count,
int start = 0)
{
if (start < 0)
{
throw new ArgumentOutOfRangeException("start");
}
if (count < 0)
{
throw new ArgumentOutOfRangeException("count");
}
using (var m = source.GetEnumerator())
{
for (var i = 0; i < count + start; i++)
{
if (!m.MoveNext())
{
if (i < start)
{
throw new ArgumentOutOfRangeException("start");
}
m.Reset();
m.MoveNext();
}
if (i >= start)
{
yield return m.Current;
}
}
}
}
to be used, like this,
var result1 = (new[] { 1, 2, 3, 4, 5 }).TakeLoop(6, 3);
or this,
var result2 = (new[] { 7, 13, 6, 9, 17 }).TakeLoop(2, 4);
its a simple iteration , you can use a simple loop to do this:
List list;// add values
int itemNeeded; //set item need
int startPostion; //set the start postion
for(int i=0;i<itemNeeded;i++){
add to newList the item at (startPosition++ % length of list)
}
I have a series of number as such: [1 2 4 8 16 32 64 128], if I input a number, i.e. 66, then the output should be 64 and 2. If I input 87, then the output should be 64, 16, 4, 2, 1.
(Basically, it first divide by the largest possible number, find the remainder, then keep dividing by the largest number possible, until the remainder is 0. Or another way maybe just to subtract the largest possible number and keep subtracting like that until it reaches 0.)
I'm thinking of a recursive function, but not really sure. Any help?
Thanks.
class Program
{
[Flags]
enum Bits
{
_1 = 1,
_2 = 2,
_4 = 4,
_8 = 8,
_16 = 16,
_32 = 32,
_64 = 64,
_128 = 128
}
static void Main(string[] args)
{
var b = (Bits)87;
Console.WriteLine(b);
Console.ReadKey();
}
}
Here's the iterative version
public static IEnumerable<int> FindIndex(int input)
{
var power = 0;
while (input > 0)
{
var digit = input % 2;
if (digit == 1)
{
yield return (int)Math.Pow(2, power);
}
input /= 2;
power++;
}
}
and here's the recursive version
public static void FindIndexRec(int input, int power, ICollection<int> numbers)
{
if (input == 0)
{
return;
}
var digit = input % 2;
if (digit == 1)
{
numbers.Add((int)Math.Pow(2, power));
}
FindIndexRec(input / 2, ++power, numbers);
}
and you can call it like
var numbers = new List<int>();
FindIndexRec(input, 0, numbers);
You could use bit masks. In fact, scratch that - you should use bit masks! That is almost certainly how the error code was created, and it's how it should be picked apart. Anything else is highly likely to confuse your audience of other programmers.
I make no claims to represent all programmers, nor do I claim to be any good, but I am a programmer, and all the other answers confused me. It's obviously a bitwise "problem", so why obfuscate?
There's no need to even store the result anywhere, since it's as quick to recompute it every time, like so:
for(int i=0;i<8;++i) {
if((error&(1<<i))!=0 {
// 1<<i is in the resulting list.
}
}
List<int> additives = new List<int>()
List<int> sourceNumbers = new List<int> { 1, 2, 4, 8, 16, 32, 64, 128 };
int sourceNumber = 87;
foreach(var number in sourceNumbers.Reverse())
{
if(sourceNumber % number > 0)
{
additives.Add(number);
sourceNumber -= number;
}
else
{
additives.Add(number);
break;
}
}
Here's a pretty naive iterated solution in pseudocode. Try to take it somewhere more interesting from here. You can do it recursively, you can convert the integer to binary representation and iterate on that string, id imagine theres a clever way to do it with LINQ very concisely, etc.
v = inputNumber
while(v > 0):
temp = greatest power of 2 less than v
print temp
v -= temp
PS - this does not explictly store the series in question - it presumes powers of 2.
string calculate(int input)
{
string output = string.Empty;
int[] series = new int[] { 1, 2, 4, 8, 16, 32, 64, 128 };
foreach (int i in series.Reverse<int>())
{
if (input >= i)
{
output += i.ToString() + " ";
input -= i;
}
}
return output;
}
This one will work with input numbers greater than 256:
int[] calculate(int input)
{
List<int> retVal = new List<int>();
string output = string.Empty;
int[] series = new int[] { 1, 2, 4, 8, 16, 32, 64, 128 };
foreach (int i in series.Reverse<int>())
{
while (input >= i)
{
retVal.Add(i);
input -= i;
}
}
return retVal.ToArray();
}
Ex:
var result = calculate(284);
// result = 128, 128, 16, 8, 4
IEnumerable<int> GetFlags(int input,IEnumerable<int> series)
{
foreach (int value in series)
if ((input & value)==value)
yield return value;
}
Or LINQ solution to the problem.
IEnumerable<int> GetFlags(int input, IEnumerable<int> series)
{
return series.Where(x => (x & input) == x);
}
Programming aside, you can also look at it mathematically. It's basically 2 to the power of (integer value of) log(2, input)
Putting this in a recursive function would be easy ofcourse, it would even look simple and smooth, but I doubt it being less computationally complex and it sure doesn't help with your homework, just thought I would throw it here.