Чи є в C # простий спосіб створити ординари для числа? Наприклад:
- 1 повертає 1-е
- 2 повертає 2-е
- 3 повертає 3-е
- ... тощо
Чи можна це зробити через String.Format()
чи є для цього доступні функції?
Чи є в C # простий спосіб створити ординари для числа? Наприклад:
Чи можна це зробити через String.Format()
чи є для цього доступні функції?
Відповіді:
На цій сторінці ви знайдете повний перелік усіх спеціальних правил форматування чисел:
http://msdn.microsoft.com/en-us/library/0c899ak8.aspx
Як бачимо, там немає нічого про порядкові порядки, тому це неможливо зробити за допомогою String.Format. Однак не дуже важко написати функцію, щоб це зробити.
public static string AddOrdinal(int num)
{
if( num <= 0 ) return num.ToString();
switch(num % 100)
{
case 11:
case 12:
case 13:
return num + "th";
}
switch(num % 10)
{
case 1:
return num + "st";
case 2:
return num + "nd";
case 3:
return num + "rd";
default:
return num + "th";
}
}
Оновлення: технічно ординари не існують для <= 0, тому я оновив код вище. Також видалено зайві ToString()
методи.
Також зауважте, це не є інтернаціоналізованим. Я поняття не маю, як виглядають ординари на інших мовах.
Пам’ятайте про інтернаціоналізацію!
Рішення тут працюють лише для англійської мови. Речі стають набагато складнішими, якщо вам потрібно підтримувати інші мови.
Наприклад, іспанською мовою "1-й" буде писатись як "1.o", "1.a", "1.os" або "1.as", залежно від того, що ви рахуєте - чоловіче, жіноче або множинне !
Тож якщо вашому програмному забезпеченню потрібно підтримувати різні мови, намагайтеся уникати порядків.
Моя версія версії Джессі щодо Сту та Самюдсона :)
Тест одиниці включений, щоб показати, що прийнята відповідь неправильна, коли число <1
/// <summary>
/// Get the ordinal value of positive integers.
/// </summary>
/// <remarks>
/// Only works for english-based cultures.
/// Code from: http://stackoverflow.com/questions/20156/is-there-a-quick-way-to-create-ordinals-in-c/31066#31066
/// With help: http://www.wisegeek.com/what-is-an-ordinal-number.htm
/// </remarks>
/// <param name="number">The number.</param>
/// <returns>Ordinal value of positive integers, or <see cref="int.ToString"/> if less than 1.</returns>
public static string Ordinal(this int number)
{
const string TH = "th";
string s = number.ToString();
// Negative and zero have no ordinal representation
if (number < 1)
{
return s;
}
number %= 100;
if ((number >= 11) && (number <= 13))
{
return s + TH;
}
switch (number % 10)
{
case 1: return s + "st";
case 2: return s + "nd";
case 3: return s + "rd";
default: return s + TH;
}
}
[Test]
public void Ordinal_ReturnsExpectedResults()
{
Assert.AreEqual("-1", (1-2).Ordinal());
Assert.AreEqual("0", 0.Ordinal());
Assert.AreEqual("1st", 1.Ordinal());
Assert.AreEqual("2nd", 2.Ordinal());
Assert.AreEqual("3rd", 3.Ordinal());
Assert.AreEqual("4th", 4.Ordinal());
Assert.AreEqual("5th", 5.Ordinal());
Assert.AreEqual("6th", 6.Ordinal());
Assert.AreEqual("7th", 7.Ordinal());
Assert.AreEqual("8th", 8.Ordinal());
Assert.AreEqual("9th", 9.Ordinal());
Assert.AreEqual("10th", 10.Ordinal());
Assert.AreEqual("11th", 11.Ordinal());
Assert.AreEqual("12th", 12.Ordinal());
Assert.AreEqual("13th", 13.Ordinal());
Assert.AreEqual("14th", 14.Ordinal());
Assert.AreEqual("20th", 20.Ordinal());
Assert.AreEqual("21st", 21.Ordinal());
Assert.AreEqual("22nd", 22.Ordinal());
Assert.AreEqual("23rd", 23.Ordinal());
Assert.AreEqual("24th", 24.Ordinal());
Assert.AreEqual("100th", 100.Ordinal());
Assert.AreEqual("101st", 101.Ordinal());
Assert.AreEqual("102nd", 102.Ordinal());
Assert.AreEqual("103rd", 103.Ordinal());
Assert.AreEqual("104th", 104.Ordinal());
Assert.AreEqual("110th", 110.Ordinal());
Assert.AreEqual("111th", 111.Ordinal());
Assert.AreEqual("112th", 112.Ordinal());
Assert.AreEqual("113th", 113.Ordinal());
Assert.AreEqual("114th", 114.Ordinal());
Assert.AreEqual("120th", 120.Ordinal());
Assert.AreEqual("121st", 121.Ordinal());
Assert.AreEqual("122nd", 122.Ordinal());
Assert.AreEqual("123rd", 123.Ordinal());
Assert.AreEqual("124th", 124.Ordinal());
}
Простий, чистий, швидкий
private static string GetOrdinalSuffix(int num)
{
if (num.ToString().EndsWith("11")) return "th";
if (num.ToString().EndsWith("12")) return "th";
if (num.ToString().EndsWith("13")) return "th";
if (num.ToString().EndsWith("1")) return "st";
if (num.ToString().EndsWith("2")) return "nd";
if (num.ToString().EndsWith("3")) return "rd";
return "th";
}
Або ще краще, як метод розширення
public static class IntegerExtensions
{
public static string DisplayWithSuffix(this int num)
{
if (num.ToString().EndsWith("11")) return num.ToString() + "th";
if (num.ToString().EndsWith("12")) return num.ToString() + "th";
if (num.ToString().EndsWith("13")) return num.ToString() + "th";
if (num.ToString().EndsWith("1")) return num.ToString() + "st";
if (num.ToString().EndsWith("2")) return num.ToString() + "nd";
if (num.ToString().EndsWith("3")) return num.ToString() + "rd";
return num.ToString() + "th";
}
}
Тепер ви можете просто зателефонувати
int a = 1;
a.DisplayWithSuffix();
або навіть так прямо, як
1.DisplayWithSuffix();
Вам доведеться прокатати своє. Зверху голови:
public static string Ordinal(this int number)
{
var work = number.ToString();
if ((number % 100) == 11 || (number % 100) == 12 || (number % 100) == 13)
return work + "th";
switch (number % 10)
{
case 1: work += "st"; break;
case 2: work += "nd"; break;
case 3: work += "rd"; break;
default: work += "th"; break;
}
return work;
}
Потім ви можете зробити
Console.WriteLine(432.Ordinal());
Відредаговано за винятками 11.12.13. Я РОЗПОВІСТЮ кажу з верхньої частини голови :-)
Відредаговано для 1011 року - інші це вже виправили, просто хочуть переконатися, що інші не схоплять цю неправильну версію.
Мені швидше сподобалися елементи з рішень Сту та Самюдсона , і я спільно працював над тим, що, на мою думку, є корисним комбо:
public static string Ordinal(this int number)
{
const string TH = "th";
var s = number.ToString();
number %= 100;
if ((number >= 11) && (number <= 13))
{
return s + TH;
}
switch (number % 10)
{
case 1:
return s + "st";
case 2:
return s + "nd";
case 3:
return s + "rd";
default:
return s + TH;
}
}
Хоча я ще цього не оцінював, ви повинні мати можливість досягти кращих показників, уникаючи всіх умовних заяв.
Це Java, але порт на C # є тривіальним:
public class NumberUtil {
final static String[] ORDINAL_SUFFIXES = {
"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th"
};
public static String ordinalSuffix(int value) {
int n = Math.abs(value);
int lastTwoDigits = n % 100;
int lastDigit = n % 10;
int index = (lastTwoDigits >= 11 && lastTwoDigits <= 13) ? 0 : lastDigit;
return ORDINAL_SUFFIXES[index];
}
public static String toOrdinal(int n) {
return new StringBuffer().append(n).append(ordinalSuffix(n)).toString();
}
}
Зауважте, зменшення умовних умов та використання пошуку масиву повинно прискорити продуктивність, якщо генерується багато порядкових знаків у тісному циклі. Однак я також визнаю, що це не так читабельно, як рішення заяви справи.
Подібно до рішення Райана, але ще більш базового, я просто використовую звичайний масив і використовую день, щоб шукати правильний порядковий номер:
private string[] ordinals = new string[] {"","st","nd","rd","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","th","st","nd","rd","th","th","th","th","th","th","th","st" };
DateTime D = DateTime.Now;
String date = "Today's day is: "+ D.Day.ToString() + ordinals[D.Day];
У мене не було потреби, але я вважаю, що ви могли б використовувати багатовимірний масив, якби хотіли мати підтримку декількох мов.
Оскільки я пам’ятаю свої дні в Uni, цей метод вимагає мінімальних зусиль з боку сервера.
Я використовую цей клас розширення:
public static class Int32Extensions
{
public static string ToOrdinal(this int i)
{
return (i + "th")
.Replace("1th", "1st")
.Replace("2th", "2nd")
.Replace("3th", "3rd");
}
}
Запитувана версія "менш зайвої" версії відповіді самджудсона ...
public static string AddOrdinal(int number)
{
if (number <= 0) return number.ToString();
string GetIndicator(int num)
{
switch (num % 100)
{
case 11:
case 12:
case 13:
return "th";
}
switch (num % 10)
{
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}
return number + GetIndicator(number);
}
public static
і перейменував би його на більш мнемічне ім'я (тобто "OrdinalSuffix"). Користувач може захотіти, щоб числова частина була в різних форматах (тобто з комами).
private static string GetOrd(int num) => $"{num}{(!(Range(11, 3).Any(n => n == num % 100) ^ Range(1, 3).All(n => n != num % 10)) ? new[] { "ˢᵗ", "ⁿᵈ", "ʳᵈ" }[num % 10 - 1] : "ᵗʰ")}";
Якщо хтось шукає один лайнер: p
public static string OrdinalSuffix(int ordinal)
{
//Because negatives won't work with modular division as expected:
var abs = Math.Abs(ordinal);
var lastdigit = abs % 10;
return
//Catch 60% of cases (to infinity) in the first conditional:
lastdigit > 3 || lastdigit == 0 || (abs % 100) - lastdigit == 10 ? "th"
: lastdigit == 1 ? "st"
: lastdigit == 2 ? "nd"
: "rd";
}
EDIT : Як у коментарі зазначає YM_Industries, відповідь samjudson працює на цифри понад 1000, коментар нікефа, схоже, пішов, і я не можу пригадати, у чому була проблема. Залишив цю відповідь тут для часу порівняння.
Дуже багато з них не працюють для цифр> 999, як нікфф зазначив у коментарі (EDIT: зараз відсутнє).
Ось версія , заснована від модифікованої версії samjudson «и загальноприйнятий відповідь , що робить.
public static String GetOrdinal(int i)
{
String res = "";
if (i > 0)
{
int j = (i - ((i / 100) * 100));
if ((j == 11) || (j == 12) || (j == 13))
res = "th";
else
{
int k = i % 10;
if (k == 1)
res = "st";
else if (k == 2)
res = "nd";
else if (k == 3)
res = "rd";
else
res = "th";
}
}
return i.ToString() + res;
}
Також Шахзад Куреши «s відповідь з допомогою маніпуляцій з рядками працює відмінно, однак у нього є зниження продуктивності. Для генерації багатьох з них програма прикладу LINQPad робить версію рядка в 6-7 разів повільнішою, ніж ціле ціле (хоча вам доведеться генерувати багато, щоб помітити).
Приклад LINQPad:
void Main()
{
"Examples:".Dump();
foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 10000013 })
Stuff.GetOrdinal(i).Dump();
String s;
System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew();
for(int iter = 0; iter < 100000; iter++)
foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
s = Stuff.GetOrdinal(i);
"Integer manipulation".Dump();
sw.Elapsed.Dump();
sw.Restart();
for(int iter = 0; iter < 100000; iter++)
foreach(int i in new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 22, 113, 122, 201, 202, 211, 212, 2013, 1000003, 1000013 })
s = (i.ToString() + Stuff.GetOrdinalSuffix(i));
"String manipulation".Dump();
sw.Elapsed.Dump();
}
public class Stuff
{
// Use integer manipulation
public static String GetOrdinal(int i)
{
String res = "";
if (i > 0)
{
int j = (i - ((i / 100) * 100));
if ((j == 11) || (j == 12) || (j == 13))
res = "th";
else
{
int k = i % 10;
if (k == 1)
res = "st";
else if (k == 2)
res = "nd";
else if (k == 3)
res = "rd";
else
res = "th";
}
}
return i.ToString() + res;
}
// Use string manipulation
public static string GetOrdinalSuffix(int num)
{
if (num.ToString().EndsWith("11")) return "th";
if (num.ToString().EndsWith("12")) return "th";
if (num.ToString().EndsWith("13")) return "th";
if (num.ToString().EndsWith("1")) return "st";
if (num.ToString().EndsWith("2")) return "nd";
if (num.ToString().EndsWith("3")) return "rd";
return "th";
}
}
Виходячи з інших відповідей:
public static string Ordinal(int n)
{
int r = n % 100, m = n % 10;
return (r<4 || r>20) && (m>0 && m<4) ? n+" stndrd".Substring(m*2,2) : n+"th";
}
FWIW, для MS-SQL цей вираз зробить роботу. Зберігайте перший WHEN ( WHEN num % 100 IN (11, 12, 13) THEN 'th'
) як перший у списку, оскільки це покладається на спробу перед іншими.
CASE
WHEN num % 100 IN (11, 12, 13) THEN 'th' -- must be tried first
WHEN num % 10 = 1 THEN 'st'
WHEN num % 10 = 2 THEN 'nd'
WHEN num % 10 = 3 THEN 'rd'
ELSE 'th'
END AS Ordinal
Для Excel:
=MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)
Вираз (MOD(A1-11,100)>2)
TRUE (1) для всіх чисел, крім будь-якого, що закінчується 11,12,13
(FALSE = 0). Так 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)
закінчується 1 за 11.12.13, інакше:
1 оцінює 3
2 до 5,
3 - 7
інших: 9
- і потрібні 2 символи вибираються "thstndrdth"
починаючи з цієї позиції.
Якщо ви дійсно хочете перетворити це досить безпосередньо в SQL, це працювало для мене для кількох тестових значень:
DECLARE @n as int
SET @n=13
SELECT SubString( 'thstndrdth'
, (SELECT MIN(value) FROM
(SELECT 9 as value UNION
SELECT 1+ (2* (ABS(@n) % 10) * CASE WHEN ((ABS(@n)+89) % 100)>2 THEN 1 ELSE 0 END)
) AS Mins
)
, 2
)
Це реалізація в dart
і може бути змінена відповідно до мови.
String getOrdinalSuffix(int num){
if (num.toString().endsWith("11")) return "th";
if (num.toString().endsWith("12")) return "th";
if (num.toString().endsWith("13")) return "th";
if (num.toString().endsWith("1")) return "st";
if (num.toString().endsWith("2")) return "nd";
if (num.toString().endsWith("3")) return "rd";
return "th";
}
Хоча тут є багато хороших відповідей, я думаю, що є місце для іншого, цього разу на основі відповідності шаблонів, якщо не для іншого, то принаймні для дискусійної читабельності
public static string Ordinals1(this int number)
{
switch (number)
{
case int p when p % 100 == 11:
case int q when q % 100 == 12:
case int r when r % 100 == 13:
return $"{number}th";
case int p when p % 10 == 1:
return $"{number}st";
case int p when p % 10 == 2:
return $"{number}nd";
case int p when p % 10 == 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
і що робить це рішення особливим? нічого, крім того, що я додаю деякі міркування щодо ефективності для різних інших рішень
відверто кажучи, я сумніваюся, що ефективність дійсно має значення для цього конкретного сценарію (кому дійсно потрібні порядкові мільйони чисел), але, принаймні, це деякі порівняння, які слід враховувати ...
1 мільйон предметів для довідки (ваш фрезер може змінюватися залежно від технічних характеристик, звичайно)
з узгодженням шаблонів та поділами (ця відповідь)
~ 622 мс
з узгодженням шаблону та рядками (ця відповідь)
~ 1967 мс
з двома перемикачами та відділеннями (прийнята відповідь)
~ 637 мс
з одним перемикачем і діленнями (інша відповідь)
~ 725 мс
void Main()
{
var timer = new Stopwatch();
var numbers = Enumerable.Range(1, 1000000).ToList();
// 1
timer.Reset();
timer.Start();
var results1 = numbers.Select(p => p.Ordinals1()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and divisions");
// 2
timer.Reset();
timer.Start();
var results2 = numbers.Select(p => p.Ordinals2()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with pattern matching and strings");
// 3
timer.Reset();
timer.Start();
var results3 = numbers.Select(p => p.Ordinals3()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with two switches and divisons");
// 4
timer.Reset();
timer.Start();
var results4 = numbers.Select(p => p.Ordinals4()).ToList();
timer.Stop();
timer.Elapsed.TotalMilliseconds.Dump("with one switche and divisons");
}
public static class Extensions
{
public static string Ordinals1(this int number)
{
switch (number)
{
case int p when p % 100 == 11:
case int q when q % 100 == 12:
case int r when r % 100 == 13:
return $"{number}th";
case int p when p % 10 == 1:
return $"{number}st";
case int p when p % 10 == 2:
return $"{number}nd";
case int p when p % 10 == 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
public static string Ordinals2(this int number)
{
var text = number.ToString();
switch (text)
{
case string p when p.EndsWith("11"):
return $"{number}th";
case string p when p.EndsWith("12"):
return $"{number}th";
case string p when p.EndsWith("13"):
return $"{number}th";
case string p when p.EndsWith("1"):
return $"{number}st";
case string p when p.EndsWith("2"):
return $"{number}nd";
case string p when p.EndsWith("3"):
return $"{number}rd";
default:
return $"{number}th";
}
}
public static string Ordinals3(this int number)
{
switch (number % 100)
{
case 11:
case 12:
case 13:
return $"{number}th";
}
switch (number % 10)
{
case 1:
return $"{number}st";
case 2:
return $"{number}nd";
case 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
public static string Ordinals4(this int number)
{
var ones = number % 10;
var tens = Math.Floor(number / 10f) % 10;
if (tens == 1)
{
return $"{number}th";
}
switch (ones)
{
case 1:
return $"{number}th";
case 2:
return $"{number}nd";
case 3:
return $"{number}rd";
default:
return $"{number}th";
}
}
}
Інший однолінійний, але без порівнянь, лише індексуючи регулярний вираз, перетворюється на масив.
public static string GetOrdinalSuffix(int input)
{
return new []{"th", "st", "nd", "rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(), "(?<!1)[1-3]$").Value)];
}
Версію PowerShell можна додатково скоротити:
function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] }
Ось клас розширення DateTime. Копіюйте, вставляйте та насолоджуйтесь
загальнодоступний статичний клас DateTimeExtensions {
public static string ToStringWithOrdinal(this DateTime d)
{
var result = "";
bool bReturn = false;
switch (d.Day % 100)
{
case 11:
case 12:
case 13:
result = d.ToString("dd'th' MMMM yyyy");
bReturn = true;
break;
}
if (!bReturn)
{
switch (d.Day % 10)
{
case 1:
result = d.ToString("dd'st' MMMM yyyy");
break;
case 2:
result = d.ToString("dd'nd' MMMM yyyy");
break;
case 3:
result = d.ToString("dd'rd' MMMM yyyy");
break;
default:
result = d.ToString("dd'th' MMMM yyyy");
break;
}
}
if (result.StartsWith("0")) result = result.Substring(1);
return result;
}
}
Результат:
9 жовтня 2014 року
Ще одна альтернатива, яку я використав, грунтуючись на всіх інших пропозиціях, але не потребує спеціального корпусу:
public static string DateSuffix(int day)
{
if (day == 11 | day == 12 | day == 13) return "th";
Math.DivRem(day, 10, out day);
switch (day)
{
case 1:
return "st";
case 2:
return "nd";
case 3:
return "rd";
default:
return "th";
}
}