简体   繁体   中英

sorting a List<Map<String, String>>

I have a list of a map of strings:

List<Map<String, String>> list = new ArrayList<Map<String, String>>();

This gets populated with the following:

Map<String, String> action1 = new LinkedHashMap<>();
map.put("name", "CreateFirstName");
map.put("nextAction", "CreateLastName");

Map<String, String> action2 = new LinkedHashMap<>();
map.put("name", "CreateAddress");
map.put("nextAction", "CreateEmail");

Map<String, String> action3 = new LinkedHashMap<>();
map.put("name", "CreateLastName");
map.put("nextAction", "CreateAddress");

Map<String, String> action4 = new LinkedHashMap<>();
map.put("name", "CreateEmail");

list.add(action1);
list.add(action2);
list.add(action3);
list.add(action4);

action4 doesn't have a nextAction because it is the last action, but might be easier to just give it a nextAction that is a placeholder for no next action?

Question: How can I sort my list, so that the actions are in order? ie: the nextAction of an action, is the same as the name of the next action in the list.

Instead of using a Map to store the properties of an action (the name and the nextAction ), create your own type that's composed of those properties:

class Action {
    private String name;
    //nextAction

    public void perform() {
        //do current action
        //use nextAction to perform the next action
    }
}

The nextAction can now be a reference to the next action:

abstract class Action implements Action {
    private String name;
    private Action nextAction;

    public Action(String name) {
        this.name = name;
    }

    public final void perform() {
        perform(name);
        nextAction.perform();
    }

    protected abstract void perform(String name);
}

You can now create your actions by subtyping the Action class:

class CreateFirstName extends Action {
    public CreateFirstName(Action nextAction) {
        super("CreateFirstName", nextAction);
    }

    protected final void perform(String name) {
        System.out.println("Performing " + name);
    }
}

And chain them together:

Action action = new CreateFirstName(new CreateLastName(new CreateEmail(...)));

The nested expressions can get pretty messy, but we'll get to that later. There's a bigger problem here.

action4 doesn't have a nextAction because it is the last action, but might be easier to just give it a nextAction that is a placeholder for no next action

The same problem applies to the code above.

Right now, every action must have a next action, due to the constructor Action(String, Action) . We could take the easy route and pass in a placeholder for no next action ( null being the easiest route):

class End extends Action {
    public End() {
        super("", null);
    }
}

And do a null check:

//class Action
public void perform() {
        perform(name);

        if(nextAction != null) {
            nextAction.perform(); //performs next action
        }
    }

But this would be a code smell . You can stop reading here and use the simple fix, or continue below for the more involved (and educational) route.


There's a good chance that when you do use null, you're falling victim to a code smell. Although it doesn't apply to all cases (due to Java's poor null safety), you should try to avoid null if possible . Instead, rethink your design as in this example. If all else fails, use Optional .

The last action is not the same as the other actions. It can still perform like the other, but it has different property requirements.

This means they could both share the same behavior abstraction, but must differ when it comes to defining properties:

interface Action {
    void perform();
}

abstract class ContinuousAction implements Action {
    private String name;
    private Action nextAction;

    public ContinuousAction(String name) {
        this.name = name;
    }

    public final void perform() {
        perform(name);
        nextAction.perform();
    }

    protected abstract void perform(String name);
}

abstract class PlainAction implements Action {
    private String name;

    public PlainAction(String name) {
        this.name = name;
    }

    public final void perform() {
        perform(name);
    }

    protected abstract void perform(String name);
}

The last action would extend PlainAction , while the others would extend ContinuousAction .

Lastly, to prevent long chains:

new First(new Second(new Third(new Fourth(new Fifth(new Sixth(new Seventh(new Eighth(new Ninth(new Tenth())))))))))

You could specify the next action within each concrete action:

class CreateFirstName extends ContinuousAction {
    public CreateFirstName() {
         super("CreateFirstName", new CreateLastName());
    }

    //...
}

class CreateLastName extends ContinuousAction {
    public CreateLastName() {
        super("CreateLastName", new CreateEmail());
    }

    //...
}

class CreateEmail extends PlainAction {
    public CreateEmail() {
         super("CreateEmail");
    }

    //...
}

The ContinuousAction and PlainAction can be abstracted further. They are both named actions (they have names), and that property affects their contract in the samw way (passing it to the template method process(String) ):

abstract class NamedAction implements Action {
    private String name;

    public NamedAction(String name) {
        this.name = name;
    }

    public final void perform() {
        perform(name);
    }

    protected abstract void perform(String name);
}

//class ContinuousAction extends NamedAction
//class PlainAction extends NamedAction

Although this seems to be a case of the XY-Problem , and this list of maps is certainly not a "nicely designed data model", and there is likely a representation that is "better" in many ways (although nobody can give recommendations about what the "best" model could be, as long as the overall goal is not known), this is the task that you have at hand, and here is how it could be solved:

First of all, you have to determine the first element of the sorted list. This is exactly the map that has a "name" entry that does not appear as the "nextAction" entry of any other map.

After you have this first map, you can add it to the (sorted) list. Then, determining the next element boils down to finding the map whose "name" is the same as the "nextAction" of the previous map. To quickly find these successors, you can build a map that maps each "name" entry to the map itself.

Here is a basic implementation of this sorting approach:

import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class SortListWithMaps
{
    public static void main(String[] args)
    {
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();

        Map<String, String> action1 = new LinkedHashMap<>();
        action1.put("name", "CreateFirstName");
        action1.put("nextAction", "CreateLastName");

        Map<String, String> action2 = new LinkedHashMap<>();
        action2.put("name", "CreateAddress");
        action2.put("nextAction", "CreateEmail");

        Map<String, String> action3 = new LinkedHashMap<>();
        action3.put("name", "CreateLastName");
        action3.put("nextAction", "CreateAddress");

        Map<String, String> action4 = new LinkedHashMap<>();
        action4.put("name", "CreateEmail");

        list.add(action1);
        list.add(action2);
        list.add(action3);
        list.add(action4);        

        // Make it a bit more interesting...
        Collections.shuffle(list);

        System.out.println("Before sorting");
        for (Map<String, String> map : list)
        {
            System.out.println(map);
        }

        List<Map<String, String>> sortedList = sort(list);

        System.out.println("After sorting");
        for (Map<String, String> map : sortedList)
        {
            System.out.println(map);
        }
    }

    private static List<Map<String, String>> sort(
        List<Map<String, String>> list)
    {
        // Compute a map from "name" to the actual map
        Map<String, Map<String, String>> nameToMap = 
            new LinkedHashMap<String, Map<String,String>>();
        for (Map<String, String> map : list)
        {
            String name = map.get("name");
            nameToMap.put(name, map);
        }

        // Determine the first element for the sorted list. For that,
        // create the set of all names, and remove all of them that
        // appear as the "nextAction" of another entry
        Set<String> names = 
            new LinkedHashSet<String>(nameToMap.keySet());
        for (Map<String, String> map : list)
        {
            String nextAction = map.get("nextAction");
            names.remove(nextAction);
        }
        if (names.size() != 1)
        {
            System.out.println("Multiple possible first elements: " + names);
            return null;
        }

        // Insert the elements, in sorted order, into the result list
        List<Map<String, String>> result = 
            new ArrayList<Map<String, String>>();
        String currentName = names.iterator().next();
        while (currentName != null)
        {
            Map<String, String> element = nameToMap.get(currentName);
            result.add(element);
            currentName = element.get("nextAction");
        }
        return result;
    }
}

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