गो में 2D स्लाइस बनाने का एक संक्षिप्त तरीका क्या है?


105

मैं ए टूर ऑफ गो से गुजरते हुए गो सीख रहा हूं । वहाँ अभ्यास में से एक मुझे dyपंक्तियों और dxस्तंभों का 2 डी टुकड़ा बनाने के लिए कहता है uint8। मेरा वर्तमान तरीका, जो काम करता है, वह है:

a:= make([][]uint8, dy)       // initialize a slice of dy slices
for i:=0;i<dy;i++ {
    a[i] = make([]uint8, dx)  // initialize a slice of dx unit8 in each of dy slices
}

मुझे लगता है कि इसे शुरू करने के लिए प्रत्येक स्लाइस के माध्यम से पुनरावृत्ति करना बहुत अधिक कठिन है। और अगर स्लाइस के अधिक आयाम थे, तो कोड अनिर्दिष्ट हो जाएगा। क्या गो में 2 डी (या एन-आयामी) स्लाइस को शुरू करने का एक संक्षिप्त तरीका है?

जवाबों:


151

अधिक संक्षिप्त तरीका नहीं है, आपने जो किया वह "सही" तरीका है; क्योंकि स्लाइस हमेशा एक-आयामी होते हैं, लेकिन उच्च-आयामी वस्तुओं के निर्माण के लिए रचना की जा सकती है। अधिक विवरण के लिए यह प्रश्न देखें: जाओ: दो आयामी सरणी की मेमोरी प्रतिनिधित्व कैसे है

एक चीज जिस पर आप सरलीकरण कर सकते हैं वह है for rangeनिर्माण का उपयोग करना :

a := make([][]uint8, dy)
for i := range a {
    a[i] = make([]uint8, dx)
}

यह भी ध्यान दें कि यदि आप एक समग्र शाब्दिक के साथ अपने स्लाइस को शुरू करते हैं , तो आपको यह "मुफ्त" के लिए मिलता है, उदाहरण के लिए:

a := [][]uint8{
    {0, 1, 2, 3},
    {4, 5, 6, 7},
}
fmt.Println(a) // Output is [[0 1 2 3] [4 5 6 7]]

हां, इसकी अपनी सीमा है क्योंकि प्रतीत होता है कि आपको सभी तत्वों की गणना करनी होगी; लेकिन कुछ तरकीबें हैं, अर्थात् आपको सभी मानों की गणना करने की आवश्यकता नहीं है, केवल वे जो स्लाइस के तत्व प्रकार के शून्य मान नहीं हैं । इसके बारे में अधिक जानकारी के लिए, गोलंग सरणी आरंभीकरण में बंद आइटम देखें ।

उदाहरण के लिए यदि आप ऐसा स्लाइस चाहते हैं जहां पहले 10 तत्व शून्य हों, और फिर निम्न प्रकार से 1और 2, इसे इस तरह बनाया जा सकता है:

b := []uint{10: 1, 2}
fmt.Println(b) // Prints [0 0 0 0 0 0 0 0 0 0 1 2]

यह भी ध्यान दें कि यदि आप स्लाइस के बजाय सरण का उपयोग करते हैं , तो इसे बहुत आसानी से बनाया जा सकता है:

c := [5][5]uint8{}
fmt.Println(c)

आउटपुट है:

[[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]

सरणियों के मामले में आपको "बाहरी" सरणी पर चलना नहीं पड़ता है और "आंतरिक" सरणियों को आरम्भ करना है, क्योंकि सरणियाँ वर्णनात्मक नहीं हैं, बल्कि मान हैं। ब्लॉग पोस्ट देखें Arrays, स्लाइस (और स्ट्रिंग्स): अधिक विवरण के लिए 'परिशिष्ट' के यांत्रिकी

गो खेल के मैदान पर उदाहरणों की कोशिश करें ।


चूंकि एक सरणी का उपयोग कोड को सरल करता है, मैं ऐसा करना चाहूंगा। एक संरचना में यह कैसे निर्दिष्ट किया जाता है? मुझे लगता है कि cannot use [5][2]string literal (type [5][2]string) as type [][]string in field valueजब मुझे लगता है कि मैं कह रहा हूँ कि एक टुकड़ा है, तो मुझे असाइन करने का प्रयास करें।
एरिक लिंडसे

इसे स्वयं पता लगाया, और जानकारी जोड़ने के लिए उत्तर को संपादित किया।
एरिक लिंडसे

1
@EricLindsey जबकि आपका संपादन अच्छा है, मैं अब भी इसे अस्वीकार करने जा रहा हूं क्योंकि मैं सरणियों के उपयोग को प्रोत्साहित नहीं करना चाहता क्योंकि आरंभिकता आसान है। गो में, ऐरे सेकेंडरी हैं, स्लाइस जाने का रास्ता है। विवरण के लिए, देखें कि Go में एक सरणी को दूसरे में जोड़ने का सबसे तेज़ तरीका क्या है? Arrays के अपने स्थान भी हैं, विवरण के लिए, देखें कि गो में सरणियाँ क्यों हैं?
इजा

काफी उचित है, लेकिन मेरा मानना ​​है कि जानकारी अभी भी योग्यता है। मैं अपने संपादन के साथ समझाने की कोशिश कर रहा था कि यदि आपको वस्तुओं के बीच विभिन्न आयामों के लचीलेपन की आवश्यकता है तो स्लाइस जाने का रास्ता है। दूसरी ओर, यदि आपकी जानकारी कठोर रूप से संरचित है और हमेशा समान रहेगी, तो सरणियों को न केवल आरंभ करना आसान है, वे अधिक कुशल भी हैं। मैं संपादन कैसे सुधार सकता हूं?
एरिक लिंडसे

@EricLindsey मैं देख रहा हूं कि आपने एक और संपादन किया है जो पहले ही दूसरों द्वारा खारिज कर दिया गया था। अपने संपादन में आप तेजी से तत्व का उपयोग करने के लिए सरणियों का उपयोग करने के लिए कह रहे थे। ध्यान दें कि गो कई चीजों का अनुकूलन करता है, और यह मामला नहीं हो सकता है, स्लाइस बस के रूप में तेज़ हो सकते हैं। विवरण के लिए, ऐरे बनाम स्लाइस: एक्सेसिंग स्पीड देखें
आईसीजे

12

मैट्रिक्स बनाने के लिए स्लाइस का उपयोग करने के दो तरीके हैं। आइए उनके बीच के अंतरों पर एक नज़र डालें।

पहली विधि:

matrix := make([][]int, n)
for i := 0; i < n; i++ {
    matrix[i] = make([]int, m)
}

दूसरी विधि:

matrix := make([][]int, n)
rows := make([]int, n*m)
for i := 0; i < n; i++ {
    matrix[i] = rows[i*m : (i+1)*m]
}

पहली विधि के संबंध में, लगातार makeकॉल करना यह सुनिश्चित नहीं करता है कि आप एक सन्निहित मैट्रिक्स के साथ समाप्त हो जाएंगे, इसलिए आपके पास स्मृति में विभाजित मैट्रिक्स हो सकता है। आइए दो गो दिनचर्या के साथ एक उदाहरण के बारे में सोचें जो इसका कारण बन सकती है:

  1. दिनचर्या # 0 make([][]int, n)के लिए आवंटित स्मृति प्राप्त करने के लिए चलती है matrix, 0x000 से 0x07F तक स्मृति का एक टुकड़ा प्राप्त करना।
  2. फिर, यह लूप शुरू करता है और पहली पंक्ति करता है make([]int, m), 0x080 से 0x0FF तक हो रही है।
  3. दूसरे पुनरावृत्ति में यह अनुसूचक द्वारा पूर्वनिर्धारित हो जाता है।
  4. शेड्यूलर प्रोसेसर को # 1 रूटीन देता है और यह चलने लगता है। यह भी make(अपने उद्देश्यों के लिए) का उपयोग करता है और 0x100 से 0x17F (रूटीन की पहली पंक्ति के ठीक बगल में) या 0 हो जाता है।
  5. थोड़ी देर के बाद, यह पहले से ही समाप्त हो जाता है और दिनचर्या # 0 फिर से चलने लगती है।
  6. यह make([]int, m)दूसरी लूप पुनरावृत्ति के अनुरूप होता है और दूसरी पंक्ति के लिए 0x180 से 0x1FF तक हो जाता है। इस बिंदु पर, हमें पहले से ही दो विभाजित पंक्तियाँ मिलीं।

दूसरी विधि के साथ make([]int, n*m), रूटीनिटी को सुनिश्चित करते हुए, सभी मैट्रिक्स को एक ही स्लाइस में आवंटित किया जाता है। उसके बाद, प्रत्येक पंक्ति के अनुरूप उप-भागों में मैट्रिक्स पॉइंटर्स को अपडेट करने के लिए एक लूप की आवश्यकता होती है।

आप दोनों विधियों का उपयोग करके असाइन की गई मेमोरी में अंतर देखने के लिए गो प्लेग्राउंड में ऊपर दिखाए गए कोड के साथ खेल सकते हैं । ध्यान दें कि मैंने runtime.Gosched()केवल प्रोसेसर की पैदावार और अनुसूचक को एक और दिनचर्या में बदलने के लिए मजबूर करने के उद्देश्य से उपयोग किया था ।

कौन सा उपयोग करना है? पहली विधि के साथ सबसे खराब स्थिति की कल्पना करें, अर्थात प्रत्येक पंक्ति किसी अन्य पंक्ति में स्मृति के बगल में नहीं है। फिर, यदि आपका प्रोग्राम मैट्रिक्स तत्वों (उन्हें पढ़ने या लिखने के लिए) के माध्यम से पुनरावृत्त करता है, तो संभवतः अधिक स्थानीय डेटा की वजह से दूसरी विधि की तुलना में अधिक कैश मिसेज (इसलिए उच्च विलंबता) होगा। दूसरी ओर, दूसरी विधि के साथ, मैट्रिक्स के लिए आवंटित स्मृति का एक भी टुकड़ा प्राप्त करना संभव नहीं हो सकता है, क्योंकि मेमोरी विखंडन (सभी मेमोरी में विखंडू), भले ही सैद्धांतिक रूप से इसके लिए पर्याप्त मुक्त मेमोरी हो सकती है। ।

इसलिए, जब तक कि बहुत अधिक मेमोरी विखंडन न हो और आवंटित किया जाने वाला मैट्रिक्स काफी बड़ा हो, आप डेटा लोकलिटी का लाभ पाने के लिए हमेशा दूसरी विधि का उपयोग करना चाहेंगे।


2
golang.org/doc/effective_go.html#slices स्लाइस-देशी वाक्यविन्यास का लाभ उठाने के लिए सन्निहित स्मृति तकनीक को करने का एक चतुर तरीका दिखाता है (उदाहरण के लिए स्पष्ट रूप से स्लाइस सीमाओं की गणना करने की आवश्यकता नहीं है जैसे (i 1) * m)
मैग्नस

0

पिछले उत्तरों में हमने उस स्थिति पर विचार नहीं किया जब शुरुआती लंबाई अज्ञात है। इस मामले के लिए आप मैट्रिक्स बनाने के लिए निम्नलिखित तर्क का उपयोग कर सकते हैं

items := []string{"1.0", "1.0.1", "1.0.2", "1.0.2.1.0"}
mx := make([][]string, 0)
for _, item := range items {
    ind := strings.Count(item, ".")
    for len(mx) < ind+1 {
        mx = append(mx, make([]string, 0))
    }
    mx[ind] = append(mx[ind], item)

}

fmt.Println(mx)

https://play.golang.org/p/pHgggHr4nbB


1
मुझे यकीन नहीं है कि यह "संक्षिप्त तरीका" की ओपी सीमाओं के भीतर है, जैसा कि उन्होंने कहा था "मुझे लगता है कि इसे शुरू करने के लिए प्रत्येक स्लाइस के माध्यम से पुनरावृत्ति करना बहुत अधिक क्रिया है"।
मार्कोस कैनेलस मेयो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.