How do I sort string numbers? [duplicate] - arrays

This question already has answers here:
Sort on a string that may contain a number
(24 answers)
Closed 3 years ago.
The output of this string should be sorted as follows.
The output of this string should be sorted as follows.
public static void main(String[] args) {
String input="";
List<String> items = Arrays.asList(input.split("\\s*,\\s*"));
System.out.println("items: " + items);
Collections.sort(items, new Comparator<String>() {
public int compare(String o1, String o2) {
String o1StringPart = o1.replaceAll("\\d", "");
String o2StringPart = o2.replaceAll("\\d", "");
if (o1StringPart.equalsIgnoreCase(o2StringPart)) {
return extractInt(o1) - extractInt(o2);
}
return o1.compareTo(o2);
}
int extractInt(String s) {
String num = s.replaceAll("\\D", "");
// return 0 if no digits found
return num.isEmpty() ? 0 : Integer.parseInt(num);
}
});
for (String s : items) {
System.out.println(s);
}} }

I assume that all the numeric are integer and all the alphabets are A to Z, you can transform those strings which contains / or - into floating points first, then replace all the alphabets to empty strings. Finally, compare their values as Double.
For example, 51/1 will be 51.1 and 571-573B will be 571.573.
Code snippet
public int compare(String o1, String o2) {
String n1 = o1.replace("-", ".").replace("/", ".").replaceAll("[A-Z]", "");
String n2 = o2.replace("-", ".").replace("/", ".").replaceAll("[A-Z]", "");
// This equals above statements
//String n1 = o1.replaceAll("[-/]", ".").replaceAll("[A-Z]", "");
//String n2 = o2.replaceAll("[-/]", ".").replaceAll("[A-Z]", "");
int result = Double.compare(Double.valueOf(n1), Double.valueOf(n2));
return (result == 0) ? o1.compareTo(o2) : result;
}
This is not the most elegant way, but I think it should work!

Use the below snippet code. Firstly, you need to compare the integer part of the string and in case the integer part is equal compare the string part.
import java.io.*;
import java.util.*;
import java.util.regex.*;
/*
* To execute Java, please define "static void main" on a class
* named Solution.
*
* If you need more classes, simply define them inline.
*/
class Solution {
public static void main(String[] args) {
String input = "605,2A,401-2A,32C,21F,201A,605A,401-1A,200-2E,583-58D,583/58E,583-57D,542,2B,1,542/2E,605B,32D,3,603,4,6,5,60,201C,542/2D,40,20,50,200-2C,21C,800A,200A,571-573B,51/2,470/1,51/1,571-573C,454-1,444-446";
List < String > items = Arrays.asList(input.split("\\s*,\\s*"));
System.out.println("items: " + items);
Pattern pattern = Pattern.compile("^\\d+");
Collections.sort(items, new Comparator < String > () {
public int compare(String o1, String o2) {
int intDiff = extractInt(o1) - extractInt(o2);
if (intDiff == 0) {
return o1.compareTo(o2);
}
return intDiff;
}
int extractInt(String s) {
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
String num = matcher.group(0);
return Integer.parseInt(num);
}
return 0;
}
});
for (String s: items) {
System.out.println(s);
}
}
}

This answer handles comparison between numbers like 20-10A and 20-2A
public static void main(String[] args) {
String s = "605,2A,401-2A,32C,21F,201A,605A,401-1A,200-2E,583-58D,583/58E,583-57D,542,2B,1,542/2E," +
"605B,32D,3,603,4,6,5,60,201C,542/2D,40,20,50,200-2C,21C,800A,200A,571-573B,51/2,470/1,51/1," +
"571-573C,454-1,444-446";
String[] strings = s.split(",");
Arrays.sort(strings, App::compare);
System.out.println(Arrays.deepToString(strings));
}
public static int compare(String o1, String o2) {
if (startsWithDelim(o1)) return compare(o1.substring(1), o2);
if (startsWithDelim(o2)) return compare(o1, o2.substring(1));
List<String> n1 = extractInt(o1);
List<String> n2 = extractInt(o2);
if (n1 != null && n2 != null) {
Integer n1int = Integer.parseInt(n1.get(0));
Integer n2int = Integer.parseInt(n2.get(0));
String n1Remaining = n1.get(1);
String n2Remaining = n2.get(1);
int intCompare = n1int.compareTo(n2int);
return intCompare == 0 ? compare(n1Remaining, n2Remaining) : intCompare;
}
if (n1 == null && n2 == null)
return o1.compareTo(o2);
else if (n1 == null) return -1;
else return 1;
}
static List<String> extractInt(String s) {
Pattern pattern = Pattern.compile("^\\d+");
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
String num = matcher.group(0);
return List.of(num, s.substring(matcher.end(0)));
}
return null;
}
static boolean startsWithDelim(String s) {
return (s.startsWith("/") || s.startsWith("-"));
}

I will provide some tips
In order compare the items 1,20,200-2C,3,32C,32D,4 (this is the default sort order, as string) you need to check if any number is included in the string. This can be done using a regular expression. You can find a lot of examples online with regular expressions that can bring you the numbers in the string back.
Modify your comparator and check if any of the two string that are to be compared include any number and return the appropriate result.
the extractInt method can be similar to as #Pankaj Saini 's suggestion
Integer extractInt(String s) {
Pattern pattern = Pattern.compile("^\\d+");
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
String num = matcher.group(0);
return Integer.parseInt(num);
}
return null;
}
The compare method can be like this
public int compare(String o1, String o2) {
if(extractInt(o1)!=null && extractInt(o2)!=null){
if(extractInt(o1).equals(extractInt(o2)))
{
return o1.substring(extractInt(o1).toString().length())
.compareTo(o2.substring(extractInt(o2).toString().length()));
}
return extractInt(o1).compareTo(extractInt(o2));
}
else if(extractInt(o1)!=null)
{
return -1;
}
else if(extractInt(o2)!=null)
{
return 1;
}
else{
return o1.compareTo(o2);
}
}

Related

Salesforce Apex class to sort years and year ranges giving incorrect output

Tried writing a sort method where input was given like a string of comma-delimited years and year ranges String input = '2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013';
Expectation is to get a string of comma-delimited years and year ranges,and remove all duplicates and invalid inputs.
Below is class written which is not giving me correct output
public class sortYearAndYearRangesString {
public static List<String> sortSpecialString(String input) {
system.debug(input);
List<String> inputList = input.split('');
system.debug(inputList);
Map<Integer,String> stringMap = new Map<Integer,String>();
system.debug(stringMap);
List<String> output = new List<String>();
for (Integer i=0; i<inputList.size(); i++) {
String charac = inputList[i];
if(!charac.isAlphaNumeric()) {
system.debug(charac);
stringMap.put(i,charac);
}else {
output.add(charac);
system.debug(output);
}
}
String finalString = String.join(output,'');
system.debug(finalString);
List<String> resultList = finalString.reverse().split('');
for( Integer I : stringMap.keySet() ){
system.debug(I);
resultList.add(I,stringMap.get(I));
system.debug(resultList);
}
return resultList;
}
Tried validating the solution in Anonymous Apex but no success
public static void validateSolution() {
String input = '2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013';
List<Integer> expected = new List<Integer> {2013,2017,2018,2020,2021,2022,2023,2025};
List<Integer> actual = sortYearAndYearRangesString(input);
System.assertEquals(expected, actual, 'Invalid Results');
}
}
Your help is appreciated
Regards
Carolyn
According to your test case, you should also define at least a constant for a maximum value, in order to exclude 20261. Probably you need a minimum too.
I used 1700 as min and 4000 as max because these are the limits for a Date or Datatime field: docs
Moreover the method must return a List<Integer> instead of a List<String>.
You don't need a Map, just a Set would work.
public class SortYearAndYearRangesString {
private static final Integer MAX_YEAR = 4000;
private static final Integer MIN_YEAR = 1700;
public static List<Integer> sortSpecialString(String input) {
Set<Integer> output = new Set<Integer>();
List<String> yearsList = input.split(',');
for (String yearString : yearsList) {
yearString = yearString.trim();
if (yearString.isNumeric()) {
try {
Integer year = Integer.valueOf(yearString);
if (year >= MIN_YEAR && year <= MAX_YEAR) {
output.add(year);
}
} catch (TypeException e) {
System.debug(e.getMessage());
}
} else {
List<String> range = yearString.split('-');
if (range.size() == 2 && range[0].isNumeric() && range[1].isNumeric()) {
try {
// Modify the following two lines once you know how to handle range like 1300-1500 or 3950-4150
Integer firstYear = Math.max(Integer.valueOf(range[0]), MIN_YEAR);
Integer lastYear = Math.min(Integer.valueOf(range[1]), MAX_YEAR);
while (firstYear <= lastYear) {
output.add(firstYear++);
}
} catch (TypeException e) {
System.debug(e.getMessage());
}
}
}
}
List<Integer> sortedYears = new List<Integer>(output);
sortedYears.sort();
return sortedYears;
}
}
If a range that exceed the boundaries (like 1300-1500 or 3950-4150) should be treated as invalid and skipped, please change these lines
Integer firstYear = Math.max(Integer.valueOf(range[0]), MIN_YEAR);
Integer lastYear = Math.min(Integer.valueOf(range[1]), MAX_YEAR);
while (firstYear <= lastYear) {
output.add(firstYear++);
}
as follow:
Integer firstYear = Integer.valueOf(range[0]);
Integer lastYear = Integer.valueOf(range[1]);
if (firstYear >= MIN_YEAR && lastYear <= MAX_YEAR) {
while (firstYear <= lastYear) {
output.add(firstYear++);
}
}
I tested it in anonymous console with the following code:
String input = '2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013';
List<Integer> expected = new List<Integer> {2013,2017,2018,2020,2021,2022,2023,2025};
List<Integer> actual = SortYearAndYearRangesString.sortSpecialString(input);
System.debug(actual);
System.assertEquals(expected, actual, 'Invalid Results');
input = '1500,2017, 2018,2020-2023,1800-1700,2020,20a9,19z5-1990,2025,20261,2013,3998-4002';
expected = new List<Integer> {2013,2017,2018,2020,2021,2022,2023,2025,3998,3999,4000};
actual = SortYearAndYearRangesString.sortSpecialString(input);
System.assertEquals(expected, actual, 'Invalid Results');
I made some changes to the class.
It does increment all ranges - and doesn't check if they're years that would make sense. You'll need to add that logic in there (e.g. 1500-1600 would return all years between 1500-1600. Prob best to cap at 1900 or something)
public class SortYearAndYearRangesString{
public static List<Integer> sortSpecialString(String input){
List<String> inputList = input.split(',');
Set<Integer> output = new Set<Integer>();
system.debug('input ' + input);
system.debug('inputList ' + inputList);
for (String s : inputList){
Set<Integer> tempSet = new Set<Integer>();
s.remove(' ');
if (s.contains('-')){
//// break the ranges and fill in years
List<String> tempSet2 = s.split('-');
for (String s2 : tempSet2){
try{
///capture valid integers
Integer tempInt = Integer.valueOf(s2);
tempSet.add(tempInt);
} catch (Exception e){
tempSet.clear();
break;
}
}
System.debug('set ' + tempSet);
if (tempSet.size() > 1){
List<Integer> tempList = new List<Integer>(tempSet);
tempList.sort ();
Integer r = tempList.size() - 1;
// iterate through the years
for (Integer i = tempList.get(0); i < tempList.get(r); i++){
tempSet.add(i) ;
}
}
} else{
try{
///capture valid integers
Integer tempInt = Integer.valueOf(s);
tempSet.add(tempInt);
} catch (Exception e){
continue;
}
}
output.addAll(tempSet);
}
// output is currently set of ints, need to convert to list of integer
List<Integer> finalOutput = new List<Integer>(output);
finalOutput.sort ();
System.debug('finalOutput :' + finalOutput);
return finalOutput;
}}

WPF Listbox Collection custom sort

I have a listbox
DropPrice
MyPrice
Price1
Price2
I want to sort it like this
Price1
Price2
DropPrice
MyPrice
I mean, if there's an item that starts with the sequence "price", it gets priority, else the smallest string should get the priority.
My source code:
var lcv = (ListCollectionView)(CollectionViewSource.GetDefaultView(_itemsSource));
var customSort = new PrioritySorting("price");
lcv.CustomSort = customSort;
internal class PrioritySorting : IComparer
{
private string _text;
public PrioritySorting(string text)
{
_text = text;
}
public int Compare(object x, object y)
{
//my sorting code here
}
}
How can i write compare method. I know, that it can return 1,0 or -1. How can i set priorities.
You just have to check if it starts with "price".
Note that I don't think that ToString() is appropriate; you should rather implement IComparer<T> and strongly type your objects in your listbox.
public int Compare(object x, object y)
{
// test for equality
if (x.ToString() == y.ToString())
{
return 0;
}
// if x is "price" but not y, x goes above
if (x.ToString().StartsWith("Price") && !y.ToString().StartsWith("Price"))
{
return -1;
}
// if y is "price" but not x, y goes above
if (!x.ToString().StartsWith("Price") && y.ToString().StartsWith("Price"))
{
return 1;
}
// otherwise, compare normally (this way PriceXXX are also compared among themselves)
return string.Compare(x.ToString(), y.ToString());
}
Here is sample code snippet for IComparer.
private class sortYearAscendingHelper : IComparer
{
int IComparer.Compare(object a, object b)
{
car c1=(car)a;
car c2=(car)b;
if (c1.year > c2.year)
return 1;
if (c1.year < c2.year)
return -1;
else
return 0;
}
}
This is more specific to your Question
internal class PrioritySorting : IComparer
{
private string _text;
public PrioritySorting(string text)
{
_text = text;
}
public int Compare(object x, object y)
{
var str1 = x as string;
var str2 = y as string;
if (str1.StartsWith("price") )
{
if (str2.StartsWith("price"))
return 0;
return 1;
}
return -1;
}
}

Comma separated string split by length but keeping the comma separation?

I have a string like this "105321,102305,321506,0321561,3215658" and i need to split this string by the comma (,) and with a specific length, so if the length of each string part is 15, the splited array must be like:
105321,102305
321506,0321561
3215658
I try several ways but i can't find the right approach to do this
The code that i have gives me an error of index out of range:
private static List<string> SplitThis(char charToSplit, string text, int maxSplit)
{
List<string> output = new List<string>();
string[] firstSplit = text.Split(charToSplit);
for(int i = 0; i < firstSplit.Length; i++)
{
string part = firstSplit[i];
if(output.Any() && output[i].Length + part.Length >= maxSplit)
{
output.Add(part);
}
else
{
if(!output.Any())
output.Add(part);
else
output[i] += "," + part;
}
}
return output;
}
Edit: i must say that the comma , must be a part of the amount of the maxSplit variable.
This one would be concise, yet not really performant
private static Pattern P = Pattern.compile("(.{1,15})(,|$)");
private static String[] split(String string) {
Matcher m = P.matcher(string);
List<String> splits = new ArrayList<String>();
while (m.find()) {
splits.add(m.group(1));
}
return splits.toArray(new String[0]);
}
The regular expression in P matches (.{1,15})(,|$):
a sequence of 1 to 15 characters = .{1,15}
followed by , or line ending
the parentheses allow grouping, the content of the first group is what you are interested in
static void Main(string[] args)
{
string originalString = "105321,102305,321506,0321561,3215658";
string[] commaSplit = originalString.Split(',');
string tempString = string.Empty;
List<string> result = new List<string>();
for (int i = 0; i < commaSplit.Length; i++ )
{
if ((tempString.Length + commaSplit[i].Length) > 15)
{
result.Add(tempString);
tempString = string.Empty;
}
if (tempString.Length > 0)
{
tempString += ',' + commaSplit[i];
}
else
{
tempString += commaSplit[i];
}
if (i == commaSplit.Length - 1 && tempString != string.Empty)
{
result.Add(tempString);
}
}
foreach (var s in result)
{
Console.WriteLine(s);
}
Console.ReadKey();
}
This may not be the best solution but it works ;)
what about this?
private static List<string> SplitThis(char charToSplit, string text, int maxSplit)
{
List<string> output = new List<string>();
string[] firstSplit = text.Split(charToSplit);
int i = 0;
while (i < firstSplit.Length)
{
string part = firstSplit[i];
while (part.Length < maxSplit)
{
if (part.Length < maxSplit && i + 1 < firstSplit.Length)
{
if ((part + "," + firstSplit[i + 1]).Length < maxSplit)
{
part += "," + firstSplit[i + 1];
i++;
}
else
{
output.Add(part);
i++;
break;
}
}
else
{
output.Add(part);
i++;
break;
}
}
}
return output;
}

Putting array with unknown variables into another array

The purpose of this code is is to define the root of the sum of the squares.
I cant figure out how to put i into j. Please help.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
int input, som, i=0;
int j = 0;
double answer;
Boolean gaDoor= true;
int [] array = new int [24];
while (gaDoor)
{
Console.Write("Specify a positive integer");
input = Convert.ToInt32(Console.ReadLine());
if (input == -1)
{
gaDoor = false;
}
else
{
if (input >= 0)
{
array[i] = input;
i++;
}
else
{
Console.WriteLine("Specify a positive integer ");
}
}
}
while (j<i)
{
sum = array [j] ^ 2;
answer = Math.Sqrt(sum);
Console.Write(answer);
}
Console.ReadKey();
}
}
}
using System;
namespace Test
{
class MainClass
{
public static void Main (string[] args)
{
int[] invoer = new int[24];
double[] resultaat = new double[24];
double totaal = 0;
double wortel = 0;
int commando = 0;
int teller = -1;
try {
// Keep going until a negative integer is entered (or a 0)
while ((commando = Convert.ToInt32 (Console.ReadLine ())) > 0) {
teller++;
invoer [teller] = commando;
}
} catch (FormatException) {
// Not a number at all.
}
teller = -1;
foreach (int i in invoer) {
teller++;
resultaat [teller] = Math.Pow (invoer [teller], 2);
totaal += resultaat [teller];
if (invoer [teller] > 0) {
Console.WriteLine ("Invoer: {0}, Resultaat: {1}", invoer [teller], resultaat [teller]);
}
}
wortel = Math.Sqrt (totaal);
Console.WriteLine ("Totaal: {0}, Wortel: {1}", totaal, wortel);
}
}
}

BigInteger parsing on Silverlight

I'm actually working on a IBAN key verification function.
To get the key i do something like :
string theKey = (98 - ((int64.Parse(value)) % 97)).ToString();
The problem is that my value is something longer than 19. So i need to use BigInteger from System.Numerics.
This references doesn't include the Parse() method.
I need a solution that would allow me to use 23char integer on Silverlight.
Yep i dont think BigInteger.Parse() is available in silverlight.
You could use Decimal just without a decimal point, as a decimal value can go up to 79,228,162,514,264,337,593,543,950,335.
(29 chars) if i counted correctly..
*Edit - the reason i chose decimal over double is that decimal has more significant figures and can therefore be more precise.
Someone on MSDN gave me a class that comes with Parse/TryParse, it works really good and i hope it'll help. Thanks for the decimal solution though, but it appears that i need to use 30 digits int as well, so biginteger was a must have :
public static class BigIntegerHelper
{
public static BigInteger Parse(string s)
{
return Parse(s, CultureInfo.CurrentCulture);
}
public static BigInteger Parse(string s, IFormatProvider provider)
{
return Parse(s, NumberStyles.Integer, provider);
}
public static BigInteger Parse(string s, NumberStyles style)
{
return Parse(s, style, CultureInfo.CurrentCulture);
}
public static BigInteger Parse(string s, NumberStyles style, IFormatProvider provider)
{
BigInteger result;
if (TryParse(s, style, provider, out result))
{
return result;
}
throw new FormatException();
}
public static bool TryParse(string s, out BigInteger b)
{
return TryParse(s, NumberStyles.Integer, CultureInfo.CurrentCulture, out b);
}
public static bool TryParse(string s, NumberStyles style, IFormatProvider formatProvider, out BigInteger value)
{
if (formatProvider == null)
{
formatProvider = CultureInfo.CurrentCulture;
}
if ((style & ~(NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowLeadingSign | NumberStyles.AllowHexSpecifier)) != NumberStyles.None)
{
throw new NotSupportedException();
}
NumberFormatInfo numberFormatInfo = (NumberFormatInfo)formatProvider.GetFormat(typeof(NumberFormatInfo));
uint num = ((style & NumberStyles.AllowHexSpecifier) != NumberStyles.None) ? 16u : 10u;
int num2 = 0;
bool flag = false;
if ((style & NumberStyles.AllowLeadingWhite) != NumberStyles.None)
{
while (num2 < s.Length && IsWhiteSpace(s[num2]))
{
num2++;
}
}
if ((style & NumberStyles.AllowLeadingSign) != NumberStyles.None)
{
int length = numberFormatInfo.NegativeSign.Length;
if (length + num2 < s.Length && string.Compare(s, num2, numberFormatInfo.NegativeSign, 0, length, CultureInfo.CurrentCulture, CompareOptions.None) == 0)
{
flag = true;
num2 += numberFormatInfo.NegativeSign.Length;
}
}
value = BigInteger.Zero;
BigInteger bigInteger = BigInteger.One;
if (num2 == s.Length)
{
return false;
}
for (int i = s.Length - 1; i >= num2; i--)
{
if ((style & NumberStyles.AllowTrailingWhite) != NumberStyles.None && IsWhiteSpace(s[i]))
{
int num3 = i;
while (num3 >= num2 && IsWhiteSpace(s[num3]))
{
num3--;
}
if (num3 < num2)
{
return false;
}
i = num3;
}
uint num4;
if (!ParseSingleDigit(s[i], (ulong)num, out num4))
{
return false;
}
if (num4 != 0u)
{
value += num4 * bigInteger;
}
bigInteger *= num;
}
if (value.Sign == 1 && flag)
{
value = -value;
}
return true;
}
private static bool IsWhiteSpace(char ch)
{
return ch == ' ' || (ch >= '\t' && ch <= '\r');
}
private static bool ParseSingleDigit(char c, ulong radix, out uint result)
{
result = 0;
if (c >= '0' && c <= '9')
{
result = (uint)(c - '0');
return true;
}
if (radix == 16uL)
{
c = (char)((int)c & -33);
if (c >= 'A' && c <= 'F')
{
result = (uint)(c - 'A' + '\n');
return true;
}
}
return false;
}
}

Resources