Max Rukhlov
Max Rukhlov

Reputation: 330

Filter python dictionary with filter() to list of dictionary values

I have a dictionary consists of string or dictionaries

parameters = {
"date": {'date':"2015-12-13"},
"name": "Nick",
"product": "BT Adapter",
"transaction-id": ""
}

And i need to get list like ['2015-12-13', 'BT Adapter', 'Nick'].

If it has no dictionaries in it, print filter(lambda x: x if len(x) > 0 and type(x) is not dict else None, parameters.values()) works perfectly, but with dictionary in it i tried to extract it's value with

print filter(lambda x: x if len(x) > 0 and type(x) is not dict else map(lambda y: x[y], x.keys()), parameters.values())

and i get AttributeError: 'str' object has no attribute 'keys'. How all values can be extracted?

Upvotes: 2

Views: 3161

Answers (4)

Dmitrii Kuragin
Dmitrii Kuragin

Reputation: 46

#!/usr/bin/env python
# -*- coding: utf-8 -*-


def magic(params):
    dicts = [params]
    values = []

    while len(dicts):
        d = dicts.pop()

        for value in d.values():
            if isinstance(value, dict):
                dicts.append(value)
            elif isinstance(value, basestring) and len(value) > 0:
                values.append(value)

    return values


def main():
    parameters = {
        "date": {'date': "2015-12-13"},
        "name": "Nick",
        "product": "BT Adapter",
        "transaction-id": ""
    }

    print(magic(parameters))


if __name__ == '__main__':
    main()

Upvotes: 1

Chaitanya Sama
Chaitanya Sama

Reputation: 330

It is failing for the last attribute "transaction-id" because its length is 0.

This would work for you:

print filter(lambda x: x if len(x) >= 0 and type(x) is not dict else map(lambda y: x[y], x.keys()), parameters.values())

Upvotes: 0

brianpck
brianpck

Reputation: 8254

It looks to me like you are trying to do a map operation with your filter, showing a bit of a misunderstanding. The function in a filter essentially must return True or False to determine whether to keep a certain value in the second argument, so it does not make sense to filter in this way:

filter(lambda x: x if len(x) > 0 and type(x) is not dict else None, parameters.values())

Better:

filter(lambda x: len(x) > 0 and type(x) is not dict, parameters.values())
# better idiom
filter(lambda x: len(x) > 0 and not isinstance(x, dict), parameters.values())

This is especially true for your next attempt, which attempts to map inside a filter function. It's better to think of this as a two-step process:

  1. Remove empty strings/lists (anything where lex(x) < 1)
  2. Get values of dictionaries

Let's try this instead, if you need to use filter. I'm using Python3, which means that I have to do a lot of ugly list casting:

list(map(lambda x: list(x.values()) if isinstance(x, dict) else x, filter(lambda x: len(x) > 0, parameters.values())))

This produces:

[['2015-12-13'], 'BT Adapter', 'Nick']

This is not pythonic at all: filter and map are much more at home in list comprehensions like the following:

[list(v.values()) if isinstance(v, dict) else v for v in parameters.values() if len(v) > 0]

Let me know if you also need to flatten the list after getting the dictionary values.

Upvotes: 2

Bakuriu
Bakuriu

Reputation: 101959

You are misusing the filter function. The argument to filter is a predicate, which is a function that returns a true/false value and filter returns the elements from the second argument for which that function returns true.

For example:

print(list(filter(lambda x: 5, [1,2,3,4,5])))
[1, 2, 3, 4, 5]

Since bool(5) == True filter returns all the elements.

Since you pass as second argument the value parameters.values() there is no way to obtain your expected result by just passing a predicate to filter.

What you want to do is something like:

from itertools import chain

def listify(value):
    if isinstance(value, dict):
        return value.values()
    elif value:
        return [value]
    return ()

print(list(chain.from_iterable(map(listify, parameters.values()))))

So first you convert the values into sequences, and then you concatenate them using chain.from_iterable.

The empty values are removed by listify since it returns an empty sequence in that case. Sample run:

In [2]: parameters = {
   ...: "date": {'date':"2015-12-13"},
   ...: "name": "Nick",
   ...: "product": "BT Adapter",
   ...: "transaction-id": ""
   ...: }

In [3]: print(list(chain.from_iterable(map(listify, parameters.values()))))
['2015-12-13', 'Nick', 'BT Adapter']

Also: it doesn't make sense to write a complex lambda function with nested conditionals, so just use a def and write it properly. lambdas are fine only if they are extremely short.

Upvotes: 4

Related Questions