Finding the lowest values in an array of dict which also have matching attributes, returning the largest grouping

Viewed 52

This is easy enough to do this with a few loops but I am sure there is a far more efficient way to achieve this and I am keen to learn.

Consider the following array of dict which represents data pulled from a nosql database.

x = [
    {
        "loc" : "alpha",
        "tag" : 1,
        "dist" : 5
    },
    {
        "loc" : "bravo",
        "tag" : 0,
        "dist" : 2
    },
    {
        "loc" : "charlie",
        "tag" : 5,
        "dist" : 50
    },
    {
        "loc" : "delta",
        "tag" : 4,
        "dist" : 2
    },
    {
        "loc" : "echo",
        "tag" : 2,
        "dist" : 30
    },
    {
        "loc" : "foxtrot",
        "tag" : 4,
        "dist" : 2
    },
    {
        "loc" : "gamma",
        "tag" : 4,
        "dist" : 2
    },
    {
        "loc" : "hotel",
        "tag" : 0,
        "dist" : 2
    },
]

I would like to find all the items with the lowest 'dist' value, and should there be more than one dict with the same lowest value, I want the grouping on the attribute 'tag' that has the most dicts with the same lowest value.

for example, the desired returned data from the above would be:

r = [
    {
        "LocationName" : "delta",
        "tag" : 4,
        "dist" : 2
    },
    {
        "loc" : "foxtrot",
        "tag" : 4,
        "dist" : 2
    },
    {
        "loc" : "gamma",
        "tag" : 4,
        "dist" : 2
    }
]

Summary: dist:2 is the lowest value, [bravo, delta, foxtrot, gamma, hotel] all have a dist of 2, [bravo, hotel] have a tag of:0 and [delta, foxtrot, gamma] have a tag of:4 . An array of the dicts [delta, foxtrot, gamma] are returned as they have more with the same matching tag and the lowest dist.

I am using python 3.6.

Thank you for the help and interest!

2 Answers

You can specify a key (that is, a lambda function) for max() and min() that can help with this. For your first test,

lowest_single_dist = min(x, key=lambda i: i["dist"])

returns the element in x with the lowest value for "dist". If you then want all of the elements with that tag value, you can use a list comprehension:

lowest_dists = [i for i in x if i["dist"] == lowest_single_dist["dist"]]

To get the largest grouping, I would first create a set of the possible values for "tag" in that subset, and then check how many of each there are in lowest_dists, then take whichever one has the highest count:

tags = [i["tag"] for i in lowest_dists]              # get a list of just the tags
ct = {t: tags.count(t) for t in set(tags)}           # make a dict of tag:count for each unique tag
max_tag = max(ct, key=lambda x: ct[x])               # find the largest count and get the largest tag
r = [i for i in lowest_dists if i["tag"] == max_tag] # use another list comprehension to get all the max tags

If you wanted to shorten it all into a two-liner, you could be less pythonic and do this:

m = min(x, key=lambda i: (i["dist"], -1 * max([j["tag"] for j in x if j["dist"] == i["dist"]].count(i["tag"])))
r = [i for i in x if i["tag"] == m["tag"] and i["dist"] == m["dist"]]

This takes advantage of the fact that you can return a tuple as the key for sorting, and the second value of the tuple will only be checked if the first is equal. I'll expand that first line a bit and explain what each part is doing:

m = min(x, key=lambda i: (
    i["dist"], -1 * max(
        [j["tag"] for j in x if j["dist"] == i["dist"]].count(i["tag"])
    ))
  • The innermost list comprehension generates a list of the tags for all elements in x with the same value for "dist" as i
  • Then, take the count of tags that are the same as i
  • Multiply by -1 to make it negative so that min() behaves correctly
  • make a tuple of i["dist"] and the value we just calculated (the frequency of i["tag"] in x), and return that for each element
  • assign to m the element of the list that has the lowest value for "dist" and the most frequent value for "tag"
  • assign to r the sublist of elements in x with the same value for "dist" and "tag"

So basically the same process as above, but shorter, less efficient, and a bit more complicated.

sort the list of dictionaries by the value they have at 'dist' and take the lowest

x.sort(key= lambda x:x['dist'])
lowest = x[0]['dist']

create a list of dictionaries with the value for 'dist' equal to the lowest

x2 = [i for i in x if i['dist']==lowest]

This should be your answer. If there is more than one item in the list, repeat the above procedure.

if len(x2)>1:
  x3 = [i['tag'] for i in x2]
  mode = max(set(x3), key=x3.count)
  r = [i for i in x if i['tag']==mode]
Related