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
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);
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.
I have a list with ~100k of integer pairs like these ones:
0, 12
0, 14
0, 1
0, 8
0, 2
0, 4
0, 3
1, 5
1, 11
1, 8
1, 2
2, 7
2, 9
2, 4
2, 5
2, 13
3, 12
3, 10
3, 4
3, 6
...
I need to sort them like
0, 1
0, 2
0, 3
0, 4
0, 8
0, 12
0, 14
1, 2
1, 5
1, 8
1, 11
2, 4
2, 5
2, 7
2, 9
2, 13
3, 4
3, 6
...
Currently I am doing:
myList.Sort(comparer);
when the comparer is defined as:
class EdgeIntersectComparer : IComparer<EdgeIntersect>
{
public int Compare(EdgeIntersect l1, EdgeIntersect l2)
{
if (l1.V1 < l2.V1)
return -1;
if (l1.V1 > l2.V1)
return 1;
if (l1.V2 < l2.V2)
return -1;
if (l1.V2 > l2.V2)
return 1;
return 0;
}
}
What can I do to improve execution speed? Is there any smarter approach to the problem?
Thanks.
EDIT:
Tested myList.OrderBy(e => e.V1).ThenBy(e => e.V2) and it's slower.
You had commented in a deleted post that V1 is already sorted.
In addition by V1 the list is already sorted.
I did a test using data already ordered by V1, but with V2 initialized with random numbers. I found this faster than your method:
myList = myList.GroupBy(x => x.V1).SelectMany(x => x.OrderBy(y => y.V2)).ToList();
This only works if V1 is already sorted.
Just as a possible option you coult try an Array instead of a List. (it depends on your context). If you can't:
Asuming:
public class Pair
{
public int First { get; private set; }
public int Second { get; private set; }
public Pair(int first, int second)
{
this.First = first;
this.Second = second;
}
}
And how the List is already ordered by the first item, maybe something like this? not sure if this will be faster:
public static List<Pair> FullOrderedList(List<Pair> SemiOrderedList)
{
List<Pair> FList = new List<Pair>();
List<Pair> demi = new List<Pair>();
int MaxNumber = SemiOrderedList.Count;
int compared = 0;
for (int i = 0; i < MaxNumber; i++)
{
int first = SemiOrderedList[i].First;
if (compared == first)
{
demi.Add(SemiOrderedList[i]);
}
else
{
compared++;
FList.AddRange(demi.OrderBy(x => x.Second));
demi.Clear();
}
}
return FList;
}
A small speed increase can be gained by optimizing your comparer:
if (l1.V1 == l2.V1)
{
if (l1.V2 > l2.V2) return 1;
else return -1;
}
else if (l1.V1 < l2.V1)
return -1;
else return 1;
max 2 statements to check, rather than your 4.
How can I do this for example if I have 1, 2, 3, 4, 5, 6, 7 array and I am in 4th position (number 5) and if you have to move it to the right 4 positions you should be in position 1 (number 2). The same goes with negative numbers but you move to left. I guess there is a need of while(true) loop?
Lets assume i is the index and n is the size of the array.
For positive i the needed index = i%n
For negative i i%n returns negative residue, so the needed index is n+i%n
You can use
int index(int i, int n) {
return i%n < 0 ? n + (i%n) : i%n;
}
You can calculate your index like this:
var newIndex = (index + 4) % 7;
So the fourth position becomes (4+4) % 7 or 1.
Always clearer with named functions and followable code path instead of voodoo one liners that work
public void MyTest()
{
var testData = new[] { 1, 2, 3, 4, 5, 6, 7 };
Assert.AreEqual(2, TraverseCircularArray(testData, 5, 3));
Assert.AreEqual(6, TraverseCircularArray(testData, 2, -4));
}
private int TraverseCircularArray(int[] array, int currentIndex, int interval)
{
var i = array[currentIndex];
if (currentIndex + interval < 0)
i = array[array.Length + (interval + currentIndex)];
else if (currentIndex + interval >= array.Length)
i = array[currentIndex - interval - 1];
else
i = array[currentIndex + interval];
return i;
}
The question is there is and unsorted array and the maximum value should be smaller than the length. I have to find the duplicate record in the array. The condition is to use a loop only once. This is what i have achieved so far. I wanted to know if there was any other approach through which i can achieve this.
int[] Arr = { 9, 5, 6, 3, 8, 2, 5, 1, 7, 4 };
int[] Arr2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
for (int i = 0; i < Arr.Length; i++)
{
if (Arr2[Arr[i]] == 0)
{
Arr2[Arr[i]] = Arr[i];
}
else
{
Console.WriteLine("duclicate found");
}
}
Use any Set implementation, say HashSet<T>, e.g.
HashSet<int> hs = new HashSet<int>();
int[] Arr = { 9, 5, 6, 3, 8, 2, 5, 1, 7, 4 };
foreach (item in Arr)
if (hs.Contains(item)) {
Console.WriteLine("duplicate found");
// break; // <- uncomment this if you want one message only
}
else
hs.Add(item);
Edit: since hs.Add returns bool a shorter and more efficient code can be put:
HashSet<int> hs = new HashSet<int>();
int[] Arr = { 9, 5, 6, 3, 8, 2, 5, 1, 7, 4 };
foreach (item in Arr)
if (!hs.Add(item)) {
Console.WriteLine("duplicate found");
// break; // <- uncomment this if you want one message only
}
Since you have this condition :
The question is there is and unsorted array and the maximum value should be smaller than the length.
Also assuming only positive numbers, which in your example applies
This can be done using O(n) time and O(1) space without using any LINQ, Dictionary, Hashing etc.
int[] arr = { 9, 5, 6, 3, 8, 2, 5, 1, 7, 4 };
for (int i = 0; i < arr.Length; i++)
{
if (arr[Math.Abs(arr[i])] >= 0)
arr[Math.Abs(arr[i])] = -arr[Math.Abs(arr[i])];
else
Console.WriteLine("Duplicate found " + Math.Abs(arr[i]).ToString() + "\n");
}
This is the Element Distinctness Problem.
This problem cannot be solved strictly linearly without additional space.
The two common approaches to solve the problem are:
Using a HashSet - populate it while iterating and abort if you find a match - O(n) time on average and O(n) space
Sort and iterate, after the array is sorted, duplicates will be adjacent to each other and easy to detect. This is O(nlogn) time and very little extra space.
The fastest way to obtain all duplicates, using LINQ, is this:
var duplicates = Arr.GroupBy(s => s).SelectMany(d => d.Skip(1));
This will return an IEnumerable of all duplicate elements in Arr, and you can use the following check to contain if there were any duplicates:
if (duplicates.Any())
{
// We have a duplicate!
}
This will work if only array a[] contains numbers in range [0,n-1] {as in your question} and n is not very large to avoid integer range overflow .
for(i=0;i<n;i++)
{
if(a[a[i]%n]>=n)
**duplicate is a[i]** !
else
a[a[i]%n]+=n;
}
Time complexity : O(N)
Space complexity : O(1) !
try this code using LINQ
int[] listOfItems = new[] { 4, 2, 3, 1, 6, 4, 3 };
var duplicates = listOfItems
.GroupBy(i => i)
.Where(g => g.Count() > 1)
.Select(g => g.Key);
foreach (var d in duplicates)
Console.WriteLine("The duplicate is "+d);