Есть ли простой способ создания ординалов в С#?
Есть ли простой способ в С# создать Ordinals для номера? Например:
- 1 возвращает 1-й
- 2 возвращает 2-й
- 3 возвращает третий
- ... и т.д.
Можно ли это сделать через String.Format()
или есть ли какие-либо функции, доступные для этого?
Ответы
Ответ 1
На этой странице вы найдете полный список правил пользовательского численного форматирования:
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().
Также обратите внимание, что это не интернационализировано. Я не знаю, как выглядят ординалы на других языках.
Ответ 2
Помните интернационализацию!
Решения здесь работают только на английском языке. Все становится намного сложнее, если вам нужно поддерживать другие языки.
Например, на испанском языке "1-й" будет записан как "1.o", "1.a", "1.os" или "1.as" в зависимости от того, является ли вещь, которую вы считаете, мужским, женский или множественный!
Итак, если ваше программное обеспечение должно поддерживать разные языки, постарайтесь избежать ординалов.
Ответ 3
Моя версия версии Джесса версий 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());
}
Ответ 4
Простой, чистый, быстрый
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();
Ответ 5
Вам придется сворачивать самостоятельно. С головы:
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 - другие уже исправили это, просто хочу удостовериться, что другие не захватили эту неправильную версию.
Ответ 6
Мне нравились элементы как из 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;
}
}
Ответ 7
Пока я еще не тестировал это, вы должны иметь возможность получить лучшую производительность, избегая всех условных операторов 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.
Ответ 8
Подобно решению 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, этот метод требует минимальных усилий с сервера.
Ответ 9
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
Ответ 10
Я использую этот класс расширения:
public static class Int32Extensions
{
public static string ToOrdinal(this int i)
{
return (i + "th")
.Replace("1th", "1st")
.Replace("2th", "2nd")
.Replace("3th", "3rd");
}
}
Ответ 11
Запрашивается версия ответа на вопрос "меньше избыточности" 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);
}
Ответ 12
РЕДАКТИРОВАТЬ. Как отмечает 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";
}
}
Ответ 13
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";
}
Ответ 14
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
)
Ответ 15
Основываясь на других ответах:
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";
}
Ответ 16
Это реализация в 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";
}
Ответ 17
Хотя здесь есть много хороших ответов, я думаю, что есть место для другого, на этот раз на основе сопоставления с образцом, если не для чего-то еще, то, по крайней мере, для дискуссионной читабельности
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";
}
}
}
Ответ 18
Вот класс расширения 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 года
Ответ 19
Другая альтернатива, которую я использовал на основе всех других предложений, но не требует специальной оболочки:
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";
}
}