I have a few classes that have a sequence of integer, those sequences are registered in another class that checks if a number in the sequence isn't already used.
The sequences are for the most contiguous, going from a number to another.
For now I've been using a simple List that means if a sequence represents from 5000 to 15000 there will be 10000 elements in the List.
I would like to replace it by something more suitable that can represents range in a simple element.
In my particular case, I would also like those ranges to represent an object (the class where the sequence originated from), so that when I lookup for a number, I have access to its origin rather than looking through each class to see if they contain the number I'm looking for.
Here is my pseudocode with the results I expect:
/* int is the integer type, while string is the "tag" object */
var animals = new IntRangeArray<int, string>();
animals.Add(1, "dog");
// [0] begin: 1, end: 1, object: "dog"
animals.Add(2, "dog");
// [0] begin: 1, end: 2, object: "dog"
/* AddRange with C#7.0 ValueTuple */
animals.AddRange((4,14), "dog");
// [0] begin: 1, end: 2, object: "dog"
// [1] begin: 4, end: 14, object: "dog"
animals.Add(3, "dog");
// [0] begin: 1, end: 14, object: "dog"
/* All sequences have been merged because they are contiguous and have the same tag */
animals.AddRange( new int[]{ 15, 17, 18, 19 }, "dog");
// [0] begin: 1, end: 15, object: "dog"
// [1] begin: 17, end: 19, object: "dog"
animals.Add(16, "cat");
// [0] begin: 1, end: 15, object: "dog"
// [1] begin: 16, end: 16, object: "cat"
// [2] begin: 17, end: 19, object: "dog"
animals.Remove(8);
// [0] begin: 1, end: 7, object: "dog"
// [1] begin: 9, end: 15, object: "dog"
// [2] begin: 16, end: 16, object: "cat"
// [3] begin: 17, end: 18, object: "dog"
animals.At(11);
// struct { Begin = 9, End = 15, Tag = "dog" }
animals.RemoveWithTag("dog");
// [0] begin: 16, end: 16, object: "cat"
animals.TagOf(16);
// "cat"
I could not find any classes within the .NET Framework that implements this behavior so I would like to know how could I implement this or if there is any already existing implementation.
For this kind of thing I usually end up writing my own classes. Here's what I would do for this:
First, the Range class, which has a Begin, End, and Tag. It also has some helper methods to simplify querying for ranges that are overlapping and adjacent, or for doing case-insensitive tag comparison, and for outputting a string value:
class Range
{
public int Begin { get; set; }
public int End { get; set; }
public string Tag { get; set; }
public bool CombineWith(Range other)
{
Range combinedRange;
if (TryCombine(this, other, out combinedRange))
{
this.Begin = combinedRange.Begin;
this.End = combinedRange.End;
return true;
}
return false;
}
public bool IsAdjacentTo(Range other)
{
return AreAdjacent(this, other);
}
public bool OverlapsWith(Range other)
{
return AreOverlapping(this, other);
}
public bool ContainsIndex(int index)
{
return this.Begin <= index && this.End >= index;
}
public bool TagEquals(string tag)
{
if (this.Tag == null) return tag == null;
return this.Tag.Equals(tag, StringComparison.OrdinalIgnoreCase);
}
public static bool TryCombine(Range first, Range second, out Range combined)
{
combined = new Range();
if (first == null || second == null) return false;
if (!TagsEqual(first, second)) return false;
if (!AreAdjacent(first, second) && !AreOverlapping(first, second)) return false;
combined.Begin = Math.Min(first.Begin, second.Begin);
combined.End = Math.Max(first.End, second.End);
combined.Tag = first.Tag;
return true;
}
public static bool AreAdjacent(Range first, Range second)
{
if (first == null || second == null) return false;
if (!Range.TagsEqual(first, second)) return false;
return (first.Begin == second.End + 1) ||
(first.End == second.Begin - 1);
}
public static bool AreOverlapping(Range first, Range second)
{
if (first == null || second == null) return false;
return (first.Begin >= second.Begin && first.Begin <= second.End) ||
(first.End >= second.Begin && first.End <= second.End);
}
public static bool TagsEqual(Range first, Range second)
{
if (first == null || second == null) return false;
return first.TagEquals(second.Tag);
}
public override string ToString()
{
return $"begin: {Begin}, end: {End}, tag: {Tag}";
}
}
Next is your IntRangeArray class, which manages the adding and removing of items in the a list of Range objects:
class IntRangeArray
{
private readonly List<Range> ranges = new List<Range>();
public bool Add(int index, string tag)
{
return AddRange(index, index, tag);
}
public bool AddRange(IEnumerable<int> indexes, string tag)
{
if (indexes == null || string.IsNullOrWhiteSpace(tag)) return false;
bool result = true;
foreach (var index in indexes)
{
if (!Add(index, tag)) result = false;
}
return result;
}
public bool AddRange(Tuple<int, int> range, string tag)
{
return AddRange(range.Item1, range.Item2, tag);
}
public bool AddRange(int begin, int end, string tag)
{
if (begin < 0 || end < 0 || string.IsNullOrWhiteSpace(tag)) return false;
var newRange = new Range {Begin = begin, End = end, Tag = tag};
var overlappingRanges = ranges.Where(r => r.OverlapsWith(newRange)).ToList();
var adjacentRanges = ranges.Where(r => r.IsAdjacentTo(newRange)).ToList();
if (overlappingRanges.Any())
{
if (!overlappingRanges.All(r => r.TagEquals(newRange.Tag)))
{
return false;
}
foreach (var overlappingRange in overlappingRanges)
{
newRange.CombineWith(overlappingRange);
ranges.Remove(overlappingRange);
}
}
foreach (var adjacentRange in adjacentRanges)
{
newRange.CombineWith(adjacentRange);
ranges.Remove(adjacentRange);
}
ranges.Add(newRange);
return true;
}
public string At(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
return matchingRange?.ToString() ?? $"No item exists at {index}";
}
public void Remove(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
if (matchingRange == null) return;
if (matchingRange.Begin == matchingRange.End)
{
ranges.Remove(matchingRange);
}
else if (index == matchingRange.Begin)
{
matchingRange.Begin += 1;
}
else if (index == matchingRange.End)
{
matchingRange.End -= 1;
}
else
{
// Split the range by creating a new one for the beginning
var newRange = new Range
{
Begin = matchingRange.Begin,
End = index - 1,
Tag = matchingRange.Tag
};
matchingRange.Begin = index + 1;
ranges.Add(newRange);
}
}
public void RemoveWithTag(string tag)
{
ranges.RemoveAll(r => r.TagEquals(tag));
}
public string TagOf(int index)
{
var matchingRange = ranges.SingleOrDefault(r => r.ContainsIndex(index));
return matchingRange == null ? $"No item exists at {index}" : matchingRange.Tag;
}
public override string ToString()
{
if (ranges == null || !ranges.Any()) return "No items exist.";
ranges.Sort((x, y) => x.Begin.CompareTo(y.Begin));
var output = new StringBuilder();
for(int i = 0; i < ranges.Count; i++)
{
output.AppendLine($"[{i}] {ranges[i]}");
}
return output.ToString();
}
}
To test it out, I just copied and pasted your code sample above:
private static void Main()
{
/* int is the integer type, while string is the "tag" object */
var animals = new IntRangeArray();
animals.Add(1, "dog");
Console.WriteLine(animals);
animals.Add(2, "dog");
Console.WriteLine(animals);
/* AddRange with C#7.0 ValueTuple */
animals.AddRange(Tuple.Create(4, 14), "dog");
Console.WriteLine(animals);
animals.Add(3, "dog");
Console.WriteLine(animals);
animals.AddRange(new int[] { 15, 17, 18, 19 }, "dog");
Console.WriteLine(animals);
animals.Add(16, "cat");
Console.WriteLine(animals);
animals.Remove(8);
Console.WriteLine(animals);
Console.WriteLine(animals.At(11));
animals.RemoveWithTag("dog");
Console.WriteLine(animals);
Console.WriteLine(animals.TagOf(16));
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
And the output is as you expected (except there is one item different, but that was a bug on your side):
Here is my implementation. I created a help class to manage integer ranges using SortedList and then created the main class to manage tagged integer ranges using a Dictionary to consolidate the tags.
class IntegerRangeCollection {
SortedList<int, int> ranges = new SortedList<int, int>();
public class Range {
public int begin, end;
}
public IntegerRangeCollection() {
}
public IntegerRangeCollection(int b, int e) {
this.Add(b, e);
}
public void Add(int b, int e) {
if (ranges.Any()) {
if (ranges.ContainsKey(b)) {
if (e > ranges[b])
ranges[b] = e;
}
else
ranges.Add(b, e);
FixUp();
}
else
ranges.Add(b, e); // new ranges list
}
public void Add(int p) => this.Add(p, p);
public void Remove(int p) {
var r = ranges.Where(pr => pr.Key <= p && p <= pr.Value).First();
if (r.Key == p) { // Remove Range begin
ranges.Remove(r.Key);
if (p+1 <= r.Value)
ranges.Add(p+1, r.Value);
}
else if (p == r.Value) // Remove Range end
ranges[r.Key] = p - 1;
else { // Split Range
ranges[r.Key] = p-1;
ranges.Add(p+1, r.Value);
}
}
public Range At(int n) {
var kvr = ranges.Where(kv => kv.Key <= n && n <= kv.Value);
if (kvr.Any()) {
var kvrf = kvr.First();
return new Range { begin = kvrf.Key, end = kvrf.Value };
}
else
return null;
}
public bool Contains(int n) => ranges.Where(kv => kv.Key <= n && n <= kv.Value).Any();
public bool IsEmpty() => !ranges.Any();
private bool DoFixUp() { // remove any overlapping ranges
foreach (var r in ranges) {
foreach (var pr in ranges.Where(pr => r.Key != pr.Key && r.Value == pr.Key - 1)) { // consolidate adjacent ranges
ranges.Remove(pr.Key);
ranges[r.Key] = pr.Value;
return true;
}
foreach (var pr in ranges.Where(pr => r.Key != pr.Key && pr.Key <= r.Value && r.Value <= pr.Value)) { // overlap end
if (pr.Key > r.Key) { // partial overlap, extend beginning
ranges.Remove(pr.Key);
ranges[r.Key] = pr.Value;
return true;
}
else { // complete overlap, remove
ranges.Remove(r.Key);
return true;
}
}
}
return false;
}
private void FixUp() {
while (DoFixUp())
;
}
}
class ObjectRangeCollection<objType> where objType : class {
Dictionary<objType, IntegerRangeCollection> d = new Dictionary<objType, IntegerRangeCollection>();
public void Add(int begin, int end, objType obj) {
if (d.TryGetValue(obj, out var ranges))
ranges.Add(begin, end);
else
d.Add(obj, new IntegerRangeCollection(begin, end));
}
public void Add(int p, objType obj) => Add(p, p, obj);
public void AddRange(ValueTuple<int, int> r, objType obj) => Add(r.Item1, r.Item2, obj);
public void AddRange(int[] rs, objType obj) {
foreach (var r in rs)
this.Add(r, r, obj);
}
public class AtAnswer {
public int begin, end;
public object tag;
}
public AtAnswer At(int p) {
var possibles = d.Where(kv => kv.Value.Contains(p));
if (possibles.Any()) {
var kv = possibles.First();
var r = kv.Value.At(p);
return new AtAnswer { tag = kv.Key, begin = r.begin, end = r.end };
}
else
return null;
}
public objType TagOf(int p) {
var possibles = d.Where(kv => kv.Value.Contains(p));
if (possibles.Any())
return possibles.First().Key;
else
return null;
}
public void Remove(int p) {
var possibles = d.Where(kv => kv.Value.Contains(p));
if (possibles.Any()) {
foreach (var kv in possibles) {
kv.Value.Remove(p);
if (kv.Value.IsEmpty())
d.Remove(kv.Key);
}
}
}
public void RemoveWithTag(objType aTag) {
d.Remove(aTag);
}
}
Try something like this
List<KeyValuePair<int, string>> animals = new List<KeyValuePair<int,string>>();
List<KeyValuePair<int, string>> newValues = Enumerable.Repeat(1,11).Select((x,i) => new KeyValuePair<int,string>(i + 4, "dog")).ToList();
animals.AddRange(newValues);
Dictionary<string, List<Tuple<int, int>>> Test = new Dictionary<string,
List<Tuple<int, int>>>();
int rangebegin=1;
int rangeend=4;
Test.Add("dog", new List<Tuple<int, int>>
{ new Tuple<int, int>(rangebegin, rangend) });
rangebegin=16;
rangend=22;
Test[dog].Add(new Tuple<int, int>(rangebegin, rangeend));
// multiple ranges stored under same key
// you can easily calculate which range your number is in and get
// that range
Related
I have a dictionary[mapData] as below and check key is a [Dictionary< string, DateTime >] exists in [mapData], but it did not work:
var mapData = new Dictionary<Dictionary<string, DateTime>, List<Student>>();
foreach (var st in listStudent)
{
// Create Key
var dicKey = new Dictionary<string, DateTime>();
dicKey.Add(st.Name, st.Birthday);
// Get mapData
if (!mapData.ContainsKey(dicKey)) // ===> Can not check key exists
{
mapData.Add(dicKey, new List<Student>());
}
mapData[dicKey].Add(st);
}
I tried with extension method as the below, but also not work:
public static bool Contains<Tkey>(this Dictionary<Tkey, List<Student>> dic, Tkey key)
{
if (dic.ContainsKey(key))
return true;
return false;
}
Any tips on these will be great help. Thanks in advance.
this is because you try to find the key based on object reference not the key content.
Your dictionnary key is a composite of Key + value (string + DateTime) reference.
this wont work unless you rewrite and IEqualityComparer.
var objectA = new ObjectA();
var objectB = new ObjectA();
objectA != objectB unless you rewritte the equals.
EDIT
This sample is comming from MSDN https://msdn.microsoft.com/en-us/library/ms132151(v=vs.110).aspx
using System;
using System.Collections.Generic;
class Example
{
static void Main()
{
BoxEqualityComparer boxEqC = new BoxEqualityComparer();
var boxes = new Dictionary<Box, string>(boxEqC);
var redBox = new Box(4, 3, 4);
AddBox(boxes, redBox, "red");
var blueBox = new Box(4, 3, 4);
AddBox(boxes, blueBox, "blue");
var greenBox = new Box(3, 4, 3);
AddBox(boxes, greenBox, "green");
Console.WriteLine();
Console.WriteLine("The dictionary contains {0} Box objects.",
boxes.Count);
}
private static void AddBox(Dictionary<Box, String> dict, Box box, String name)
{
try {
dict.Add(box, name);
}
catch (ArgumentException e) {
Console.WriteLine("Unable to add {0}: {1}", box, e.Message);
}
}
}
public class Box
{
public Box(int h, int l, int w)
{
this.Height = h;
this.Length = l;
this.Width = w;
}
public int Height { get; set; }
public int Length { get; set; }
public int Width { get; set; }
public override String ToString()
{
return String.Format("({0}, {1}, {2})", Height, Length, Width);
}
}
class BoxEqualityComparer : IEqualityComparer<Box>
{
public bool Equals(Box b1, Box b2)
{
if (b2 == null && b1 == null)
return true;
else if (b1 == null | b2 == null)
return false;
else if(b1.Height == b2.Height && b1.Length == b2.Length
&& b1.Width == b2.Width)
return true;
else
return false;
}
public int GetHashCode(Box bx)
{
int hCode = bx.Height ^ bx.Length ^ bx.Width;
return hCode.GetHashCode();
}
}
// The example displays the following output:
// Unable to add (4, 3, 4): An item with the same key has already been added.
//
// The dictionary contains 2 Box objects.
You don't want to go there. Complex objects are not meant to be keys in a dictionary. I would suggest to move the dictionary to the value and create a more tree-like structure.
There is one possibility to get this working, but I would advice against it for above reasons: the implementation of a custom IEqualityComparer which compares the keys in the dictionary against another dictionary.
I have a list of ranges and I would like to find out if they overlap.
I have the following code. Which does not seem to be working. Is there an easier way to do this or a way that works :)
Thanks in advance for any advice.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private IList<Range> rangeList;
private void Form1_Load(object sender, EventArgs e)
{
rangeList.Add(new Range{FromNumber = 0, ToNumber = 100});
rangeList.Add(new Range { FromNumber = 101, ToNumber = 200 });
// this range should over lap and throw an exception
rangeList.Add(new Range { FromNumber = 199, ToNumber = 300 });
}
private bool RangesOverlap()
{
var bigList = new List<List<int>>();
foreach (var range in this.rangeList)
{
bigList.Add(new List<int> { range.FromNumber , range.ToNumber });
}
IEnumerable<IEnumerable<int>> lists = bigList;
return lists
.Where(c => c != null && c.Any())
.Aggregate(Enumerable.Intersect)
.ToList().Count > 0;
}
}
public class Range
{
public int FromNumber { get; set; }
public int ToNumber { get; set; }
}
First merge numbers and then check generated list is in sorted order:
rangeList
.OrderBy(p => p.FromNumber)
.Select(p => new[] { p.FromNumber, p.ToNumber })
.SelectMany(p => p)
.Aggregate((p, q) => q >= p ? q : int.MaxValue) == int.MaxValue
In the past I faced a challenge where I had to write a validating algorithm for ranges that are created by the user (integers or reals). I had to check 3 things:
Ranges are continuous
Ranges are not overlapping
LOW value must be always <= than HIGH
So I came up with the following PHP algorithm.
//Let's hardcode an array of integers (it can be of reals as well):
$range = array
(
array(1, 13),
array(14, 20),
array(21, 45),
array(46, 50),
array(51, 60)
);
//And the validation algorithm:
$j = 0;
$rows = sizeof($range);
$step = 1; // This indicates the step of the values.
// 1 for integers, 0.1 or 0.01 for reals
for ($x=0; $x<$rows; $x++)
for ($y=0; $y<$rows; $y++) {
if ( ($range[$x][0] <= $range[$y][0]) AND ($range[$y][0] <= $range[$x][1]) AND ($x != $y) ) {
echo "Ranges intercepting"; // replace with error handling code
break 3;
}
if ( $range[$x][0] > $range[$x][1] ) {
echo "Not valid high & low"; // replace with error handling code
break 3;
}
if ( $range[$x][0] - $range[$y][1] == $step ) {
$j++;
}
}
if ( $j != $rows - $step )
echo "Not continuous"; // replace with error handling code
We iterate through the ranges and compare them in pairs. For each pair we:
find overlapping ranges and raise an error
find start & end points in reverse and raise an error
If none of the above occurs, we count the difference of end - start points. This number must be equals to the number of ranges minus the step (1, 0.1, 0.01, etc). Otherwise we raise an error.
Hope this helps!
You can fulfill your new requirement with a slight modification of RezaArabs answer:
rangeList
.Select(p => new[] { p.FromNumber, p.ToNumber })
.SelectMany(p => p.Distinct())
.Aggregate((p, q) => q >= p ? q : int.MaxValue) == int.MaxValue
The solution to this problem can be as simple as writing your own RangeList : IList<Range> whose Add() method throws an exception when the specified range overlaps with one or more ranges that are already in the collection.
Working example:
class Range
{
public int FromNumber { get; set; }
public int ToNumber { get; set; }
public bool Intersects(Range range)
{
if ( this.FromNumber <= range.ToNumber )
{
return (this.ToNumber >= range.FromNumber);
}
else if ( this.ToNumber >= range.FromNumber )
{
return (this.FromNumber <= range.ToNumber);
}
return false;
}
}
class RangeList : IList<Range>
{
private readonly IList<Range> innerList;
#region Constructors
public RangeList()
{
this.innerList = new List<Range>();
}
public RangeList(int capacity)
{
this.innerList = new List<Range>(capacity);
}
public RangeList(IEnumerable<Range> collection)
{
if ( collection == null )
throw new ArgumentNullException("collection");
var overlap = from left in collection
from right in collection.SkipWhile(right => left != right)
where left != right
select left.Intersects(right);
if ( overlap.SkipWhile(value => value == false).Any() )
throw new ArgumentOutOfRangeException("collection", "The specified collection contains overlapping ranges.");
this.innerList = new List<Range>(collection);
}
#endregion
private bool IsUniqueRange(Range range)
{
if ( range == null )
throw new ArgumentNullException("range");
return !(this.innerList.Any(range.Intersects));
}
private Range EnsureUniqueRange(Range range)
{
if ( !IsUniqueRange(range) )
{
throw new ArgumentOutOfRangeException("range", "The specified range overlaps with one or more other ranges in this collection.");
}
return range;
}
public Range this[int index]
{
get
{
return this.innerList[index];
}
set
{
this.innerList[index] = EnsureUniqueRange(value);
}
}
public void Insert(int index, Range item)
{
this.innerList.Insert(index, EnsureUniqueRange(item));
}
public void Add(Range item)
{
this.innerList.Add(EnsureUniqueRange(item));
}
#region Remaining implementation details
public int IndexOf(Range item)
{
return this.innerList.IndexOf(item);
}
public void RemoveAt(int index)
{
this.innerList.RemoveAt(index);
}
public void Clear()
{
this.innerList.Clear();
}
public bool Contains(Range item)
{
return this.innerList.Contains(item);
}
public void CopyTo(Range[] array, int arrayIndex)
{
this.innerList.CopyTo(array, arrayIndex);
}
public int Count
{
get { return this.innerList.Count; }
}
public bool IsReadOnly
{
get { return this.innerList.IsReadOnly; }
}
public bool Remove(Range item)
{
return this.innerList.Remove(item);
}
public IEnumerator<Range> GetEnumerator()
{
return this.innerList.GetEnumerator();
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return this.innerList.GetEnumerator();
}
#endregion
}
Usage:
IList<Range> rangeList = new RangeList();
try
{
rangeList.Add(new Range { FromNumber = 12, ToNumber = 12 });
rangeList.Add(new Range { FromNumber = 13, ToNumber = 20 }); // should NOT overlap
rangeList.Add(new Range { FromNumber = 12, ToNumber = 20 }); // should overlap
}
catch ( ArgumentOutOfRangeException exception )
{
Console.WriteLine(exception.Message);
}
I got a lot of data from a database, which are results from a search function. Now I've a List<string[]> which has duplicated elements of type string[]. The string[] in the list are the search results.
I know that every new created array has a different instance so i can't use MyListOfArrays.Distinct().ToList().
Maybe it's a very basic question...
My question is, are there any functions built in to remove a duplicated string[] form the List<string[]>? Or do I have to write it by my selfe?
Thank you
You can use distinct method with custom equalityComparer
IEnumerable<string[]> distinct = inputStringArrayList.Distinct(new EqualityComparer());
EqualityComparer
class EqualityComparer : IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if (x.Length != y.Length)
{
return false;
}
if (x.Where((t, i) => t != y[i]).Any())
{
return false;
}
return true;
}
public int GetHashCode(string[] obj)
{
return obj.GetHashCode();
}
}
Alternative Equals Method
public bool Equals(string[] x, string[] y)
{
return x.SequenceEqual(y);
}
Here I am assuming you are having exact same string arrays with same content at same index.
Correction from Matthew Watson
public int GetHashCode(string[] obj)
{
if (obj == null)
return 0;
int hash = 17;
unchecked
{
foreach (string s in obj)
hash = hash*23 + ((s == null) ? 0 : s.GetHashCode());
}
return hash;
}
I have corrected the answer from #Muctadir Dinar.
(He deserves credit for the answer - I am just correcting it and providing a complete test program):
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
sealed class EqualityComparer: IEqualityComparer<string[]>
{
public bool Equals(string[] x, string[] y)
{
if (ReferenceEquals(x, y))
return true;
if (x == null || y == null)
return false;
return x.SequenceEqual(y);
}
public int GetHashCode(string[] obj)
{
if (obj == null)
return 0;
int hash = 17;
unchecked
{
foreach (string s in obj)
hash = hash*23 + ((s == null) ? 0 : s.GetHashCode());
}
return hash;
}
}
class Program
{
private void run()
{
var list = new List<string[]>
{
strings(1, 10),
strings(2, 10),
strings(3, 10),
strings(2, 10),
strings(4, 10)
};
dump(list);
Console.WriteLine();
var result = list.Distinct(new EqualityComparer());
dump(result);
}
static void dump(IEnumerable<string[]> list)
{
foreach (var array in list)
Console.WriteLine(string.Join(",", array));
}
static string[] strings(int start, int count)
{
return Enumerable.Range(start, count)
.Select(element => element.ToString())
.ToArray();
}
static void Main(string[] args)
{
new Program().run();
}
}
}
A simple and not very efficient approach would be to use string.Join on the string[]:
list = list
.GroupBy(strArr => string.Join("|", strArr))
.Select(g => g.First())
.ToList();
I have a collection of Layers where they have names and colors. What I want to do is to sort these first based on colors, then based on their names:
class Layer
{
public string Name {get; set;}
public LayerColor Color {get; set;}
}
enum LayerColor
{
Red,
Blue,
Green
}
Like:
(red) layer2
(red) layer7
(blue) layer0
(blue) layer3
...
I was looking at SortedList but that acts like a Dictionary so doesn't allow for duplicate items.
Also I am using an API where I get the list of Layers by creation order, so I need to get the full list of Layers to sort them the way I want.
Eventually the list of Layers will be binded to a WPF UI where the users will have the ability to add new Layers, so that's why I wanted the internal list to always be sorted as the performance is not important (the number of Layers are less than a thousand).
In the end the Layers I sorted will be accessed via something like this:
class Image
{
public MySortedList<Layer> Layers {get; set;}
}
What's the best way to do this?
A little late to the party, but up for posterity's sake.
in order to optimise separation of concerns, I wrote a wrapper class which keeps a list sorted (and allows duplicates), as below:
public class OrderedList<T> : IList<T>, ICollection<T>, IList, ICollection, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, IEnumerable
{
#region Fields
readonly List<T> _list;
readonly IComparer<T> _comparer;
#endregion
#region Constructors
OrderedList(List<T> list, IComparer<T> comparer)
{
_list = list;
_comparer = comparer;
}
public OrderedList()
: this(new List<T>(), Comparer<T>.Default)
{
}
public OrderedList(IComparer<T> comparer)
: this(new List<T>(), comparer)
{
}
public OrderedList(IEnumerable<T> collection)
: this(collection, Comparer<T>.Default)
{
}
public OrderedList(IEnumerable<T> collection, IComparer<T> comparer)
: this(new List<T>(collection), comparer)
{
_list.Sort(comparer);
}
public OrderedList(int capacity)
: this(new List<T>(capacity), Comparer<T>.Default)
{
}
public OrderedList(int capacity, IComparer<T> comparer)
: this(new List<T>(capacity), comparer)
{
}
//yet to be implemented
//public void OrderedList(Comparison<T> comparison);
#endregion
#region Properties
public int Capacity { get { return _list.Capacity; } set { _list.Capacity = value; } }
public int Count { get { return _list.Count; } }
object IList.this[int index] { get { return _list[index]; } set { _list[index] = (T)value; } }
public T this[int index] { get { return _list[index]; } set { _list[index] = value; } }
//public bool IsSynchronized { get { return false; } }
bool ICollection.IsSynchronized { get { return false; } }
//public object SyncRoot { get { return _list; } }
object ICollection.SyncRoot { get { return _list; } } //? should return this
bool IList.IsFixedSize { get { return false; } }
bool IList.IsReadOnly { get { return false; } }
bool ICollection<T>.IsReadOnly { get { return false; } }
#endregion
#region Methods
void ICollection<T>.Add(T item)
{
Add(item);
}
/// <summary>
/// Adds a new item to the appropriate index of the SortedList
/// </summary>
/// <param name="item">The item to be removed</param>
/// <returns>The index at which the item was inserted</returns>
public int Add(T item)
{
int index = BinarySearch(item);
if (index < 0)
{
index = ~index;
}
_list.Insert(index, item);
return index;
}
int IList.Add(object item)
{
return Add((T)item);
}
//NOT performance tested against other ways algorithms yet
public void AddRange(IEnumerable<T> collection)
{
var insertList = new List<T>(collection);
if (insertList.Count == 0)
{
return;
}
if (_list.Count == 0)
{
_list.AddRange(collection);
_list.Sort(_comparer);
return;
}
//if we insert backwards, index we are inserting at does not keep incrementing
insertList.Sort(_comparer);
int searchLength = _list.Count;
for (int i=insertList.Count-1;i>=0;i--)
{
T item = insertList[i];
int insertIndex = BinarySearch(0, searchLength, item);
if (insertIndex < 0)
{
insertIndex = ~insertIndex;
}
else
{
while (--insertIndex>=0 && _list[insertIndex].Equals(item)) { }
insertIndex++;
}
if (insertIndex<=0)
{
_list.InsertRange(0, insertList.GetRange(0, i+1 ));
break;
}
searchLength = insertIndex-1;
item = _list[searchLength];
int endInsert = i;
while (--i>=0 && _comparer.Compare(insertList[i], item) > 0) { }
i++;
_list.InsertRange(insertIndex, insertList.GetRange(i, endInsert - i +1));
}
}
public int BinarySearch(T item)
{
return _list.BinarySearch(item, _comparer);
}
public int BinarySearch(int index, int count, T item)
{
return _list.BinarySearch(index,count,item, _comparer);
}
public ReadOnlyCollection<T> AsReadOnly()
{
return _list.AsReadOnly();
}
public void Clear() { _list.Clear(); }
public bool Contains(T item) { return BinarySearch(item) >= 0; }
bool IList.Contains(object item)
{
return Contains((T)item);
}
public List<TOutput> ConvertAll<TOutput>(Converter<T, TOutput> converter) { return _list.ConvertAll(converter); }
public void CopyTo(T[] array) { _list.CopyTo(array); }
public void CopyTo(T[] array, int arrayIndex) { _list.CopyTo(array,arrayIndex); }
void ICollection.CopyTo(Array array, int arrayIndex) { _list.CopyTo((T[])array, arrayIndex); }
public void CopyTo(int index, T[] array, int arrayIndex, int count) { _list.CopyTo(index, array, arrayIndex, count); }
public void ForEach(Action<T> action)
{
foreach (T item in _list)
{
action(item);
}
}
IEnumerator IEnumerable.GetEnumerator() { return _list.GetEnumerator(); }
public IEnumerator<T> GetEnumerator() { return _list.GetEnumerator(); }
public List<T> GetRange(int index, int count) { return _list.GetRange(index,count); }
public bool Remove(T item)
{
int index = BinarySearch(item);
if (index < 0)
{
return false;
}
_list.RemoveAt(index);
return true;
}
void IList.Remove(object item)
{
Remove((T)item);
}
public void RemoveAt(int index) { _list.RemoveAt(index); }
public void RemoveRange(int index, int count) { _list.RemoveRange(index, count); }
public T[] ToArray() { return _list.ToArray(); }
public void TrimExcess() { _list.TrimExcess(); }
/// <summary>
/// Find the first index of the given item
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int IndexOf(T item)
{
int index = BinarySearch(item);
if (index < 0) return -1;
while(--index >= 0 && _list[index].Equals(item)){}
return index+1;
}
int IList.IndexOf(object item)
{
return IndexOf((T)item);
}
/// <summary>
/// Find the last index of the given item
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public int LastIndexOf(T item)
{
int index = BinarySearch(item);
if (index < 0) return -1;
while (++index < _list.Count && _list[index].Equals(item)) { }
return index-1;
}
/// <summary>
/// Return all values within bounds specified
/// </summary>
/// <param name="min">Minimum Bound</param>
/// <param name="max">Maximum Bound</param>
/// <returns>subset of list with values within or equal to bounds specified</returns>
public T[] WithinRange(T min, T max)
{
if (_comparer.Compare(min,max) > 0)
{
throw new ArgumentException("min must be <= max");
}
int minSearchLength;
int maxIndex = _list.BinarySearch(max, _comparer);
if (maxIndex >= 0)
{
minSearchLength = maxIndex + 1;
while (++maxIndex < _list.Count && _comparer.Compare(max, _list[maxIndex]) == 0) { }
--maxIndex;
}
else
{
minSearchLength = ~maxIndex;
if (minSearchLength <= 0)
{
return new T[0];
}
maxIndex = minSearchLength - 1;
}
int minIndex = _list.BinarySearch(0, minSearchLength, min, _comparer);
if (minIndex >= 0)
{
while (--minIndex >= 0 && _comparer.Compare(max, _list[minIndex]) == 0) { }
++minIndex;
}
else
{
minIndex = ~minIndex;
if (minIndex > maxIndex)
{
return new T[0];
}
}
int length = maxIndex - minIndex + 1;
var returnVar = new T[length];
_list.CopyTo(minIndex, returnVar, 0, length);
return returnVar;
}
#endregion
#region NotImplemented
const string _insertExceptionMsg = "SortedList detemines position to insert automatically - use add method without an index";
void IList.Insert(int index, object item)
{
throw new NotImplementedException(_insertExceptionMsg);
}
void IList<T>.Insert(int index, T item)
{
throw new NotImplementedException(_insertExceptionMsg);
}
#endregion
}
Tests written are not extensive (or pretty) but are included in case anyone wanted to expand on them
[TestClass]
public class TestOrderedList
{
[TestMethod]
public void TestIntegerList()
{
var startList = new List<int>(new int[] { 5, 2, 1, 4, 5, 5, 2 });
var olist = new OrderedList<int>(startList);
startList = startList.OrderBy(l => l).ToList();
CollectionAssert.AreEqual(startList, olist);
Assert.AreEqual(0, olist.Add(0));
int nextInc = olist.Max() + 1;
Assert.AreEqual(olist.Count, olist.Add(nextInc));
CollectionAssert.AreEqual(startList.Concat(new int[] { 0, nextInc }).OrderBy(l => l).ToList(), olist);
Assert.IsTrue(olist.Remove(0));
Assert.IsFalse(olist.Remove(0));
Assert.IsTrue(olist.Remove(nextInc));
CollectionAssert.AreEqual(startList, olist);
var addList = new List<int>(new int[] { 5, -1, 2, 2, -1, 3, 2 });
olist.AddRange(addList);
addList = startList.Concat(addList).OrderBy(l => l).ToList();
CollectionAssert.AreEqual(addList, olist);
olist.Remove(-1);
addList.Remove(-1);
CollectionAssert.AreEqual(addList, olist);
olist.Remove(2);
addList.Remove(2);
CollectionAssert.AreEqual(addList, olist);
olist = new OrderedList<int>();
int[] seed = new int[] { -2, -2 };
olist.AddRange(seed);
CollectionAssert.AreEqual(seed, olist);
olist.AddRange(new int[] { });
olist.AddRange(new int[] { -2 });
CollectionAssert.AreEqual(seed.Concat(new int[] { -2 }).ToList(), olist);
olist.AddRange(new int[] { -3 });
CollectionAssert.AreEqual((new int[] { -3, -2 }).Concat(seed).ToList(), olist);
}
[TestMethod]
public void TestIndexOf()
{
var test = new OrderedList<int>(new[] { 0, -1, -2 });
Assert.AreEqual(0, test.IndexOf(-2));
Assert.AreEqual(2, test.IndexOf(0));
test.Add(-2);
Assert.AreEqual(0, test.IndexOf(-2));
Assert.AreEqual(1, test.LastIndexOf(-2));
test.Add(0);
Assert.AreEqual(3, test.IndexOf(0));
Assert.AreEqual(4, test.LastIndexOf(0));
}
[TestMethod]
public void TestRangeFinding()
{
var test = new OrderedList<int> { 2 };
CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(0, 6));
CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(0, 2));
CollectionAssert.AreEqual(new[] { 2 }, test.WithinRange(2, 4));
CollectionAssert.AreEqual(new int[0], test.WithinRange(-6, 0));
CollectionAssert.AreEqual(new int[0], test.WithinRange(6, 8));
test = new OrderedList<int>();
CollectionAssert.AreEqual(new int[0], test.WithinRange(6, 8));
test = new OrderedList<int>{ -4, -2, 0 ,4, 6, 6 };
CollectionAssert.AreEqual(new[] { 0, 4 }, test.WithinRange(0, 4));
CollectionAssert.AreEqual(new[] { 0, 4 }, test.WithinRange(-1, 5));
CollectionAssert.AreEqual(new[] { 6, 6 }, test.WithinRange(6, 8));
CollectionAssert.AreEqual(new[] { 6, 6 }, test.WithinRange(5, 8));
CollectionAssert.AreEqual(new[] { -4, -2 }, test.WithinRange(-5, -1));
CollectionAssert.AreEqual(new[] { -4, }, test.WithinRange(-4, -3));
CollectionAssert.AreEqual(new int[0], test.WithinRange(-6, -5));
Assert.ThrowsException<ArgumentException>(() => test.WithinRange(6, 4));
}
}
Did you search for it? Generic SortedList and SortedList.
So I missed the duplicate part which make it a little bit harder I agree. But here is how I would solve it:
var sortedList = new SortedList<LayerColor, SortedList<Layer, Layer>>();
var redSortedList = new SortedList<Layer, Layer>();
// Add all layers associated with the color red
sortedList.Add(LayerColor.Red, redSortedList);
Will that work for you. Also, I would prefer to use linq but if you really want a sorted list my solution will most likely work.
Last try:) :
public class YourClass
{
private List<Layer> _layers;
public List<Layer> Layers
{
get
{
_layers = _layers.OrderBy(y => y.LayerColor).ThenBy(y => y.Name).ToList();
return _layers;
}
set
{
_layers = value;
}
}
}
Note that I'm writing directly in browser without testing it in VS (sitting on OS X), but you probably get the point.
You could use the regular List<T>, but call the Sort() method prior to displaying the list, and after new values are added. That should give you the functionality that you need. The performance will be good enough for this application.
Of course, you will have to define your own comparison for it to use, but that shouldn't be too much trouble.
If you don't have any hooks into the add event that can be used to sort the list, then you can wrap the list in a custom collection class as #Justin recommends.
If the sort is only for display purposes, let WPF handle it:
ICollectionView view = CollectionViewSource.GetDefaultView(Layers);
view.SortDescriptions.Add(new SortDescription("Color", ListSortDirection.Ascending);
view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending);
then just bind Layers to your UI ItemsControl.
You're on the right track. I would create a custom collection class that inherits from Collection. In this custom collection you can override on the on insert/on delete methods and sort your collection as items are added/removed from it.
using System.Linq, do:
from layer in layers
orderby layer.Color, layer.Name
select layer
Start by implementing the IComparable interface in Layer and declaring a CompareTo method. Then use a SortedList collection to store your object.
public class Layer : IComparable {
public int CompareTo(object obj) {
//return -1 if this is before obj, 0 is same, 1 is after.
}
}
You can use arraylist and do below linq query to sort them
ArrayList myList = new ArrayList();
Layer obj1 = new Layer();
obj1.Color = LayerColor.Red;
obj1.Name = "Layer1";
myList.Add(obj1);
Layer obj2 = new Layer();
obj2.Color = LayerColor.Green;
obj2.Name = "Layer2";
myList.Add(obj2);
Layer obj3 = new Layer();
obj3.Color = LayerColor.Blue;
obj3.Name = "Layer3";
myList.Add(obj3);
Layer obj4 = new Layer();
obj4.Color = LayerColor.Green;
obj4.Name = "Layer4";
myList.Add(obj4);
var mySortedList = myList.OfType<Layer>().OrderBy(l => l.Color)
.ThenBy(l => l.Name);
Read the edit below for more information.
I have some code below that I use to split a generic list of Object when the item is of a certain type.
public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, TokenType type) {
List<List<object>> t = new List<List<object>>();
int currentT = 0;
t.Add(new List<object>());
foreach (object list in tokens) {
if ((list is Token) && (list as Token).TokenType == type) {
currentT++;
t.Add(new List<object>());
}
else if ((list is TokenType) && ((TokenType)list )== type) {
currentT++;
t.Add(new List<object>());
}
else {
t[currentT].Add(list);
}
}
return t.ToArray();
}
I dont have a clear question as much as I am curious if anyone knows of any ways I can optimize this code. I call it many times and it seems to be quite the beast as far as clock cycles go. Any ideas? I can also make it a Wiki if anyone is interested, maybe we can keep track of the latest changes.
Update: Im trying to parse out specific tokens. Its a list of some other class and Token classes. Token has a property (enum) of TokenType. I need to find all the Token classes and split on each of them.
{a b c T d e T f g h T i j k l T m}
would split like
{a b c}{d e}{f g h}{i j k l}{m}
EDIT UPDATE:
It seems like all of my speed problems come into the constant creation and addition of Generic Lists. Does anyone know how I can go about this without that?
This is the profile of what is happening if it helps anyone.
Your code looks fine.
My only suggestion would be replacing IEnumerable<object> with the non-generic IEnumerable. (In System.Collections)
EDIT:
On further inspection, you're casting more times than necessary.
Replace the if with the following code:
var token = list as Token;
if (token != null && token.TokenType == type) {
Also, you can get rid your currentT variable by writing t[t.Count - 1] or t.Last(). This will make the code clearer, but might have a tiny negative effect on performance.
Alternatively, you could store a reference to the inner list in a variable and use it directly. (This will slightly improve performance)
Finally, if you can change the return type to List<List<Object>>, you can return t directly; this will avoid an array copy and will be noticeably faster for large lists.
By the way, your variable names are confusing; you should swap the names of t and list.
Type-testing and casts can be a performance killer. If at all possible, your token types should implement a common interface or abstract class. Instead of passing in and object, you should pass in an IToken which wraps your object.
Here's some concept code you can use to get started:
using System;
using System.Collections.Generic;
namespace Juliet
{
interface IToken<T>
{
bool IsDelimeter { get; }
T Data { get; }
}
class DelimeterToken<T> : IToken<T>
{
public bool IsDelimeter { get { return true; } }
public T Data { get { throw new Exception("No data"); } }
}
class DataToken<T> : IToken<T>
{
public DataToken(T data)
{
this.Data = data;
}
public bool IsDelimeter { get { return false; } }
public T Data { get; private set; }
}
class TokenFactory<T>
{
public IToken<T> Make()
{
return new DelimeterToken<T>();
}
public IToken<T> Make(T data)
{
return new DataToken<T>(data);
}
}
class Program
{
static List<List<T>> SplitTokens<T>(IEnumerable<IToken<T>> tokens)
{
List<List<T>> res = new List<List<T>>();
foreach (IToken<T> token in tokens)
{
if (token.IsDelimeter)
{
res.Add(new List<T>());
}
else
{
if (res.Count == 0)
{
res.Add(new List<T>());
}
res[res.Count - 1].Add(token.Data);
}
}
return res;
}
static void Main(string[] args)
{
TokenFactory<string> factory = new TokenFactory<string>();
IToken<string>[] tokens = new IToken<string>[]
{
factory.Make("a"), factory.Make("b"), factory.Make("c"), factory.Make(),
factory.Make("d"), factory.Make("e"), factory.Make(),
factory.Make("f"), factory.Make("g"), factory.Make("h"), factory.Make(),
factory.Make("i"), factory.Make("j"), factory.Make("k"), factory.Make("l"), factory.Make(),
factory.Make("m")
};
List<List<string>> splitTokens = SplitTokens(tokens);
for (int i = 0; i < splitTokens.Count; i++)
{
Console.Write("{");
for (int j = 0; j < splitTokens[i].Count; j++)
{
Console.Write("{0}, ", splitTokens[i][j]);
}
Console.Write("}");
}
Console.ReadKey(true);
}
}
}
In principle, you can create instances of IToken<object> to have it generalized to tokens of multiple classes.
A: An all-lazy implementation will suffice if you just iterate through the results in a nested foreach:
using System;
using System.Collections.Generic;
public static class Splitter
{
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, Predicate<T> match)
{
using (IEnumerator<T> enumerator = source.GetEnumerator())
{
while (enumerator.MoveNext())
{
yield return Split(enumerator, match);
}
}
}
static IEnumerable<T> Split<T>(IEnumerator<T> enumerator, Predicate<T> match)
{
do
{
if (match(enumerator.Current))
{
yield break;
}
else
{
yield return enumerator.Current;
}
} while (enumerator.MoveNext());
}
}
Use it like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace MyTokenizer
{
class Program
{
enum TokenTypes { SimpleToken, UberToken }
class Token { public TokenTypes TokenType = TokenTypes.SimpleToken; }
class MyUberToken : Token { public MyUberToken() { TokenType = TokenTypes.UberToken; } }
static void Main(string[] args)
{
List<object> objects = new List<object>(new object[] { "A", Guid.NewGuid(), "C", new MyUberToken(), "D", new MyUberToken(), "E", new MyUberToken() });
var splitOn = TokenTypes.UberToken;
foreach (var list in objects.Split(x => x is Token && ((Token)x).TokenType == splitOn))
{
foreach (var item in list)
{
Console.WriteLine(item);
}
Console.WriteLine("==============");
}
Console.ReadKey();
}
}
}
B: If you need to process the results some time later and you wish to do it out-of-order, or you partition on one thread and then possibly dispatch the segments to multiple threads, then this would probably provide a good starting point:
using System;
using System.Collections.Generic;
using System.Linq;
public static class Splitter2
{
public static IEnumerable<IEnumerable<T>> SplitToSegments<T>(this IEnumerable<T> source, Predicate<T> match)
{
T[] items = source.ToArray();
for (int startIndex = 0; startIndex < items.Length; startIndex++)
{
int endIndex = startIndex;
for (; endIndex < items.Length; endIndex++)
{
if (match(items[endIndex])) break;
}
yield return EnumerateArraySegment(items, startIndex, endIndex - 1);
startIndex = endIndex;
}
}
static IEnumerable<T> EnumerateArraySegment<T>(T[] array, int startIndex, int endIndex)
{
for (; startIndex <= endIndex; startIndex++)
{
yield return array[startIndex];
}
}
}
C: If you really must return a collection of List<T> -s - which I doubt, unless you explicitly want to mutate them some time later on -, then try to initialize them to a given capacity before copying:
public static List<List<T>> SplitToLists<T>(this IEnumerable<T> source, Predicate<T> match)
{
List<List<T>> lists = new List<List<T>>();
T[] items = source.ToArray();
for (int startIndex = 0; startIndex < items.Length; startIndex++)
{
int endIndex = startIndex;
for (; endIndex < items.Length; endIndex++)
{
if (match(items[endIndex])) break;
}
List<T> list = new List<T>(endIndex - startIndex);
list.AddRange(EnumerateArraySegment(items, startIndex, endIndex - 1));
lists.Add(list);
startIndex = endIndex;
}
return lists;
}
D: If this is still not enough, I suggest you roll your own lightweight List implementation that can copy a range directly to its internal array from another instance.
My first thought would be instead of looking up t[currentT] all the time, just store a currentList and add directly to that.
This is the best I could do to eliminate as much of the allocation times as possible for the function (should only allocate when it goes over the capacity, which should be no more than what is required to create the largest sub list in the results). I've tested this implementation and it works as you described.
Please note that the results of the prior sub list are destroyed when the next list in the group is accessed.
public static IEnumerable<IEnumerable> Split(this IEnumerable tokens, TokenType type)
{
ArrayList currentT = new ArrayList();
foreach (object list in tokens)
{
Token token = list as Token;
if ((token != null) && token.TokenType == type)
{
yield return currentT;
currentT.Clear();
//currentT = new ArrayList(); <-- Use this instead of 'currentT.Clear();' if you want the returned lists to be a different instance
}
else if ((list is TokenType) && ((TokenType)list) == type)
{
yield return currentT;
currentT.Clear();
//currentT = new ArrayList(); <-- Use this instead of 'currentT.Clear();' if you want the returned lists to be a different instance
}
else
{
currentT.Add(list);
}
}
}
EDIT
Here's another version that doesn't make use of another list at all (shouldn't be doing any allocations). Not sure how well this will compare, but it does work as requested (also I've got no idea how this one will go if you try to cache a sub 'array').
Also, both of these require a "using System.Collections" statement (in addition to the Generic namespace).
private static IEnumerable SplitInnerLoop(IEnumerator iter, TokenType type)
{
do
{
Token token = iter.Current as Token;
if ((token != null) && token.TokenType == type)
{
break;
}
else if ((iter.Current is TokenType) && ((TokenType)iter.Current) == type)
{
break;
}
else
{
yield return iter.Current;
}
} while (iter.MoveNext());
}
public static IEnumerable<IEnumerable> Split(this IEnumerable tokens, TokenType type)
{
IEnumerator iter = tokens.GetEnumerator();
while (iter.MoveNext())
{
yield return SplitInnerLoop(iter, type);
}
}
I think that there are broken cases for these scenarios where assuming that list items are lower case letters, and the item with matching token type is T:
{ T a b c ... };
{ ... x y z T };
{ ... j k l T T m n o ... };
{ T }; and
{ }
Which will result in:
{ { } { a b c ... } };
{ { ... x y z } { } };
{ { ... j k l } { } { } { m n o ... } };
{ { } }; and
{ }
Doing a straight refactoring:
public static IEnumerable<object>[] Split(this IEnumerable<object> tokens,
TokenType type) {
var outer = new List<List<object>>();
var inner = new List<object>();
foreach (var item in tokens) {
Token token = item as token;
if (token != null && token.TokenType == type) {
outer.Add(inner);
inner = new List<object>();
continue;
}
inner.Add(item);
}
outer.Add(inner);
return outer.ToArray();
}
To fix the broken cases (assuming that those are truly broken), I recommend:
public static IEnumerable<object>[] Split(this IEnumerable<object> tokens,
TokenType type) {
var outer = new List<List<object>>();
var inner = new List<object>();
var enumerator = tokens.GetEnumerator();
while (enumerator.MoveNext()) {
Token token = enumerator.Current as token;
if (token == null || token.TokenType != type) {
inner.Add(enumerator.Current);
}
else if (inner.Count > 0) {
outer.Add(inner);
inner = new List<object>();
}
}
return outer.ToArray();
}
Which will result in:
{ { a b c ... } };
{ { ... x y z } };
{ { ... j k l } { m n o ... } };
{ }; and
{ }
Using LINQ you could try this: (I did not test it...)
public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, TokenType type)
{
List<List<object>> l = new List<List<object>>();
l.Add(new List<object>());
return tokens.Aggregate(l, (c, n) =>
{
var t = n as Token;
if (t != null && t.TokenType == type)
{
t.Add(new List<object>());
}
else
{
l.Last().Add(n);
}
return t;
}).ToArray();
}
Second try:
public static IEnumerable<object>[] Split(this IEnumerable<object> tokens, TokenType type)
{
var indexes = tokens.Select((t, index) => new { token = t, index = index }).OfType<Token>().Where(t => t.token.TokenType == type).Select(t => t.index);
int prevIndex = 0;
foreach (int item in indexes)
{
yield return tokens.Where((t, index) => (index > prevIndex && index < item));
prevIndex = item;
}
}
Here is one possibility
The Token class ( could be what ever class )
public class Token
{
public string Name { get; set; }
public TokenType TokenType { get; set; }
}
Now the Type enum ( this could be what ever other grouping factor )
public enum TokenType
{
Type1,
Type2,
Type3,
Type4,
Type5,
}
The Extention Method (Declare this anyway you choose)
public static class TokenExtension
{
public static IEnumerable<Token>[] Split(this IEnumerable<Token> tokens)
{
return tokens.GroupBy(token => ((Token)token).TokenType).ToArray();
}
}
Sample of use ( I used a web project to spin this )
List<Token> tokens = new List<Token>();
tokens.Add(new Token { Name = "a", TokenType = TokenType.Type1 });
tokens.Add(new Token { Name = "b", TokenType = TokenType.Type1 });
tokens.Add(new Token { Name = "c", TokenType = TokenType.Type1 });
tokens.Add(new Token { Name = "d", TokenType = TokenType.Type2 });
tokens.Add(new Token { Name = "e", TokenType = TokenType.Type2 });
tokens.Add(new Token { Name = "f", TokenType = TokenType.Type3 });
tokens.Add(new Token { Name = "g", TokenType = TokenType.Type3 });
tokens.Add(new Token { Name = "h", TokenType = TokenType.Type3 });
tokens.Add(new Token { Name = "i", TokenType = TokenType.Type4 });
tokens.Add(new Token { Name = "j", TokenType = TokenType.Type4 });
tokens.Add(new Token { Name = "k", TokenType = TokenType.Type4 });
tokens.Add(new Token { Name = "l", TokenType = TokenType.Type4 });
tokens.Add(new Token { Name = "m", TokenType = TokenType.Type5 });
StringBuilder stringed = new StringBuilder();
foreach (Token token in tokens)
{
stringed.Append(token.Name);
stringed.Append(", ");
}
Response.Write(stringed.ToString());
Response.Write("</br>");
var q = tokens.Split();
foreach (var list in tokens.Split())
{
stringed = new StringBuilder();
foreach (Token token in list)
{
stringed.Append(token.Name);
stringed.Append(", ");
}
Response.Write(stringed.ToString());
Response.Write("</br>");
}
So all I am soing is using Linq, feel free to add or remove, you can actualy go crazy on this and group on many diferent properties.
Do you need to convert it to an array? You could potentially use LINQ and delayed execution to return the results.
EDIT:
With the clarified question it would be hard to bend LINQ to make it return the results the way you want. If you still want to have the execution of each cycle delayed you could write your own enumerator.
I recommend perf testing this compared to the other options to see if there is a performance gain for your scenario if you attempt this approach. It might cause more overhead managing the iterator which would be bad for cases with little data.
I hope this helps.
// This is the easy way to make your own iterator using the C# syntax
// It will return sets of separated tokens in a lazy fashion
// This sample is based on the version provided by #Ants
public static IEnumerable<IEnumerable<object>> Split(this IEnumerable<object> tokens,
TokenType type) {
var current = new List<object>();
foreach (var item in tokens)
{
Token token = item as Token;
if (token != null && token.TokenType == type)
{
if( current.Count > 0)
{
yield return current;
current = new List<object>();
}
}
else
{
current.Add(item);
}
}
if( current.Count > 0)
yield return current;
}
Warning: This compiles but has still might have hidden bugs. It is getting late here.
// This is doing the same thing but doing it all by hand.
// You could use this method as well to lazily iterate through the 'current' list as well
// This is probably overkill and substantially more complex
public class TokenSplitter : IEnumerable<IEnumerable<object>>, IEnumerator<IEnumerable<object>>
{
IEnumerator<object> _enumerator;
IEnumerable<object> _tokens;
TokenType _target;
List<object> _current;
bool _isDone = false;
public TokenSplitter(IEnumerable<object> tokens, TokenType target)
{
_tokens = tokens;
_target = target;
Reset();
}
// Cruft from the IEnumerable and generic IEnumerator
public IEnumerator<IEnumerable<object>> GetEnumerator() { return this; }
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerable<object> Current { get { return _current; } }
public void Dispose() { }
#region IEnumerator Members
object System.Collections.IEnumerator.Current { get { return Current; } }
// See if there is anything left to get
public bool MoveNext()
{
if (_isDone) return false;
FillCurrent();
return !_isDone;
}
// Reset the enumerators so that you could reuse this structure if you wanted
public void Reset()
{
_isDone = false;
_enumerator = _tokens.GetEnumerator();
_current = new List<object>();
FillCurrent();
}
// Fills the current set of token and then begins the next set
private void FillCurrent()
{
// Try to accumulate as many tokens as possible, this too could be an enumerator to delay the process more
bool hasNext = _enumerator.MoveNext();
for( ; hasNext; hasNext = _enumerator.MoveNext())
{
Token token = _enumerator.Current as Token;
if (token == null || token.TokenType != _target)
{
_current.Add(_enumerator.Current);
}
else
{
_current = new List<object>();
}
}
// Continue removing matching tokens and begin creating the next set
for( ; hasNext; hasNext = _enumerator.MoveNext())
{
Token token = _enumerator.Current as Token;
if (token == null || token.TokenType != _target)
{
_current.Add(_enumerator.Current);
break;
}
}
_isDone = !hasNext;
}
#endregion
}