简体   繁体   中英

How can I sort this ArrayList the way that I want?

Here is a simple sorting program of an ArrayList:

ArrayList<String> list = new ArrayList<String>();

list.add("1_Update");
list.add("11_Add");
list.add("12_Delete");
list.add("2_Create");

Collections.sort(list);
for (String str : list) {
  System.out.println(str.toString());
}

I was expecting the output of this program as:

1_Update
2_Create
11_Add
12_Delete

But when I run this program I am getting output as:

11_Add
12_Delete
1_Update
2_Create

Why is this and how do I get the ArrayList to sort as shown in the expected output?

You could write a custom comparator:

Collections.sort(list, new Comparator<String>() {
    public int compare(String a, String b) {
        return Integer.signum(fixString(a) - fixString(b));
    }
    private int fixString(String in) {
        return Integer.parseInt(in.substring(0, in.indexOf('_')));
    }
});

When you sort this type of data as a string, it is comparing the characters themselves, including the digits. All of the string that begin with "1", for example, will end up together. So the order ends up similar to this...

1 10 100 2 20 200

At no point does the sort "realize" that you are assigning meaning to subsets of the string, such as the variable length numbers at the front of the string. When sorting numbers as strings, padding to the left with zeros as much as required to cover the largest number can help, but it does not really solve the problem when you don't control the data, as in your example. In that case, the sort would be...

001 002 010 020 100 200

It is sorted as text (alphabetically), not as numbers. To get around this you could implement a custom comparator as suggested in the answer by nsayer.

It is doing a lexicographic comparison. It compares the first character in each string sorting them. It then compares the second string of those with the same first charater. When it compares the '_' character to a number, it is greater in value than any single number character just like 8 > 7 and a > 9. Remember it is doing a character comparison and not a numeric comparison.

There are ways to implement your own custom sorting routing which may be better than renaming your script names.

If renaming your script names is an option, this may allow other script tools to be used. One format may be

01_create_table.sql
02_create_index.sql
11_assign_privileges.sql

By keeping your first two digits to two characters, the lexicographic comparison will work.

The Collections.sort() method's docs says:

Sorts the specified list into ascending order, according to the natural ordering of its elements.

Which means for Strings that you are going to get the list in alphabetic order. The String 11_assign_privileges.sql comes before the string 1_create_table.sql and 12_07_insert_static_data.sql comes before 1_create_table.sql etc. So the program is working as expected.

You can add the IComparable interface and then sort by a specific property. If you have a collections of items of a store for example maybe you want to sort them by price or by category etc. If you want to order by name here is an example:

note how the ArrayList is sorted by the name property of the items. If you do not add the IComparable then when you use the sort method it will throw an error.

在此输入图像描述

static void Main(string[] args)
    {
        ArrayList items = new ArrayList();
        items.Add(new Item("book", 12.32));
        items.Add(new Item("cd", 16.32));
        items.Add(new Item("bed", 124.2));
        items.Add(new Item("TV", 12.32));

        items.Sort();

        foreach (Item temp in items)
            Console.WriteLine("Name:{0} Price:{1}", temp.name, temp.price);
        Console.Read();            
    }


    class Item: IComparable
    {
        public string name;
        public double price;

        public Item(string _name, double _price)
        {
            this.name = _name;
            this.price = _price;
        }

        public int CompareTo(object obj)
        {   
            //note that I use the name property I may use a different one
            int temp = this.name.CompareTo(((Item)obj).name);
            return temp;
        }
    }

Because strings are sorted in a alphabetic ordering and the underscore character is after characters for numbers. You have to provide a comparator implementing "Natural Order" to achieve desired result.

The string compare algorithm compare each character at a time. 1 sorts before 2 . It doesn't matter that it is followed by a 1 or a 2 .

So 100 would sort before 2 . If you don't want this behavior, you need a compare algorithm that handles this case.

As others have stated, the elements will be sorted alphabetically by default. The solution is define a concrete java.util.Comparator class and pass it as a second argument to the sort method. Your comparator will need to parse out the leading integers from the strings and compare them.

To have Collection.sort() sort arbitrarily you can use

Collections.sort(List list, Comparator c)  

Then simply implement a Comparator that splits the string and sorts first based on the number and then on the rest or however you want it to sort.

Everyone has already pointed out that the explanation is that your strings are sorting as strings, and a number have already directed your attention to Natural Order string comparison. I'll just add that it's a great exercise to write that comparator yourself, and a great opportunity to practice test-driven development. I've used it to demonstrate TDD at Code Camp; slides & code are here .

As stated above, you are looking for a Comparator implementation that implements a natural sort. Jeff Atwood wrote an excellent post on natural sorting some time ago - it's well worth a read.

If you're looking for a Java implementation I have found this one to be useful: http://www.davekoelle.com/alphanum.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM