多键哈希表

5

最近我参加了一次关于在DS中保存大量员工详细信息的面试。

我提出了使用员工ID作为键的HashMap解决方案。

接下来的问题是,如果用户想按姓名搜索如何实现。我建议使用员工姓名作为键,并将所有具有相同姓名的员工保存为ArrayList。

接下来的问题有些棘手,需要创建一个映射,用户可以根据员工ID或员工姓名进行搜索。如何在映射中实现这一点?

以内存高效的方式实现它。


一个技巧是使用[name + id]作为键。如果id是整数而name是字符串,那么冲突应该是不可能的。 - Tunaki
1
可能更适合程序员网站,因为这个问题不涉及寻求解决编程问题的帮助。 - user146043
1
@Tunaki 这会有什么帮助呢?想象一下,如果我需要检索所有的“Peter”,我应该请求哪个键? - Alex Salauyou
1
@Tunaki 那如果我按名称搜索呢?怎么过滤? - Suresh Atta
Map<Employee,String> - SatyaTNV
显示剩余5条评论
4个回答

1
我想出了一个解决方案,请发表您的建议。
步骤1:使用员工ID作为键,员工对象作为值形成哈希映射表。
步骤2:对于相同的名字,创建一个员工ID列表,该列表与名字匹配。例如:name = 'XYZ' id={101,102,103,...}
步骤3:将此名称作为键,数组列表作为值插入到同一映射中。
在这里,我们不会两次存储完整的员工详细信息,只是试图维护名称和ID之间的关系,因此在内存效率上相对更高。

1
这是一个不太优美的解决方案(是的——非常不好,不要在生产环境中使用!),但如果键是不同类型且一个不是另一个的子类型(例如long和String),它将起作用。通过两个键将每个员工放置,并通过提供的键(id或name)获取。
Map<?, List<Employee>> map = new HashMap<>();

public void putEmployee(Employee e) {
    map.put(e.id, Arrays.asList(e));    // put by id
    if (!map.containsKey(e.name)) {
        map.put(e.name, new ArrayList<>());
    }
    map.get(e.name).add(e);             // put by name
}

public Employee getById(long id) {
    return map.containsKey(id) ? map.get(id).get(0) : null;
}

public List<Employee> getByName(String name) {
    return map.containsKey(name) ? map.get(name) : Collections.emptyList();
}

在生产代码中,我会使用两个单独的地图或自定义字典类。


我会为一家满意于这样的事情的公司工作。 - async
@async 我相信问题是关于Java特性,而不是关于编写清晰代码的。我需要强调答案中的第一句和最后一句话。 - Alex Salauyou
我认为这个问题只是一个愚蠢的问题。如果他们想知道候选人是否了解Java的特性,他们应该明确地问这个问题,而不是编造这种情况。 - async
顺便说一下,我不是在评判你的解决方案,而是问题本身。 - async
@ Sasha:感谢您花时间提供解决方案。但我觉得这就像创建一个包含记录两次的地图(一次作为ID,一次作为名称)。 - Lathy
@Lathy 是的,但它们都是指向同一对象的引用。无论如何,您需要两个索引:一个用于id,另一个用于name。 - Alex Salauyou

1
这是一个很容易回答的问题:只需将ID转换为字符串并将员工存储两次 - 一次在名称下,另一次在ID作为字符串下。
对于ID,使用List作为值的想法很好 - 列表大小为1。
请注意最好使用两个映射,因为您每个ID只有一个员工,并且不必处理大小为1的列表作为退化情况,所以:
Map<Integer, Employee> employeesById;
Map<String, Set<Employee>> employeesByName;

特别注意,仅使用一个映射表并不能减少内存使用量。事实上,与将员工分别存储在ID键和名称键的不同映射表中相比,您将使用更多的内存。

谢谢您的想法。但是将员工详细信息存储两次会使我们消耗更多的内存。是否有更好的解决方案可用? - Lathy
1
@Lathy 不会的,你不会消耗更多的内存!只有引用被存储在映射中,而不是对象本身(内存中只有每个员工的一个副本),所以你拥有的只是另一个映射中的映射条目(实际上只是一对引用)。此外,你不必创建一个不必要的列表来存储单个条目。使用2个映射的内存使用量将会更少,而不是更多。 - Bohemian
嗯,我同意。你对我的解决方案有什么看法? - Lathy
@lathy,你的解决方案需要一个 Map<Object, Object> - 混合键类型和混合值类型。如果这样做可以完成工作,那么为什么不呢?但是你必须牺牲Java的强类型检查来实现它,而且这并不是必要的,也没有实际帮助。如果面试问题只是一个“谜题”,不打算用于生产,那么你的解决方案是很好的 - 它不比要求你只使用一个map的人为和糟糕的设计要差。 - Bohemian
我有一个疑问,你说在上面提到的解决方案中将保存名称作为键,但是新对象(值)不会覆盖旧值吗? - Lathy
@Lathy 请查看显示地图类型的编辑。ID是唯一的,因此不会更改值。至于Set,请在第一次放置时创建一个Set,然后将其添加到那里。 - Bohemian

0
一种方法是创建一个可以通过名称或ID进行搜索的Key对象:
public enum KeyType {
    ID, NAME;
}

public class SearchKey {
    private KeyType keyType;
    private String value;

    // constructor and getters snipped for brevity's sake

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        SearchKey searchKey = (SearchKey) o;

        return keyType == searchKey.keyType && value.equals(searchKey.value);

    }

    @Override
    public int hashCode() {
        int result = keyType.hashCode();
        result = 31 * result + value.hashCode();
        return result;
    }

public class Directory {
    private Map<SearchKey, Set<Employee>> directory = new HashMap<>();

    public void addEmployee(Employee e) {
        // Add search by id
        directory.put
            (new SearchKey(KeyType.ID, e.getId()), Collections.singleton(e));

        // Add search by name
        SearchKey searchByName = new SearchKey(KeyType.NAME, e.getName());
        Set<Employee> employees = directory.get(searchByName);
        if (employees == null) {
            employees = new HashSet<>();
            directory.put(searchByName, employees);
        }
        employees.add(e);
    }

    public Employee getById (String id) {
        // Assume that the ID is unique
        return directory.get(new SearchKey(KeyType.ID, id)).iterator().next();
    }

    public Set<Employee> getByName (String name) {
        return directory.get(new SearchKey(KeyType.NAME, name));
    }
}

类型安全的 Map<?, Employee> 变体 - Alex Salauyou

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接