Есть ли простой способ в С# создать Ordinals для номера? Например:
- 1 возвращает 1-й
- 2 возвращает 2-й
- 3 возвращает третий
- ... и т.д.
Можно ли это сделать через String.Format()
или есть ли какие-либо функции, доступные для этого?
Есть ли простой способ в С# создать Ordinals для номера? Например:
Можно ли это сделать через 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" в зависимости от того, является ли вещь, которую вы считаете, мужским, женский или множественный!
Итак, если ваше программное обеспечение должно поддерживать разные языки, постарайтесь избежать ординалов.
Моя версия версии Джесса версий Stu и samjudson:)
Включено unit test, чтобы показать, что принятый ответ неверен, когда число < 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 - другие уже исправили это, просто хочу удостовериться, что другие не захватили эту неправильную версию.
Мне нравились элементы как из Stu, так и samjudson и сработали вместе в том, что я думаю полезная комбо:
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;
}
}
Пока я еще не тестировал это, вы должны иметь возможность получить лучшую производительность, избегая всех условных операторов case.
Это java, но порт на С# тривиален:
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();
}
}
Примечание. Сокращение условных выражений и использование поиска массива должны ускорять работу, если генерировать множество ординалов в узком цикле. Тем не менее, я также признаю, что это не так читаемо, как решение case case.
Подобно решению Ryan, но даже более базовому, я просто использую простой массив и использую день для поиска правильного порядкового номера:
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 day is: "+ D.Day.ToString() + ordinals[D.Day];
У меня не было необходимости, но я бы предположил, что вы можете использовать многомерный массив, если хотите иметь поддержку нескольких языков.
Из того, что я помню из своих дней Uni, этот метод требует минимальных усилий с сервера.
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 class Int32Extensions
{
public static string ToOrdinal(this int i)
{
return (i + "th")
.Replace("1th", "1st")
.Replace("2th", "2nd")
.Replace("3th", "3rd");
}
}
Запрашивается версия ответа на вопрос "меньше избыточности" samjudson...
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);
}
РЕДАКТИРОВАТЬ. Как отмечает YM_Industries в комментарии, ответ samjudson РАБОТАЕТ для чисел более 1000, nickf комментарий, похоже, ушел, и я не могу вспомнить, в чем проблема, которую я видел. Оставьте этот ответ здесь для сравнения времени.
Многие из них не работают для чисел > 999, поскольку nickf указано в комментарии (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;
}
Также Shahzad Qureshi ответить, используя строковые манипуляции, прекрасно работает, однако имеют штраф за исполнение. Для генерации многих из них пример программы 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 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";
}
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
)
Основываясь на других ответах:
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";
}
Это реализация в 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 ms
с сопоставлением с образцом и строками (этот ответ)
~1967 ms
с двумя переключателями и делениями (принятый ответ)
~637 ms
с одним переключателем и делениями (другой ответ)
~725 ms
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";
}
}
}
Вот класс расширения 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";
}
}