Czy istnieje prosty sposób w C # na tworzenie porządków dla liczby? Na przykład:
- 1 zwraca 1. pozycję
- 2 zwraca 2. miejsce
- 3 zwraca 3. miejsce
- ...itp
Czy można to zrobić za pomocą String.Format()
czy dostępne są jakieś funkcje?
Czy istnieje prosty sposób w C # na tworzenie porządków dla liczby? Na przykład:
Czy można to zrobić za pomocą String.Format()
czy dostępne są jakieś funkcje?
Odpowiedzi:
Ta strona zawiera pełną listę wszystkich niestandardowych reguł formatowania numerycznego:
http://msdn.microsoft.com/en-us/library/0c899ak8.aspx
Jak widać, w porządkach nie ma nic, więc nie można tego zrobić za pomocą String.Format. Jednak nie jest tak trudno napisać funkcję, aby to zrobić.
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";
}
}
Aktualizacja: Techniczne porządki nie istnieją dla <= 0, więc zaktualizowałem powyższy kod. Usunięto także zbędne ToString()
metody.
Należy również pamiętać, że nie jest to internacjonalizowane. Nie mam pojęcia, jak wyglądają porządki w innych językach.
Pamiętaj o internacjonalizacji!
Rozwiązania tutaj działają tylko w języku angielskim. Sprawy stają się znacznie bardziej złożone, jeśli potrzebujesz obsługi innych języków.
Na przykład w języku hiszpańskim „1.” byłoby napisane jako „1.o”, „1.a”, „1.os” lub „1.as” w zależności od tego, czy liczysz coś męskiego, żeńskiego czy mnogiego !
Więc jeśli twoje oprogramowanie musi obsługiwać różne języki, staraj się unikać porządków.
Moja wersja wersji Jesse'a Stu i wersji Samjudson :)
Uwzględniono test jednostkowy, aby wykazać, że zaakceptowana odpowiedź jest niepoprawna, gdy liczba <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());
}
Prosto, czysto, szybko
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";
}
Lub jeszcze lepiej, jako metoda rozszerzenia
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";
}
}
Teraz możesz po prostu zadzwonić
int a = 1;
a.DisplayWithSuffix();
lub nawet tak bezpośredni jak
1.DisplayWithSuffix();
Musisz rzucić własne. Z czubka głowy:
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;
}
Możesz wtedy zrobić
Console.WriteLine(432.Ordinal());
Edytowano dla wyjątków 11.12.2013. Powiedziałem z góry :-)
Edycja dla 1011 - inni już to naprawili, po prostu upewnij się, że inni nie pobiorą tej niepoprawnej wersji.
Raczej podobały mi się elementy z rozwiązań Stu i samjudson i połączyłem je w coś, co moim zdaniem jest użyteczną kombinacją:
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;
}
}
Chociaż nie przeprowadziłem jeszcze testu porównawczego, powinieneś być w stanie uzyskać lepszą wydajność, unikając wszystkich instrukcji warunkowych.
To jest Java, ale port do C # jest trywialny:
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();
}
}
Zwróć uwagę, że ograniczenie warunkowe i użycie wyszukiwania tablicowego powinno przyspieszyć wydajność, jeśli generujesz wiele rzędnych w ciasnej pętli. Przyznaję jednak, że nie jest to tak czytelne jak rozwiązanie instrukcji case.
Podobne do rozwiązania Ryana, ale nawet bardziej podstawowe, po prostu używam prostej tablicy i używam dnia, aby wyszukać poprawną liczbę porządkową:
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];
Nie miałem takiej potrzeby, ale zakładam, że możesz użyć wielowymiarowej tablicy, jeśli chcesz mieć obsługę wielu języków.
Z tego, co pamiętam z czasów Uni, ta metoda wymaga minimalnego wysiłku ze strony serwera.
Używam tej klasy rozszerzenia:
public static class Int32Extensions
{
public static string ToOrdinal(this int i)
{
return (i + "th")
.Replace("1th", "1st")
.Replace("2th", "2nd")
.Replace("3th", "3rd");
}
}
Żądano wersji „mniej redundancji” odpowiedzi 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);
}
public static
i zmieniłem jego nazwę na bardziej mnemoniczną nazwę (tj. „OrdinalSuffix”). Dzwoniący może chcieć, aby część liczbowa była w różnych formatach (np. Przecinkami).
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] : "ᵗʰ")}";
Jeśli ktoś szuka jednej wkładki: str
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";
}
EDYCJA : Jak zauważa YM_Industries w komentarzu, odpowiedź samjudsona DZIAŁA dla liczb powyżej 1000, wydaje się, że komentarz Nickfa zniknął i nie pamiętam, jaki był problem. Pozostaw tę odpowiedź tutaj dla czasów porównania.
Okropnie wiele z nich nie działa dla liczb> 999, jak nick zauważył w komentarzu (EDYCJA: teraz brakuje).
Oto wersja oparta na zmodyfikowanej wersji zaakceptowanej odpowiedzi samjudsona , która spełnia.
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;
}
Również Shahzad Qureshi „s odpowiedź za pomocą manipulacji ciąg działa dobrze, jednak ma spadku wydajności. Aby wygenerować wiele z nich, przykładowy program LINQPad sprawia, że wersja łańcucha jest 6-7 razy wolniejsza niż ta liczba całkowita (chociaż trzeba by generować dużo, aby zauważyć).
Przykład 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";
}
}
Na podstawie innych odpowiedzi:
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, dla MS-SQL, to wyrażenie wykona zadanie. Zachowaj pierwszy WHEN ( WHEN num % 100 IN (11, 12, 13) THEN 'th'
) jako pierwszy na liście, ponieważ zależy to od wypróbowania go przed innymi.
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
W przypadku programu Excel:
=MID("thstndrdth",MIN(9,2*RIGHT(A1)*(MOD(A1-11,100)>2)+1),2)
Wyrażenie (MOD(A1-11,100)>2)
ma wartość PRAWDA (1) dla wszystkich liczb z wyjątkiem każdego kończącego się na 11,12,13
(FAŁSZ = 0). Tak więc 2 * RIGHT(A1) * (MOD(A1-11,100)>2) +1)
kończy się jako 1 na 11.12.2013, w przeciwnym razie:
1 ocenia na 3
2 do 5,
3 do 7
innych: 9
- i wymagane 2 znaki są wybierane "thstndrdth"
z tej pozycji.
Jeśli naprawdę chcesz przekonwertować to dość bezpośrednio na SQL, działało to dla mnie dla kilku wartości testowych:
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
)
Jest to implementacja dart
i może być modyfikowana zgodnie z językiem.
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";
}
Chociaż jest tu wiele dobrych odpowiedzi, myślę, że jest miejsce na inną, tym razem opartą na dopasowaniu wzorców, jeśli nie na niczym innym, to przynajmniej na dyskusyjną czytelność
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";
}
}
a co wyróżnia to rozwiązanie? nic poza faktem, że dodałem rozważania dotyczące wydajności dla różnych innych rozwiązań
szczerze mówiąc, wątpię, czy wydajność naprawdę ma znaczenie w tym konkretnym scenariuszu (który naprawdę potrzebuje porządków milionów liczb), ale przynajmniej przedstawia pewne porównania, które należy wziąć pod uwagę ...
1 milion pozycji w celach informacyjnych (twój przebieg może się różnić w zależności od specyfikacji maszyny)
z dopasowaniem wzorca i podziałami (ta odpowiedź)
~ 622 ms
z dopasowaniem wzorca i łańcuchami (ta odpowiedź)
~ 1967 ms
z dwoma przełącznikami i podziałami (zaakceptowana odpowiedź)
~ 637 ms
z jednym przełącznikiem i podziałami (inna odpowiedź)
~ 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";
}
}
}
Kolejny jednowierszowy, ale bez porównań tylko poprzez indeksowanie wyniku wyrażenia regularnego do tablicy.
public static string GetOrdinalSuffix(int input)
{
return new []{"th", "st", "nd", "rd"}[Convert.ToInt32("0" + Regex.Match(input.ToString(), "(?<!1)[1-3]$").Value)];
}
Wersję PowerShell można dodatkowo skrócić:
function ord($num) { return ('th','st','nd','rd')[[int]($num -match '(?<!1)[1-3]$') * $matches[0]] }
Oto klasa DateTime Extension. Kopiuj, wklej i ciesz się
publiczna klasa statyczna 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;
}
}
Wynik:
9 października 2014 r
Kolejna alternatywa, której użyłem na podstawie wszystkich innych sugestii, ale nie wymaga specjalnej obudowy:
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";
}
}