Find next string in a list of string - c#

I have a list of UserNames in a comma delimited string. I want to find next one of the input username.
For Example:
var s0 = "abc,deF,ghi,jkl";
var s1 = "abc";
var s2 = "def";
var s3 = "ghi";
var s4 = "jkl";
Result should be:
NextInString(s0,s1 ) == "def"
NextInString(s0,s2 ) == "ghi"
NextInString(s0,s3 ) == "jkl"
NextInString(s0,s4 ) == "jkl"
Here is what I have:
string NextInString(string listOfNames, string userName)
{
if(listOfNames == string.Empty || userName == string.Empty)
return string.Empty;
var s = listOfNames.Split(',');
var count = 0;
foreach (var element in s)
{
if (element == userName)break;
count++;
}
if (s.Length -1 == count)
{
return s[count];
}
else return s[ count + 1 ];
}
My question is, is there a better/easier way to approach this?

If you take the extra step to ensure your string list is trimmed, you can just use the IndexOf() method of List<T>:
string csv = "test1, test2, test3, test4";
List<string> names = csv.Split(',').Select(x => x.Trim()).ToList();
Then your NextInString() method (I think this is a poorly named method) would look like this:
private static string NextInString(List<string> names, string userName)
{
int index = names.IndexOf(userName);
if(names.Count - 1 == index || index == -1)
{
return "No result";
}
else
{
return names[index + 1];
}
}
I made a fiddle here

You can use Linq like this:
string userName = "abc";
string listOfNames = "abc,xyz,123";
var names = listOfNames
.Split(',')
.Select((n, i) => new {name = n, index =i} )
.ToArray();
var firstMatch = names.FirstOrDefault(n => n.name == userName);
var result = firstMatch == null
? string.Empty
: firstMatch.index == names.Length - 1
? string.Empty
: names[firstMatch.index + 1].name;

Here is the LINQ approach:
string NextInString(string listOfNames, string userName)
{
if(listOfNames == string.Empty || userName == string.Empty) return string.Empty;
var names = listOfNames.Split(',');
return names
.SkipWhile(x => x != userName)
.Skip(1)
.FirstOrDefault() ?? names.Last();
}

You can make a nice little extension method to do this after the string is split, like so:
static class IListExtensions
{
public static T FindItemAfter<T>(this IList<T> list, T targetItem)
{
return list[list.IndexOf(targetItem)+ 1];
}
}
You can use it like this:
static void Main(string[] args)
{
var list = "cat,dog,rat".Split(',');
Console.WriteLine(list.FindItemAfter("cat"));
Console.WriteLine(list.FindItemAfter("dog"));
Console.ReadLine();
}
It returns:
dog
rat
This overload will allow you to specify a default value that gets returned if the requested item isn't found, or the next item would be outside the list.
public static T FindItemAfter<T>(this IList<T> list, T targetItem, T defaultValue)
{
var index = list.IndexOf(targetItem);
if (index == -1 || index >= list.Count - 1)
{
return defaultValue;
}
return list[index + 1];
}

How about something like this?
var s = listOfNames.Split(',');
for (var i = 0; i < s.count; i++)
{
if (i == s.count - 1)
{
return string.Format("No user after {0} was found", userName);
}
else if (s[i] == userName)
{
return s[i + 1];
}
}

Related

How to sort descending a string list contains strings with alphabets & numbers in C# .net?

I have values in a string list like
AB1001_A
AB1001_B
AB1002_2
AB1002_C
AB1003_0
AB1003_
AB1003_B
AB1003_A
AB1001_0
AB1001_1
AB1001_2
AB1001_C
AB1002_B
AB1002_A
And I wanted to sort this by ascending order and the suffixes in descending order like below
AB1001_2
AB1001_1
AB1001_0
AB1001_C
AB1001_B
AB1001_A
AB1002_0
AB1002_B
AB1002_A
AB1003_0
AB1003_B
AB1003_A
AB1003_
How can I code it in C#.net?
It is quite strange sorting, but if you really need it, try something like this:
List<string> lItemsOfYourValues = new List<string>() {"AB1001_A","AB1001_B","AB1001_0" /*and next your values*/};
List<Tuple<string,string,string>> lItemsOfYourProcessedValues = new List<Tuple<string,string,string>>();
string[] arrSplitedValue;
for(int i = 0; i < lItemsOfYourValues.Count; i++)
{
arrSplitedValue = lItemsOfYourValues[i].Split("_");
lItemsOfYourProcessedValues.add(new Tuple<string,string,string>(lItemsOfYourValues[i], arrSplitedValue[0], arrSplitedValue[1]));
}
List<string> lSortedValues = lItemsOfYourProcessedValues.OrderBy(o => o.Item2).ThenByDescending(o => o.Item3).Select(o => o.Item1).ToList();
It looks like you have an error in your expected results, since AB1002_2 is in the input but not in the expected results.
Assuming that's just an error, and further assuming that the suffixes are limited to a single character or digit, you can solve the sorting by writing a special comparer like so:
static int compare(string x, string y)
{
var xParts = x.Split('_', StringSplitOptions.RemoveEmptyEntries);
var yParts = y.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (xParts.Length != yParts.Length)
return yParts.Length - xParts.Length; // No suffix goes after suffix.
if (xParts.Length == 0) // Should never happen.
return 0;
int comp = string.Compare(xParts[0], yParts[0], StringComparison.Ordinal);
if (comp != 0 || xParts.Length == 1)
return comp;
if (char.IsDigit(xParts[1][0]) && !char.IsDigit(yParts[1][0]))
return -1; // Digits go before non-digit.
if (!char.IsDigit(xParts[1][0]) && char.IsDigit(yParts[1][0]))
return 1; // Digits go before non-digit.
return string.Compare(yParts[1], xParts[1], StringComparison.Ordinal);
}
Which you can then use to sort a string list, array or IEnumerable<string>, like so:
using System;
using System.Collections.Generic;
using System.Linq;
namespace Demo
{
static class Program
{
static void Main()
{
var strings = new []
{
"AB1001_A",
"AB1001_B",
"AB1002_2",
"AB1002_C",
"AB1003_0",
"AB1003_",
"AB1003_B",
"AB1003_A",
"AB1001_0",
"AB1001_1",
"AB1001_2",
"AB1001_C",
"AB1002_B",
"AB1002_A",
};
static int compare(string x, string y)
{
var xParts = x.Split('_', StringSplitOptions.RemoveEmptyEntries);
var yParts = y.Split('_', StringSplitOptions.RemoveEmptyEntries);
if (xParts.Length != yParts.Length)
return yParts.Length - xParts.Length;
if (xParts.Length == 0)
return 0;
int comp = string.Compare(xParts[0], yParts[0], StringComparison.Ordinal);
if (comp != 0 || xParts.Length == 1)
return comp;
if (char.IsDigit(xParts[1][0]) && !char.IsDigit(yParts[1][0]))
return -1; // Digits go before non-digit.
if (!char.IsDigit(xParts[1][0]) && char.IsDigit(yParts[1][0]))
return 1; // Digits go before non-digit.
return string.Compare(yParts[1], xParts[1], StringComparison.Ordinal);
}
var stringList = strings.ToList();
stringList.Sort(compare);
Console.WriteLine("Sorted list:");
Console.WriteLine(string.Join("\n", stringList));
var stringArray = strings.ToArray();
Array.Sort(stringArray, compare);
Console.WriteLine("\nSorted array:");
Console.WriteLine(string.Join("\n", stringArray));
var sequence = strings.Select(element => element);
var sortedSeq = sequence.OrderBy(element => element, Comparer<string>.Create(compare));
Console.WriteLine("\nSorted sequence:");
Console.WriteLine(string.Join("\n", sortedSeq));
}
}
}
Try it online on .Net Fiddle
Finally I got the soln by this
var mystrings = new []
{
"AB1001_A",
"AB1001_B",
"AB1002_2",
"AB1002_C",
"AB1003_0",
"AB1003_",
"AB1003_B",
"AB1003_A",
"AB1001_0",
"AB1001_1",
"AB1001_2",
"AB1001_C",
"AB1002_B",
"AB1002_A",
};
mystrings.Cast<string>().OrderBy(x => PadNumbers(x));
and then PadNumbers function as like below
public static string PadNumbers(string input)
{
return Regex.Replace(input, "[0-9]+", match => match.Value.PadLeft(10, '0'));
}

Check if string contains characters in certain order in C#r

I have a code that's working right now, but it doesn't check if the characters are in order, it only checks if they're there. How can I modify my code so the the characters 'gaoaf' are checked in that order in the string?
Console.WriteLine("5.feladat");
StreamWriter sw = new StreamWriter("keres.txt");
sw.WriteLine("gaoaf");
string s = "";
for (int i = 0; i < n; i++)
{
s = zadatok[i].nev+zadatok[i].cim;
if (s.Contains("g") && s.Contains("a") && s.Contains("o") && s.Contains("a") && s.Contains("f") )
{
sw.WriteLine(i);
sw.WriteLine(zadatok[i].nev + zadatok[i].cim);
}
}
sw.Close();
You can convert the letters into a pattern and use Regex:
var letters = "gaoaf";
var pattern = String.Join(".*",letters.AsEnumerable());
var hasletters = Regex.IsMatch(s, pattern, RegexOptions.IgnoreCase);
For those that needlessly avoid .*, you can also solve this with LINQ:
var ans = letters.Aggregate(0, (p, c) => p >= 0 ? s.IndexOf(c.ToString(), p, StringComparison.InvariantCultureIgnoreCase) : p) != -1;
If it is possible to have repeated adjacent letters, you need to complicate the LINQ solution slightly:
var ans = letters.Aggregate(0, (p, c) => {
if (p >= 0) {
var newp = s.IndexOf(c.ToString(), p, StringComparison.InvariantCultureIgnoreCase);
return newp >= 0 ? newp+1 : newp;
}
else
return p;
}) != -1;
Given the (ugly) machinations required to basically terminate Aggregate early, and given the (ugly and inefficient) syntax required to use an inline anonymous expression call to get rid of the temporary newp, I created some extensions to help, an Aggregate that can terminate early:
public static TAccum AggregateWhile<TAccum, T>(this IEnumerable<T> src, TAccum seed, Func<TAccum, T, TAccum> accumFn, Predicate<TAccum> whileFn) {
using (var e = src.GetEnumerator()) {
if (!e.MoveNext())
throw new Exception("At least one element required by AggregateWhile");
var ans = accumFn(seed, e.Current);
while (whileFn(ans) && e.MoveNext())
ans = accumFn(ans, e.Current);
return ans;
}
}
Now you can solve the problem fairly easily:
var ans2 = letters.AggregateWhile(-1,
(p, c) => s.IndexOf(c.ToString(), p+1, StringComparison.InvariantCultureIgnoreCase),
p => p >= 0
) != -1;
Why not something like this?
static bool CheckInOrder(string source, string charsToCheck)
{
int index = -1;
foreach (var c in charsToCheck)
{
index = source.IndexOf(c, index + 1);
if (index == -1)
return false;
}
return true;
}
Then you can use the function like this:
bool result = CheckInOrder("this is my source string", "gaoaf");
This should work because IndexOf returns -1 if a string isn't found, and it only starts scanning AFTER the previous match.

How to write a function that generates ID by taking missing items in a sequence?

How can I write an algorithm that can take unused ID's out of a sequence starting from 1 to 99 in the format "C00"? For example NewId(['C01', 'C02', 'C03']) should emit 'C04', but NewId(['C02', 'C03', 'C04']) should emit C01, and NewId(['C01', 'C03', 'C04']) should result in C02.
I wrote an implementation but the result is wrong.
Example : CAT_ID : C01, C02, C05, C06, C11. When I run it, the expected result is C03. My algorithm is as follows:
Sort ID asc
Go through every item in the list
Compare first value with the next, if they are not the same, add 1 and exit loop.
This is my code:
public static string Get_AreaID_Auto()
{
string result = "";
if (db.TESTs.ToList().Count <= 0)
{
result = "01";
}
else
{
int maxId = 0;
foreach (var item in db.TESTs.OrderBy(e => e.CAT_ID).ToList())
{
if (int.Parse(item.CAT_ID.Substring(1)) + 1 != int.Parse(item.CAT_ID.Substring(1)))
{
maxId = int.Parse(item.CAT_ID.Substring(1) + 1);
break;
}
}
switch (maxId.ToString().Length)
{
case 1:
if (maxId == 9)
{
result = "10";
}
else
result = "0" + (maxId + 1);
break;
case 2:
result = "" + (maxId + 1);
break;
default:
break;
}
}
return "C" + result;
}
Can someone point out what is wrong?
This should work for you:
public static string Get_AreaID_Auto()
{
var existing = db.TESTs.Select(e => e.CAT_ID).OrderBy(x => x).ToList();
if (existing.Count == 0)
{
return "C01";
}
else
{
return
existing
.Concat(new [] { "" })
.Select((x, n) => new
{
actual = x,
expected = String.Format("C{0:00}", n + 1),
})
.Where(x => x.actual != x.expected)
.Select(x => x.expected)
.First();
}
}
This uses a generate and test approach. No parsing necessary.
I just realised with the .Concat(new [] { "" }) change that the if statement is now no longer required. You can do this instead:
public static string Get_AreaID_Auto()
{
return
db.TESTs
.Select(e => e.CAT_ID)
.OrderBy(x => x)
.ToArray()
.Concat(new [] { "" })
.Select((x, n) => new
{
actual = x,
expected = String.Format("C{0:00}", n + 1),
})
.Where(x => x.actual != x.expected)
.Select(x => x.expected)
.First();
}
Try this
public static string Get_AreaID_Auto()
{
string result = "";
if (db.TESTs.ToList().Count <= 0)
{
result = "01";
}
else
{
var item = db.TESTs.OrderByDescending(e => e.CAT_ID).First();
result = int.Parse(item.CAT_ID.Substring(1)) + 1;
}
return string.Format("C{0:D3}",result);
}
Updated Code...Now Try this
public static string Get_AreaID_Auto()
{
string result = "";
if (db.TESTs.ToList().Count <= 0)
{
result = "01";
}
else
{
var items = db.TESTs.OrderBy(e => e.CAT_ID).ToArray();
for(int i=0;i<items.count;i++)
{
if ((i==items.count-1) || (int.Parse(items[i].CAT_ID.Substring(1)) + 1 != int.Parse(items[i+1].CAT_ID.Substring(1))))
{
result = int.Parse(items[i].CAT_ID.Substring(1) + 1);
break;
}
}
}
return string.Format("C{0:D2}",result);
}
Here is a solution I think would work:
var items = db.TESTs.Select(x => int.Parse(x.CAT_ID.Substring(1))).OrderBy(v => v).ToArray();
if(!items.Any())
return "C01";
int current = 0;
for (int i = 0; i < items.Length; i++)
{
if (items[i] > current + 1)
return "C" + (current + 1) .ToString("00");
current = items[i];
}
return "C" + (items.Max() + 1).ToString("00");

Array not working when more than one value is provided

This is my code for my Page_Load:
string _group_array = Group_Array.Get_Group_Array(3);
string[] groups = new string[] { _group_array };
foreach (string group in groups)
{
GridView grdv = new GridView();
grdv.DataSource = Connections.isp_GET_GRIDVIEW_DATA("STDNG", group, "", "");
grdv.DataBind();
gridview_holder.Controls.Add(grdv);
}
And this is the code for my Group_Array class:
public static String Get_Group_Array(int count)
{
string _cs_group_array = "";
if(count == 1)
{
_cs_group_array = "A";
}
else if(count == 2)
{
_cs_group_array = "A, B";
}
else if (count == 3)
{
_cs_group_array = "A, B, C";
}
return _cs_group_array;
}
My issue is that when my count is greater than 1, my groups does not work. Any ideas as to why?
Problem1 : you are not seperating the group string _group_array using comma as delimiter .
Solution1 : you need to split the group string _group_array using comma as delimiter.
Note: you can use Split() function for Splitting the string.
Replace This:
string[] groups = new string[] { _group_array };
With This:
string[] groups = _group_array.Split(',');
Problem 2: you have spaces between the strings.
Solution 2: you need to remove the spaces between the strings.
Replace This:
else if(count == 2)
{
_cs_group_array = "A, B";
}
else if (count == 3)
{
_cs_group_array = "A, B, C";
}
With This:
else if(count == 2)
{
_cs_group_array = "A,B";
}
else if (count == 3)
{
_cs_group_array = "A,B,C";
}
Complete Code:
string _group_array = Group_Array.Get_Group_Array(3);
string[] groups = _group_array.Split(',');
foreach (string group in groups)
{
GridView grdv = new GridView();
grdv.DataSource = Connections.isp_GET_GRIDVIEW_DATA("STDNG", group, "", "");
grdv.DataBind();
gridview_holder.Controls.Add(grdv);
}
public static String Get_Group_Array(int count)
{
string _cs_group_array = "";
if(count == 1)
{
_cs_group_array = "A";
}
else if(count == 2)
{
_cs_group_array = "A,B";
}
else if (count == 3)
{
_cs_group_array = "A,B,C";
}
return _cs_group_array;
}
I'm going to suggest what I think is a cleaner approach:
string[] groups = new [] { "A", "B", "C" }.Take(count);
Your Get_Group_Array method always returns one string, not a string array.That's why your foreach is executing only once because your groups array has only one element. According to your code you can change your method like this:
public static string[] Get_Group_Array(int count)
{
string _cs_group_array = "";
if(count == 1)
{
_cs_group_array = "A";
}
else if(count == 2)
{
_cs_group_array = "A, B";
}
else if (count == 3)
{
_cs_group_array = "A, B, C";
}
return _cs_group_array.Split(',');
}
Then call it:
string[] groups = Group_Array.Get_Group_Array(3);
If you want to make it better:
public static string[] Get_Group_Array(int count)
{
string[] chars = new string[count];
for (int i = 0; i < count; i++)
{
chars[i] = ((char) i + 65).ToString();
}
return chars;
}

ToString() of copied NameValueCollection doesn't output desired results

I have a NameValueCollection in a usercontrol that is initialized like so:
private NameValueCollection _nameValues = HttpUtility.ParseQueryString(Request.QueryString.ToString());
When I call the ToString() on this it generates a proper querystring which I can use for an updated url.
However, when I copy the NameValueCollection via its constructor like so:
var nameValues = new NameValueCollection(_nameValues);
And then try to form an url:
var newUrl = String.Concat(_rootPath + "?" + nameValues.ToString());
It outputs an url like this:
"http://www.domain.com?System.Collections.Specialized.NameValueCollection"
How can I copy a NameValueCollection so that the ToString() method outputs desired results?
The problem is there are two actual types in your code. The fist one is System.Web.HttpValueCollection which has it's ToString method overriden to get the result you expect and the second one is System.Collection.Specialized.NameValueCollection which does not override ToString. What you can do, if you really need to use System.Collection.Specialized.NameValueCollection is to create an extension method.
public static string ToQueryString(this NameValueCollection collection)
{
var array = (from key in collection.AllKeys
from value in collection.GetValues(key)
select string.Format("{0}={1}", HttpUtility.UrlEncode(key), HttpUtility.UrlEncode(value))).ToArray();
return "?" + string.Join("&", array);
}
and use it:
var newUrl = String.Concat(_rootPath,nameValues.ToQueryString());
It is not NameValueCollection that provides the string formatting. That functionality is in an internal class System.Web.HttpValueCollection that is returned by HttpUtility.ParseQueryString.
So you will not be able to achieve this behavior by using built in functionality. Your best bet would be to create an extension method that formats the values in a URL format.
Here is the method from HttpValueCollection class - you might be able to use it with some modifications.
// System.Web.HttpValueCollection
internal virtual string ToString(bool urlencoded, IDictionary excludeKeys)
{
int count = this.Count;
if (count == 0)
{
return string.Empty;
}
StringBuilder stringBuilder = new StringBuilder();
bool flag = excludeKeys != null && excludeKeys["__VIEWSTATE"] != null;
for (int i = 0; i < count; i++)
{
string text = this.GetKey(i);
if ((!flag || text == null || !text.StartsWith("__VIEWSTATE", StringComparison.Ordinal)) && (excludeKeys == null || text == null || excludeKeys[text] == null))
{
if (urlencoded)
{
text = HttpValueCollection.UrlEncodeForToString(text);
}
string value = (text != null) ? (text + "=") : string.Empty;
string[] values = this.GetValues(i);
if (stringBuilder.Length > 0)
{
stringBuilder.Append('&');
}
if (values == null || values.Length == 0)
{
stringBuilder.Append(value);
}
else
{
if (values.Length == 1)
{
stringBuilder.Append(value);
string text2 = values[0];
if (urlencoded)
{
text2 = HttpValueCollection.UrlEncodeForToString(text2);
}
stringBuilder.Append(text2);
}
else
{
for (int j = 0; j < values.Length; j++)
{
if (j > 0)
{
stringBuilder.Append('&');
}
stringBuilder.Append(value);
string text2 = values[j];
if (urlencoded)
{
text2 = HttpValueCollection.UrlEncodeForToString(text2);
}
stringBuilder.Append(text2);
}
}
}
}
}
return stringBuilder.ToString();
}
internal static string UrlEncodeForToString(string input)
{
return HttpUtility.UrlEncodeUnicode(input);
}
Calling .ToString() on a name value collection will just give you the namespace it belongs to.
I suspect you want the key and value out of it, Assuming that it's the first in the collection why not just do:
var newUrl = String.Concat(_rootPath + "?" + nameValues.GetKey(0) + nameValues.Get(0));
You can have this as an extension method:
public static string ToString(this NameValueCollection nvc, int idx)
{
if(nvc == null)
throw new NullReferenceException();
string key = nvc[idx];
if(nvc.HasKeys() && !string.IsNullOrEmpty(key))
{
return string.Concat(key, nvc.Get(key)); //maybe want some formatting here
}
return string.Empty;
}
Usage:
NameValueCollection nvc = new NameValueCollection();
string foo = nvc.ToString(0); //gets key + value at index 0

Categories