আপনার কি সর্বদা এক্সরেঞ্জ () এর ওপরে ()?


460

কেন অথবা কেন নয়?


36
আমাদের অ পাইথন ছেলেদের জন্য কেউ 2 এর মধ্যে পার্থক্যটি সংক্ষেপে বর্ণনা করতে পারে? হতে পারে "এক্সরেঞ্জ () হরিিং রেঞ্জ () করে এমন কিছু করে তবে এক্স, ওয়াই এবং জেডকে সমর্থন করে"
আউটলা প্রোগ্রামার

87
পরিসর (এন) সমস্ত সংখ্যার 0..n-1 সমেত একটি তালিকা তৈরি করে। আপনি যদি পরিসীমা (1000000) করেন তবে এটি একটি সমস্যা, কারণ আপনার> 4 এমবি তালিকা শেষ হবে। এক্সরেঞ্জ একটি তালিকা বলে ভান করে এমন কোনও বস্তু ফিরিয়ে দিয়ে এর সাথে ডিল করে, তবে সূচকের জন্য যে সংখ্যাটি চেয়েছিল তা ঠিক কাজ করে এবং তা ফেরত দেয়।
ব্রায়ান


4
মূলত, যেখানে range(1000)একটি হয় list, xrange(1000)এমন একটি বস্তু যা একটির মতো কাজ করে generator(যদিও এটি অবশ্যই এক নয় )। এছাড়াও, xrangeদ্রুত হয়। আপনি করতে পারেন import timeit from timeitএবং তারপর একটি পদ্ধতি মাত্র যে করতে for i in xrange: passএবং জন্য অন্য range, তারপর না timeit(method1)এবং timeit(method2)আর দেখ আর দেখ, xrange প্রায় দ্বিগুণ হিসাবে দ্রুত মাঝে মাঝে (যে যখন আপনি একটি তালিকা প্রয়োজন হবে না)। (আমার জন্য, i in xrange(1000):passi in range(1000):pass13.31672596931457521.190124988555908
বনামের

আর একটি পারফরম্যান্স পরীক্ষাxrange(100) 20% এর চেয়ে দ্রুততর হিসাবে দেয় range(100)
এভেজেনি সার্জিভ

উত্তর:


443

পারফরম্যান্সের জন্য, বিশেষত যখন আপনি একটি বৃহত্তর পরিসরের মাধ্যমে পুনরাবৃত্তি করেন, xrange()সাধারণত ভাল। তবে, আপনি পছন্দ করতে পারেন এমন কয়েকটি ক্ষেত্রে এখনও রয়েছে range():

  • পাইথন 3-এ, range()যা xrange()করত তা করে এবং xrange()বিদ্যমান না। যদি আপনি কোডটি লিখতে চান যা পাইথন 2 এবং পাইথন 3 উভয়টিতে চলে তবে আপনি ব্যবহার করতে পারবেন না xrange()

  • range()আসলে কিছু ক্ষেত্রে দ্রুত হতে পারে - যেমন। যদি একই ক্রমটি একাধিকবার পুনরাবৃত্তি হয়। xrange()প্রতিবার পূর্ণসংখ্যার অবজেক্টটি পুনর্গঠন করতে range()হবে তবে এতে সত্যিকারের পূর্ণসংখ্যার বস্তু থাকবে। (তবে এটি সর্বদা স্মৃতির ক্ষেত্রে আরও খারাপ সম্পাদন করবে)

  • xrange()সত্যিকারের তালিকার প্রয়োজন এমন সমস্ত ক্ষেত্রে ব্যবহারযোগ্য নয়। উদাহরণস্বরূপ, এটি টুকরা, বা কোনও তালিকা পদ্ধতি সমর্থন করে না।

[সম্পাদনা] এখানে বেশ কয়েকটি পোস্ট উল্লেখ করা হয়েছে range()যে কীভাবে 2to3 সরঞ্জাম দ্বারা আপগ্রেড করা হবে। রেকর্ডের জন্য, এর কয়েকটি নমুনা ব্যবহারের range()এবং এর উপর সরঞ্জাম চালনার আউটপুটxrange()

RefactoringTool: Skipping implicit fixer: buffer
RefactoringTool: Skipping implicit fixer: idioms
RefactoringTool: Skipping implicit fixer: ws_comma
--- range_test.py (original)
+++ range_test.py (refactored)
@@ -1,7 +1,7 @@

 for x in range(20):
-    a=range(20)
+    a=list(range(20))
     b=list(range(20))
     c=[x for x in range(20)]
     d=(x for x in range(20))
-    e=xrange(20)
+    e=range(20)

যেমন আপনি দেখতে পাচ্ছেন, লুপ বা বোধগম্যতার জন্য যখন ব্যবহৃত হয় বা ইতিমধ্যে তালিকা () দিয়ে মোড়ানো থাকে, তখন পরিসর অপরিবর্তিত থাকে।


5
"পরিসীমা একটি পুনরুক্তি হয়ে উঠবে" বলতে কী বোঝ? এটি কি "জেনারেটর" হওয়া উচিত নয়?
মাইকেল মায়ার

4
নং জেনারেটর একটি নির্দিষ্ট ধরণের পুনরাবৃত্তিকে বোঝায় এবং নতুন rangeকোনওভাবেই কোনও পুনরুক্তিকারী নয়।
ব্যবহারকারী 2357112

আপনার দ্বিতীয় বুলেটটি আসলে কোনও অর্থবোধ করে না। আপনি বলছেন যে আপনি কোনও বস্তু একাধিকবার ব্যবহার করতে পারবেন না; অবশ্যই আপনি পারেন! xr = xrange(1,11)পরের লাইনে চেষ্টা করুন for i in xr: print " ".join(format(i*j,"3d") for j in xr)এবং ভয়েলা! আপনার দশটি পর্যন্ত টাইম-টেবিল রয়েছে। এটা তোলে যেমন ঠিক একই কাজ করে r = range(1,11)এবং for i in r: print " ".join(format(i*j,"3d") for j in r)... সবকিছু Python2 মধ্যে একটি বস্তুর হয়। আমি মনে করি আপনি যা বলতে চেয়েছেন তা হ'ল আপনি rangeবিরোধী হিসাবে সূচক-ভিত্তিক বোঝাপড়াটি (যদি এটি বোধ হয়) আরও ভাল করতে পারেন xrange। পরিসীমা খুব কমই খুব সহজ, আমি মনে করি
dylnmc

(যথেষ্ট রুম) যদিও, আমি কি করতে কি মনে করে যে rangeআপনি একটি ব্যবহার করতে চান তাহলে কুশলী হতে পারে listএকটি লুপ এবং তারপর কিছু অবস্থার বা যে তালিকা সংযোজন জিনিষের উপর ভিত্তি করে নির্দিষ্ট সূচকের পরিবর্তন করেন, তারপর rangeস্পষ্টভাবে ভাল। তবে, xrangeএটি কেবল দ্রুত এবং কম স্মৃতি ব্যবহার করে, তাই বেশিরভাগ লুপ অ্যাপ্লিকেশনগুলির পক্ষে এটি সেরা বলে মনে হয়। উদাহরণস্বরূপ রয়েছে - প্রশ্নকারীর প্রশ্নে ফিরে যাওয়া - খুব কমই কিন্তু অস্তিত্ব, rangeআরও ভাল কোথায় হবে। আমি যতটা ভাবছি তেমন কদাচিৎ নয়, তবে আমি অবশ্যই xrange95% সময় ব্যবহার করি ।
dylnmc

129

না, তাদের উভয়েরই ব্যবহার রয়েছে:

xrange()পুনরাবৃত্তি করার সময় ব্যবহার করুন , এটি স্মৃতি সংরক্ষণ করে। বলুন,

for x in xrange(1, one_zillion):

বরং:

for x in range(1, one_zillion):

অন্যদিকে, range()যদি আপনি প্রকৃতপক্ষে সংখ্যার একটি তালিকা চান তবে ব্যবহার করুন ।

multiples_of_seven = range(7,100,7)
print "Multiples of seven < 100: ", multiples_of_seven

42

আপনার যখন সত্যিকারের তালিকার প্রয়োজন তখনই আপনার পক্ষে পক্ষপাত range()করা উচিত xrange()। উদাহরণস্বরূপ, আপনি যখন ফিরিয়েছেন তালিকাটি সংশোধন করতে চান range()বা যখন আপনি এটি স্লাইস করতে চান। পুনরাবৃত্তির জন্য বা এমনকি কেবলমাত্র সাধারণ সূচকের জন্য, xrange()সূক্ষ্মভাবে কাজ করবে (এবং সাধারণত আরও কার্যকরভাবে)। একটি বিন্দু রয়েছে যেখানে খুব ছোট তালিকার range()তুলনায় কিছুটা দ্রুত xrange(), তবে আপনার হার্ডওয়্যার এবং অন্যান্য বিভিন্ন বিবরণের উপর নির্ভর করে বিরতি-সমাপ্তি দৈর্ঘ্য 1 বা 2 এর ফলেও হতে পারে; উদ্বিগ্ন হওয়ার মতো কিছু নয়। পছন্দ xrange()


30

আর একটি পার্থক্য হ'ল এক্সরেঞ্জ () সি ইনটসের চেয়ে বড় সংখ্যাগুলিকে সমর্থন করতে পারে না, সুতরাং আপনি যদি অজস্র সংখ্যার সাহায্যে বিল্ট ব্যবহার করে একটি পরিসর পেতে চান তবে আপনাকে ব্যাপ্তি () ব্যবহার করতে হবে।

Python 2.7.3 (default, Jul 13 2012, 22:29:01) 
[GCC 4.7.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> range(123456787676676767676676,123456787676676767676679)
[123456787676676767676676L, 123456787676676767676677L, 123456787676676767676678L]
>>> xrange(123456787676676767676676,123456787676676767676679)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OverflowError: Python int too large to convert to C long

পাইথন 3 এ সমস্যা নেই:

Python 3.2.3 (default, Jul 14 2012, 01:01:48) 
[GCC 4.7.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> range(123456787676676767676676,123456787676676767676679)
range(123456787676676767676676, 123456787676676767676679)

13

xrange()আরও কার্যকর কারণ বস্তুর তালিকা তৈরি করার পরিবর্তে এটি একবারে কেবল একটি বস্তু তৈরি করে। 100 টি পূর্ণসংখ্যা এবং তাদের সমস্ত ওভারহেডের পরিবর্তে এবং তাদের রাখার জন্য তালিকার পরিবর্তে আপনার কাছে একবারে একটি করে পূর্ণসংখ্যা থাকে। দ্রুত প্রজন্ম, আরও ভাল মেমরি ব্যবহার, আরও কার্যকর কোড।

আমার বিশেষত কোনও কিছুর জন্য একটি তালিকা প্রয়োজন না হলে আমি সর্বদা পক্ষে থাকি xrange()


8

পরিসর () একটি তালিকা ফেরত দেয়, এক্সরেঞ্জ () একটি এক্সরেঞ্জ অবজেক্ট প্রদান করে।

এক্সরেঞ্জ () কিছুটা দ্রুত এবং আরও কিছুটা মেমরি দক্ষ। তবে লাভ খুব একটা বড় নয়।

একটি তালিকার দ্বারা ব্যবহৃত অতিরিক্ত স্মৃতি অবশ্যই কেবল অপচয় করা নয়, তালিকার আরও কার্যকারিতা রয়েছে (স্লাইস, পুনরাবৃত্তি, সন্নিবেশ, ...)। ডকুমেন্টেশনে সঠিক পার্থক্য খুঁজে পাওয়া যাবে । কোনও অস্থির নিয়ম নেই, যা প্রয়োজন তা ব্যবহার করুন।

পাইথন 3.0.০ এখনও বিকাশে রয়েছে, তবে আইআইআরসি রেঞ্জ () খুব কমই ২. এক্স এর এক্সরেঞ্জ () এর সাথে দেখাবে এবং তালিকা তৈরি করতে ব্যবহৃত হবে (রেঞ্জ ())।


5

আমি কেবল এটিই বলতে চাই যে এটি কাটা এবং সূচক কার্যকারিতা সহ একটি এক্সরেঞ্জ অবজেক্ট পাওয়া সত্যিই কঠিন নয় difficult আমি এমন কিছু কোড লিখেছি যা বেশ সুন্দরভাবে ডাঙে কাজ করে এবং এটি যতটা গণনা করা যায় তার জন্য এক্সরঞ্জের মতোই দ্রুত।

from __future__ import division

def read_xrange(xrange_object):
    # returns the xrange object's start, stop, and step
    start = xrange_object[0]
    if len(xrange_object) > 1:
       step = xrange_object[1] - xrange_object[0]
    else:
        step = 1
    stop = xrange_object[-1] + step
    return start, stop, step

class Xrange(object):
    ''' creates an xrange-like object that supports slicing and indexing.
    ex: a = Xrange(20)
    a.index(10)
    will work

    Also a[:5]
    will return another Xrange object with the specified attributes

    Also allows for the conversion from an existing xrange object
    '''
    def __init__(self, *inputs):
        # allow inputs of xrange objects
        if len(inputs) == 1:
            test, = inputs
            if type(test) == xrange:
                self.xrange = test
                self.start, self.stop, self.step = read_xrange(test)
                return

        # or create one from start, stop, step
        self.start, self.step = 0, None
        if len(inputs) == 1:
            self.stop, = inputs
        elif len(inputs) == 2:
            self.start, self.stop = inputs
        elif len(inputs) == 3:
            self.start, self.stop, self.step = inputs
        else:
            raise ValueError(inputs)

        self.xrange = xrange(self.start, self.stop, self.step)

    def __iter__(self):
        return iter(self.xrange)

    def __getitem__(self, item):
        if type(item) is int:
            if item < 0:
                item += len(self)

            return self.xrange[item]

        if type(item) is slice:
            # get the indexes, and then convert to the number
            start, stop, step = item.start, item.stop, item.step
            start = start if start != None else 0 # convert start = None to start = 0
            if start < 0:
                start += start
            start = self[start]
            if start < 0: raise IndexError(item)
            step = (self.step if self.step != None else 1) * (step if step != None else 1)
            stop = stop if stop is not None else self.xrange[-1]
            if stop < 0:
                stop += stop

            stop = self[stop]
            stop = stop

            if stop > self.stop:
                raise IndexError
            if start < self.start:
                raise IndexError
            return Xrange(start, stop, step)

    def index(self, value):
        error = ValueError('object.index({0}): {0} not in object'.format(value))
        index = (value - self.start)/self.step
        if index % 1 != 0:
            raise error
        index = int(index)


        try:
            self.xrange[index]
        except (IndexError, TypeError):
            raise error
        return index

    def __len__(self):
        return len(self.xrange)

সত্যিই, আমি মনে করি পুরো ইস্যুটি একদম নির্বোধ এবং এক্সরঞ্জের এগুলি যাইহোক করা উচিত ...


হ্যাঁ রাজি; সম্পূর্ণ ভিন্ন প্রযুক্তি থেকে, অলস করতে লোডাসের কাজটি পরীক্ষা করে দেখুন: github.com/lodash/lodash/issues/274 । কাটা ইত্যাদি এখনও যতটা সম্ভব অলস হওয়া উচিত এবং যেখানে না, কেবল তখনই সংস্কার করা উচিত।
রব গ্রান্ট

4

বইটিতে একটি ভাল উদাহরণ দেওয়া হয়েছে: প্র্যাকটিকাল পাইথন বাই ম্যাগনাস লাই হেটল্যান্ড

>>> zip(range(5), xrange(100000000))
[(0, 0), (1, 1), (2, 2), (3, 3), (4, 4)]

পূর্ববর্তী উদাহরণে আমি এক্সরঞ্জের পরিবর্তে ব্যাপ্তি ব্যবহার করার পরামর্শ দেব না — যদিও প্রথম প্রথম পাঁচটি সংখ্যা প্রয়োজন, পরিসীমা সমস্ত সংখ্যা গণনা করে এবং এতে অনেক সময় নিতে পারে। এক্সরেঞ্জের সাথে, এটি কোনও সমস্যা নয় কারণ এটি কেবল প্রয়োজনীয় সংখ্যাগুলি গণনা করে।

হ্যাঁ আমি @ ব্রায়ানের উত্তরটি পড়েছি: পাইথন 3-তে, পরিসর () যাইহোক জেনারেটর এবং এক্সরেঞ্জ () এর অস্তিত্ব নেই।


3

এই কারণে সীমার সাথে যান:

1) এক্সরেঞ্জ নতুন পাইথন সংস্করণে চলে যাবে। এটি আপনাকে ভবিষ্যতের সহজ সামঞ্জস্য দেয়।

2) ব্যাপ্তি এক্সরেঞ্জের সাথে সম্পর্কিত দক্ষতাগুলি গ্রহণ করবে।


13
এটি করবেন না। এক্সরেঞ্জ () চলে যাবে, তবে অন্য অনেক জিনিসও চলে যাবে। আপনি যে পাইথন ২.x কোডটি পাইথন ৩.x কোডটিতে অনুবাদ করতে ব্যবহার করবেন তা স্বয়ংক্রিয়ভাবে xrange () কে ব্যাপ্তিতে () অনুবাদ করবে তবে পরিসীমা () কম দক্ষ তালিকায় (পরিসীমা ()) অনুবাদ করা হবে।
থমাস ওয়াউটারস

10
টমাস: এটি আসলে তার চেয়ে কিছুটা স্মার্ট। এটি পরিসীমা () এর এমন পরিস্থিতিতে অনুবাদ করবে যেখানে স্পষ্টভাবে এর কেবলমাত্র সরল পরিসীমা () এর জন্য প্রকৃত তালিকার (যেমন লুপের জন্য বা বোঝার জন্য) প্রয়োজন নেই। কেবলমাত্র এটির ক্ষেত্রে যেখানে এটি পরিবর্তনশীল হিসাবে নির্ধারিত হয় বা সরাসরি ব্যবহার করা হয় সেগুলি অবশ্যই তালিকা () দিয়ে মুড়িয়ে রাখতে হবে
ব্রায়ান

2

ঠিক আছে, এখানে ট্রেড অফস এবং এক্সরেঞ্জ বনাম পরিসরের সুবিধা সম্পর্কে আলাদা মতামত হিসাবে সবাই everyone এগুলি বেশিরভাগই সঠিক, এক্সরেঞ্জ একটি পুনরাবৃত্তিকারী এবং ব্যাপ্তিটি বেরিয়ে আসে এবং একটি আসল তালিকা তৈরি করে। বেশিরভাগ ক্ষেত্রে, আপনি দুটির মধ্যে সত্যই কোনও পার্থক্য লক্ষ্য করবেন না। (আপনি এক্সরেঞ্জের সাথে নয় তবে পরিসরের সাথে মানচিত্র ব্যবহার করতে পারেন তবে এটি আরও মেমরি ব্যবহার করে))

আমার মনে হয় আপনি কী সমাবেশ করতে চান তা হ'ল পছন্দের পছন্দটি এক্সরেঞ্জ। পাইথন 3-তে ব্যাপ্তিটি পুনরাবৃত্তিকারী, তাই কোড রূপান্তর সরঞ্জাম 2to3 এক্সরঞ্জের সমস্ত ব্যবহারকে ব্যাপ্তিতে সঠিকভাবে রূপান্তরিত করবে এবং ব্যাপ্তির ব্যবহারের জন্য একটি ত্রুটি বা সতর্কতা এনে দেবে। আপনি যদি ভবিষ্যতে সহজেই আপনার কোডটি রূপান্তর করতে চান তা নিশ্চিত করতে চান, আপনি কেবলমাত্র এক্সরঞ্জ ব্যবহার করবেন এবং আপনি তালিকাটি চান তা নিশ্চিত হয়ে গেলে (xrange) তালিকাভুক্ত করবেন। আমি শিকাগোতে এই বছর পাইকনে সিপিথন স্প্রিন্ট চলাকালীন শিখেছি (২০০৮))


8
এটা সত্যি না. "এক্স ইন রেঞ্জ (20)" এর মতো কোডটি রেঞ্জ হিসাবে ছেড়ে যাবে, এবং "x = রেঞ্জ (20)" এর মতো কোডকে "x = তালিকা (রেঞ্জ (20))" তে রূপান্তর করা হবে - কোনও ত্রুটি নেই। আরও, আপনি যদি কোড লিখতে চান যা ২.6 এবং both.০ উভয়ের মধ্যে চলবে, তবে পরিসীমা () হ'ল সংক্ষিপ্ততার ফাংশন যুক্ত না করেই আপনার একমাত্র বিকল্প।
ব্রায়ান

2
  • range(): range(1, 10)1 থেকে 10 সংখ্যা থেকে একটি তালিকা ফেরত দেয় এবং মেমরিতে পুরো তালিকা ধরে রাখে।
  • xrange(): পছন্দ করুন range()তবে তালিকার পরিবর্তে পরিবর্তনের পরিবর্তে এমন একটি বস্তু প্রদান করে যা চাহিদা অনুযায়ী পরিসরে সংখ্যাগুলি উত্পন্ন করে। লুপিংয়ের জন্য, এটি তুলনায় হালকা দ্রুত range()এবং আরও মেমরি দক্ষ। xrange()পুনরুক্তির মতো বস্তু এবং চাহিদা অনুসারে সংখ্যা উত্পন্ন করে (অলস মূল্যায়ন)।
In [1]: range(1,10)
Out[1]: [1, 2, 3, 4, 5, 6, 7, 8, 9]

In [2]: xrange(10)
Out[2]: xrange(10)

In [3]: print xrange.__doc__
Out[3]: xrange([start,] stop[, step]) -> xrange object

range()xrange()পাইথন 3 তে ব্যবহৃত একই জিনিসটি ব্যবহার করে এবং পাইথন 3-তে কোনও শব্দ xrange()নেই range()some যদি আপনি একই ক্রমটি একাধিকবার পুনরাবৃত্তি করেন তবে কিছু দৃশ্যে আসলেই দ্রুত হতে পারে। xrange()প্রতিবার পূর্ণসংখ্যার অবজেক্টটি পুনর্গঠন করতে range()হবে তবে এতে সত্যিকারের পূর্ণসংখ্যার বস্তু থাকবে।


2

বেশিরভাগ পরিস্থিতিতে xrangeতুলনায় দ্রুত হলেও rangeপারফরম্যান্সের পার্থক্য বেশ ন্যূনতম। নীচের ছোট প্রোগ্রামটি একটি rangeএবং একটিতে পুনরাবৃত্তির তুলনা করে xrange:

import timeit
# Try various list sizes.
for list_len in [1, 10, 100, 1000, 10000, 100000, 1000000]:
  # Time doing a range and an xrange.
  rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n'%list_len, number=1000)
  xrtime = timeit.timeit('a=0;\nfor n in xrange(%d): a += n'%list_len, number=1000)
  # Print the result
  print "Loop list of len %d: range=%.4f, xrange=%.4f"%(list_len, rtime, xrtime)

নীচের ফলাফলগুলি দেখায় যে xrangeসত্যই দ্রুত, তবে ঘাম নেওয়ার পক্ষে যথেষ্ট নয়।

Loop list of len 1: range=0.0003, xrange=0.0003
Loop list of len 10: range=0.0013, xrange=0.0011
Loop list of len 100: range=0.0068, xrange=0.0034
Loop list of len 1000: range=0.0609, xrange=0.0438
Loop list of len 10000: range=0.5527, xrange=0.5266
Loop list of len 100000: range=10.1666, xrange=7.8481
Loop list of len 1000000: range=168.3425, xrange=155.8719

সুতরাং সব উপায়ে ব্যবহার করুন xrange, তবে আপনি যদি কোনও বাধা হার্ডওয়ারে না থাকেন তবে এটি সম্পর্কে খুব বেশি চিন্তা করবেন না।


আপনার list_lenব্যবহৃত হচ্ছে না এবং তাই আপনি কেবল 100 দৈর্ঘ্যের তালিকাগুলির জন্য এই কোডটি চালাচ্ছেন
মারক

আমি আসলে তালিকার দৈর্ঘ্য পরিবর্তন করার পরামর্শ দেব:rtime = timeit.timeit('a=0;\nfor n in range(%d): a += n' % list_len, number=1000)
চিহ্নিত করুন

বাহ, এটি একটি ভাল সপ্তাহ হতে চলেছে, ধন্যবাদ, স্থির। তবুও, কোনও বড় পার্থক্য নয়।
স্পিড প্লেন
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.