2009-09-08 10 views
6

Chcę posortować listę słowników za pomocą klucza słownika, gdzie nie chcę rozróżniać wielkich i małych liter.python: łącz sort-key-funkcje itemgetter i str.lower

dict1 = {'name':'peter','phone':'12355'} 
dict2 = {'name':'Paul','phone':'545435'} 
dict3 = {'name':'klaus','phone':'55345'} 
dict4 = {'name':'Krishna','phone':'12345'} 
dict5 = {'name':'Ali','phone':'53453'} 
dict6 = {'name':'Hans','phone':'765756'} 
list_of_dicts = [dict1,dict2,dict3,dict4,dict5,dict6] 

key_field = 'name' 
list_of_dicts.sort(key=itemgetter(key_field)) 
# how to combine key=itemgetter(key_field) and key=str.lower? 
for list_field in list_of_dicts: 
    print list_field[key_field] 

powinien zapewnić

Ali, Hans, klaus, Krishna, Paul, peter 

a nie

klaus, peter, Ali, Hans, Krishna, Paul 

Odpowiedz

10

w ogólnym przypadku, Będę chciał napisać funkcję ekstrakcji klucza do celów sortowania; tylko w szczególnych (choć ważnych) przypadkach zdarza się, że można ponownie użyć istniejącego wywołania, aby wyodrębnić klucze dla ciebie, lub po prostu połączyć kilka istniejących (w "szybki i brudny" sposób, używając lambda, ponieważ nie ma żadnych wbudowanych w taki sposób, aby zrobić skład funkcji).

Jeśli często trzeba wykonać te dwa rodzaje działań dla kluczowych ekstrakcji (get element i wywołać metodę na tej pozycji), proponuję:

def combiner(itemkey, methodname, *a, **k): 
    def keyextractor(container): 
    item = container[itemkey] 
    method = getattr(item, methodname) 
    return method(*a, **k) 
    return keyextractor 

tak listofdicts.sort(key=combiner('name', 'lower')) zadziała w Twoim przypadku.

Należy zauważyć, że podczas gdy nadmierne uogólnienie ma koszty, gustowne i umiarkowane uogólnienie (pozostawiając klucz produktu, nazwę metody i argumenty metody, jeśli takie istnieją, w tym przypadku), generalnie przynosi korzyści - jedna funkcja ogólna, nie bardziej złożone niż tuzin szczegółowych i wyspecjalizowanych (z ekstraktorem, metodą wywołania lub obiema, o których mowa w kodzie), będą łatwiejsze do utrzymania (i, oczywiście, o wiele łatwiejsze do ponownego wykorzystania!).

2
def lower_getter(field): 
    def _getter(obj): 
     return obj[field].lower() 
    return _getter 

list_of_dicts.sort(key=lower_getter(key_field)) 
+0

i jako plus, zostanie ona automatycznie pracować z obu bytestrings i ciągi Unicode. – nosklo

12

Jak o tym:

list_of_dicts.sort(key=lambda a: a['name'].lower()) 
+0

otrzymuję błąd "Nie iteracyjnych" – chovy

4

Prawdopodobnie powinieneś użyć lambda ze względu na czytelność. Ale jako interesujące badanie funkcji wyższych rzędów, tutaj jest rozszerzona wersja q-combinator w Pythonie (znana również jako kombinator dziwnych ptaków). Pozwala to na stworzenie nowej funkcji poprzez komponowanie dwie funkcje

def compose(inner_func, *outer_funcs): 
    if not outer_funcs: 
     return inner_func 
    outer_func = compose(*outer_funcs) 
    return lambda *args, **kwargs: outer_func(inner_func(*args, **kwargs)) 

from operator import itemgetter, methodcaller 
name_lowered = compose(itemgetter('name'), methodcaller('lower')) 
print(name_lowered({'name': 'Foo'})) 

Jeśli odwrócić definicje wewnętrzna i zewnętrzna w funkcji compose, masz bardziej tradycyjne (b-COMBINATOR Bluebird). Lubię q-combinator więcej ze względu na podobieństwo do rur unix.

4

To rozwiązanie będzie używać ustawień regionalnych systemu, a jako bonus, będzie sortować ewentualne inne znaki zgodnie z bieżącymi ustawieniami narodowymi (wstawi "ü" po "u" w niemieckiej lokalizacji itp.).

from locale import setlocale, strxfrm, LC_ALL 
import operator 

# call setlocale to init current locale 
setlocale(LC_ALL, "") 

def locale_keyfunc(keyfunc): 
    def locale_wrapper(obj): 
    return strxfrm(keyfunc(obj)) 
    return locale_wrapper 

list_of_dicts.sort(key=locale_keyfunc(operator.itemgetter("name"))) 

Oczywiście wykorzystuje to sortowanie regionalne to "naturalny" interfejs użytkownika, który chcesz emulować przy pomocy .lower()

Jestem zdumiony, że moduł Pythona locale jest nieznane i nieużywane, to na pewno jest ważnym składnikiem w aplikacji piszę (tłumaczona na wiele języków, ale moduł locale jest ważne dla nawet uzyskanie jeden modułu prawo. Przykład: w szwedzkim "V" i "W" sortuj podobnie, więc musisz je zestawić: locale to wszystko dla ciebie.). W ustawieniach regionalnych (nie domyślnych), to powróci do sortowania "a" po "Z".

+0

Jest to miłe sugestia, wystarczy zmienić keyfunc do: def keyfunc (DIC): powrót strxfrm (DIC [ "nazwa"]) – Francesco

+0

Francesco: Teraz używa bardziej konfigurowalny fabrykę styl (choć może być wyspecjalizowany, aby być szybszym, rzadko ma to znaczenie). – u0b34a0f6ae

4

Osobiście życzę były dwie funkcje w standardowej biblioteki Pythona (prawdopodobnie w functools):

def compose(*funcs): 
    """ 
    Compose any number of unary functions into a single unary 
    function. 

    >>> import textwrap 
    >>> str.strip(textwrap.dedent(compose.__doc__)) == compose(str.strip, textwrap.dedent)(compose.__doc__) 
    True 
    """ 

    compose_two = lambda f1, f2: lambda v: f1(f2(v)) 
    return reduce(compose_two, funcs) 

def method_caller(method_name, *args, **kwargs): 
    """ 
    Return a function that will call a named method on the 
    target object with optional positional and keyword 
    arguments. 

    >>> lower = method_caller('lower') 
    >>> lower('MyString') 
    'mystring' 
    """ 
    def call_method(target): 
     func = getattr(target, method_name) 
     return func(*args, **kwargs) 
    return call_method 

I wprowadziły je na własny użytek w jaraco.util.functools.

Tak czy inaczej, teraz twój kod jest dość przejrzysty, samodokumentowany i solidny (IMO).

lower = method_caller('lower') 
get_name = itemgetter('name') 
lowered_name = compose(lower, get_name) 

list_of_dicts.sort(key=lowered_name) 
3
from functools import partial 

def nested_funcs(*funcs): 
    return partial(reduce, lambda arg, func: func(arg), funcs) 


sorted(list_of_dicts, key=nested_funcs(itemgetter('name'), str.strip, str.lower))