Print all subsets that sum up to specific value - c#
I've being trying for some time now to find all the elements (including non-consecutive) of an array that sum up to specific value:
using System;
namespace ProgrammingBasics
{
class Exercise
{
static void Main()
{
PrintArray(arr);
SubarrayWithSum();
}
//--------------------------------------------------------------------
/*
Data members.
*/
// targer array
static int[] arr = { 2, 1, 2, 4, 3, 5, 2, 6 };
// target sum
static int sum = 14;
//--------------------------------------------------------------------
/*
Method: IsSubarrayWithSum(arr, sum);
It returns a bool value that indicates
whether there is a subarray within arr
with elements that sum up to specific value.
*/
static void SubarrayWithSum()
{
int depth = 0;
int startIndex = 0;
int endIndex = 1;
CheckAllCombinations(new int[arr.Length], startIndex, endIndex, depth);
}
//--------------------------------------------------------------------
/*
Method: CheckAllCombinations(subarray, sum);
*/
static void CheckAllCombinations(int[] subarray, int startIndex, int endIndex, int depth)
{
if (depth >= arr.Length)
{
return;
}
//Console.ReadKey();
for (int i = startIndex; i < endIndex; i++)
{
subarray[i] = arr[i];
//Console.WriteLine("startIndex = {0}, depth = {1}, i = {2}", startIndex, depth, i);
if (IsWantedSum(subarray))
{
Console.Write("S = {0} -> yes", sum);
PrintSubArray(subarray);
}
//PrintArray(subarray);
//Console.ReadKey();
CheckAllCombinations(new int [arr.Length], startIndex += 1, endIndex = (endIndex < arr.Length)? endIndex + 1 : endIndex, depth += 1);
}
}
//--------------------------------------------------------------------
/*
Method: IsWantedSum(int[] arr)
*/
static bool IsWantedSum(int[] arr)
{
int currentSum = 0;
for (int i = 0; i < arr.Length; i++)
{
currentSum += arr[i];
}
if (currentSum == sum)
{
return true;
}
else
{
return false;
}
}
//--------------------------------------------------------------------
/*
Method: PrintArray();
*/
static void PrintArray(int[] subarray)
{
Console.Write("{");
for (int i = 0; i < subarray.Length; i++)
{
Console.Write(subarray[i]);
if (i < subarray.Length -1) Console.Write(", ");
}
Console.WriteLine("}");
}
//--------------------------------------------------------------------
/*
Method: PrintSubArray();
*/
static void PrintSubArray(int[] subarray)
{
Console.Write("(");
for (int i = 0; i < subarray.Length; i++)
{
if (subarray[i] != 0)Console.Write(subarray[i]);
if (subarray[i] != 0 && i < subarray.Length - 1) Console.Write(" + ");
}
Console.WriteLine(" = {0})", sum);
}
}
}
I'm getting somewhat partially right result:
{2, 1, 2, 4, 3, 5, 2, 6}
S = 14 -> yes(4 + 3 + 5 + 2 + = 14)
S = 14 -> yes(2 + 4 + 3 + 5 + = 14)
S = 14 -> yes(4 + 3 + 5 + 2 + = 14)
S = 14 -> yes(4 + 3 + 5 + 2 + = 14)
S = 14 -> yes(2 + 4 + 3 + 5 + = 14)
S = 14 -> yes(4 + 3 + 5 + 2 + = 14)
with duplications and missing sub-arrays of non-consecutive elements such as:
yes (1 + 2 + 5 + 6 = 14)
Could someone give me a hint on the problems of my algorithm and probably suggest a correction / new implementation?
Here's a simple way to do it with combinations. There's probably a better way to store them (I'm thinking using a dictionary to encode all the sub sums you already have). At the end of the day, if you want to account for non-consecutive elements, you're going to have to get the sub arrays that are possible in this case, and not just look at consecutive choices. Credit for the combination algorithm goes to ojlovecd here .
class Exercise
{
static void Main()
{
PrintArray(arr);
// SubarrayWithSum();
var result = GetCombination(arr);
foreach(var list in result)
{
var total = list.Sum();
if (total == sum)
PrintArray(list);
}
}
static List<int> arr = new List<int> { 2, 1, 2, 4, 3, 5, 2, 6 };
static int sum = 14;
static List<List<int>> GetCombination(List<int> list)
{
var result = new List<List<int>>();
result.Add(new List<int>());
double count = Math.Pow(2, list.Count);
for (int i = 1; i <= count - 1; i++)
{
string str = Convert.ToString(i, 2).PadLeft(list.Count, '0');
for (int j = 0; j < str.Length; j++)
{
if (str[j] == '1')
{
result[i - 1].Add(list[j]);
}
}
result.Add(new List<int>());
}
return result;
}
static void PrintArray(List<int> subarray)
{
Console.Write("{");
for (int i = 0; i < subarray.Count; i++)
{
Console.Write(subarray[i]);
if (i < subarray.Count - 1) Console.Write(", ");
}
Console.WriteLine("}");
}
}
I think the duplicates are occurring because you have zeroes in the array that you are adding. See updated code which runs quicker.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace ProgrammingBasics
{
class Exercise
{
static void Main()
{
PrintArray(arr);
SubarrayWithSum();
}
//--------------------------------------------------------------------
/*
Data members.
*/
// targer array
static int[] arr = { 2, 1, 2, 4, 3, 5, 2, 6 };
// target sum
static int sum = 14;
//--------------------------------------------------------------------
/*
Method: IsSubarrayWithSum(arr, sum);
It returns a bool value that indicates
whether there is a subarray within arr
with elements that sum up to specific value.
*/
static void SubarrayWithSum()
{
int depth = 0;
int endIndex = arr.Length - 1;
CheckAllCombinations(new int[arr.Length], depth);
}
//--------------------------------------------------------------------
/*
Method: CheckAllCombinations(subarray, sum);
*/
static void CheckAllCombinations(int[] subarray, int depth)
{
//Console.ReadKey();
for (int i = depth; i < arr.Length; i++)
{
subarray[depth] = arr[i];
Console.WriteLine("depth = {0}, i = {1}, array = '{2}' ", depth, i, string.Join(",", subarray.Select(x => x.ToString()).ToArray()));
int currentSum = subarray.Take(depth + 1).Sum();
if (currentSum == sum)
{
Console.Write("S = {0} -> yes : ", sum);
Console.WriteLine(string.Join(",", subarray.Take(depth + 1)));
}
//PrintArray(subarray);
//Console.ReadKey();
if (currentSum < sum)
{
CheckAllCombinations(subarray, depth + 1);
}
}
}
//--------------------------------------------------------------------
/*
Method: IsWantedSum(int[] arr)
*/
//--------------------------------------------------------------------
/*
Method: PrintArray();
*/
static void PrintArray(int[] subarray)
{
Console.Write("{");
for (int i = 0; i < subarray.Length; i++)
{
Console.Write(subarray[i]);
if (i < subarray.Length - 1) Console.Write(", ");
}
Console.WriteLine("}");
}
//--------------------------------------------------------------------
/*
Method: PrintSubArray();
*/
static void PrintSubArray(int[] subarray)
{
Console.Write("(");
for (int i = 0; i < subarray.Length; i++)
{
if (subarray[i] != 0) Console.Write(subarray[i]);
if (subarray[i] != 0 && i < subarray.Length - 1) Console.Write(" + ");
}
Console.WriteLine(" = {0})", sum);
}
}
}
OK, here is small attempt to briefly describe the info that I went through to understand the problem1 and implement a basic solution.
It turns out that The Subset Sum Problem is considered a special case of the Knapsack Problem in which we are searching to maximize profit while keeping value called weight under specific capacity, but in our case the profit and weight associated with each value are identical.
There are variety of great solutions described in "Knapsack Problems" - Keller, Pferschy, Pisinger, however, for the time being the simplest solution that I could implement and understand, not carrying about complexity and / or efficacy, looks like this:
//--------------------------------------------------------------------
/*
Method: FindSubArray();
Base case: - if current sum == targer sum: print current elements.
- if index == arr.Length: terminate search.
Recursive step:
- do not/select element with index and do not/update the current sum; recursive call with updated current sum and index.
*/
static void FindSubArray(int index, int currentSum, bool[] subArray)
{
// base case
if (currentSum == targetSum)
{
PrintSubArray(subArray);
}
else if (index == arr.Length)
{
return;
}
else
{
// recursive calls
subArray[index] = true;
currentSum += arr[index];
FindSubArray(index + 1, currentSum, subArray);
currentSum -= arr[index]; // restore previous value of the sum signifying: element not selected
subArray[index] = false;
FindSubArray(index + 1, currentSum, subArray);
}
}
where PrintSubArray(subArray); prints all the elements of arr marked with true in subArray.
Output:
{2, 1, 2, 4, 3, 5, 2, 6}
S = 14 -> yes (2 + 1 + 2 + 4 + 3 + 2 + = 14)
S = 14 -> yes (2 + 1 + 2 + 4 + 5 + = 14)
S = 14 -> yes (2 + 1 + 2 + 3 + 6 = 14)
S = 14 -> yes (2 + 1 + 4 + 5 + 2 + = 14)
S = 14 -> yes (2 + 1 + 3 + 2 + 6 = 14)
S = 14 -> yes (2 + 1 + 5 + 6 = 14)
S = 14 -> yes (2 + 2 + 4 + 6 = 14)
S = 14 -> yes (2 + 2 + 3 + 5 + 2 + = 14)
S = 14 -> yes (2 + 4 + 3 + 5 + = 14)
S = 14 -> yes (2 + 4 + 2 + 6 = 14)
S = 14 -> yes (1 + 2 + 4 + 5 + 2 + = 14)
S = 14 -> yes (1 + 2 + 3 + 2 + 6 = 14)
S = 14 -> yes (1 + 2 + 5 + 6 = 14)
S = 14 -> yes (1 + 4 + 3 + 6 = 14)
S = 14 -> yes (1 + 5 + 2 + 6 = 14)
S = 14 -> yes (2 + 4 + 3 + 5 + = 14)
S = 14 -> yes (2 + 4 + 2 + 6 = 14)
S = 14 -> yes (4 + 3 + 5 + 2 + = 14)
S = 14 -> yes (3 + 5 + 6 = 14)
The book that I'm reading states the problem simply as: "Find if there is a sub array with elements that sum up to specific value'.
Related
Implement a recursive program that displays all combinations of operators to reach a given sum
I have to write a program that displays all the combinations of operators( + and -), to put between numbers from 1 to N (N>=2), in order to reach a targeted value X. It should write "N/A" if there is no possibility. For the input: n=6 x=3 It displays: 1 + 2 + 3 - 4 - 5 + 6 = 3 1 + 2 - 3 + 4 + 5 - 6 = 3 1 - 2 - 3 - 4 + 5 + 6 = 3 using System; namespace ConsoleApp1 { class Program { static bool counter; static void Generate(int n, int x, int currentIndex, int result, string expression) { counter = true; if (currentIndex == n + 1) { if (result == x) { Console.WriteLine(expression + " = " + x); } return; } Generate(n, x, currentIndex + 1, result + currentIndex, expression + " + " + currentIndex); Generate(n, x, currentIndex + 1, result - currentIndex, expression + " - " + currentIndex); } static void Main() { int n = Convert.ToInt32(Console.ReadLine()); int x = Convert.ToInt32(Console.ReadLine()); const int doi = 2; Generate(n, x, doi, 1, "1"); if (!counter) { Console.WriteLine("N/A"); } Console.ReadLine(); } } } It gives me the error : JRM003 (Error) : Don't use static fields. (line: 7, character: 7). Where can I place the "counter" in order to track if there is possibility of reaching to the targeted value, and get rid of the error.
Here's how I'd do it. Count in binary treating 0 as subtraction and 1 as addition: public static void Main(string[] args) { int n, x; String response1, response2; Console.Write("Enter a value for 'n' [n>=2]: "); response1 = Console.ReadLine(); Console.Write("Enter a value for 'x': "); response2 = Console.ReadLine(); if (int.TryParse(response1, out n) && int.TryParse(response2, out x)) { if (n >= 2) { List<String> solutions = new List<string>(); int[] numbers = Enumerable.Range(1, n).ToArray(); int width = numbers.Length - 1; int max = (int)Math.Pow(2, numbers.Length - 1); for(int i=0; i < max; i++) { int sum = numbers[0]; String binary = Convert.ToString(i, 2).PadLeft(width, '0'); String equation = numbers[0].ToString(); for(int d=0; d<binary.Length; d++) { char operation = binary[d]; equation = equation + ((operation == '0') ? " - " : " + ") + numbers[d + 1]; sum = sum + ((operation == '0') ? -1 : 1) * numbers[d + 1]; } equation = equation + " = " + sum; if (sum == x) { solutions.Add(equation); } } if (solutions.Count == 0) { Console.WriteLine("N/A"); } else { foreach(String solution in solutions) { Console.WriteLine(solution); } } } else { Console.WriteLine("'n' must be greater than or equal to 2."); } } else { Console.WriteLine("Invalid value for 'n' or 'x'!"); } Console.WriteLine("Press Enter to Quit."); Console.ReadLine(); } Output: Enter a value for 'n' [n>=2]: 6 Enter a value for 'x': 3 1 - 2 - 3 - 4 + 5 + 6 = 3 1 + 2 - 3 + 4 + 5 - 6 = 3 1 + 2 + 3 - 4 - 5 + 6 = 3 Press Enter to Quit.
Can you please help me with a clue on how can I find all possible combinations for a given set of number from 1 to N, using +- operators [duplicate]
This question already has answers here: Linq query to get all numbers (positive and negative) up to N that sum up to number K (5 answers) Listing all permutations of a string/integer (28 answers) Closed 1 year ago. Given input: N = 6, X = 3 The output should be: 1 + 2 + 3 - 4 - 5 + 6 = 3 1 + 2 - 3 + 4 + 5 - 6 = 3 1 - 2 - 3 - 4 + 5 + 6 = 3 So far I could manage this: //returns a string of numbers from 1 to N static string Numbers(int maxNumber) => maxNumber > 1 ? Numbers(maxNumber - One) + maxNumber :"1"; and a function that generates all possible combinations for +- but the problem is that I want to combine the +- resulted string with numbers from 1 to N: static void Permute(char[] arry, int i, int n) { int j; if (i == n) Console.WriteLine(arry); else { for (j = i; j <= n; j++) { Swap(ref arry[i], ref arry[j]); Permute(arry, i + 1, n); Swap(ref arry[i], ref arry[j]); //backtrack } } } static void Swap(ref char a, ref char b) { char tmp; tmp = a; a = b; b = tmp; }
This looks like a very different form of "permute". For N integers, you have D=N-1 decisions to make, each of which can be either a + or a -. Two options is: "binary", so, if this is me, I would compute (2^D)-1 (which gives us the upper bound), then do a for loop from zero to that number (inclusive), and do the math: each binary digit is a decision point, and we could say 0===- and 1===+; see what the result is: if it is the number you wanted: log it. For N=6 we have D=5, and 32 attempts to do; 0 thru 31: int N = 6, X = 3; // how many decisions is that? var decisions = N - 1; // treat each -/+ as one of "decisions" binary digits var binaryUpperLimit = (1 << decisions) - 1; for (int i = 0; i <= binaryUpperLimit; i++) { // compute the sum int sum = 1; // the 1 is a permenant fixture, it seems // determine each decision using binary arithmetic for (int place = 0; place < decisions; place++) { int digit = place + 2; if ((i & (1 << place)) == 0) { sum -= digit; } else { sum += digit; } } // is that what we wanted? if (sum == X) { // we have a "hit"; repeat the above to output this Console.Write("1"); for (int place = 0; place < decisions; place++) { if ((i & (1 << place)) == 0) { Console.Write(" - "); } else { Console.Write(" + "); } int digit = place + 2; Console.Write(digit); } Console.Write(" = "); Console.WriteLine(sum); } } (if the initial 1 can be negative, you'll need to adjust to add an extra decision, start the sum at zero, and make digit be +1 instead of +2)
Outputting certain number of elements from array c#
How could I go about, lets say for example outputting 3 elements of an array per line on the console? And doing this for the whole array? Is there anything like the Java Scanner that could help me?
You can do some thing like this. It will print elements in a single line and will go to new line after printing three elements in a row. for(int i=0; i<arr.Length; ++i) { Console.Write(arr[i]+" "); if((i+1)%3==0){ Console.WriteLine(""); } }
Linq solution: int[] data = Enumerable.Range(1, 20).ToArray(); int groupSize = 3; var result = Enumerable .Range(0, data.Length / groupSize + (data.Length % groupSize == 0 ? 0 : 1)) .Select(index => data.Skip(index * groupSize).Take(groupSize)) .Select(items => string.Join(", ", items)); Console.Write(string.Join(Environment.NewLine, result)); Output: 1, 2, 3 4, 5, 6 7, 8, 9 10, 11, 12 13, 14, 15 16, 17, 18 19, 20
Let arrayElements be the input, then you can group them into chunks of specified size and collect values from each groups and display it: List<int> arrayElements = new List<int>() { 1,5,2,3,6,4,87,96,54,5,4,5,6,2,5,9,5,5,5,6,2,45,6}; int chunkSize = 3; var results = arrayElements.Select((x, i) => new { Key = i / chunkSize , Value = x }) .GroupBy(x => x.Key, x => x.Value) .Select(x=>String.Join(" ",x.ToList())) .ToList(); foreach (string chunk in results) { Console.WriteLine(chunk); } Working Example
One way to do this would be with a for loop. You could use something like the following: // input already defined as array for (int i = 0; i < input.Length; i += 3) { Console.WriteLine(input[i] + ' ' + input[i + 1] + ' ' + input [i + 2]); } This would require that your array had a length that was a multiple of three; if this wasn't the case, you'd need to add some sort of logic checking that input[i + 1] and input[i + 2] existed within the array. A possible, albeit somewhat verbose solution, would be like so: for (int i = 0; i < input.Length; i += 3) { if (i + 2 >= input.length) { Console.WriteLine(input[i] + ' ' + input[i + 1] + ' ' + input[i + 2]); } else if (input[i + 1] >= input.length) { Console.WriteLine(input[i] + ' ' + input[i + 1]); } else { Console.WriteLine(input[i]); } }
A recursion related issue in c#
This is the background to this question: Background Take any integer n greater than 1 and apply the following algorithm If n is odd then n = n × 3 + 1 else n = n / 2 If n is equal to 1 then stop, otherwise go to step 1 The following demonstrates what happens when using a starting n of 6 6 - 3 - 10 - 5 - 16 - 8 - 4 - 2 - 1 After 8 generations of the algorithm we get to 1. It is conjectured that for every number greater than 1 the repeated application of this algorithm will eventually get to 1. The question is how can I find a number that takes exactly 500 generations to reduce to 1? The code below is my version but appearntly got some wrong logic. Could you help me correct this? Thanks in advance. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Sequence1 { class Program { static void Main(string[] args) { int start = 1; int flag = 0; int value; while(true){ int temp = (start - 1) / 3; string sta = temp.ToString(); if (Int32.TryParse(sta, out value) ) { if (((start - 1) / 3) % 2 == 1) { start = (start - 1) / 3; flag++; if (flag == 500) { break; } } else { start = start * 2; flag++; if (flag == 500) { break; } } } else { start = start * 2; flag++; if (flag == 500) { break; } } } Console.WriteLine("result is {0}", start); Console.ReadLine(); } } }
Since your question's title is "A recursion related issue", I will give you a recursive solution. int Process(int input, int maxRecursionDepth) { // condition to break recursion if (maxRecursionDepth == 0 || input == 1) return input; if (input % 2 == 1) // odd case return Process(input * 3 + 1, maxRecursionDepth - 1); else // even case return Process(input / 2, maxRecursionDepth - 1); } Now to find all number in a specified range, that return 1 after exactly 500 recursions: int startRange = 1, endRange = 1000; int maxDepth = 500; List<int> resultList = new List<int>(); for (int i = startRange; i <= endRange; i++) { if (Process(i, maxDepth) == 1) resultList.Add(i); }
Your problem is a part of Collatz conjecture (about recursively defined function) which has not been solved yet: http://en.wikipedia.org/wiki/Collatz_conjecture so I think brute force is a good way out: public static int GetMinNumber(int generations) { if (generations < 0) throw new ArgumentOutOfRangeException("generations"); // Memoization will be quite good here // but since it takes about 1 second (on my computer) to solve the problem // and it's a throwaway code (all you need is a number "1979515") // I haven't done the memoization for (int result = 1; ; ++result) { int n = result; int itterations = 0; while (n != 1) { n = (n % 2) == 0 ? n / 2 : 3 * n + 1; itterations += 1; if (itterations > generations) break; } if (itterations == generations) return result; } } ... int test1 = GetMinNumber(8); // <- 6 int test2 = GetMinNumber(500); // <- 1979515
Observing the problem, 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1 In the third iteration we hit the number 10, which is smaller than 13 So instead of calculating the sequence count every time we can use a cache. static int GetMinCollatz(int maxChain) { const long number = 1000000; int minNumber = 0; // Temporary values int tempCount = 0; long temp = 0; // Cache int[] sequenceCache = new int[number + 1]; // Fill the array with -1 for (int index = 0; index < sequenceCache.Length; index++) { sequenceCache[index] = -1; } sequenceCache[1] = 1; for (int index = 2; index <= number; index++) { tempCount = 0; temp = index; // If the number is repeated then we can find // sequence count from cache while (temp != 1 && temp >= index) { if (temp % 2 == 0) temp = temp / 2; else temp = temp * 3 + 1; tempCount++; } sequenceCache[index] = tempCount + sequenceCache[temp]; if (sequenceCache[index] == maxChain) { minNumber = index; } } return minNumber; } For more details refer project euler and this.
A recursive solution private void ReduceTo1(int input, ref int totalCount) { totalCount++; if (input % 2 == 0) { input = input / 2; } else { input = input * 3 + 1; } if (input != 1) ReduceTo1(input, ref totalCount); } to test int desireValue = 0; for (int i = 1; i < 100000; i++) { int totalCount = 0; ReduceTo1(i, ref totalCount); if (totalCount >= 500) { desireValue = i; break; } }
Display 1,2,3,4,5,6,8,10,11 as 1-6,8,10-11
I have this sequence 1,2,3,4,5,6,8,10,11 Expected output is 1-6,8,10-11 This problem is about formatting the sequence in easy readable form I tried with c# and used many if & else. Interviewer said, there is some simple algorithm to do this. I have no idea how to achive this very simple. Also for 1,2,3 i shown 1-3. They said its wrong!. Is there any design pattern(interpreter) involved in this logic?
Here is one way of doing it: int[] numbers = { 1, 2, 3, 4, 5, 6, 8, 10, 11 }; int start, end; for (int i = 0; i < numbers.Length; i++) { start = numbers[i]; while (i < numbers.Length - 1 && numbers[i] + 1 == numbers[i + 1]) i++; end = numbers[i]; if(start == end) Console.WriteLine(start); else Console.WriteLine(start + " - " + end); } This will display subsequent numbers that grow incrementally as range. Numbers that are not increasing linearly are not written as part of a range. Here is another version of the first approach, it utilizes the same for loop to iterate on range: int temp = numbers[0], start, end; for (int i = 0; i < numbers.Length; i++) { start = temp; if (i < numbers.Length - 1 ) // if subsequent numbers are incremental loop further if (numbers[i] + 1 == numbers[i + 1]) continue; // if they are not, number at index i + 1 is a new 'start' for the next iteration else temp = numbers[i + 1]; end = numbers[i]; if (start == end) Console.WriteLine(start); else Console.WriteLine(start + " - " + end); }
A simple implementation in C# could look like this: public string Format(IEnumerable<int> input) { var result = string.Empty; var previous = -1; var start = -1; var first = true; foreach(var i in input) { if(start == -1) start = i; else if(previous + 1 != i) { result += FormatRange(start, previous, first); first = false; start = i; } previous = i; } if(start != -1) result += FormatRange(start, previous, first); return result; } public string FormatRange(int start, int end, bool isFirst) { var result = string.Empty; if(!isFirst) result += ", "; if(start == end) result += start; else result += string.Format("{0}-{1}", start, end); return result; } This will also output 1-3 for the input 1,2,3, which is perfectly valid. Without a specification what the output should be instead it's impossible to answer that part.
Probably not a suitable answer for an interview question, but using LINQ is another way to solve this. int[] numbers = { 1, 2, 3, 4, 5, 6, 8, 10, 11 }; var remains = numbers.AsEnumerable(); while (remains.Any()) { int first = remains.First(); int last = remains.TakeWhile((x, i) => x - first == i).Last(); remains = remains.Skip(last - first + 1); Console.Write(first + (first == last ? "" : "-" + last) + (remains.Any() ? "," : Environment.NewLine)); }
The following groups consecutive integers, and outputs a string for each group. However, it also allows you to specify the minimum length of group which you want to hyphenate; anything less will just give you the individual numbers. Thus if you only want to hyphenate groups of 4 or more, you can pass in 4; if you want to hyphenate pairs, you can pass in 2. (I'd want to use 3 myself, but I can't tell what they want.) It also doesn't keep any collections of numbers as it goes along, because you don't need to. Method: static IEnumerable<string> Group(IEnumerable<int> input, int minLength) { int currentStart = int.MinValue; int currentLength = 0; foreach (int c in input) { if (currentLength > 0) if (currentStart + currentLength == c) currentLength++; else { if (currentLength >= minLength) yield return string.Format("{0}-{1}", currentStart, currentStart + currentLength - 1); else for (int i = currentStart; i < currentStart + currentLength; i++) yield return i.ToString(); currentStart = c; currentLength = 1; } else { currentStart = c; currentLength = 1; } } if (currentLength >= minLength) yield return string.Format("{0}-{1}", currentStart, currentStart + currentLength + 1); else for (int i = currentStart; i < currentStart + currentLength; i++) yield return i.ToString(); } Usage: int minCount = 3; int[] input = new[] { 1, 2, 3, 4, 5, 6, 8, 10, 11 }; Console.WriteLine(String.Join(",", Group(input, minCount)));
Java code: int[] arr = {1,2,3,4,5,6,8,10,11}; int start = arr[0], last = arr[0]; String output = ""; for (int i = 1; i <= arr.length; i++) { if (i == arr.length || arr[i] != last+1) { if (output.length() != 0) output += ","; if (start == last) output += start; else output += start + "-" + last; if (i != arr.length) start = last = arr[i]; } else last = arr[i]; } System.out.println(output);
Heres my best attempt. Not clever, but simple enough to satisfy that requirement I believe. I'm still pretty confused as to why "1-3" was wrong though.... var numbers = new int[] { 1, 2, 3, 4, 5, 6, 8, 10, 11, 12 }; var groups = new Dictionary<int, int>(); groups.Add(numbers.First(), numbers.First()); foreach (var num in numbers.Skip(1)) { var grp = groups.Last(); if (grp.Value + 1 == num) { groups[grp.Key] = num; } else { groups.Add(num, num); } } var output = string.Join(",", groups.Select(grp => (grp.Key == grp.Value) ? grp.Value.ToString() : grp.Key.ToString() + "-" + grp.Value.ToString())); Note: of course using the dictionary and linq etc is completely unnecessary (and way too specific for an answer requiring an algorithm), but I thought it highlighted the grouping aspect of the problem nicely
This is no valid C# code but to show the Idea. Sort the list from Min to Max then do this: For i = Min to Max { if i < MaxFound continue; int step = 1; Output = i; while Found(i + Step) { Step++; MaxFound = i + Step; } if i < MaxFound Output = (i + "-" + MaxFound); Output += ", "; }
Here is one of the approach: public static void main(String[] args) { print(1, 2, 3, 4, 5, 7, 9, 10, 12); } public static void print(int ... nums) { System.out.print(nums[0]); int idx = 1; for(int i = 1; i < nums.length; i++, idx++) { if(nums[i] - nums[i - 1] != 1) { if(idx > 1) { System.out.print(" - " + nums[i - 1]); } System.out.print(", " + nums[i]); idx = 0; } } if(idx > 1) System.out.println(" - " + nums[nums.length - 1]); }
Here's a Haskell version: import Data.List parseRange [] = "" parseRange n = let range = takeWhile (\x -> isInfixOf [x,x+1] n) n in if not (null range) then show (head range) ++ "-" ++ show (last range + 1) ++ (if length (tail n) > 1 then "," else "") ++ parseRange (drop (length range + 1) n) else show (head n) ++ (if null (tail n) then "" else ",") ++ parseRange (drop 1 n) Output: *Main> parseRange [1,2,3,4,5,6,8,10,11] "1-6,8,10-11"
And a way to do it with fold in F# - just for fun. let parseRange numbers = numbers |> Seq.fold (fun list n -> match list with |(a,b) :: tail when b+1 = n -> (a, n) :: tail |_ -> (n,n) :: list) [] |> List.rev |> Seq.map (fun (a,b) -> if a = b then sprintf "%i" a else sprintf "%i-%i" a b) |> String.concat ","