How to Convert decimal number to time or vice versa - c#

here is an example
if 8.30 is there it should be 8 hours 30 minute
if 8 hour 20 minutes then 8.20
Please tell whether it is possible ? if yes
how ?

When people talk about decimal hours, they usually mean 0.1 = 6 minutes.
So, the correct formula to convert 8.3 would be:
8 hours + 3 * 6 minutes = 8:18
To convert 8:20 to decimal it would be:
8 + 20/6 = 8.333333 (probably round to 8.3)

If it always be separated with . and you want it for displaying then simply use this:
var ar="8.30".split(new[]{'.'});
Console.Write("{0} hours {1} minutes",ar[0], ar[1]);
PS: Here we are sure to have two elements in array, but please check length of array ar before using ar[1]

My approach would look something like this. (This is ruby so you'll have to convert it yourself but the logic is whats important here)
def zeropad(number)
return ((number.to_f < 10) ? "0" : "") + number.round.to_s
end
def decimal_to_time(value)
t = value.split(".") #returns an array of ["hour", "minutes"]
hours, minutes = t[0], t[1]
minutes = zeropad( (minutes.to_f / 10**minutes.length) * 60 ) # parse the minutes into a time value
return (minutes.to_i == 0) ? hours : hours + ":" + minutes
end
def findTime(value)
value =~ /^\d+\.\d+/ ? decimal_to_time(value) : value
end
Where findTime("5.015") gives you the appropriate time value.
I've tested this across the following tests and they all pass.
| entered_time | expected_results|
| "5.6" | "5:36" |
| "5.9" | "5:54" |
| "5.09" | "5:05" |
| "5.0" | "5" |
| "5.00" | "5" |
| "5.015" | "5:01" |
| "6.03" | "6:02" |
| "5.30" | "5:18" |
| "4.2" | "4:12" |
| "8.3" | "8:18" |
| "8.33" | "8:20" |
| "105.5" | "105:30" |
| "16.7" | "16:42" |
| "Abc" | "Abc" |
| "5:36" | "5:36" |
| "5:44" | "5:44" |

Here's a couple of extension methods (for DateTime and Decimal) that do the job:
public static class DecimalToTimeConverters
{
public static DateTime ToDateTime(this decimal value)
{
string[] parts = value.ToString().Split(new char[] { '.' });
int hours = Convert.ToInt32(parts[0]);
int minutes = Convert.ToInt32(parts[1]);
if ((hours > 23) || (hours < 0))
{
throw new ArgumentOutOfRangeException("value", "decimal value must be no greater than 23.59 and no less than 0");
}
if ((minutes > 59) || (minutes < 0))
{
throw new ArgumentOutOfRangeException("value", "decimal value must be no greater than 23.59 and no less than 0");
}
DateTime d = new DateTime(1, 1, 1, hours, minutes, 0);
return d;
}
public static Decimal ToDecimal(this DateTime datetime)
{
Decimal d = new decimal();
d = datetime.Hour;
d = d + Convert.ToDecimal((datetime.Minute * 0.01));
return d;
}
}
I tested this very quickly in an ASP.net webpage (I had a web project open at the time) using the following in a new blank page, and it seemed to work a treat:
protected void Page_Load(object sender, EventArgs e)
{
Response.Clear();
Decimal d = new decimal();
d = 3.45M;
Response.Write(d.ToDateTime().ToString());
Response.Write("<br />");
DateTime d2 = new DateTime(2009, 1, 1, 4, 55, 0);
Response.Write(d2.ToDecimal().ToString());
}

As per Rob but substitute
string[] parts = value.ToString().Split(new char[] { '.' });
int hours = Convert.ToInt32(parts[0]);
int minutes = Convert.ToInt32(parts[1]);
as
int hours = (int)value;
int minutes = (int)((value - minutes) * 100);
no strings or reliance on current culture (the assumption that the '.' is the decimal point)

How can I parse the txtDuration.Text Value into a decimal value?
if (txtDuration.Text)
{
var duration = int.Parse(txtDuration.Text);
var timespan = Boolean.Parse(hdfToggleDuration.Value) ? new TimeSpan (0, 0, duration, 0) : new TimeSpan (0, duration, 0, 0);
DateTime end = start.Add(timespan);
}

Related

Replace a Number with a Random Number

I want to make my own little encryption (it not must be secure).
I thought it would be a good idea, if the entered password would be converted to binary and then I want to change all the 1 to (2 | 3 | 9 | 7) so if we had
1101 = 2907 | 9703 ... and so on
So if we would enter 9703 we could decrypt it to 1101 again.
But I can’t find a Method to Replace these.
The Replace() Method would only do this:
1101 -> 2202 | 1101 -> 9909
And yes, i know that is not a good Method to encrypt something but I just want to code a very simple encryption on my own.
int[] replace_Ones = { 2, 3, 9, 7 };
int ServerEncryption = 1101010000111001;
ServerEncryption.Replace(1, 2);
Starting from this:
int[] replace_Ones = { 2, 3, 9, 7 }; // I removed the 0 ;)
long ServerEncryption = 1101010000111001;
You can make a method that does the following:
long FunnyEncrypt( long pseudobinary )
{
long result = 0;
long scope = 1;
while( pseudobinary > 0 )
{
// place 2 or 3 or 7 or 9 0 or 1
result += scope * GetRandomReplacement() * ( pseudobinary % 10 );
scope *= 10;
pseudobinary = pseudobinary / 10; // decimal right shift 110 / 10 -> 11
}
return result;
}
Disclaimer: untested! Corrections welcome.
GetRandomReplacement is left for practice :D - but it basically is "pick a random int out of [0..3] and use it as index into the array of [2,3,7,9]".
Example:
example input: 1101
| iteration | input | result | scope | -> | input | result | scope | GetRandomRepl |
| 1 | 1101 | 0 | 1 | -> | 110 | 7 | 10 | 7 |
| 2 | 110 | 7 | 10 | -> | 11 | 7 | 100 | - |
| 3 | 11 | 7 | 100 | -> | 1 | 307 | 1000 | 3 |
| 4 | 1 | 307 | 1000 | -> | 0 | 2307 | 10000 | 2 |
=> Result = 2307
EDIT: changed to long after testing, did not see the number is too big for int: https://dotnetfiddle.net/5X4lZu
If the problem is to replace '1' value of a binary string to a random value in { 2, 3, 9, 7 } you could do like this:
char[] replace_Ones = { '2', '3', '9', '7' };
StringBuilder ServerEncryption =new StringBuilder("1101010000111001");
Random r = new Random();
for (int i=0 ; i < ServerEncryption.Length ; i++)
{
if (ServerEncryption[i] != '0')
ServerEncryption[i] = replace_Ones[r.Next(replace_Ones.Length)];
}
Console.WriteLine(ServerEncryption);
dotnetFiddle

Logarithmic distribution of profits among game winners

I have a gave, which, when it's finished, has a table of players and their scores.
On the other hand i have a virtual pot of money that i want to distribute among these winners. I'm looking for a SQL query or piece of C# code to do so.
The descending sorted table looks like this:
UserId | Name | Score | Position | % of winnings | abs. winnings $
00579 | John | 754 | 1 | ? | 500 $
98983 | Sam | 733 | 2 | ? | ?
29837 | Rick | 654 | 3 | ? | ? <- there are 2 3rd places
21123 | Hank | 654 | 3 | ? | ? <- there are 2 3rd places
99821 | Buck | 521 | 5 | ? | ? <- there is no 4th, because of the 2 3rd places
92831 | Joe | 439 | 6 | ? | ? <- there are 2 6rd places
99281 | Jack | 439 | 6 | ? | ? <- there are 2 6rd places
12345 | Hal | 412 | 8 | ? | ?
98112 | Mick | 381 | 9 | ? | ?
and so on, until position 50
98484 | Sue | 142 | 50 | ? | 5 $
Be aware of the double 3rd and 6th places.
Now i want to distribute the total amount of (virtual) money ($ 10,000) among the first 50 positions. (It would be nice if the positions to distribute among (which is now 50) can be a variable).
The max and min amount (for nr 1 and nr 50) are fixed at 500 and 5.
Does anyone have a good idea for a SQL query or piece of C# code to fill the columns with % of winnings and absolute winnings $ correctly?
I prefer to have a distribution that looks a bit logarithmic like this: (which makes that the higher positions get relatively more than the lower ones).
.
|.
| .
| .
| .
| .
| .
| .
| .
| .
I haven't done SQL since 1994, but I like C# :-). The following might suit, adjust parameters of DistributeWinPot.DistributeWinPot(...) as required:
private class DistributeWinPot {
private static double[] GetWinAmounts(int[] psns, double TotWinAmounts, double HighWeight, double LowWeight) {
double[] retval = new double[psns.Length];
double fac = -Math.Log(HighWeight / LowWeight) / (psns.Length - 1), sum = 0;
for (int i = 0; i < psns.Length; i++) {
sum += retval[i] = (i == 0 || psns[i] > psns[i - 1] ? HighWeight * Math.Exp(fac * (i - 1)) : retval[i - 1]);
}
double scaling = TotWinAmounts / sum;
for (int i = 0; i < psns.Length; i++) {
retval[i] *= scaling;
}
return retval;
}
public static void main(string[] args) {
// set up dummy data, positions in an int array
int[] psns = new int[50];
for (int i = 0; i < psns.Length; i++) {
psns[i] = i+1;
}
psns[3] = 3;
psns[6] = 6;
double[] WinAmounts = GetWinAmounts(psns, 10000, 500, 5);
for (int i = 0; i < psns.Length; i++) {
System.Diagnostics.Trace.WriteLine((i + 1) + "," + psns[i] + "," + string.Format("{0:F2}", WinAmounts[i]));
}
}
}
Output from that code was:
1,1,894.70
2,2,814.44
3,3,741.38
4,3,741.38
5,5,614.34
6,6,559.24
7,6,559.24
8,8,463.41
9,9,421.84
10,10,384.00
11,11,349.55
12,12,318.20
13,13,289.65
14,14,263.67
15,15,240.02
16,16,218.49
17,17,198.89
18,18,181.05
19,19,164.81
20,20,150.03
21,21,136.57
22,22,124.32
23,23,113.17
24,24,103.02
25,25,93.77
26,26,85.36
27,27,77.71
28,28,70.74
29,29,64.39
30,30,58.61
31,31,53.36
32,32,48.57
33,33,44.21
34,34,40.25
35,35,36.64
36,36,33.35
37,37,30.36
38,38,27.64
39,39,25.16
40,40,22.90
41,41,20.85
42,42,18.98
43,43,17.27
44,44,15.72
45,45,14.31
46,46,13.03
47,47,11.86
48,48,10.80
49,49,9.83
50,50,8.95
Then how about this?
Select userid, log(score),
10000 * log(score) /
(Select Sum(log(score))
From TableName
Where score >=
(Select Min(score)
from (Select top 50 score
From TableName
Order By score desc) z))
From TableName
Order By score desc

Linq-To-SQL GroupBy: How to calculate percentage column

I have a database table of events. Each event has a category and a date/time.
(actually events have sub-categories which are contained in categories and various other properties, left out in this simplified example)
I want to generate a list with an entry for each hour and category, listing the number of events and their percentage.
date | category | number of events | percentage
----------------------------------------------------------
2012-01-01 1:00 | cat-1 | 10 | 50%
2012-01-01 1:00 | cat-2 | 10 | 50%
2012-01-01 2:00 | cat-1 | 1 | 10%
2012-01-01 2:00 | cat-2 | 9 | 90%
2012-01-01 3:00 | cat-1 | 3 | 100%
...
I am having trouble calculating the percentage efficiently.
Here is what I have so far:
var results = datacontext.events.
groupBy(e=>new
{ // group by category and hour
category = e.category,
datetime = new DateTime (
e.datetime.Year, e.datetime.Month, e.datetime.Day,
e.datetime.Hour, 0, 0 )
// use date+hour for grouping, ignore minutes/seconds
}).
select(group => new
{
category = group.Key.category,
datetime = group.Key.datetime,
numberOfEvents = group.count(),
percentage = group.count() / ["total number of events for this hour"] * 100.0
}
How can I get the value for [total number of events for this hour], ideally in an elegant and efficient way?
Here is what I came up with:
var results = from events in datacontext.events
let date = new DateTime(
events.datetime.Year, events.datetime.Month, events.datetime.Day,
events.datetime.Hour, 0, 0)
let total = datacontext.events.Where(x => x.datetime.Date == date.Date && x.datetime.Hour == date.Hour).Count()
group events by new
{ // group by category and hour
category = events.category,
datetime = date,
total = total
} into e
select new
{
category = e.Key.category,
day = e.Key.datetime.Day,
numberOfEvents = e.Count(),
percentage = (float)e.Count() / (float)e.Key.total
};
Two points of focus:
Using floats for percentage
using let to calculate the total for the Date
You will have to group by day first and then to select totals from there...something like:
var resultsPerDay = datacontext.events.groupBy(e=>new {
datetime = new DateTime (
e.datetime.Year, e.datetime.Month, e.datetime.Day,
e.datetime.Hour, 0, 0 )
}
}).select(group=>new {
datetime = group.Key.datetime,
totalForADay = group.Key.count()
});
var results = datacontext.events.
groupBy(e=>new
{ // group by category and hour
category = e.category,
datetime = new DateTime (
e.datetime.Year, e.datetime.Month, e.datetime.Day,
e.datetime.Hour, 0, 0 )
// use date+hour for grouping, ignore minutes/seconds
}).
select(group => new
{
category = group.Key.category,
datetime = group.Key.datetime,
numberOfEvents = group.count(),
percentage = 100 * group.count() / resultsPerDay.where(p=>p.datetime == group.Key.datetime).select(p=>p.totalForADay).SingleOrDefault()
}
Not sure if this is the most efficient way, though. Also, not sure if syntax is good since I didn't put this through compiler, but you get the idea.

Finding first day of calendar

What i want to do is to create a simple calendar, and I want to find the first day of the first week of a specific month. My calendar is a Monday -> Sunday calendar and the following code works, but as you can see it's not that nice. Anyone have any better idea on how to get the first date in the calendar.
var now = new DateTime(Year, Month, 1);
now = now.AddDays(1-(int)now.DayOfWeek);
now = now.Day > 15 ? now : now.AddDays(-7);
The calendar will end up looking like this:
| < | Jan 2011 | > |
------------------------------------
| Mo | Tu | We | Th | Fr | Sa | Su |
|[27]| 28 | 29 | 30 | 31 | 01 | 02 |
| 03 | 04 | 05 | 06 | 07 | 08 | 09 |
| .. | .. | .. | .. | .. | .. | .. |
| .. | .. | .. | .. | .. | .. | .. |
| 31 | 01 | 02 | 03 | 04 | 05 | 06 |
And in this "image" it's the [27] date that i'm trying to find.
Solution (Found i better/cleaner to loop then calculate):
public DateTime FirstDay()
{
var date = new DateTime(Date.Year, Date.Month, 1);
while (true)
{
if (date.DayOfWeek == DayOfWeek.Monday)
return date;
date = date.AddDays(-1);
}
return date;
}
public DateTime LastDay()
{
var date = new DateTime(Date.Year, Date.Month,
DateTime.DaysInMonth(Date.Year, Date.Month));
while (true)
{
if (date.DayOfWeek == DayOfWeek.Sunday)
return date;
date = date.AddDays(1);
}
return date;
}
/BR
Andreas
I would just do this. It is so easy to understand:
var firstDayOfMonth = new DateTime(year, month, 1);
DateTime startOfCalendar =
FirstDayOfWeekOnOrBefore(
firstDayOfMonth,
DayOfWeek.Monday
);
public static DateTime FirstDayOfWeekOnOrBefore(
DateTime date,
DayOfWeek dayOfWeek
) {
while(date.DayOfWeek != dayOfWeek) {
date = date.AddDays(-1);
}
return date;
}
Additionally, if you want to change your calendar to start on something other than Monday, it's trivial now. A solution using modulo arithmetic would not be as maintainable.
You can use modulo to calculate the number of filler days without a conditional statement:
DateTime firstOfMonth=new DateTime(year,month,1);
var weekDay=firstOfMonth.DayOfWeek;
int fillerDays=((int)weekDay+6)%7;
DateTime firstDayInCalendar=firstOfMonth.AddDays(-fillerDays);
You can try this assuming first day ur referring to is Monday
DateTime dt = new DateTime(2011, 2, 2);
Console.WriteLine(dt.AddDays((8 - (int)dt.DayOfWeek) % 7));
I do not like while loops, because they are expensive when used with LINQ
Hope someone else can reuse this code: (if you are in the USA, then just remove [ + 6) % 7)] in two lines)
/// <summary>
/// Expands the month.
/// | < | Jan 2011 | > |
/// ------------------------------------
/// | Mo | Tu | We | Th | Fr | Sa | Su |
/// |[27]| 28 | 29 | 30 | 31 | 01 | 02 |
/// | 03 | 04 | 05 | 06 | 07 | 08 | 09 |
/// | .. | .. | .. | .. | .. | .. | .. |
/// | .. | .. | .. | .. | .. | .. | .. |
/// | 31 | 01 | 02 | 03 | 04 | 05 | 06 |
/// </summary>
/// <param name="start">Some day in the month of interest, the start date is updated to become the date of firstDayInCalendar</param>
/// <returns>The number of days to show. This value is either (28, 35 or 42)</returns>
public static int ExpandMonth(ref DateTime start)
{
DateTime first = new DateTime(start.Year, start.Month, 1);
DateTime last = new DateTime(start.Year, start.Month, DateTime.DaysInMonth(start.Year, start.Month));
start = first.AddDays(-((int)first.DayOfWeek + 6) % 7);
last = last.AddDays(7 - ((int)last.DayOfWeek + 6) % 7);
return last.Subtract(start).Days;
}
//Thomas
I found myself needing to do this quite often, so I created the following extension method.
public static DateTime FirstDateOfCalendarMonth(this DateTime dt, DayOfWeek firstDayOfWeek = DayOfWeek.Sunday)
{
dt = new DateTime(dt.Year, dt.Month, 1);
while (dt.DayOfWeek != firstDayOfWeek){
dt = dt.AddDays(-1);
}
return dt;
}
Use it like this
var firstCalDate = DateTime.Now.FirstDateOfCalendarMonth();
It defaults to Sunday as the first DayOfWeek, but you can pass it whatever DayOfWeek you like, like this:
var firstCalDate = DateTime.Now.FirstDateOfCalendarMonth(DayOfWeek.Monday);
DateTime start_date = Cal_start_date.SelectedDate;
DateTime end_date = Cal_end_date.SelectedDate;
Dictionary<string, int> dic_week_day = new Dictionary<string, int>();
dic_week_day["Sunday"] = 1;
dic_week_day["Monday"] = 2;
dic_week_day["Tuesday"] = 3;
dic_week_day["Wednesday"] = 4;
dic_week_day["Thursday"] = 5;
dic_week_day["Friday"] = 6;
dic_week_day["Saturday"] = 7;
DateTime first_day = start_date.AddDays(1 - start_date.Day);
int selected_day = dic_week_day[Drp_day_of_week.SelectedValue.ToString()];
string str_html = "";
for (DateTime i = first_day; i <= end_date; i = i.AddMonths(1))
{
int day_of_week = dic_week_day[i.DayOfWeek.ToString()];
DateTime temp_date;
if (day_of_week > selected_day)
{
temp_date = i.AddDays((7 - day_of_week) + selected_day);
}
else
{
temp_date = i.AddDays(selected_day - day_of_week);
}
DateTime last_day_of_month = (temp_date.AddMonths(1)).AddDays(-temp_date.Day);
if (Drp_occurrence.SelectedValue.ToString() == "odd")
{
if (start_date <= temp_date && temp_date <= end_date && temp_date <= last_day_of_month)
{
str_html += "<br />" + temp_date.ToString();
}
DateTime res_date = temp_date.AddDays(14);
if (start_date <= res_date && res_date <= end_date && res_date <= last_day_of_month)
{
str_html += " , " + res_date.ToString();
}
res_date = temp_date.AddDays(28);
if (start_date <= res_date && res_date <= end_date && res_date <= last_day_of_month)
{
str_html += " , " + res_date.ToString();
}
}
else if (Drp_occurrence.SelectedValue.ToString() == "even")
{
DateTime res_date = temp_date.AddDays(7);
if (start_date <= res_date && res_date <= end_date && res_date <= last_day_of_month)
{
str_html += "<br />" + res_date.ToString();
}
res_date = temp_date.AddDays(21);
if (start_date <= res_date && res_date <= end_date && res_date <= last_day_of_month)
{
str_html += " , " + res_date.ToString();
}
}
else
{
int occurrence = Int32.Parse(Drp_occurrence.SelectedValue.ToString());
DateTime res_date = temp_date.AddDays((occurrence - 1) * 7);
if (start_date <= res_date && res_date <= end_date && res_date <= last_day_of_month)
{
str_html += "<br />" + res_date.ToString();
}
}
}
Div_result.InnerHtml = str_html;
UPDATE: Following code has a bug! Use modulo arithmetic, to compensate the circumstance, .NET begins the week on sunday! See other solutions here (without the one with an extra function).
If you need to find the "27" (i.e. the first day for the display of a month) simply use this:
DateTime monthStart = new DateTime(year, month, 1);
DateTime monthDisplayStart = monthStart.AddDays(-((int)monthStart.DayOfWeek - 1));

Contains is faster than StartsWith?

A consultant came by yesterday and somehow the topic of strings came up. He mentioned that he had noticed that for strings less than a certain length, Contains is actually faster than StartsWith. I had to see it with my own two eyes, so I wrote a little app and sure enough, Contains is faster!
How is this possible?
DateTime start = DateTime.MinValue;
DateTime end = DateTime.MinValue;
string str = "Hello there";
start = DateTime.Now;
for (int i = 0; i < 10000000; i++)
{
str.Contains("H");
}
end = DateTime.Now;
Console.WriteLine("{0}ms using Contains", end.Subtract(start).Milliseconds);
start = DateTime.Now;
for (int i = 0; i < 10000000; i++)
{
str.StartsWith("H");
}
end = DateTime.Now;
Console.WriteLine("{0}ms using StartsWith", end.Subtract(start).Milliseconds);
Outputs:
726ms using Contains
865ms using StartsWith
I've tried it with longer strings too!
Try using StopWatch to measure the speed instead of DateTime checking.
Stopwatch vs. using System.DateTime.Now for timing events
I think the key is the following the important parts bolded:
Contains:
This method performs an ordinal
(case-sensitive and
culture-insensitive) comparison.
StartsWith:
This method performs a word
(case-sensitive and culture-sensitive)
comparison using the current culture.
I think the key is the ordinal comparison which amounts to:
An ordinal sort compares strings based
on the numeric value of each Char
object in the string. An ordinal
comparison is automatically
case-sensitive because the lowercase
and uppercase versions of a character
have different code points. However,
if case is not important in your
application, you can specify an
ordinal comparison that ignores case.
This is equivalent to converting the
string to uppercase using the
invariant culture and then performing
an ordinal comparison on the result.
References:
http://msdn.microsoft.com/en-us/library/system.string.aspx
http://msdn.microsoft.com/en-us/library/dy85x1sa.aspx
http://msdn.microsoft.com/en-us/library/baketfxw.aspx
Using Reflector you can see the code for the two:
public bool Contains(string value)
{
return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
}
public bool StartsWith(string value, bool ignoreCase, CultureInfo culture)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (this == value)
{
return true;
}
CultureInfo info = (culture == null) ? CultureInfo.CurrentCulture : culture;
return info.CompareInfo.IsPrefix(this, value,
ignoreCase ? CompareOptions.IgnoreCase : CompareOptions.None);
}
I figured it out. It's because StartsWith is culture-sensitive, while Contains is not. That inherently means StartsWith has to do more work.
FWIW, here are my results on Mono with the below (corrected) benchmark:
1988.7906ms using Contains
10174.1019ms using StartsWith
I'd be glad to see people's results on MS, but my main point is that correctly done (and assuming similar optimizations), I think StartsWith has to be slower:
using System;
using System.Diagnostics;
public class ContainsStartsWith
{
public static void Main()
{
string str = "Hello there";
Stopwatch s = new Stopwatch();
s.Start();
for (int i = 0; i < 10000000; i++)
{
str.Contains("H");
}
s.Stop();
Console.WriteLine("{0}ms using Contains", s.Elapsed.TotalMilliseconds);
s.Reset();
s.Start();
for (int i = 0; i < 10000000; i++)
{
str.StartsWith("H");
}
s.Stop();
Console.WriteLine("{0}ms using StartsWith", s.Elapsed.TotalMilliseconds);
}
}
StartsWith and Contains behave completely different when it comes to culture-sensitive issues.
In particular, StartsWith returning true does NOT imply Contains returning true. You should replace one of them with the other only if you really know what you are doing.
using System;
class Program
{
static void Main()
{
var x = "A";
var y = "A\u0640";
Console.WriteLine(x.StartsWith(y)); // True
Console.WriteLine(x.Contains(y)); // False
}
}
I twiddled around in Reflector and found a potential answer:
Contains:
return (this.IndexOf(value, StringComparison.Ordinal) >= 0);
StartsWith:
...
switch (comparisonType)
{
case StringComparison.CurrentCulture:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.CurrentCultureIgnoreCase:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.InvariantCulture:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.InvariantCultureIgnoreCase:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.Ordinal:
return ((this.Length >= value.Length) && (nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0));
case StringComparison.OrdinalIgnoreCase:
return ((this.Length >= value.Length) && (TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0));
}
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
And there are some overloads so that the default culture is CurrentCulture.
So first of all, Ordinal will be faster (if the string is close to the beginning) anyway, right? And secondly, there's more logic here which could slow things down (although so so trivial)
Here is a benchmark of using StartsWith vs Contains.
As you can see, StartsWith using ordinal comparison is pretty good, and you should take note of the memory allocated for each method.
| Method | Mean | Error | StdDev | Median | Gen 0 | Gen 1 | Gen 2 | Allocated |
|----------------------------------------- |-------------:|-----------:|-------------:|-------------:|----------:|------:|------:|----------:|
| EnumEqualsMethod | 1,079.67 us | 43.707 us | 114.373 us | 1,059.98 us | 1019.5313 | - | - | 4800000 B |
| EnumEqualsOp | 28.15 us | 0.533 us | 0.547 us | 28.34 us | - | - | - | - |
| ContainsName | 1,572.15 us | 152.347 us | 449.198 us | 1,639.93 us | - | - | - | - |
| ContainsShortName | 1,771.03 us | 103.982 us | 306.592 us | 1,749.32 us | - | - | - | - |
| StartsWithName | 14,511.94 us | 764.825 us | 2,255.103 us | 14,592.07 us | - | - | - | - |
| StartsWithNameOrdinalComp | 1,147.03 us | 32.467 us | 93.674 us | 1,153.34 us | - | - | - | - |
| StartsWithNameOrdinalCompIgnoreCase | 1,519.30 us | 134.951 us | 397.907 us | 1,264.27 us | - | - | - | - |
| StartsWithShortName | 7,140.82 us | 61.513 us | 51.366 us | 7,133.75 us | - | - | - | 4 B |
| StartsWithShortNameOrdinalComp | 970.83 us | 68.742 us | 202.686 us | 1,019.14 us | - | - | - | - |
| StartsWithShortNameOrdinalCompIgnoreCase | 802.22 us | 15.975 us | 32.270 us | 792.46 us | - | - | - | - |
| EqualsSubstringOrdinalCompShortName | 4,578.37 us | 91.567 us | 231.402 us | 4,588.09 us | 679.6875 | - | - | 3200000 B |
| EqualsOpShortNametoCharArray | 1,937.55 us | 53.821 us | 145.508 us | 1,901.96 us | 1695.3125 | - | - | 8000000 B |
Here is my benchmark code
https://gist.github.com/KieranMcCormick/b306c8493084dfc953881a68e0e6d55b
Let's examine what ILSpy says about these two...
public virtual int IndexOf(string source, string value, int startIndex, int count, CompareOptions options)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
if (value == null)
{
throw new ArgumentNullException("value");
}
if (startIndex > source.Length)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
if (source.Length == 0)
{
if (value.Length == 0)
{
return 0;
}
return -1;
}
else
{
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException("startIndex", Environment.GetResourceString("ArgumentOutOfRange_Index"));
}
if (count < 0 || startIndex > source.Length - count)
{
throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_Count"));
}
if (options == CompareOptions.OrdinalIgnoreCase)
{
return source.IndexOf(value, startIndex, count, StringComparison.OrdinalIgnoreCase);
}
if ((options & ~(CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreSymbols | CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth)) != CompareOptions.None && options != CompareOptions.Ordinal)
{
throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFlag"), "options");
}
return CompareInfo.InternalFindNLSStringEx(this.m_dataHandle, this.m_handleOrigin, this.m_sortName, CompareInfo.GetNativeCompareFlags(options) | 4194304 | ((source.IsAscii() && value.IsAscii()) ? 536870912 : 0), source, count, startIndex, value, value.Length);
}
}
Looks like it considers culture as well, but is defaulted.
public bool StartsWith(string value, StringComparison comparisonType)
{
if (value == null)
{
throw new ArgumentNullException("value");
}
if (comparisonType < StringComparison.CurrentCulture || comparisonType > StringComparison.OrdinalIgnoreCase)
{
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
if (this == value)
{
return true;
}
if (value.Length == 0)
{
return true;
}
switch (comparisonType)
{
case StringComparison.CurrentCulture:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.CurrentCultureIgnoreCase:
return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.InvariantCulture:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.None);
case StringComparison.InvariantCultureIgnoreCase:
return CultureInfo.InvariantCulture.CompareInfo.IsPrefix(this, value, CompareOptions.IgnoreCase);
case StringComparison.Ordinal:
return this.Length >= value.Length && string.nativeCompareOrdinalEx(this, 0, value, 0, value.Length) == 0;
case StringComparison.OrdinalIgnoreCase:
return this.Length >= value.Length && TextInfo.CompareOrdinalIgnoreCaseEx(this, 0, value, 0, value.Length, value.Length) == 0;
default:
throw new ArgumentException(Environment.GetResourceString("NotSupported_StringComparison"), "comparisonType");
}
By contrast, the only difference I see that appears relevant is an extra length check.

Categories

Resources