क्या मुझे एक सूची या एक सरणी का उपयोग करना चाहिए?


22

मैं आइटम नंबर के लिए UPC की गणना करने के लिए एक विंडो फॉर्म पर काम कर रहा हूं।

मैं सफलतापूर्वक एक ऐसा आइटम बनाता हूं, जो एक समय में एक आइटम नंबर / UPC को संभालता है, अब मैं इसका विस्तार करना चाहता हूं और इसे कई आइटम नंबर / UPC के लिए करना चाहता हूं।

मैंने शुरू किया है और एक सूची का उपयोग करने की कोशिश की है, लेकिन मैं फंसता रहता हूं। मैंने एक सहायक वर्ग बनाया:

public class Codes
{
    private string incrementedNumber;
    private string checkDigit;
    private string wholeNumber;
    private string wholeCodeNumber;
    private string itemNumber;

    public Codes(string itemNumber, string incrementedNumber, string checkDigit, string wholeNumber, string wholeCodeNumber)
    {
        this.incrementedNumber = incrementedNumber;
        this.checkDigit = checkDigit;
        this.wholeNumber = wholeNumber;
        this.wholeCodeNumber = wholeCodeNumber;
        this.itemNumber = itemNumber;
    }

    public string ItemNumber
    {
        get { return itemNumber; }
        set { itemNumber = value; }
    }

    public string IncrementedNumber
    { 
        get { return incrementedNumber; }
        set { incrementedNumber = value; } 
    }

    public string CheckDigit
    {
        get { return checkDigit; }
        set { checkDigit = value; }
    }

    public string WholeNumber
    {
        get { return wholeNumber; }
        set { wholeNumber = value; }
    }

    public string WholeCodeNumber
    {
        get { return wholeCodeNumber; }
        set { wholeCodeNumber = value; }
    }

}

फिर मैं अपने कोड पर शुरू हो गया, लेकिन मुद्दा यह है कि यह प्रक्रिया वृद्धिशील है, जिसका अर्थ है कि मुझे चेकबॉक्स के माध्यम से ग्रिडव्यू से आइटम नंबर मिलता है और उन्हें सूची में डाल दिया जाता है। फिर मैं डेटाबेस से अंतिम यूपीसी प्राप्त करता हूं, चेकडिट को छीनता हूं, फिर एक-एक करके संख्या बढ़ाता हूं और सूची में डालता हूं। फिर मैंने नए नंबर के लिए चेकडिट की गणना की और सूची में डाल दिया। और यहाँ पर मुझे पहले से ही एक Out of Memory Exception मिलता है। यहाँ मेरे पास अब तक का कोड है:

List<Codes> ItemNumberList = new List<Codes>();


    private void buttonSearch2_Click(object sender, EventArgs e)
    {
        //Fill the datasets
        this.immasterTableAdapter.FillByWildcard(this.alereDataSet.immaster, (textBox5.Text));
        this.upccodeTableAdapter.FillByWildcard(this.hangtagDataSet.upccode, (textBox5.Text));
        this.uPCTableAdapter.Fill(this.uPCDataSet.UPC);
        string searchFor = textBox5.Text;
        int results = 0;
        DataRow[] returnedRows;
        returnedRows = uPCDataSet.Tables["UPC"].Select("ItemNumber = '" + searchFor + "2'");
        results = returnedRows.Length;
        if (results > 0)
        {
            MessageBox.Show("This item number already exists!");
            textBox5.Clear();
            //clearGrids();
        }
        else
        {
            //textBox4.Text = dataGridView1.Rows[0].Cells[1].Value.ToString();
            MessageBox.Show("Item number is unique.");
        }
    }

    public void checkMarks()
    {

        for (int i = 0; i < dataGridView7.Rows.Count; i++)
        {
            if ((bool)dataGridView7.Rows[i].Cells[3].FormattedValue)
            {
                {
                    ItemNumberList.Add(new Codes(dataGridView7.Rows[i].Cells[0].Value.ToString(), "", "", "", ""));
                }
            }
        }
    }

    public void multiValue1()
    {
        _value = uPCDataSet.UPC.Rows[uPCDataSet.UPC.Rows.Count - 1]["UPCNumber"].ToString();//get last UPC from database
        _UPCNumber = _value.Substring(0, 11);//strip out the check-digit
        _UPCNumberInc = Convert.ToInt64(_UPCNumber);//convert the value to a number

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            _UPCNumberInc = _UPCNumberInc + 1;
            _UPCNumberIncrement = Convert.ToString(_UPCNumberInc);//assign the incremented value to a new variable
            ItemNumberList.Add(new Codes("", _UPCNumberIncrement, "", "", ""));//**here I get the OutOfMemoreyException**
        }

        for (int i = 0; i < ItemNumberList.Count; i++)
        {
            long chkDigitOdd;
            long chkDigitEven;
            long chkDigitSubtotal;
            chkDigitOdd = Convert.ToInt64(_UPCNumberIncrement.Substring(0, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(2, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(4, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(6, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(8, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(10, 1));
            chkDigitOdd = (3 * chkDigitOdd);
            chkDigitEven = Convert.ToInt64(_UPCNumberIncrement.Substring(1, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(3, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(5, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(7, 1)) + Convert.ToInt64(_UPCNumberIncrement.Substring(9, 1));
            chkDigitSubtotal = (300 - (chkDigitEven + chkDigitOdd));
            _chkDigit = chkDigitSubtotal.ToString();
            _chkDigit = _chkDigit.Substring(_chkDigit.Length - 1, 1);
            ItemNumberList.Add(new Codes("", "",_chkDigit, "", ""));
        }

क्या यह सही तरीका है इसके बारे में जाने के लिए, एक सूची का उपयोग करके, या मुझे एक अलग तरीके से देखना चाहिए?


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

2
एक साइड नोट के रूप में, ये सभी निजी क्षेत्र (आपकी Codeकक्षा में) निरर्थक हैं और वास्तव में शोर के अलावा और कुछ नहीं, { get; private set; }पर्याप्त होगा।
कोनराड मोरावस्की

5
क्या इसके लिए प्रश्न शीर्षक वास्तव में सटीक है? यह वास्तव में एक सूची बनाम सरणी प्रश्न की तरह प्रतीत नहीं होता है , इसलिए मैं अपने कार्यान्वयन प्रश्न को बेहतर कैसे बना सकता हूं । कहा जा रहा है, यदि आप तत्वों को जोड़ रहे हैं या निकाल रहे हैं, तो आप एक सूची (या अन्य लचीली डेटा संरचना) चाहते हैं। Arrays केवल वास्तव में अच्छे होते हैं जब आपको पता होता है कि आपको शुरू में कितने तत्वों की आवश्यकता है।
KChaloux

@ केचलौक्स, यह बहुत ज्यादा है जो मैं जानना चाहता था। क्या एक सूची इस बारे में जाने का सही तरीका है, या मुझे इसे आगे बढ़ाने के लिए एक अलग तरीके से देखना चाहिए? तो ऐसा लगता है कि एक सूची एक अच्छा तरीका है, मुझे सिर्फ अपने तर्क को समायोजित करना है।
campagnolo_1

1
@ टेलस्टाइन, मैं अपने कोड में सुधार करने के लिए इतना नहीं पूछ रहा था कि मैं क्या करने की कोशिश कर रहा हूं।
campagnolo_1

जवाबों:


73

मैं अपनी टिप्पणी का विस्तार करूंगा:

... यदि आप तत्वों को जोड़ रहे हैं या निकाल रहे हैं, तो आप एक सूची (या अन्य लचीली डेटा संरचना) चाहते हैं। Arrays केवल वास्तव में अच्छे होते हैं जब आपको पता होता है कि आपको शुरू में कितने तत्वों की आवश्यकता है।

एक त्वरित ब्रेकडाउन

जब आपके पास निश्चित संख्या में ऐसे तत्व होते हैं जो बदलने की संभावना नहीं है, और आप इसे गैर-क्रमिक रूप से एक्सेस करना चाहते हैं, तो ऐरे अच्छे हैं ।

  • निर्धारित माप
  • फास्ट एक्सेस - O (1)
  • धीमे आकार दें - O (n) - हर तत्व को एक नए सरणी में कॉपी करने की आवश्यकता है!

लिंक्ड-लिस्ट त्वरित परिवर्धन और निष्कासन के लिए या तो अंत में अनुकूलित किए जाते हैं, लेकिन बीच में पहुंचने के लिए धीमी होती हैं।

  • चर आकार
  • मध्य में धीमी पहुँच - O (n)
    • वांछित सूचकांक तक पहुंचने के लिए सिर से शुरू होने वाले प्रत्येक तत्व को पार करने की आवश्यकता है
  • हेड पर तेज़ पहुँच - O (1)
  • टेल पर संभावित तेजी से पहुंच
    • O (1) यदि कोई संदर्भ टेल एंड पर संग्रहित है (जैसा कि एक डबल-लिंक्ड सूची के साथ)
    • O (n) यदि कोई संदर्भ संग्रहीत नहीं है (बीच में नोड तक पहुँचने के रूप में एक ही जटिलता)

सरणी सूचियाँ (जैसे List<T>C # में!) दोनों का मिश्रण हैं, जिसमें काफी तेज़ जोड़ियाँ और यादृच्छिक पहुँच हैं। List<T> जब आप उपयोग करने के लिए निश्चित नहीं होंगे तो अक्सर आपका गो-टू कलेक्शन हो जाएगा।

  • एक बैकिंग संरचना के रूप में एक सरणी का उपयोग करता है
  • अपने आकार के बारे में होशियार है - जब वह इससे बाहर निकलता है तो अपने वर्तमान स्थान का दोगुना आवंटन करता है।
    • इससे O (log n) आकार बदल जाता है, जो हर बार हमारे द्वारा जोड़े / हटाए जाने से बेहतर होता है
  • फास्ट एक्सेस - O (1)

कैसे काम करता है

अधिकांश भाषाएं स्मृति में सन्निहित डेटा के रूप में सारणीबद्ध करती हैं, जिनमें से प्रत्येक तत्व समान आकार का होता है। मान लीजिए कि हमारे पास एक सरणी थी int(दशमलव पते का उपयोग करते हुए [पता: मान], क्योंकि मैं आलसी हूं।

[0: 10][32: 20][64: 30][96: 40][128: 50][160: 60]

इन तत्वों में से प्रत्येक एक 32-बिट पूर्णांक है, इसलिए हम जानते हैं कि यह मेमोरी (32 बिट्स) में कितना स्थान लेता है। और हम पहले तत्व को पॉइंटर का मेमोरी एड्रेस जानते हैं।

उस सरणी में किसी अन्य तत्व के मूल्य को प्राप्त करना बहुत आसान है:

  • पहले तत्व का पता लें
  • प्रत्येक तत्व की भरपाई करें (स्मृति में इसका आकार)
  • वांछित सूचकांक द्वारा ऑफसेट को गुणा करें
  • पहले तत्व के पते पर अपना परिणाम जोड़ें

मान लीजिए कि हमारा पहला तत्व '0' पर है। हम जानते हैं कि हमारा दूसरा तत्व '32' (0 + (32 * 1)) पर है, और हमारा तीसरा तत्व 64 (0 + (32 * 2)) पर है।

तथ्य यह है कि हम इन सभी मूल्यों को स्मृति में एक दूसरे के बगल में संग्रहीत कर सकते हैं इसका मतलब है कि हमारा सरणी जितना संभव हो उतना कॉम्पैक्ट है। इसका मतलब यह भी है कि काम जारी रखने के लिए हमारे सभी तत्वों को एक साथ रहने की आवश्यकता है !

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

लिंक की गई सूची

सरणियों के विपरीत, लिंक्ड सूचियों को स्मृति में एक दूसरे के बगल में होने के लिए अपने सभी तत्वों की आवश्यकता नहीं होती है। वे नोड्स से बने होते हैं, जो निम्न जानकारी संग्रहीत करते हैं:

Node<T> {
    Value : T      // Value of this element in the list
    Next : Node<T> // Reference to the node that comes next
}

सूची में ही ज्यादातर मामलों में सिर और पूंछ (पहले और अंतिम नोड्स) का संदर्भ रहता है , और कभी-कभी इसके आकार पर भी नज़र रखता है।

यदि आप सूची के अंत में एक तत्व जोड़ना चाहते हैं, तो आपको केवल पूंछ प्राप्त करने की आवश्यकता है , और अपने मूल्य वाले Nextएक नए को संदर्भित करने के लिए इसे बदलें Node। अंत से निकालना समान रूप से सरल है - बस Nextपूर्ववर्ती नोड के मूल्य को कम करना।

दुर्भाग्य से, यदि आपके पास LinkedList<T>1000 तत्व हैं, और आप तत्व 500 चाहते हैं, तो 500 वें तत्व पर दाएं कूदने का कोई आसान तरीका नहीं है, जैसे कि एक सरणी के साथ है। आपको सिर पर शुरू करने की आवश्यकता है , और Nextनोड पर जाना जारी रखें , जब तक कि आपने इसे 500 बार नहीं किया है।

यही कारण है कि जोड़ने और हटाने से LinkedList<T>तेज है (जब अंत में काम कर रहा है), लेकिन मध्य तक पहुंच धीमी है।

संपादित करें : ब्रायन टिप्पणी में बताते हैं कि लिंक की गई सूचियों में पृष्ठ दोष होने का जोखिम है, क्योंकि सन्निहित स्मृति में संग्रहीत नहीं होने के कारण। यह बेंचमार्क के लिए कठिन हो सकता है, और लिंक्ड सूचियों को थोड़ा धीमा भी कर सकता है, जितना कि आप उम्मीद कर सकते हैं कि सिर्फ उनके समय की जटिलताएं दी गई हैं।

दोनों ओर से लाभदायक

List<T>दोनों के लिए समझौता करता है T[]और LinkedList<T>एक समाधान के साथ आता है जो ज्यादातर परिस्थितियों में काफी तेज और आसान है।

आंतरिक रूप से, List<T>एक सरणी है! इसे अभी भी आकार बदलने के दौरान इसके तत्वों की नकल के हुप्स के माध्यम से कूदना पड़ता है, लेकिन यह कुछ साफ चालें खींचता है।

शुरुआत के लिए, किसी एकल तत्व को जोड़ने से आमतौर पर प्रतिलिपि बनाने का कारण नहीं बनता है। List<T>यह सुनिश्चित करता है कि अधिक तत्वों के लिए हमेशा पर्याप्त जगह हो। जब यह बाहर निकलता है, तो केवल एक नए तत्व के साथ एक नए आंतरिक सरणी को आवंटित करने के बजाय , यह कई नए तत्वों के साथ एक नया सरणी आवंटित करेगा (वर्तमान में जितना संभव हो उतना दोगुना!)।

कॉपी ऑपरेशंस महंगे हैं, इसलिए List<T>जितना संभव हो सके उन पर कटौती करें, जबकि अभी भी तेजी से रैंडम एक्सेस की अनुमति है। साइड इफेक्ट के रूप में, यह सीधे-अप सरणी या लिंक की गई सूची की तुलना में थोड़ा अधिक स्थान बर्बाद कर सकता है, लेकिन यह आमतौर पर ट्रेडऑफ के लायक है।

टी एल; डॉ

का उपयोग करें List<T>। यह सामान्य रूप से आप क्या चाहते हैं, और यह इस स्थिति में आपके लिए सही प्रतीत होता है (जहां आप कॉल कर रहे हैं। संशोधन) ()। यदि आप अनिश्चित हैं कि आपको क्या चाहिए, List<T>तो शुरू करने के लिए एक अच्छी जगह है।

उच्च-प्रदर्शन के लिए एरर्स अच्छा है, "मुझे पता है कि मुझे एक्स तत्वों की आवश्यकता है"। वैकल्पिक रूप से, वे त्वरित, वन-ऑफ के लिए उपयोगी हैं "मुझे इन एक्स चीजों को समूहित करने की आवश्यकता है जो मैंने पहले से ही एक साथ परिभाषित किया है ताकि मैं उनके ऊपर लूप कर सकूं" संरचनाएं।

कई अन्य संग्रह कक्षाएं हैं। Stack<T>एक लिंक की गई सूची की तरह है जो केवल शीर्ष से संचालित होती है। Queue<T>प्रथम-प्रथम-प्रथम सूची के रूप में काम करता है। Dictionary<T, U>चाबियाँ और मूल्यों के बीच एक अनियंत्रित, साहचर्य मानचित्रण है। उनके साथ खेलें और प्रत्येक की ताकत और कमजोरियों को जानें। वे आपके एल्गोरिदम बना या तोड़ सकते हैं।


2
कुछ मामलों में, सरणी के संयोजन का उपयोग करने और उपयोग intकरने योग्य तत्वों की संख्या का संकेत देने के लिए फायदे हो सकते हैं। अन्य बातों के अलावा, एक से दूसरे सरणी में कई तत्वों को थोक-कॉपी करना संभव है, लेकिन सूचियों के बीच की नकल करने के लिए आमतौर पर व्यक्तिगत रूप से प्रसंस्करण तत्वों की आवश्यकता होती है। इसके अलावा, सरणी तत्वों refको चीजों की तरह पास किया जा सकता है Interlocked.CompareExchange, जबकि सूची आइटम नहीं कर सकते।
सुपरकैट

2
काश मैं एक से अधिक उत्थान दे पाता। मैं उपयोग-केस के अंतरों को जानता था, और सरणियों / लिंक की गई सूचियों ने कैसे काम किया, लेकिन मैंने कभी नहीं सोचा या सोचा नहीं था कि List<>हुड के तहत कैसे काम किया जाता है।
बोबसन

1
एक सूची में एक तत्व को जोड़ना <T> परिशोधन O (1) है; जोड़ने वाली सूची का उपयोग करने के लिए तत्वों को जोड़ने की दक्षता सामान्य रूप से पर्याप्त औचित्य नहीं है (और एक परिपत्र सूची आपको आगे की ओर O (1) में आगे और पीछे जोड़ने की अनुमति देती है)। लिंक की गई सूचियों में बहुत अधिक प्रदर्शन idiosyncrasies है। उदाहरण के लिए, स्मृति में संचित रूप से संग्रहीत नहीं होने का मतलब है कि पूरी लिंक की गई सूची पर पुनरावृत्ति करना पृष्ठ दोष को ट्रिगर करने की अधिक संभावना है ... और यह बेंचमार्क के लिए कठिन है। लिंक की गई सूची का उपयोग करने का बड़ा औचित्य यह है कि जब आप दो सूचियों को समेटना चाहते हैं (O (1) में किया जा सकता है) या बीच में तत्व जोड़ें।
ब्रायन

1
मुझे स्पष्ट करना चाहिए। जब मैंने सर्कुलर लिस्ट कहा, तो मेरा मतलब एक सर्कुलर ऐरे लिस्ट था, सर्कुलर लिंक्ड लिस्ट नहीं। सही शब्द deque (डबल-एंडेड कतार) होगा। वे अक्सर एक अपवाद के रूप में एक सूची (हुड के तहत सरणी) के रूप में बहुत अधिक उसी तरह लागू होते हैं: एक आंतरिक पूर्णांक मान होता है, "पहला" जो इंगित करता है कि सरणी का कौन सा सूचकांक पहला तत्व है। एक तत्व को वापस जोड़ने के लिए, आप बस 1 को "पहले" से घटाते हैं (यदि आवश्यक हो तो सरणी की लंबाई के चारों ओर लपेटते हुए)। एक तत्व का उपयोग करने के लिए, आप बस पहुंचते हैं (index+first)%length
ब्रायन

2
कुछ चीजें हैं जो आप एक सूची के साथ नहीं कर सकते हैं जो आप एक सादे सरणी के साथ कर सकते हैं, उदाहरण के लिए, एक रेफरी पैरामीटर के रूप में एक सूचकांक तत्व को पारित करना।
इयान गोल्डबी

6

हालांकि KChaloux का उत्तर बहुत अच्छा है, मैं एक और विचार बताना चाहूंगा: List<T>एक ऐरे की तुलना में बहुत अधिक शक्तिशाली है। List<T>बहुत सारी परिस्थितियों में तरीके बहुत उपयोगी होते हैं - एक ऐरे के पास ये तरीके नहीं होते हैं और आपको वर्कअराउंड को लागू करने में बहुत समय लग सकता है।

इसलिए, एक विकास के नजरिए से, मैं लगभग हमेशा उपयोग करता हूं List<T>क्योंकि जब अतिरिक्त आवश्यकताएं होती हैं, तो जब आप एक का उपयोग कर रहे होते हैं, तो उन्हें लागू करना बहुत आसान होता है List<T>

यह एक अंतिम समस्या की ओर जाता है: मेरा कोड (मुझे आपके बारे में पता नहीं है) में 90% शामिल हैं List<T>, इसलिए Arrays वास्तव में फिटिंग नहीं है। जब मैं उन्हें पास करता हूं, तो मुझे उनकी .toList()विधि को कॉल करना होगा और उन्हें सूची में बदलना होगा - यह कष्टप्रद है और इतना धीमा है कि किसी ऐरे का उपयोग करने से कोई प्रदर्शन लाभ खो जाता है।


यह सच है, यह सूची <टी> का उपयोग करने का एक और अच्छा कारण है - यह आपके लिए कक्षा में सीधे निर्मित के लिए बहुत अधिक कार्यक्षमता प्रदान करता है।
KChaloux

1
LINQ किसी भी IEnumerable (एक सरणी सहित) के लिए उस कार्यक्षमता का एक बहुत जोड़कर क्षेत्र को समतल नहीं करता है? क्या आधुनिक C # (4+) में कुछ बचा है जो आप केवल एक सूची <T> के साथ कर सकते हैं और एक सरणी में नहीं?
डेव

1
@ क्या एरे / लिस्ट का विस्तार ऐसा लगता है। इसके अलावा, मैं कहना चाहूंगा कि एक सूची बनाने / संभालने के लिए वाक्यविन्यास सरणियों की तुलना में बहुत अच्छे हैं।
क्रिश्चियन सॉयर

2

किसी ने भी इस हिस्से का उल्लेख नहीं किया है: "और यहाँ मुझे पहले से ही एक आउट ऑफ़ मेमोरी एक्सेप्शन मिलता है।" जो पूरी तरह से है

for (int i = 0; i < ItemNumberList.Count; i++)
{
    ItemNumberList.Add(new item());
}

यह देखने के लिए सादा क्यों है। मैं नहीं जानता कि क्या आप एक अलग सूची में जोड़ना चाहते हैं, या बस ItemNumberList.Countअपना वांछित परिणाम प्राप्त करने के लिए लूप से पहले एक चर के रूप में संग्रहीत करना चाहिए , लेकिन यह बस टूट गया है।

Programmers.SE "... सॉफ्टवेयर विकास के बारे में वैचारिक प्रश्नों में रुचि रखता है ..." के लिए है, और अन्य उत्तरों ने इसे ऐसा माना। इसके बजाय http://codereview.stackexchange.com आज़माएँ , जहाँ यह प्रश्न फिट होगा। लेकिन यहां तक ​​कि यह भयावह है, क्योंकि हम केवल यह मान सकते हैं कि यह कोड शुरू होता है _Click, जिसमें multiValue1त्रुटि होने पर आपको कोई कॉल नहीं है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.