IComparer for string that checks if x starts with y - c#

I've got an array of strings and I need to get all strings, that start with some 'prefix'. I wanna use Array.BinarySearch(). Is it possible? And how should I write a comparer if so?

No, you cannot use BinarySearch in this case. You could use Enumerable.Where instead:
Dim query = From str In array Where str.StartsWith("prefix")
or with (ugly in VB.NET) method synatx:
query = array.Where(Function(str) str.StartsWith("prefix"))
Edit: whoops, C#
var query = array.Where(s => s.StartsWith("prefix"));
Use ToArray if you want to create a new filtered array.

It's easy to create your own StartsWithComparer:
class StartsWithComparer : IComparer<string>
{
public int Compare(string a, string b) {
if(a.StartsWith(b)) {
return 0;
}
return a.CompareTo(b);
}
}
As others pointed out, this will only return one index. You can have a couple of helpers to return all items:
IEnumerable<string> GetBefore(IList<string> sorted, int foundIndex, string prefix) {
for(var i = foundIndex - 1; i >= 0; i--) {
if(sorted[i].StartsWith(prefix)) {
yield return sorted[i];
}
}
}
IEnumerable<string> GetCurrentAndAfter(IList<string> sorted, int foundIndex, string prefix) {
for(var i = foundIndex; i < sorted.Count; i++) {
if(sorted[i].StartsWith(prefix)) {
yield return sorted[i];
}
}
}
Then to use it:
var index = sorted.BinarySearch("asdf", new StartsWithComparer());
var previous = GetBefore(sorted, index, "asdf");
var currentAndAfter = GetCurrentAndAfter(sorted, index, "asdf");
You can wrap the whole thing in your own class, with a single method that returns all items that start with your prefix.

Related

Split a list of objects into sub-lists of contiguous elements using LINQ?

I have a simple class Item:
public class Item
{
public int Start { get; set;}
public int Stop { get; set;}
}
Given a List<Item> I want to split this into multiple sublists of contiguous elements. e.g. a method
List<Item[]> GetContiguousSequences(Item[] items)
Each element of the returned list should be an array of Item such that list[i].Stop == list[i+1].Start for each element
e.g.
{[1,10], [10,11], [11,20], [25,30], [31,40], [40,45], [45,100]}
=>
{{[1,10], [10,11], [11,20]}, {[25,30]}, {[31,40],[40,45],[45,100]}}
Here is a simple (and not guaranteed bug-free) implementation that simply walks the input data looking for discontinuities:
List<Item[]> GetContiguousSequences(Item []items)
{
var ret = new List<Item[]>();
var i1 = 0;
for(var i2=1;i2<items.Length;++i2)
{
//discontinuity
if(items[i2-1].Stop != items[i2].Start)
{
var num = i2 - i1;
ret.Add(items.Skip(i1).Take(num).ToArray());
i1 = i2;
}
}
//end of array
ret.Add(items.Skip(i1).Take(items.Length-i1).ToArray());
return ret;
}
It's not the most intuitive implementation and I wonder if there is a way to have a neater LINQ-based approach. I was looking at Take and TakeWhile thinking to find the indices where discontinuities occur but couldn't see an easy way to do this.
Is there a simple way to use IEnumerable LINQ algorithms to do this in a more descriptive (not necessarily performant) way?
I set of a simple test-case here: https://dotnetfiddle.net/wrIa2J
I'm really not sure this is much better than your original, but for the purpose of another solution the general process is
Use Select to project a list working out a grouping
Use GroupBy to group by the above
Use Select again to project the grouped items to an array of Item
Use ToList to project the result to a list
public static List<Item[]> GetContiguousSequences2(Item []items)
{
var currIdx = 1;
return items.Select( (item,index) => new {
item = item,
index = index == 0 || items[index-1].Stop == item.Start ? currIdx : ++currIdx
})
.GroupBy(x => x.index, x => x.item)
.Select(x => x.ToArray())
.ToList();
}
Live example: https://dotnetfiddle.net/mBfHru
Another way is to do an aggregation using Aggregate. This means maintaining a final Result list and a Curr list where you can aggregate your sequences, adding them to the Result list as you find discontinuities. This method looks a little closer to your original
public static List<Item[]> GetContiguousSequences3(Item []items)
{
var res = items.Aggregate(new {Result = new List<Item[]>(), Curr = new List<Item>()}, (agg, item) => {
if(!agg.Curr.Any() || agg.Curr.Last().Stop == item.Start) {
agg.Curr.Add(item);
} else {
agg.Result.Add(agg.Curr.ToArray());
agg.Curr.Clear();
agg.Curr.Add(item);
}
return agg;
});
res.Result.Add(res.Curr.ToArray()); // Remember to add the last group
return res.Result;
}
Live example: https://dotnetfiddle.net/HL0VyJ
You can implement ContiguousSplit as a corutine: let's loop over source and either add item into current range or return it and start a new one.
private static IEnumerable<Item[]> ContiguousSplit(IEnumerable<Item> source) {
List<Item> current = new List<Item>();
foreach (var item in source) {
if (current.Count > 0 && current[current.Count - 1].Stop != item.Start) {
yield return current.ToArray();
current.Clear();
}
current.Add(item);
}
if (current.Count > 0)
yield return current.ToArray();
}
then if you want materialization
List<Item[]> GetContiguousSequences(Item []items) => ContiguousSplit(items).ToList();
Your solution is okay. I don't think that LINQ adds any simplification or clarity in this situation. Here is a fast solution that I find intuitive:
static List<Item[]> GetContiguousSequences(Item[] items)
{
var result = new List<Item[]>();
int start = 0;
while (start < items.Length) {
int end = start + 1;
while (end < items.Length && items[end].Start == items[end - 1].Stop) {
end++;
}
int len = end - start;
var a = new Item[len];
Array.Copy(items, start, a, 0, len);
result.Add(a);
start = end;
}
return result;
}

Convert a for loop nested in a ForEach loop to LINQ

I'm getting a compilation error "not all code paths return a value" on the following code, How come?!
public class SomeEntity
{
public int m_i;
public SomeEntity(int i)
{
m_i = i;
}
public override string ToString()
{
return m_i.ToString();
}
public static int someFunction(int i) { return i + 100; }
public static IEnumerable GetEntities()
{
int [] arr = {1,2,3};
foreach (int i in arr)
{
// for (int i = 0; i < someArray.Count();i++)
// yield return new SomeEntity(someFunction(i));
// *** Equivalent linq function ***
return Enumerable.Range(0, 7).Select(a => new SomeEntity(someFunction(a)));
}
}
}
I can't seem to figure this out.....
I tried converting the outer foreach loop to a linq expression
public static IEnumerable GetEntities()
{
int [] arr = {1,2,3};
return arr.Select(Xenv =>
Enumerable.Range(0, 7).Select(a => new SomeEntity(someFunction(a)))
);
}
but then I just got an error :/
Because it is possible that arr is empty and you'll not return inside the foreach loop. Put a return after the foreach loop.
public static IEnumerable GetEntities()
{
int[] arr = { 1, 2, 3 };
foreach (int i in arr)
{
// for (int i = 0; i < someArray.Count();i++)
// yield return new SomeEntity(someFunction(i));
// *** Equivalent linq function ***
return Enumerable.Range(0, 7).Select(a => new SomeEntity(someFunction(a)));
}
return Enumerable.Empty<int>(); // <<<< this is what you need
}
The yield code you are replacing returned IEnumrable<SomeEntity>
while the new code returns IEnumarable<IEnumrable<SomeEntity>>.
you can use SelectMany
public static IEnumerable GetEntities()
{
int [] arr = {1,2,3};
return arr.SelectMany(Xenv =>
Enumerable.Range(0, 7).Select(a => new SomeEntity(someFunction(a)))
);
}
on side note, you use the old non-generic IEnumerable which prevent .Net compiler from doing type consistency checks. always use the generic one IEnumerable<> with the specific type.
The problem is with the 'foreach' loop: the program cannot assume that the loop will always iterate it through it at least once. If the array being iterated through was a length 0 the code within the loop would not be called; and therefore the return statement would not be triggered; resulting in the error that not all code paths return a value. In order to fix this you need to put a return statement outside of the loop:
public static IEnumerable GetEntities()
{
int [] arr = {1,2,3};
foreach (int i in arr)
{
// for (int i = 0; i < someArray.Count();i++)
// yield return new SomeEntity(someFunction(i));
// *** Equivalent linq function ***
return Enumerable.Range(0, 7).Select(a => new SomeEntity(someFunction(a)));
}
//INSERT RETURN STATEMENT HERE
}
If you are using any return type on your method you have to return anything with type of method in before the final braces of method like below.you no need to return anything if your using return type as void
public static IEnumerable GetEntities()
{
int [] arr = {1,2,3};
foreach (int i in arr)
{
// for (int i = 0; i < someArray.Count();i++)
// yield return new SomeEntity(someFunction(i));
// *** Equivalent linq function ***
var numcol = Enumerable.Range(0, 7).Select(a => new SomeEntity(someFunction(a)));
}
return numcol;
}

Is there a way to yield, emit or otherwise materialize a previously empty array in C# / LINQ?

I have an object with a dynamic array of strings which I've implemented as follows:
public class MyThing {
public int NumberOfThings { get; set; }
public string _BaseName { get; set; }
public string[] DynamicStringArray {
get {
List<string> dsa = new List<string>();
for (int i = 1; i <= this.NumberOfThings; i++) {
dsa.Add(string.Format(this._BaseName, i));
}
return dsa.ToArray();
}
}
}
I was trying to be a little cooler earlier and implement something that autocreated the formatted list of arrays in LINQ but I've managed to fail.
As an example of the thing I was trying:
int i = 1;
// create a list with a capacity of NumberOfThings
return new List<string>(this.NumberOfThings)
// create each of the things in the array dynamically
.Select(x => string.Format(this._BaseName, i++))
.ToArray();
It's really not terribly important in this case, and performance-wise it might actually be worse, but I was wondering if there was a cool way to build or emit an array in LINQ extensions.
Will Range help?
return Enumerable
.Range(1, this.NumberOfThings)
.Select(x => string.Format(this._BaseName, x))
.ToArray();
Your property could return an IEnumerable and you could then invoke the ToArray() extension on that, if you needed to.
public string[] DynamicStringArray
{
get
{
for (int i=1; i <= this.NumberOfThings; i++)
yield return string.Format(this._BaseName, i);
}
}
However, yields are inherently slow because of the context switching that goes on. You're better off doing this:
public string[] DynamicStringArray
{
get
{
string[] result = new string[this.NumberOfThings];
for (int i = 0; i < this.NumberOfThings; i++)
{
result[i] = string.Format(this._BaseName, i + 1));
}
return result;
}
}
Those Linq extension methods are nice for when you're feeling lazy. But if you need it to perform well you should avoid them.
I'd rather redesign a bit the current solution:
public class MyThing {
...
// Note IEnumerable<String> instead of String[]
public IEnumerable<String> DynamicString(int numberOfThings) {
if (numberOfThings < 0)
throw new ArgumentOutOfRangeException("numberOfThings");
for (int i = 0; i < numberOfThings; ++i)
yield return string.Format(this._BaseName, i + 1);
}
}
whenever you want, say, an array you can easily obtain it:
MyThing thing = ...;
// Add .ToArray() to have an array
String[] myArray = thing.DynamicString(18).ToArray();
but whenever all you want is just loop there's no need to create an array or list (materialize a result)
// no materialization: no array of 1000000 items
foreach (String item in thing.DynamicString(1000000)) {
...
}

Sort List which I use to fill ComboBox C#

Hello I've got code to fill ComboBox like this:
public ListBox fillComboBox(ListBox cb)
{
cb.Items.Clear();
foreach(string[] s in SO)
{
if (s[1].Split(',')[1].Equals("G5IDD"))
{
cb.Items.Add(s[1].Split(',')[3]);
}
}
cb.Sorted = true;
return cb;
}
In result I've got values sorted like this:
2.1
2.10
2.15
2.2
2.20
But I want it sorted like this
2.1
2.2
2.10
2.15
2.20
SO is ArrayList build by Arrays of string.
Can someone help me sort it way I want?
Thanks in advance
EDIT: Values can be like
4545_3434.2.1/1
4545_3434.2.1/2
4545_3434.2.2
4545_3434.2.2/1
Here is what I would suggest. No need for IComparer. This obviously assumes that the input will always be in the format of [int].[int].
public ListBox fillComboBox(ListBox cb)
{
cb.Items.Clear();
foreach(string[] s in SO.ToArray().OrderBy(s => Int32.Parse(s.ToString().Split('.')[0])).ThenBy(s => Int32.Parse(s.ToString().Split('.')[1])))
{
if (s[1].Split(',')[1].Equals("G5IDD"))
{
cb.Items.Add(s[1].Split(',')[3]);
}
}
return cb;
}
If you want the numbers treated as version, you can use the Version class.
public Version String2Version(string str)
{
string[] parts = str.Split('.');
return new Version(Convert.ToInt32(parts[0]), Convert.ToInt32(parts[1]));
}
public ListBox fillComboBox(ListBox cb)
{
cb.Items.Clear();
foreach(string[] s in SO)
{
if (s[1].Split(',')[1].Equals("G5IDD"))
{
cb.Items.Add( String2Version(s[1].Split(',')[3]));
}
}
cb.Sorted = true;
return cb;
}
You can use custom comparer(IComparer) in your code to achieve it,
I have provide an example.You have to change the logic of
public int Compare(object a, object b)
To achieve your specific requirement
class Program
{
private static ArrayList arl;
public static void Main(string[] args)
{
arl = new ArrayList();
arl.Add("2.1/1");
arl.Add("2.1/2");
arl.Add("2.2");
arl.Add("2.2/1");
arl.Sort(new IDDSort());
foreach (var value in arl)
{
Console.WriteLine(value);
}
Console.Read();
}
}
public class IDDSort : IComparer
{
public int Compare(object x, object y)
{
if (x == y) return 0;
var xparts = x.ToString().Replace("/","").Split('.');
var yparts = y.ToString().Replace("/", "").Split('.');
var length = new[] { xparts.Length, yparts.Length }.Max();
for (var i = 0; i < length; i++)
{
int xint;
int yint;
if (!Int32.TryParse(xparts.ElementAtOrDefault(i), out xint)) xint = 0;
if (!Int32.TryParse(yparts.ElementAtOrDefault(i), out yint)) yint = 0;
if (xint > yint) return 1;
if (yint > xint) return -1;
}
//they're equal value but not equal strings, eg 1 and 1.0
return 0;
}
}
This should work:
Array.Sort(SO, new AlphanumComparatorFast());
(if SO is the array with your version numbers)
Derive from ListBox and override Sort method implementing your own algorithm. For example the one suggested by Feroc.
Check link below:
http://msdn.microsoft.com/pl-pl/library/system.windows.forms.listbox.sort(v=vs.110).aspx
Hello I achieved my goal by coping part of array to list. (I needed only names on list not whole arrays). And used lambda expression for it.
list = list.OrderBy(x => Int32.Parse(x.Split('.')[2].Split('/')[0]))
.ThenBy(x =>
Int32.Parse(x.Split('.')[2].Split('/').Length > 1 ? x.Split('.')[2].Split('/')[1] : x.Split('.')[2].Split('/')[0])
).ToList();
So now I got it sorted like this:
0001.1
0001.2
0001.2/1
0001.2/2
0001.3
0001.3/1
etc.
Thanks everyone for help.

Get Max() of alphanumeric value

I have a dictionary containg ID which are alphanumeric (e.g. a10a10 & d10a9) from which I want the biggest ID, meaning 9 < 10 < a ...
When I use the following code, d10a9 is MAX since 9 is sorted before 10
var lsd = new Dictionary<string, string>();
lsd.Add("a", "d10a10");
lsd.Add("b", "d10a9");
string max = lsd.Max(kvp => kvp.Value);
How can I get the Max value of the IDs with the Longest string combined?
I think you may try to roll your own IComparer<string>
class HumanSortComparer : IComparer<string>
{
public int Compare(string x, string y)
{
// your human sorting logic here
}
}
Usage:
var last = collection.OrderBy(x => x.Value, new HumanSortComparer()).LastOrDefault();
if (last != null)
string max = last.Value;
this works like a charm assuming IDs always start with "d10a":
int max = lsd.Max(kvp => Convert.ToInt32(kvp.Value.Substring(4)));
Console.Write(string.Format("d10a{0}", max));
One way would be to do this
string max =lsd.Where(kvp=>kvp.Value.Length==lsd.Max(k=>k.Value.Length)).Max(kvp => kvp.Value);
however I think that this method would evalute the max length for each item so you may be better to extract it to a variable first
int maxLength=lsd.Max(kvp=>kvp.Value.Length);
string max = lsd.Where(kvp=>kvp.Value.Length == maxLength).Max(kvp => kvp.Value);
If you are going to have null strings in there you may need to perform null checks too
int maxLength=lsd.Max(kvp=>(kvp.Value??String.Empty).Length);
string max = lsd.Where(kvp=>(kvp.Value??String.Empty).Length == maxLength).Max(kvp => kvp.Value);
Alternatively treat your string as Base36 number and convert to long for the max function and then convert back again to get the max string.
string max =lsd.Max(tvp=>tvp.Value.FromBase36()).ToBase36();
public static class Base36 {
public static long FromBase36(this string src) {
return src.ToLower().Select(x=>(int)x<58 ? x-48 : x-87).Aggregate(0L,(s,x)=>s*36+x);
}
public static string ToBase36(this long src) {
StringBuilder result=new StringBuilder();
while(src>0) {
var digit=(int)(src % 36);
digit=(digit<10) ? digit+48 :digit+87;
result.Insert(0,(char)digit);
src=src / 36;
}
return result.ToString();
}
}
Finally just just the Agregate extension method instead of Max as this lets you do all the comparison logic....
lsd.Agregate(string.Empty,(a,b)=> a.Length == b.Length ? (a>b ? a:b) : (a.Length>b.Length ? a:b));
This could doesn't have null checks but you easily add them in.
I think if you did this:
var max = lsd.OrderByDescending(x => x.Value)
.GroupBy(x => x.Value.Length)
.OrderByDescending(x => x.Key)
.SelectMany(x => x)
.FirstOrDefault();
It may give you what you want.
You need StringComparer.OrdinalIgnoreCase.
Without the need to use linq, the function that do that is quite simple.
Complexity is, of course, O(n).
public static KeyValuePair<string, string> FindMax(IEnumerable<KeyValuePair<string, string>> lsd)
{
var comparer = StringComparer.OrdinalIgnoreCase;
var best = default(KeyValuePair<string, string>);
bool isFirst = true;
foreach (KeyValuePair<string, string> kvp in lsd)
{
if (isFirst || comparer.Compare(kvp.Value, best.Value) > 0)
{
isFirst = false;
best = kvp;
}
}
return best;
}
Okay - I think you need to first turn each key into a series of strings and numbers - since you need the whole number to be able to determine the comparison. Then you implement an IComparer - I've tested this with your two input strings as well as with a few others and it appears to do what you want. The performance could possibly be improved - but I was brainstorming it!
Create this class:
public class ValueChain
{
public readonly IEnumerable<object> Values;
public int ValueCount = 0;
private static readonly Regex _rx =
new Regex("((?<alpha>[a-z]+)|(?<numeric>([0-9]+)))",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
public ValueChain(string valueString)
{
Values = Parse(valueString);
}
private IEnumerable<object> Parse(string valueString)
{
var matches = _rx.Matches(valueString);
ValueCount = matches.Count;
foreach (var match in matches.Cast<Match>())
{
if (match.Groups["alpha"].Success)
yield return match.Groups["alpha"].Value;
else if (match.Groups["numeric"].Success)
yield return int.Parse(match.Groups["numeric"].Value);
}
}
}
Now this comparer:
public class ValueChainComparer : IComparer<ValueChain>
{
private IComparer<string> StringComparer;
public ValueChainComparer()
: this(global::System.StringComparer.OrdinalIgnoreCase)
{
}
public ValueChainComparer(IComparer<string> stringComparer)
{
StringComparer = stringComparer;
}
#region IComparer<ValueChain> Members
public int Compare(ValueChain x, ValueChain y)
{
//todo: null checks
int comparison = 0;
foreach (var pair in x.Values.Zip
(y.Values, (xVal, yVal) => new { XVal = xVal, YVal = yVal }))
{
//types match?
if (pair.XVal.GetType().Equals(pair.YVal.GetType()))
{
if (pair.XVal is string)
comparison = StringComparer.Compare(
(string)pair.XVal, (string)pair.YVal);
else if (pair.XVal is int) //unboxing here - could be changed
comparison = Comparer<int>.Default.Compare(
(int)pair.XVal, (int)pair.YVal);
if (comparison != 0)
return comparison;
}
else //according to your rules strings are always greater than numbers.
{
if (pair.XVal is string)
return 1;
else
return -1;
}
}
if (comparison == 0) //ah yes, but were they the same length?
{
//whichever one has the most values is greater
return x.ValueCount == y.ValueCount ?
0 : x.ValueCount < y.ValueCount ? -1 : 1;
}
return comparison;
}
#endregion
}
Now you can get the max using OrderByDescending on an IEnumerable<ValueChain> and FirstOrDefault:
[TestMethod]
public void TestMethod1()
{
List<ValueChain> values = new List<ValueChain>(new []
{
new ValueChain("d10a9"),
new ValueChain("d10a10")
});
ValueChain max =
values.OrderByDescending(v => v, new ValueChainComparer()).FirstOrDefault();
}
So you can use this to sort the string values in your dictionary:
var maxKvp = lsd.OrderByDescending(kvp => new ValueChain(kvp.Value),
new ValueChainComparer()).FirstOrDefault();

Categories