अजगर 3 में जीवन
मैंने पायथन सीखना शुरू कर दिया है और अपने पहले कार्यक्रम के रूप में कॉनवे के जीवन के खेल को चुना है। मुझे पढ़ने में दिलचस्पी होगी कि कैसे अधिक मुहावरेदार पायथन लिखें। इसके अलावा, कुछ समय के लिए मुझे फेंक दिया गया था कि सब कुछ संदर्भ द्वारा पारित किया गया था और किसी सूची का असाइनमेंट इसके मूल्यों को कॉपी नहीं करता है, लेकिन संदर्भ की प्रतिलिपि बनाता है। इसलिए, मैंने डीपकोपी फ़ंक्शन का उपयोग किया है, लेकिन मैं सोच रहा हूं कि इस मामले में सूचियां गलत विकल्प हो सकती हैं। पाइथन में बेहतर विकल्प क्या होगा?
""" Implementation of LIFE """
import copy
# PARAMETERS
# Number of generations to simulate
N_GENERATIONS = 10
# Define the field. Dots (.) are dead cells, the letter "o" represents living cells
INITIAL_FIELD = \
"""
...................
...................
...................
...................
.ooooo.ooooo.ooooo.
...................
...................
...................
...................
"""
# FUNCTIONS
def print_field(field_copy, dead_cells=' ', living_cells='x'):
"""Pretty-print the current field."""
field_string = "\n".join(["".join(x) for x in field_copy])
field_string = field_string.replace('.', dead_cells)
field_string = field_string.replace('o', living_cells)
print(field_string)
def get_neighbours(field_copy, x, y):
"""Get all neighbours around a cell with position x and y
and return them in a list."""
n_rows = len(field_copy)
n_cols = len(field_copy[0])
if y == 0:
y_idx = [y, y+1]
elif y == n_rows - 1:
y_idx = [y-1, y]
else:
y_idx = [y-1, y, y+1]
if x == 0:
x_idx = [x, x+1]
elif x == n_cols - 1:
x_idx = [x-1, x]
else:
x_idx = [x-1, x, x+1]
neigbours = [field_copy[row][col] for row in y_idx for col in x_idx if (row, col) != (y, x)]
return neigbours
def count_living_cells(cell_list):
"""Count the living cells."""
accu = 0
for cell in cell_list:
if cell == 'o':
accu = accu + 1
return accu
def update_field(field_copy):
"""Update the field to the next generation."""
new_field = copy.deepcopy(field_copy)
for row in range(len(field_copy)):
for col in range(len(field_copy[0])):
living_neighbours = count_living_cells(get_neighbours(field_copy, col, row))
if living_neighbours < 2 or living_neighbours > 3:
new_field[row][col] = '.'
elif living_neighbours == 3:
new_field[row][col] = 'o'
return new_field
# MAIN
# Convert the initial playfield to an array
field = str.splitlines(INITIAL_FIELD)
field = field[1:] # Getting rid of the empty first element due to the multiline string
field = [list(x) for x in field]
print("Generation 0")
print_field(field)
for generation in range(1, N_GENERATIONS+1):
field = update_field(field)
print(f"Generation {generation}")
print("")
print_field(field)
print("")
जवाब
मुझे लगता है कि आपके get_neighborफ़ंक्शन का उपयोग करके minऔर max, और एस का उपयोग करके साफ किया जा सकता है range:
def get_neighbours(field_copy, x, y):
"""Get all neighbours around a cell with position x and y
and return them in a list."""
n_rows = len(field_copy)
n_cols = len(field_copy[0])
min_x = max(0, x - 1)
max_x = min(x + 1, n_cols - 1)
min_y = max(0, y - 1)
max_y = min(y + 1, n_rows - 1)
return [field_copy[row][col]
for row in range(min_y, max_y + 1)
for col in range(min_x, max_x + 1)
if (row, col) != (y, x)]
यह अभी भी काफी लंबा है, लेकिन यह ifसूचकांकों की हार्ड-कोडित सूचियों के लिए सभी गन्दा प्रेषण के साथ दूर करता है । मैं भी कुछ लाइनों पर सूची समझ तोड़ दिया। जब भी मेरी समझ थोड़ी लंबी होने लगती है, मैं उन्हें वैसे ही तोड़ देता हूं। मुझे लगता है कि यह पठनीयता में काफी मदद करता है।
के लिये
"\n".join(["".join(x) for x in field_copy])
आपको इसकी आवश्यकता नहीं है []:
"\n".join("".join(x) for x in field_copy)
वर्ग कोष्ठक के बिना, यह एक सूची समझ के बजाय एक जनरेटर अभिव्यक्ति है। वे आलसी हैं, जो आपको एक सूची बनाने से बचाता है ताकि इसे खिलाया जा सके join। यहाँ अंतर बहुत बड़ा नहीं है, लेकिन लंबी सूचियों के लिए जो स्मृति को बचा सकता है।
मैं तार की 2 डी सूची के रूप में बोर्ड का प्रतिनिधित्व नहीं करेगा। यह संभावना आवश्यकता से अधिक मेमोरी का उपयोग करती है, और विशेष रूप से आपके पास अब कैसे है, आपको यह याद रखने के लिए मजबूर किया जाता है कि स्ट्रिंग प्रतीक क्या दर्शाता है। उसके ऊपर, आपके पास स्ट्रिंग प्रतीकों के दो सेट हैं : एक का उपयोग आंतरिक रूप से तर्क के लिए ( 'o'और '.'), और दूसरे के लिए जब आप प्रिंट करते हैं ( ' 'और 'x')। यह होने की तुलना में अधिक भ्रामक है।
यदि आप वास्तव में तार का उपयोग करना चाहते हैं, तो आपके पास शीर्ष पर एक वैश्विक स्थिरांक होना चाहिए जो स्पष्ट रूप से परिभाषित करता है कि स्ट्रिंग क्या है:
DEAD_CELL = '.' # At the very top somewhere
ALIVE_CELL = 'o'
. . .
if living_neighbours < 2 or living_neighbours > 3: # Later on in a function
new_field[row][col] = DEAD_CELL
elif living_neighbours == 3:
new_field[row][col] = ALIVE_CELL
'.'"मैजिक नंबर्स" की श्रेणी में आने वाले स्ट्रिंग्स इधर-उधर गिरने लगते हैं: ऐसे मान जो किसी प्रोग्राम में ढीले हो जाते हैं, जिनका स्व-व्याख्यात्मक अर्थ नहीं है। यदि किसी मूल्य का उद्देश्य स्वयं स्पष्ट नहीं है, तो इसे वर्णनात्मक नाम के साथ एक चर में संग्रहीत करें ताकि आप और आपके पाठक जानते हों कि कोड में क्या चल रहा है।
निजी तौर पर, जब मैं GoL कार्यान्वयन लिखता हूं, मैं बूलियन मानों की 1D या 2D सूची, या जीवित कोशिकाओं का प्रतिनिधित्व करने वाले टुपल्स का एक सेट का उपयोग करता हूं। बुलियन सूची संस्करणों के लिए, यदि कोई सेल जीवित है, तो यह सच है, और यदि यह मर चुका है तो यह गलत है। सेट संस्करण के लिए, एक कोशिका जीवित है यदि यह सेट में है, अन्यथा यह मृत है।
मैं एक mainसमारोह में तल पर सभी सामान टक जाएगा । जरूरी नहीं कि आप हमेशा यही चाहते हों कि आप फाइल लोड करें।
दक्षता के लिए, हर पीढ़ी को लगातार नई फ़ील्ड प्रतियां बनाने के बजाय, एक सामान्य चाल शुरू में दो अधिकार बनाने हैं, फिर उन्हें हर पीढ़ी को स्वैप करना है।
जिस तरह से मैं करता हूं वह एक क्षेत्र है write_fieldऔर एक है read_field। जैसा कि नाम से पता चलता है, सभी लिखते हैं write_field, और सभी से पढ़ता है read_field। प्रत्येक "टिक" के बाद, आप बस उन्हें स्वैप करते हैं; read_fieldनया बन जाता है write_fieldऔर write_fieldबन जाता है read_field। यह आपको deepcopyप्रति बार एक बार महंगी कॉल से बचाता है ।
आप पायथन में इस स्वैप को काफी सरलता से कर सकते हैं :
write_field, read_field = read_field, write_field
टिप्पणी १
जनरेशन 0 को प्रिंट करने के लिए किसी विशेष मामले की आवश्यकता नहीं है।
बस अपनी सीमा को 0 से शुरू करें और अपडेट करने से पहले प्रिंट करें।
for generation in range(N_GENERATIONS+1):
print(f"Generation {generation}")
print("")
print_field(field)
print("")
field = update_field(field)
टिप्पणी २
इसके अलावा, ऐसा लगता है कि आप अपने कोड को उस तरह समायोजित कर रहे हैं जिस तरह से आप INITIAL_FIELDएक बहु-स्ट्रिंग स्ट्रिंग के रूप में परिभाषित करते हैं , सिर्फ इसलिए कि यह कोड विंडो में अच्छा लगता है। यह पीछे की तरफ है।
आपको इसे स्ट्रिंग्स की एक सूची के रूप में परिभाषित करना चाहिए ताकि आपको कार्यक्रम शुरू करने से पहले स्प्लिटलाइन और उन पर अन्य चीजों को करने की ज़रूरत न पड़े। यदि आप अभी भी इसे मानव-पठनीय बनाना चाहते हैं, तो आप कुछ पंक्ति विराम \ (यदि आवश्यक हो) का उपयोग कर सकते हैं , लेकिन मुझे लगता है कि इसके बिना भी वाक्यविन्यास ठीक रहेगा।
INITIAL_FIELD = [
"...................",
"...................",
etc
]
टिप्पणी ३
def print_field(field_copy, dead_cells=' ', living_cells='x'):
यह फ़ंक्शन दो मापदंडों को स्वीकार करता है, लेकिन इसके लिए कोई कॉल कभी भी उन्हें पास नहीं करता है। इसलिए वे वास्तव में सिर्फ आंतरिक चर हैं और फ़ंक्शन परिभाषा में नहीं होना चाहिए।
टिप्पणी ४
field_string = field_string.replace('.', dead_cells)
field_string = field_string.replace('o', living_cells)
print(field_string)
यह अनावश्यक दोहराव और पढ़ने में कठिन है। मैं उन 3 पंक्तियों को एक में बाँटना चाहूँगा
print(field_string.replace('.', dead_cells).replace('o', living_cells))
टिप्पणी ५
def count_living_cells(cell_list):
"""Count the living cells."""
accu = 0
for cell in cell_list:
if cell == 'o':
accu = accu + 1
return accu
यह भी पीछे की ओर है, इस वजह से कि आप अपनी कोशिकाओं को पात्रों और तारों के रूप में कैसे दर्शाते हैं।
यह अधिक समझदार होगा मुझे लगता है कि सरल प्रोग्राम लॉजिक को प्राथमिकता देना चाहिए और प्रिंट फ़ंक्शन को आवश्यकतानुसार समायोजित करना चाहिए। यदि आप जीवित कोशिकाओं को संख्या 1 के रूप में और मृत कोशिकाओं को संख्या 0 के रूप में दर्शाते हैं, तो एक सेल सूची दिखेगी [0,1,1,0,0,1,0]और इस फ़ंक्शन को लिखा जा सकता है
return sum(cell_list)
वास्तव में, आपको अब किसी फ़ंक्शन की आवश्यकता नहीं होगी, क्योंकि यह बहुत छोटा है।
अपने प्रिंट फंक्शन में आप किसी अन्य वर्ण द्वारा 1 और 0 को मुद्रण से पहले किसी अन्य वर्ण द्वारा बदल सकते हैं।
आपके द्वारा पोस्ट किया गया कोड उन लाभों का एक अच्छा उदाहरण प्रस्तुत करता है जो वैचारिक और नामकरण स्थिरता में अधिक निवेश से नीचे की ओर बह सकते हैं। जैसा कि लिखा गया है, कोड में जीवित या मृत कोशिकाओं का प्रतिनिधित्व करने के दो अलग-अलग तरीके हैं, यह पंक्तियों / स्तंभों की भाषा और x / y निर्देशांक की भाषा के बीच आगे और पीछे टॉगल करता है, और यह fieldऔर के बीच स्विच करता है field_copy।
जब आप एक कार्यक्रम के विकास में उस बिंदु को मारते हैं, तो यह वापस कदम रखने और खुद को कुछ स्थिरता के लिए उपयोगी होता है। उदाहरण के लिए:
field : list of rows
row : list of cells
cell : either 'x' (alive) or space (dead)
r : row index
c : column index
और सभी कार्यों में सभी कोड डालकर एक ठोस नींव पर शुरू करते हैं, उपयोग करने के लिए थोड़ा सा लचीलापन जोड़ते हैं ताकि हम कमांड लाइन (डिबगिंग और परीक्षण के लिए आसान) पर पीढ़ियों के एन को अलग-अलग कर सकें। इसके अलावा, हम प्रोग्राम के एल्गोरिथम भागों और प्रोग्राम के कुछ हिस्सों के बीच एक सख्त अलगाव बनाए रखना चाहते हैं जो मुद्रण और प्रस्तुति से संबंधित है। यहाँ उस रास्ते पर शुरू करने का एक तरीका है:
import sys
ALIVE = 'x'
DEAD = ' '
INITIAL_FIELD_TEMPLATE = [
' ',
' ',
' ',
' ',
' xxxxx xxxxx xxxxx ',
' ',
' ',
' ',
' ',
]
DEFAULT_GENERATIONS = 10
def main(args):
# Setup: initial field and N of generations.
init = [list(row) for row in INITIAL_FIELD_TEMPLATE]
args.append(DEFAULT_GENERATIONS)
n_generations = int(args[0])
# Run Conway: we now have the fields for all generations.
fields = list(conway(n_generations, init))
# Analyze, report, whatever.
for i, f in enumerate(fields):
s = field_as_str(f)
print(f'\nGeneration {i}:\n{s}')
def conway(n, field):
for _ in range(n + 1):
yield field # Temporary implementation.
def field_as_str(field):
return '\n'.join(''.join(row) for row in field)
if __name__ == '__main__':
main(sys.argv[1:])
उस नींव पर शुरू करते हुए, अगला कदम conway()कुछ दिलचस्प करना है - अर्थात्, अगली पीढ़ी के लिए क्षेत्र की गणना करें। new_field()अगर हम सीमा स्थिरांक के एक जोड़े को परिभाषित प्रक्रिया आसान है।
RNG_R = range(len(INITIAL_FIELD_TEMPLATE))
RNG_C = range(len(INITIAL_FIELD_TEMPLATE[0]))
def new_field(field):
return [
[new_cell_value(field, r, c) for c in RNG_C]
for r in RNG_R
]
def new_cell_value(field, r, c):
return field[r][c] # Temporary implementation.
और फिर अगला कदम एक वास्तविक को लागू करना है new_cell_value(), जो हमें पता है कि हमें पड़ोसी कोशिकाओं के बारे में सोचने के लिए प्रेरित करेगा। इन 2 डी ग्रिड स्थितियों में, पड़ोसियों के तर्क को अक्सर (R, C)सरल डेटा संरचना में पड़ोसियों के सापेक्ष व्यक्त करके सरल बनाया जा सकता है :
NEIGHBOR_SHIFTS = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1),
]
def new_cell_value(field, r, c):
n_living = sum(
cell == ALIVE
for cell in neighbor_cells(field, r, c)
)
return (
field[r][c] if n_living == 2 else
ALIVE if n_living == 3 else
DEAD
)
def neighbor_cells(field, r, c):
return [
field[r + dr][c + dc]
for dr, dc in NEIGHBOR_SHIFTS
if (r + dr) in RNG_R and (c + dc) in RNG_C
]
एक अंतिम नोट: एक सुसंगत नामकरण सम्मेलन को अपनाने और समस्या को काफी छोटे कार्यों में शामिल करके, हम कई छोटे चर नामों से दूर हो सकते हैं, जो कोड के दृश्य वजन को हल्का करता है और पठनीयता के साथ मदद करता है। छोटे दायरे के भीतर और एक स्पष्ट संदर्भ के भीतर (दोनों महत्वपूर्ण हैं), छोटे चर नाम पठनीयता को बढ़ाते हैं । विचार करें neighbor_cells(): rऔर cकाम क्योंकि हमारे सम्मेलन हर जगह पीछा किया जाता है; RNG_Rऔर RNG_Cक्योंकि वे उस सम्मेलन में काम करते हैं; drऔर dcइसी कारण आंशिक रूप से काम करते हैं और आंशिक रूप से, क्योंकि वे एक स्पष्ट नाम कंटेनर के संदर्भ है, NEIGHBOR_SHIFTS।