Related
Got stuck on quite simple problem in my code. I need to count what I would call a nested sums of an array. Let's take as an example the array:
[1,2,2,3,6]
I want to sum them as:
0 + 1 = 1
1 + 2 = 3
1 + 2 + 2 = 5
1 + 2 + 2 + 3 = 8
1 + 2 + 2 + 3 + 6 = 14
sum = 1 + 3 + 5 + 8 + 14 = 31
Edit:
I tried to do it with stack, but it failed
int sums = 0;
Stack<int> sum = new Stack<int>();
for (int i = 0; i < queries.Length; i++)
{
sum.Push(queries[i]);
}
for (int i = 0; sum.Count != 0; i++)
{
if(i != 0)
{
sums += sum.Pop();
}
}
You can run this task in a single loop considering when you have an array of size 5, the first element is repeating 5 times, second element repeating 4 times and etc.
int[] arr = { 1, 2, 2, 3, 6 };
int mysum = 0;
for (int i = 0; i < arr.Length; i++)
mysum += (arr.Length - i) * arr[i];
Console.WriteLine(mysum);
Output:
31
Explanation:
1 = 1
1 + 2 = 3
1 + 2 + 2 = 5
1 + 2 + 2 + 3 = 8
1 + 2 + 2 + 3 + 6 = 14
========================
5*1 + 4*2 + 3*2 + 2*3 + 1*6 = 31
You can do this much more efficiently and more easily, by multiplying each value by its reversed index + 1
For example, using LINQ
int[] arr = { 1, 2, 2, 3, 6 };
var result = arr.Reverse().Select((val, i) => val * (i + 1)).Sum();
Note that .Reverse on an array (or other Collection<T>) does not actually move any items, it just reads them backwards. So this is therefore an O(n) operation, as opposed to your original solution which is O(n2 / 2)
dotnetfiddle
You can also do this procedurally, this is almost the same as #aminrd's answer.
int[] arr = { 1, 2, 2, 3, 6 };
var result = 0;
for (var i = arr.Length - 1; i >= 0; i--)
result += arr[i] * (i + 1);
Take advantage of Linq! .Sum() will add up everything in your collection. You run that twice, once per each slice, and once per each subtotal.
var input = new [] { 1, 2, 2, 3, 6 };
var totalSum = Enumerable.Range(1, input.Length).Sum(len => input[0..len].Sum());
// totalSum is 31
Enumerable.Range gets you a collection of numbers between (and including) 1 and 5 - the possible lengths of each slice of your sub arrays. You then use the range operator [0..#] to get increasingly larger slices.
Yes, this is not as clever as aminrd's solution - it's doing all the computations manually and you're performing many slices.
I was solving a CodeWars task and faced a problem.
In it you are given the array of numbers, which length is a multiple of 4, you need to visualise it as a (x1^2 + x2^2) * (x3^2 + x4^2) .... * (xn^2 + xn+1^2).
Calculate the result of this and find 2 numbers, which squares in sum, gives the result of initial sequance.
For example, you are given an array of ( 2, 1, 3, 4):
(2^2 + 1^2) * (3^2 + 4^2) = 125;
2 numbers, which squares in sum will give 125, is 2 and 11 because 4 + 121 = 125;
I wrote the code and it works with most of examples, but when i use big arrays such as
(3, 9, 8, 4, 6, 8, 7, 8, 4, 8, 5, 6, 6, 4, 4, 5) in result i receive (0,0);
I can't get the problem, help me pls and if u can use simplified english cause i am from Russia.
Here is my code:
using System;
using System.Numerics;
using System.Collections.Generic;
public class ProdSeq
{
public static BigInteger[] solve(int[] arr)
{
bool simplified = false;
var result = new BigInteger[2];
var index = 0;
BigInteger sequenceSum = 1;
for (int i = 0; i < arr.Length - 1; i+=2)
sequenceSum *= arr[i] * arr[i] + arr[i + 1] * arr[i + 1];
if (sequenceSum >= 1000000)
{
sequenceSum /= 10000;
simplified = true;
}
var list = new List<BigInteger>();
for (BigInteger i = 0; i <= (BigInteger)Math.Sqrt((double)sequenceSum + 1); i++)
list.Add(BigInteger.Multiply(i, i));
for (int i = 0; i < list.Count; i++)
{
var second = sequenceSum - list[i];
index = list.BinarySearch(second);
if (index > -1)
{
if (simplified)
{
result[0] = (BigInteger)(Math.Sqrt((double)list[i]) * 100);
result[1] = (BigInteger)(Math.Sqrt((double)list[index]) * 100);
break;
}
result[0] = (BigInteger)(Math.Sqrt((double)list[i]));
result[1] = (BigInteger)(Math.Sqrt((double)list[index]));
break;
}
}
Console.WriteLine($"A: {result[0]} B: {result[1]}");
return result;
}
}
it seems your error is coming from this line:
for (int i = 0; i <= (int)Math.Sqrt((double)sequenceSum + 1); i++)
list.Add(BigInteger.Multiply(i , i));
you have to use Biginteger.Multiply method
result = 302400 and 29092800 (lot of solutions i think)
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'.
I have a string denoting page nos like 1,2,3,4,8,9,10,15.
I want this to be shown as 1-4,8-10,15 i.e numbers in sequence are separated by hyphen enclosed by smallest and largest number in sequence.
If break in sequence, the range is to be separated by comma.
string pageNos = "5,6,7,9,10,11,12,15,16";
string result=string.Empty;
string[] arr1 = pageNos.Split(',');
int[] arr = new int[arr1.Length];
for (int x = 0; x < arr1.Length; x++) // Convert string array to integer array
{
arr[x] = Convert.ToInt32(arr1[x].ToString());
}
for (int i = 0; i < arr.Length;i++)
{
for (int j = i + 1; ; j++)
if (arr[i] == (arr[j] - 1))
result += arr[i].ToString() + "-" + arr[j].ToString();
else
result += arr[i].ToString() + ",";
}
Console.WriteLine(result);
I think the loop-within-loop is making things more confusing. Try using just a single loop, because you only need to iterate over the entire list once.
int start,end; // track start and end
end = start = arr[0];
for (int i = 1; i < arr.Length; i++)
{
// as long as entries are consecutive, move end forward
if (arr[i] == (arr[i - 1] + 1))
{
end = arr[i];
}
else
{
// when no longer consecutive, add group to result
// depending on whether start=end (single item) or not
if (start == end)
result += start + ",";
else if (end == (start + 1))
result += start + "," + end + ",";
else
result += start + "-" + end + ",";
start = end = arr[i];
}
}
// handle the final group
if (start == end)
result += start;
else
result += start + "-" + end;
Demo: http://ideone.com/7HdpS7
A little bit of LINQ will tidy this up:
static IEnumerable<Tuple<int, int>> GetRanges(IEnumerable<int> source)
{
bool started = false;
int rangeStart = 0, lastItem = 0;
foreach (int item in source)
{
if (!started)
{
rangeStart = lastItem = item;
started = true;
}
else if (item == lastItem + 1)
{
lastItem = item;
}
else
{
yield return new Tuple<int, int>(rangeStart, lastItem);
rangeStart = lastItem = item;
}
}
if (started)
{
yield return new Tuple<int, int>(rangeStart, lastItem);
}
}
static string FormatRange(Tuple<int, int> range)
{
string format = (range.Item1 == range.Item2) ? "{0:D}" : "{0:D}-{1:D}";
return string.Format(format, range.Item1, range.Item2);
}
string pageNos = "5,6,7,9,10,11,12,15,16";
int[] pageNumbers = Array.ConvertAll(pageNos.Split(','), Convert.ToInt32);
string result = string.Join(",", GetRanges(pageNumbers).Select(FormatRange));
You could use this method to get adjacent groups of numbers where each group is represented by a custom Range-class:
class Range
{
public int? Start { get; set; }
public int? End { get; set; }
}
private static IEnumerable<Range> getAdjacentRanges(IEnumerable<int> nums)
{
var ranges = new List<Range>();
if (!nums.Any())
return ranges;
var ordered = nums.OrderBy(i => i);
int lowest = ordered.First();
int last = lowest;
ranges.Add(new Range { Start = lowest });
foreach (int current in ordered)
{
lastRange = ranges[ranges.Count - 1];
if (current > last + 1)
{
lastRange.End = last;
ranges.Add(new Range { Start = current });
}
last = current;
}
return ranges;
}
The rest is easy:
var arr = new[] { 1, 2, 3, 4, 8, 9, 10, 15 };
var ranges = getAdjacentRanges(arr)
.Select(r => r.End.HasValue ? string.Format("{0}-{1}", r.Start, r.End) : r.Start.ToString());
Console.Write(string.Join(",", ranges));
output: 1-4,8-10,15
DEMO
string pageNos = "5,6,7,9,10,11,12,15,16";
string[] arr1 = pageNos.Split(',');
int[] arr = new int[arr1.Length];
for (int x = 0; x < arr1.Length; x++) // Convert string array to integer array
{
arr[x] = Convert.ToInt32(arr1[x]);
}
StringBuilder sb = new StringBuilder();
bool hyphenOpen = false;
for (int i = 0; i < arr.Length - 1; i++)
{
if (arr[i] + 1 == arr[i+1])
{
if (!hyphenOpen)
{
hyphenOpen = true;
sb.Append(arr[i] + "-");
}
}
else
{
hyphenOpen = false;
sb.Append(arr[i] + ",");
}
}
sb.Append(arr[arr.Length-1]);
Console.WriteLine(sb.ToString());
This is long and clunky, but it works.
P.S. - I left the OP's original string->int as is, see comment by JonB on question for much cleaner method.
The following JS code will also help
Remove duplicates
Sort the array
Use 2 pointers
Start at the same place
Advance the second pointer until its next element is a successor
Print the element at i and j with spaces and comma
Don’t print element at j if the index are same
Remove the trailing comma
const givenArray = [1, 6, 6, 8, 44, 45, 47, 55, 9, 11, 12, 1, 6, 88, 13, 14, 2, 3, 5, 22, 33, 57, 88];
const input = [...new Set(givenArray)].sort((a, b) => a - b);
let i = 0;
let j = 0;
let output = '';
while (i < input.length) {
while (j < input.length && (input[j] + 1) === input[j + 1]) {
j++;
}
output += `${input[i]}`;
if (i !== j) {
output += ` - ${input[j]}, `;
} else {
output += ', ';
}
i = j + 1;
j = i;
}
console.log(output.substring(0, output.lastIndexOf(",")));
Use this helper class to convert back and forth between number lists and range strings.
This copies implementation of ConvertRangeStringToNumberList() from here and ConvertNumberListToRangeString() from here with slight improvements.
using System;
using System.Collections.Generic;
using System.Linq;
public static class NumberRangeHelper
{
/// <summary>
/// Converts a string of comma separated list of numbers and ranges to the list of individual numbers it represents.
/// </summary>
/// <param name="numbers">Range in form of <c>"2,4-8,11,15-22,39"</c></param>
/// <returns>A list of numbers</returns>
public static List<int> ConvertRangeStringToNumberList(string numbers)
{
var numbersSplit = numbers.Split(',');
var convertedNumbers = new SortedSet<int>();
foreach (var strNumber in numbersSplit)
{
int number;
if (int.TryParse(strNumber, out number))
{
convertedNumbers.Add(number);
}
else
{
// try and delimited by range
if (strNumber.Contains('-'))
{
var splitRange = strNumber.Split('-');
if (splitRange.Length == 2)
{
int firstNumber;
int secondNumber;
if (Int32.TryParse(splitRange[0], out firstNumber) &&
Int32.TryParse(splitRange[1], out secondNumber))
{
for (var i = firstNumber; i <= secondNumber; ++i)
{
convertedNumbers.Add(i);
}
}
}
}
}
}
return convertedNumbers.ToList();
}
/// <summary>
/// Converts a list of numbers to their concise range representation.
/// </summary>
/// <param name="numbers">A list of numbers such as <c>new[] { 1, 2, 3, 4, 5, 12, 13, 14, 19 }</c></param>
/// <returns>A string like <c>"1-5, 12-14, 19"</c></returns>
public static string ConvertNumberListToRangeString(IEnumerable<int> numbers)
{
var items = new SortedSet<int>(numbers)
.Select((n, i) => new { number = n, group = n - i })
.GroupBy(n => n.group)
.Select(g => (g.Count() >= 3)
? g.First().number + "-" + g.Last().number
: String.Join(", ", g.Select(x => x.number))
)
.ToList();
return String.Join(", ", items);
}
}
Test:
Action<IEnumerable<int>> DumpList = l => Console.WriteLine("\t[{0}]", String.Join(", ", l));
Action<string> DumpRange = s => Console.WriteLine("\t\"{0}\"", s);
var numbers = new[] { 1, 1, 2, 3, 4, 5, 12, 13, 19, 19, 6, 7 };
DumpList(numbers);
var str = ConvertNumberListToRangeString(numbers);
DumpRange(str);
var list = ConvertRangeStringToNumberList(str);
DumpList(list);
Console.WriteLine();
str = "1-5, 12, 13, 19, 20, 21, 2-7";
DumpRange(str);
list = ConvertRangeStringToNumberList(str);
DumpList(list);
str = ConvertNumberListToRangeString(list);
DumpRange(str);
Output:
[1, 1, 2, 3, 4, 5, 12, 13, 19, 19, 6, 7]
"1-7, 12, 13, 19"
[1, 2, 3, 4, 5, 6, 7, 12, 13, 19]
"1-5, 12, 13, 19, 20, 21, 2-7"
[1, 2, 3, 4, 5, 6, 7, 12, 13, 19, 20, 21]
"1-7, 12, 13, 19-21"
i am not a C# person, but i guess here you have problem:
if (arr[i] == (arr[j] - 1))
result += arr[i].ToString() + "-" + arr[j].ToString();
you shouldn't add that in your result. but set a flag (boolean maybe), to indicate that now I start counting.
if the flag==ture and the number is not continuous any longer, that is the time to add to your result, of course with "-".
public static string HyphenateRanges(this string input)
{
if (string.IsNullOrEmpty(input))
{
return "";
}
var orderedDistinct = input.Split(',')
.Select(Int32.Parse)
.Distinct()
.OrderBy(x => x)
.ToArray();
Func<int, int, string> replaceRangeValuesWithDash =
(x, i) =>
i == 0 || // first
i == orderedDistinct.Length - 1 || // last
orderedDistinct[i + 1] - orderedDistinct[i - 1] != 2 // not in a range
? x.ToString()
: "-";
var rangeValuesDashed = orderedDistinct
.Select(replaceRangeValuesWithDash)
.ToList();
var extraDashesRemoved = rangeValuesDashed
.Where((x, i) => i == 0 || rangeValuesDashed[i - 1] != x)
.ToArray();
var formattedString = String.Join(",", extraDashesRemoved)
.Replace(",-,", "-");
return formattedString;
}
Here's a different solution that creates a List<Tuple<int, int>> with each non-sequential value and the number of sequential values that follow it. This is then turned into a string using string.Join.
string pageNos = "1,2,3,4,8,9,10,15";
// Get list of numbers as ints
var list = pageNos.Split(',').Select(i => Convert.ToInt32(i)).ToList();
// Get a list of numbers and ranges of consecutive numbers
var ranges = new List<Tuple<int, int>>();
int start = 0;
for (int i = 0; i < list.Count; i++)
{
// First item always starts a new range
if (i == 0)
{
start = list[i];
}
// Last item always ends the current range
if (i == list.Count - 1)
{
if (list[i] == list[i - 1] + 1)
{
ranges.Add(new Tuple<int, int>(start, list[i] - start));
}
else
{
ranges.Add(new Tuple<int, int>(start, list[i - 1] - start));
ranges.Add(new Tuple<int, int>(list[i], 0));
}
}
// End the current range if nonsequential
if (i > 0 && i < list.Count - 1 && list[i] != list[i - 1] + 1)
{
ranges.Add(new Tuple<int, int>(start, list[i - 1] - start));
start = list[i];
}
}
// Craete the result string
var result = string.Join(", ", ranges.Select(r => r.Item2 == 0 ? r.Item1.ToString() : string.Format("{0}-{1}", r.Item1, r.Item1 + r.Item2)));
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 ","