Python'da zincirleme listesi işlemleri
Karşılaştırmalı bir programlama dilleri kursunda öğrencilere "akışla" pratik yapmak için aşağıdaki yapmacık problemi verdim:
15 veya daha fazla oyunda yer alan oyuncular arasında ilk on oyuncuyu oyun başına puan olarak döndüren yazma işlevi. Fonksiyonunuzun girdisi, oyuncu istatistiklerinin bir listesi ile takım tarafından anahtarlanan bir nesne olacaktır. Her oyuncu istatistiği, oyuncu adı, oynanan oyun sayısı ve toplam puan sayısını içeren bir dizidir.
Örnek bir veri seti aşağıdaki gibidir:
stats = {
'ATL': [
['Betnijah Laney', 16, 263],
['Courtney Williams', 14, 193],
],
'CHI': [
['Kahleah Copper', 17, 267],
['Allie Quigley', 17, 260],
['Courtney Vandersloot', 17, 225],
],
'CONN': [
['DeWanna Bonner', 16, 285],
['Alyssa Thomas', 16, 241],
],
'DAL': [
['Arike Ogunbowale', 16, 352],
['Satou Sabally', 12, 153],
],
'IND': [
['Kelsey Mitchell', 16, 280],
['Tiffany Mitchell', 13, 172],
['Candice Dupree', 16, 202],
],
'LA': [
['Nneka Ogwumike', 14, 172],
['Chelsea Gray', 16, 224],
['Candace Parker', 16, 211],
],
'LV': [
['A’ja Wilson', 15, 304],
['Dearica Hamby', 15, 188],
['Angel McCoughtry', 15, 220],
],
'MIN': [
['Napheesa Collier', 16, 262],
['Crystal Dangerfield', 16, 254],
],
'NY': [
['Layshia Clarendon', 15, 188]
],
'PHX': [
['Diana Taurasi', 13, 236],
['Brittney Griner', 12, 212],
['Skylar Diggins-Smith', 16, 261],
['Bria Hartley', 13, 190],
],
'SEA': [
['Breanna Stewart', 16, 317],
['Jewell Loyd', 16, 223],
],
'WSH': [
['Emma Meesseman', 13, 158],
['Ariel Atkins', 15, 212],
['Myisha Hines-Allen', 15, 236],
],
}
Artık JavaScript'te, "akıcı" veya yöntem zinciri oluşturma stili kolayca görünür:
function topTenScorers(stats) {
return Object.entries(stats)
.flatMap(([team, players]) => players.map(player => [...player, team]))
.filter(([, games, ,]) => games >= 15)
.map(([name, games, points, team]) => ({ name, ppg: points / games, team }))
.sort((p1, p2) => p2.ppg - p1.ppg)
.slice(0, 10)
}
Ancak Python çözümüm (aşağıda) aynı şekilde tatmin etmiyor (Ben daha çok JavaScript programcısıyım). Python liste anlamalarının tercih edildiğini duydum mapve filter; Ben Python bir dahili olmadığını düşünüyorum flat_mapEğer rağmen, iyi, ve olabilir fantezi şeyler itertoolsPythonic programları, bence, zincirleme ziyade ara ifadeleri hesaplama için daha elverişli olma eğilimindedir. Bu yüzden şunu buldum:
def top_ten_scorers(stats):
with_teams = [[*player, team]
for (team, players) in stats.items()
for player in players]
with_ppg = [{'name': name, 'ppg': points/games, 'team': team}
for [name, games, points, team] in with_teams
if games >= 15]
return sorted(with_ppg, key=lambda k: k['ppg'], reverse=True)[:10]
Kodun mevcut Python en iyi uygulamaları tarzında olup olmadığını bilmek isterim. Python'un veri bilimcileri tarafından çok sevildiğini biliyorum ve bu sorun çok uydurulmuş olmasına rağmen bana veri bilimi gibi geliyor, bu yüzden kodumun karşılamayabileceği bir dizi en iyi uygulamanın ortaya çıkacağını düşündüm. Ayrıca, ara ifadelerin adlarıyla ilgili sorun yaşıyorum ve adımların dökümünün çok kaba mı yoksa çok mu ince olduğundan emin değilim. Temizlemek için hangi yaklaşımı kullanacağımdan emin değilim.
Elbette bir akış çözümünün bulunması zorunlu değildir; En önemlisi Python'un Zen kural (lar) ına en iyi uyan çözümdür "Bunu yapmanın bir - ve tercihen sadece bir - açık yolu olmalıdır. Yine de bu yol ilk bakışta açık olmayabilir. Flemenkçe."
Yanıtlar
Bu bir anlamda daha "akış" dır:
from heapq import nlargest
from operator import itemgetter
def top_ten_scorers(stats):
players = (dict(name=name, ppg=points/games, team=team)
for team, players in stats.items()
for name, games, points in players
if games >= 15)
return nlargest(10, players, key=itemgetter('ppg'))
Sizin with_teamsve with_ppgtamamen hesaplanmış listelerdir ve daha sonra sıraladığı sortedbir tane daha oluşturur ve ondan başka tüm öğeleri atarsınız.
My playersbir jeneratör yineleyici, istendiği gibi anında daha fazla öğe hesaplıyor. players = ...Atama yalnızca Yineleyici kurar, ama hiçbir şey henüz işlenmemiş alır.
Sonra tek tek nlargesttüketir players, şimdiye kadar görülen yalnızca ilk 10'u tutar ve sıralı olarak geri döndürür (azalan sırayla). Ayrıca, uygun oyuncuların sayısına bağlı olarak her şeyi sıralamaktan daha verimli olabilir.
Aslında, ilk iki adımınızı yararlı olmaktan çok kafa karıştırıcı buldum, çünkü with_teamsanlaşılması gereken bir ara sonuç / biçim oluşturuyor. Oyuncuları istatistiklerden direk olarak üretmek için okumanın daha basit ve daha kolay olduğunu düşünüyorum. Sonra tekrar, buna karşı önyargılı olabilir ve sizinkinden uzak olabilirim çünkü sizin de söylediğiniz gibi zincirlemeye pek alışık olmayan Python'a alışkınım.
Btw, işte Guido'dan bazı zincirleme biçimleri hakkında eski bir mesaj var . Burada sahip olduklarımızla ilgili olduğundan emin değilim, ama yine de ilginç olabilir.
dict(...)Sadece kısalık için kullandım , ancak {...}daha hızlıdır, bu yüzden ikincisini korumak isteyebilirsiniz:
Setup:
name, ppg, team = 'Betnijah Laney', 263/16, 'ATL'
Round 1:
347.041 ns dict(name=name, ppg=ppg, team=team)
128.325 ns {'name': name, 'ppg': ppg, 'team': team}
Round 2:
350.576 ns dict(name=name, ppg=ppg, team=team)
129.106 ns {'name': name, 'ppg': ppg, 'team': team}
Round 3:
347.753 ns dict(name=name, ppg=ppg, team=team)
130.734 ns {'name': name, 'ppg': ppg, 'team': team}
Bu adımları tek bir kavrayışta yazmak mümkündür - JavaScript veya Ruby'de zincirleme için Python analoğu türü. Mantığı görsel olarak aktarırsanız çok kötü okumaz. Kod düzenine bu kadar dikkat edilmediği takdirde, okuyucular ve bakımcılara çok fazla yük bindirilirdi.
from operator import itemgetter
def top_ten_scorers(stats):
return sorted(
(
dict(
name = name,
team = team,
ppg = points / games,
)
for team, players in stats.items()
for name, games, points in players
if games >= 15
),
reverse = True,
key = itemgetter('ppg'),
)[:10]
Muhtemelen bunu daha açık bir şekilde 3 adıma ayırırdım: verileri organize etmek; sipariş et; ilk 10'u seçin.
def top_ten_scorers2(stats):
players = [
dict(
name = name,
team = team,
ppg = points / games,
)
for team, players in stats.items()
for name, games, points in players
if games >= 15
]
ranked = sorted(players, reverse = True, key = itemgetter('ppg'))
return ranked[:10]
En başından, böyle bir 'yarı-işlevsel' stilin, kabul edilen cevaptaki iç içe geçmiş liste anlayışlarından "daha iyi" olduğunu düşünmediğimi, ayrıca belirli bir hoş "akışkan" / "zincir" havası olduğunu belirteceğim onları (OP'nin sözlerine göre).
Bununla birlikte, Javascript aracılığıyla gösterilen yarı işlevsel / 'zincirleme' tarzı OP türü tercih edilirse, bunun python'da da tamamen mümkün olduğunu belirtmek için bu yanıtı ekliyorum (ancak birkaç ekstra tanımlamayı gerektirebilir. yardımcı fonksiyonlar bunu etkinleştirmek için).
İşte aşağıda bir örnek. İlk olarak, python ısmarlama bir 'zincir' (diğer adıyla 'boru') operatörüne sahip olmadığından, çok basit bir tane oluşturuyoruz ( buradan alınmıştır ):
def chain( Accumulant, *Functions_list ):
for f in Functions_list: Accumulant = f( Accumulant )
return Accumulant
Ayrıca basit, curried bir reduceişlev oluşturalım, böylece bunun map -> reduceyerine gerçekleştirebiliriz flatmap:
def reduce_f( Function ):
def reductor (List):
while len( List ) > 1: List.insert( 0, Function( List.pop(0), List.pop(0) ) )
return List[0]
return reductor
Son olarak, kullanmak istediğimiz birkaç standart fonksiyonun fonksiyonel, curried versiyonlarını oluşturalım. Bunun gerekli olmadığına ve burada tanımlanan lambdaların doğrudan 'zincir'e atılmış olabileceğine dikkat edin, ancak onları burada önceden tanımlamak işleri çok daha kolay hale getirir ve bu isimleri / işlevleri doğrudan karşılaştırılabilir olmaları için seçtim. sorudaki javascript kodu işlevi:
splat_f = lambda f: lambda t: f(*t) # explode a tuple and pass it as arguments to f
map_f = lambda f: lambda _: list( map( f, _ ) )
filter_f = lambda f: lambda _: list( filter( f, _ ) )
sorted_f = lambda f: lambda _: sorted(_, key=f )
slice_f = lambda start, stop, step=1: lambda l: l[slice(start, stop, step)]
Yukarıdakilerle donanmış olarak, eşdeğer "akıcı", yöntem zinciri stilini python'da yeniden oluşturabiliriz. Neredeyse aynı görünüyor:
def topTenScores( stats ):
return chain( stats
, dict.items, list
, map_f( splat_f(lambda team, players: list(map(lambda player: [*player, team], players))))
, reduce_f( list.__add__ )
, filter_f( splat_f(lambda _1, games, _2, _3: games >= 15) )
, map_f( splat_f(lambda name, games, points, team:{'name':name,'ppg':points/games,'team':team}))
, sorted_f( lambda x : x['ppg'] )
, slice_f( 0, 10 )
)