Here is my problem, I have a query result set that looks like this:
id_1 - 0 - datetime - gps coordinates
id_2 - 0 - datetime - gps coordinates
id_3 - 1 - datetime - gps coordinates
id_4 - 1 - datetime - gps coordinates
id_5 - 1 - datetime - gps coordinates
id_6 - 1 - datetime - gps coordinates
id_7 - 0 - datetime - gps coordinates
id_8 - 0 - datetime - gps coordinates
id_9 - 0 - datetime - gps coordinates
id_10 - 0 - datetime - gps coordinates
id_11 - 1 - datetime - gps coordinates
id_12 - 1 - datetime - gps coordinates
id_13 - 0 - datetime - gps coordinates
id_14 - 0 - datetime - gps coordinates
This, obviusly returns a list, but I would like to have the sets with value 1 in the second column kept in a list of lists, where each set of lists is one that previously had a 0.
It would look something like this:
List1: [id_3], [...] , [id_6]
List2: [id_11], [...], [id_12]
I dont know the number of elements with 0 or 1 so this has to be generic in that sense
I'm using C# 4.5, and was thinking of using Linq to do this, instead of the old fashioned foreach.
Can anyone point me in the right direction?
I don't think there are something builtin in the framework but you can create an extention method for it :
public static class LinqHelper
{
public static IEnumerable<List<Item>> Partition(this IEnumerable<Item> source, Func<Item, bool> selector)
{
List<Item> currentList = new List<Item>();
foreach (var item in source)
{
if (selector(item))
{
currentList.Add(item);
}
else if (currentList.Count != 0)
{
yield return currentList;
currentList = new List<Item>();
}
}
if (currentList.Count != 0)
{
yield return currentList;
}
}
}
public class Item
{
public int Id { get; set; }
public int Val { get; set; }
}
void Main()
{
var list = new List<Item>(){
new Item{ Id = 1, Val = 0 },
new Item{ Id = 2, Val = 0 },
new Item{ Id = 3, Val = 1 },
new Item{ Id = 4, Val = 1 },
new Item{ Id = 5, Val = 1 },
new Item{ Id = 6, Val = 1 },
new Item{ Id = 7, Val = 0 },
new Item{ Id = 8, Val = 0 },
new Item{ Id = 9, Val = 0 },
new Item{ Id = 10, Val = 0 },
new Item{ Id = 11, Val = 1 },
new Item{ Id = 12, Val = 1 },
new Item{ Id = 13, Val = 0 },
new Item{ Id = 14, Val = 0 },
};
var result = list.Partition(i => i.Val == 1).Where(i => true).ToList();
}
If you're looking to avoid foreach, here's a way to do it with LINQ's Aggregate extension.
Given the following class:
public class SomeType
{
public int Id {get;set;}
public int Val {get;set;}
}
and having generated the following items:
var items = new List<SomeType>();
for(var i = 1; i <= 14; i++)
{
var val = 0;
if((3 <= i && i <= 6) || (11 <= i && i <= 12))
val = 1;
items.Add(new SomeType { Id = i, Val = val});
}
You can get the List of Lists of items with a value of 1 like so:
var grouped = items.Aggregate(new List<List<SomeType>>() { new List<SomeType>() },
(acc,elem) =>
{
if(elem.Val == 0 && acc.Last().Count != 0)
acc.Add(new List<SomeType>());
else if(elem.Val == 1)
acc.Last().Add(elem);
return acc;
}, acc => acc.Where(x => x.Count != 0));
int groupNum = 1;
foreach (var group in grouped)
{
Console.WriteLine($"Group {groupNum++}");
foreach (var item in group)
Console.WriteLine($"{item.Id} - {item.Val}");
}
/* output:
Group 1
3 - 1
4 - 1
5 - 1
6 - 1
Group 2
11 - 1
12 - 1
*/
This assumes that it's okay to add entries with the 1 value before a 0 value has occurred, and I'm not sure that's it's especially more readable than using a foreach, and you would likely be better off implementing an extension method that would likely use foreach anyways.
Try this solution;
var prev = 0;
List<int> bag = null;
var result = new List<List<int>>();
foreach(var item in list)
{
if(item.Val == 1)
{
if(prev == 0)
{
if(bag != null)
result.Add(bag);
bag = new List<int>();
}
bag.Add(item.Id);
}
prev = item.Val;
}
if(bag != null)
result.Add(bag);
result.ForEach(x => {
Console.WriteLine(String.Join(", ", x));
});
//3, 4, 5, 6
//11, 12
Related
I would like to ask for a hint how to approach in solving this task:
I have intervals ordered by min. value eg: ([1,5],[2,9],[6,7],[8,16],[11,15], [18,20]).
I should pick minimal amount of intervals, which cover the largest range.
So I should store these: ([1,5],[2,9],[8,16],[18,20]). Interval [6,7] is not stored, because it is covered by interval [2,9]. Interval [11,15] is not stored, because it is covered by [8,16].
How should I approach in solving this? Thank you:)
Linq approach
int[][] input = new[] { new[] { 1, 5 }, new[] { 2, 9 }, new[] { 6, 7 }, new[] { 8, 16 }, new[] { 11, 15 }, new[] { 18, 20 } };
int[][] result = input.Where((i1, x1) => !input.Where((i2, x2) => x1 != x2 && i2[0] <= i1[0] && i2[1] >= i1[1]).Any()).ToArray();
https://dotnetfiddle.net/x1xosT
Update: Thanks to #yuriy-faktorovich I've a added a <= and >= comparison instead < and >. Also removed the comparison with itselve.
It looks like all you need to do is keep track of the current highest range and only store numbers that are higher.
So the first highest number would be 5 so you store it, then add 9 and make it the highest number, then you reach 7 which is less than 9, so it's not stored, then you add 16 and make it the highest, 15 is less than 16, so skip it, and then finally add 20.
idk what language you're using, but the code would look something like.
foreach(var item in dataset)
{
if(item[1] > highest)
{
savedItems.Add(item);
highest = item[1];
}
}
I believe there could be local maximums, so I wrote the recursive function to look at different options
static List<(int lower,int higher)> Minimize(List<(int lower, int higher)> list, int i = 0)
{
//past last item
if (i >= list.Count) return list;
if (( i > 0 && list[i - 1].higher >= list[i].higher) ||
(i + 1 < list.Count && list[i + 1].lower == list[i].lower) ||
(i > 0 && i + 1 < list.Count && list[i + 1].lower - list[i - 1].higher <= 1))
{
var newList = list.ToList();
newList.RemoveAt(i);
var minimizedNewList = Minimize(newList);
var minimizedCurrentList = Minimize(list, i + 1);
return minimizedNewList.Count < minimizedCurrentList.Count ? minimizedNewList : minimizedCurrentList;
}
else return Minimize(list, i + 1);
}
You can test with the following
var testData = new[] {(1, 5), (2, 9), (6,7), (8, 16), (11, 15), (18, 20)};
var result = Minimize(testData.ToList());
foreach (var valueTuple in result)
{
WriteLine(valueTuple);
}
WriteLine("----------------------------");
testData = new[] { (1, 5), (5, 6), (6, 7) };
result = Minimize(testData.ToList());
foreach (var valueTuple in result)
{
WriteLine(valueTuple);
}
WriteLine("----------------------------");
testData = new[] { (1, 10), (5, 6) };
result = Minimize(testData.ToList());
foreach (var valueTuple in result)
{
WriteLine(valueTuple);
}
WriteLine("----------------------------");
testData = new[] { (1, 10), (1, 20) };
result = Minimize(testData.ToList());
foreach (var valueTuple in result)
{
WriteLine(valueTuple);
}
Here's a fiddle https://dotnetfiddle.net/EC5BrW
I have a custom object list which has month and another int value. I am getting this list from the database. So data coming from a web service as JSON. Sometimes month is not consecutive. Sometimes some month get missing. For example if this is month 7,
then months in list may contains something like this.
{1,2,3,6,7}
so I want to add the missing months which are 4,5 ----
{1,2,3,4,5,6,7}
other value should be 0 (NEW_REC_COUNT)
My Object class
public class NewData
{
public int MONTH { get; set; }
public int NEW_REC_COUNT { get; set; }
}
Sample Json
[
{
"MONTH": 1,
"NEW_REC_COUNT": 19
},
{
"MONTH": 2,
"NEW_REC_COUNT": 5
},
{
"MONTH": 3,
"NEW_REC_COUNT": 2
},
{
"MONTH": 6,
"NEW_REC_COUNT": 9
},
{
"MONTH": 7,
"NEW_REC_COUNT": 3
}
]
You can try below approach,
Select all months (int value) from list using Select
var months = NewDataList.Select(x => x.MONTH); // This will give you all integers i.e MONTHs.
Find Max() from months and create Range from 1... maxMonths
var maxMonths = months.Max();
var oneTomaxMonths = Enumerable.Range(1,maxMonths).ToList();
Now you have 2 lists i.e. months and oneToMaxMonths, use Except to get missing Months from list of New Data
var results = oneTomaxMonths.Except(months);
Foreach result create new instance with NEW_REC_COUNT = 0
POC : .net Fiddle
If you don't have a lot of data, you can try a smiple loop and Insert omitted items into the list:
List<NewData> list = ...
// If list is not guarantee to be sorted
list.Sort((a, b) => a.MONTH.CompareTo(b.MONTH));
for (int i = 0; i < list.Count - 1; ++i) {
NewData current = list[i];
NewData next = list[i + 1];
// Do we have a hole at position i + 1?
if (current.MONTH + 1 < next.MONTH) {
list.Insert(i + 1, new NewData() {
MONTH = current.MONTH + 1, // Omitted month
NEW_REC_COUNT = 0, // Default value
});
}
}
Edit: If we want months from 1 up and including the current month (DateTime.Today.Month), we can use Linq:
using System.Linq;
...
List<NewData> list = ...
// Let's generalize a bit if you want, say Q3 period
int fromMonth = 1;
int upToMonth = DateTime.Today.Month; // or 7 for testing
list = Enumerable
.Range(fromMonth, upToMonth - fromMonth + 1)
.Select(month =>
list.FirstOrDefault(item => item.MONTH == month)
?? new NewData() { MONTH = month, // Omitted month
NEW_REC_COUNT = 0 }) // Default value
.ToList();
If you want to modify existing list:
list.AddRange(Enumerable
.Range(fromMonth, upToMonth - fromMonth + 1)
.Where(month => !list.Any(item => item.MONTH == month))
.Select(month => new NewData() {
MONTH = month,
NEW_REC_COUNT = 0 })
.ToArray());
I have this scenario where I need to select the Bar Object with Val greater than 0 while keeping the Object with the current month whether Val is 0 or greater than 0.
var currentMonth = new DateTime(2015, 3, 1); // Just an example
var foo = new List<Bar>()
{
new Bar { Date = '01/01/2015', Val = 40 },
new Bar { Date = '02/01/2025', Val = 30 },
new Bar { Date = '03/01/2015', Val = 0 },
new Bar { Date = '04/01/2015', Val = 2 },
new Bar { Date = '05/01/2015', Val = 5 }
}
// I also need to select the current month whether it's Val is 0 or greater than 0
// Stuck here
var fooResult = foo.Where(f => f.Val > 0);
So the result should be something like this:
{
new Bar = { Date = '03/01/2015', Val = 0 },
new Bar = { Date = '04/01/2015', Val = 2 },
new Bar = { Date = '05/01/2015', Val = 5 }
}
Or if currentMonth is declared as this. var currentMonth = new DateTime(2015, 4, 1);
The result should be something like this:
{
new Bar { Date = '04/01/2015', Val = 2 },
new Bar { Date = '05/01/2015', Val = 5 }
}
Any help would be much appreciated. Thanks
Try this
var fooResult = foo.Where(f => f.Val > 0 || currentMonth.Month ==Convert.ToDateTime(f.Date).Month);
I have a List of items containing either 1 or 0, I'm looking to output the items only where there are six 1's back to back in the list. So only write to the console if the item in this list is part of a group of six.
1
1
1
1
1
1
0
1
1
1
0
In the above list, the first six items would be output but the bottom set of three 1s would not as they are not part of a group of six.
Is this a job for LINQ or RegEx?
You can concatenate all values into string, then split it by zeros. From substrings select those which have at least 6 characters:
List<int> values = new List<int> { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
var series = String.Concat(values)
.Split(new[] { '0' }, StringSplitOptions.RemoveEmptyEntries)
.Where(s => s.Length >= 6);
For given input data series will contain single item "111111" which you can output to console.
Classic run length encoding, O(n), lazy evaluated, stack agnostic, generic for any equatable type.
public void TestRunLength()
{
var runs = new List<int>{ 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 0, 4};
var finalGroup = RunLength(runs).FirstOrDefault(i => i.Count == 6 && i.First() == 1);
}
private IEnumerable<List<T>> RunLength<T>(IEnumerable<T> source) where T : IEquatable<T>
{
T current = default(T);
var requiresInit = true;
var list = new List<T>();
foreach (var i in source)
{
if (requiresInit)
{
current = i;
requiresInit = false;
}
if (i.Equals(current))
{
list.Add(i);
}
else
{
yield return list;
list = new List<T>{ i };
current = i;
}
}
if (list.Any())
{
yield return list;
}
}
And because it's lazy it works on infinite sequences (yes I know its not infinite, but it is large)!
public void TestRunLength()
{
var random = new Random();
var runs = Enumerable.Range(int.MinValue, int.MaxValue)
.Select(i => random.Next(0, 10));
var finalGroup = RunLength(runs)
.FirstOrDefault(i => i.Count == 6);
}
Probably it can be done with Regex too if you concatenate your numbers into a string. But I would prefer linq:
var bits = new List<int> {1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0};
int bitCountPerGroup = 6;
var result = bits // (1) (2)
.Select((x,idx) => bits.Skip(idx).TakeWhile(y => y == x))
.Where(g => g.Count() == bitCountPerGroup); // (3)
foreach (var set in result)
Console.WriteLine(string.Join(" ", set));
This code gets a number-set for each number by starting from the number (1) and taking the next numbers as long as they are equal (2). Then filter the groups and gets only those groups which have 6 numbers (3).
If for example your list is of an unknown size,or better,you do not know the items in it you could do this recursive example(note that i placed more zeros so it would fetch 2 sets of data,it works with yours also),and pass to the method the amout to group by:
//this is the datastructure to hold the results
static List<KeyValuePair<string, List<int>>> Set = new List<KeyValuePair<string, List<int>>>();
private static void GetData(List<int> lst, int group)
{
int count = 1;
int pivot = lst.First();
if (lst.Count < group)
{
return;
}
else
{
foreach (int i in lst.Skip(1))
{
if (i == pivot)
{
count++;
}
else if (count == group)
{
Set.Add(new KeyValuePair<string, List<int>>("Set of items " + pivot, lst.Take(count).ToList()));
GetData(lst.Skip(count).ToList(), group);
break;
}
else
{
GetData(lst.Skip(count).ToList(), group);
break;
}
}
}
}
Then in Main():
static void Main(string[] args)
{
List<int> test = new List<int> { 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0 };
GetData(test, 6);
foreach (var item in Set)
{
Console.WriteLine("\t" + item.Key);
foreach (var subitem in item.Value)
{
Console.WriteLine(subitem);
}
}
}
I have a datagridview on my Winform GUI which loads a CSV and gathers data with three columns:
File_ID, Details, End Line
0, sometext, 1
0, sometext, 3
0, sometext, 5
1, sometext, 9
1, sometext, 16
1, sometext, 23
2, sometext, 25
2, sometext, 27
2, sometext, 28
2, sometext, 30
I want this gridview to be translated to a list
so that I can do something like this:
pList.Add(new FileExtract(1, 1, 148165));
pList.Add(new FileExtract(2, 148165, 166926));
but a loop rather than hardcording Add(new FileExtract ... )
The list should look something like this:
ID, start, end
1, 0, 5
2, 6, 23
3, 24, 30
Note that:
- For first line: ID = 1, start = 0.
- ID = 1 relates to File_ID, think of ID as counter
- Start on ID 2, is end of ID 1 plus 1, start on ID 3 = end of ID 2 plus
The bit that works:
var filesplitc = from p in ListBoxEdit1
group p by p.file_id into grp
let MaxP = grp.Max(g => g.RunningTotal)
from p in grp
where p.RunningTotal == MaxP
select p;
var filesplitc1 = from p in filesplitc
select new { file_id = p.file_id, startingline = (p.file_id == 0) ? "0" : "", endingline = p.RunningTotal };
The bit that doesn't work:
var filesplitc2 = from p in filesplitc1
select new {
file_id = p.file_id,
startingline = p.startingline == "" ? ((from x in filesplitc1 where (Convert.ToInt32(x.file_id) <= Convert.ToInt32(p.file_id)) select x.endingline).Last()) : p.startingline,
endingline = p.endingline
};
You probably have to adjust it a bit because I don't know what data structure you put into the list box but should give you an idea. It's not Linq but trying to squash everything into a Linq query does not necessarily help readability and code maintenance.
class FileExtract
{
public int FileId { get; set; }
public int Start { get; set; }
public int End { get; set; }
}
var fileExtracts = new List<FileExtract>();
// take first entry as initializer
var current = new FileExtract
{
FileId = ListBoxEdit1.First().file_id,
Start = 0,
End = ListBoxEdit1.First().end_line
};
int lastEnd = current.End;
// skip first entry of the list as it was already used as initializer
foreach (var p in ListBoxEdit1.Skip())
{
if (current.FileId != p.file_id)
{
current.End = lastEnd;
fileExtract.Add(current);
current = new FileExtract { FileId = p.file_id, Start = lastEnd + 1, End = p.end_line };
}
lastEnd = p.end_line;
}