Python loop ที่เข้าถึงค่าก่อนหน้าและถัดไป


91

ฉันจะวนซ้ำรายการวัตถุเข้าถึงรายการก่อนหน้าปัจจุบันและถัดไปได้อย่างไร ชอบรหัส C / C ++ นี้ใน Python หรือไม่?


จะเกิดอะไรขึ้นถ้า foo อยู่ที่จุดเริ่มต้นหรือจุดสิ้นสุดของรายการ? ปัจจุบันสิ่งนี้จะออกไปนอกขอบเขตของอาร์เรย์ของคุณ
Brian

2
หากคุณต้องการให้เกิด "foo" เป็นครั้งแรกให้ทำการ "แบ่ง" จากบล็อก "for" เมื่อจับคู่
รถตู้

คุณต้องการเริ่มต้นการทำซ้ำที่องค์ประกอบที่ 1 (ไม่ใช่ 0'th) และสิ้นสุดการทำซ้ำที่องค์ประกอบสุดท้าย แต่เป็นองค์ประกอบเดียวหรือไม่
smci

รับประกันว่าfooเกิดขึ้นครั้งเดียวในรายการหรือไม่? หากเกิดขึ้นทวีคูณวิธีการบางอย่างที่นี่จะล้มเหลวหรือพบเพียงวิธีแรกเท่านั้น และหากไม่เคยเกิดขึ้นแนวทางอื่น ๆ ก็จะล้มเหลวหรือมีข้อยกเว้นเช่น ValueError การให้พยานหลักฐานบางอย่างอาจช่วยได้
smci

นอกจากนี้ตัวอย่างของคุณในที่นี้คือลำดับของวัตถุซึ่งทั้งสองมีความยาวที่ทราบและสามารถจัดทำดัชนีได้ คำตอบบางส่วนที่นี่เป็นการสรุปให้กับตัวทำซ้ำซึ่งไม่สามารถจัดทำดัชนีได้เสมอไปไม่มีความยาวเสมอไปและไม่ได้มีข้อ จำกัด เสมอไป ..
smci

คำตอบ:


110

นี้ควรทำเคล็ดลับ

foo = somevalue
previous = next_ = None
l = len(objects)
for index, obj in enumerate(objects):
    if obj == foo:
        if index > 0:
            previous = objects[index - 1]
        if index < (l - 1):
            next_ = objects[index + 1]

นี่คือเอกสารเกี่ยวกับenumerateฟังก์ชัน


18
แต่แนวทางปฏิบัติที่ดีที่สุดคืออย่าใช้ 'next' เป็นชื่อตัวแปรเนื่องจากเป็นฟังก์ชันในตัว
mkosmala

1
เวอร์ชันที่แก้ไขนี้ยังไม่ฟังดูมีเหตุผล: เมื่อสิ้นสุดการวนซ้ำobjและnext_จะเป็นวัตถุเดียวกันสำหรับการวนซ้ำครั้งสุดท้ายซึ่งอาจมีผลข้างเคียงโดยไม่ได้ตั้งใจ
TemporalWolf

ประโยคคำถามบอกอย่างชัดเจนว่า OP ต้องการเริ่มการทำซ้ำที่องค์ประกอบ 1'st (ไม่ใช่ 0'th) และสิ้นสุดการทำซ้ำที่องค์ประกอบสุดท้าย แต่เป็นหนึ่งเดียว ดังนั้นindexควรเรียกใช้1 ... (l-1)ไม่ใช่0 ... lตามที่คุณมีที่นี่และไม่จำเป็นต้องใช้ if-clauses พิเศษ Btw มีพารามิเตอร์enumerate(..., start=1)แต่ไม่ใช่สำหรับend. enumerate()ดังนั้นเราจึงไม่ได้จริงๆต้องการที่จะใช้
smci

147

วิธีแก้ปัญหาจนถึงขณะนี้จัดการกับรายการเท่านั้นและส่วนใหญ่กำลังคัดลอกรายการ จากประสบการณ์ของฉันหลายครั้งที่ไม่สามารถทำได้

นอกจากนี้พวกเขาไม่ได้จัดการกับความจริงที่ว่าคุณสามารถมีองค์ประกอบซ้ำ ๆ ในรายการได้

ชื่อคำถามของคุณระบุว่า "ค่าก่อนหน้าและค่าถัดไปภายในลูป " แต่ถ้าคุณเรียกใช้คำตอบส่วนใหญ่ที่นี่ภายในลูปคุณจะต้องวนซ้ำรายการทั้งหมดอีกครั้งในแต่ละองค์ประกอบเพื่อค้นหา

ฉันเพิ่งสร้างฟังก์ชันที่ ใช้itertoolsโมดูลแยกและหั่นชิ้นส่วนที่ทำซ้ำได้และสร้างสิ่งที่รวมกับองค์ประกอบก่อนหน้าและถัดไปเข้าด้วยกัน ไม่ใช่สิ่งที่รหัสของคุณทำ แต่ก็คุ้มค่าที่จะลองดูเพราะอาจช่วยแก้ปัญหาของคุณได้

from itertools import tee, islice, chain, izip

def previous_and_next(some_iterable):
    prevs, items, nexts = tee(some_iterable, 3)
    prevs = chain([None], prevs)
    nexts = chain(islice(nexts, 1, None), [None])
    return izip(prevs, items, nexts)

จากนั้นใช้แบบวนซ้ำและคุณจะมีรายการก่อนหน้าและถัดไป:

mylist = ['banana', 'orange', 'apple', 'kiwi', 'tomato']

for previous, item, nxt in previous_and_next(mylist):
    print "Item is now", item, "next is", nxt, "previous is", previous

ผลลัพธ์:

Item is now banana next is orange previous is None
Item is now orange next is apple previous is banana
Item is now apple next is kiwi previous is orange
Item is now kiwi next is tomato previous is apple
Item is now tomato next is None previous is kiwi

มันจะทำงานร่วมกับรายการขนาดใดก็ได้ (เนื่องจากไม่ได้คัดลอกรายการ) และสามารถทำซ้ำได้ (ไฟล์ชุด ฯลฯ ) ด้วยวิธีนี้คุณสามารถวนซ้ำตามลำดับและมีรายการก่อนหน้าและถัดไปที่พร้อมใช้งานภายในลูป ไม่จำเป็นต้องค้นหาอีกครั้งสำหรับรายการในลำดับ

คำอธิบายสั้น ๆ เกี่ยวกับรหัส:

  • tee ใช้เพื่อสร้างตัววนซ้ำอิสระ 3 ตัวบนลำดับอินพุตได้อย่างมีประสิทธิภาพ
  • chainเชื่อมโยงสองลำดับเข้าด้วยกัน ใช้ที่นี่เพื่อต่อท้ายลำดับองค์ประกอบเดียว[None]กับprevs
  • isliceใช้เพื่อสร้างลำดับขององค์ประกอบทั้งหมดยกเว้นองค์ประกอบแรกจากนั้นchainใช้เพื่อต่อท้าย a Noneต่อท้าย
  • ตอนนี้มี 3 ลำดับอิสระตามsome_iterableลักษณะดังกล่าว:
    • prevs: None, A, B, C, D, E
    • items: A, B, C, D, E
    • nexts: B, C, D, E, None
  • ในที่สุดก็izipใช้เพื่อเปลี่ยน 3 ลำดับให้เป็นลำดับแฝดสาม

โปรดทราบว่าizipจะหยุดเมื่อลำดับป้อนข้อมูลใด ๆ รับหมดดังนั้นองค์ประกอบสุดท้ายของprevsจะถูกละเว้นซึ่งเป็นที่ถูกต้อง - prevไม่มีองค์ประกอบดังกล่าวว่าองค์ประกอบสุดท้ายจะเป็นของมัน เราสามารถพยายามดึงองค์ประกอบสุดท้ายออกจากพฤติกรรมprevsแต่izipทำให้ซ้ำซ้อน

นอกจากนี้ทราบว่าtee, izip, isliceและchainมาจากitertoolsโมดูล พวกเขาทำงานตามลำดับการป้อนข้อมูลแบบทันที (อย่างเฉื่อยชา) ซึ่งทำให้มีประสิทธิภาพและไม่แนะนำความจำเป็นในการมีลำดับทั้งหมดในหน่วยความจำในคราวเดียว

ในpython 3นั้นจะแสดงข้อผิดพลาดขณะนำเข้าizipคุณสามารถใช้zipแทนizip. ไม่จำเป็นต้องนำเข้าzipมันถูกกำหนดไว้ล่วงหน้าในpython 3- แหล่งที่มา


3
@becomingGuru: ไม่จำเป็นต้องเปลี่ยน SO ให้เป็นมิเรอร์ของเอกสารอ้างอิง Python ฟังก์ชั่นทั้งหมดเหล่านี้ได้รับการอธิบายอย่างดีมาก (พร้อมตัวอย่าง) ในเอกสารอย่างเป็นทางการ
Eli Bendersky

1
@becomingGuru: เพิ่มลิงค์ไปยังเอกสาร
nosklo

6
@LakshmanPrasad ฉันอยู่ในอารมณ์ wiki ดังนั้นฉันจึงได้เพิ่มคำอธิบาย :-)
คอส

7
อาจเป็นเรื่องที่ควรค่าแก่การกล่าวถึงว่าใน Python 3 izipสามารถแทนที่ด้วยzipฟังก์ชันในตัวได้;-)
Tomasito665

1
นี่เป็นวิธีแก้ปัญหาที่ดี แต่ดูเหมือนซับซ้อนเกินไป ดูstackoverflow.com/a/54995234/1265955ซึ่งได้รับแรงบันดาลใจจากสิ่งนี้
วิคตอเรีย

6

ใช้การทำความเข้าใจรายการส่งคืน 3 ทูเพิลด้วยองค์ประกอบปัจจุบันก่อนหน้าและถัดไป:

three_tuple = [(current, 
                my_list[idx - 1] if idx >= 1 else None, 
                my_list[idx + 1] if idx < len(my_list) - 1 else None) for idx, current in enumerate(my_list)]

4

ฉันไม่รู้ว่าสิ่งนี้ยังไม่เกิดขึ้นได้อย่างไรเนื่องจากมันใช้ฟังก์ชันในตัวเท่านั้นและสามารถขยายไปยังการชดเชยอื่น ๆ ได้อย่างง่ายดาย:

values = [1, 2, 3, 4]
offsets = [None] + values[:-1], values, values[1:] + [None]
for value in list(zip(*offsets)):
    print(value) # (previous, current, next)

(None, 1, 2)
(1, 2, 3)
(2, 3, 4)
(3, 4, None)

4

นี่คือเวอร์ชันที่ใช้เครื่องกำเนิดไฟฟ้าโดยไม่มีข้อผิดพลาดขอบเขต:

def trios(iterable):
    it = iter(iterable)
    try:
        prev, current = next(it), next(it)
    except StopIteration:
        return
    for next in it:
        yield prev, current, next
        prev, current = current, next

def find_prev_next(objects, foo):
    prev, next = 0, 0
    for temp_prev, current, temp_next in trios(objects):
        if current == foo:
            prev, next = temp_prev, temp_next
    return prev, next

print(find_prev_next(range(10), 1))
print(find_prev_next(range(10), 0))
print(find_prev_next(range(10), 10))
print(find_prev_next(range(0), 10))
print(find_prev_next(range(1), 10))
print(find_prev_next(range(2), 10))

โปรดสังเกตว่าลักษณะการทำงานของขอบเขตคือเราไม่เคยมองหา "foo" ในองค์ประกอบแรกหรือองค์ประกอบสุดท้ายซึ่งแตกต่างจากรหัสของคุณ อีกครั้งความหมายขอบเขตนั้นแปลก ... และยากที่จะเข้าใจจากรหัสของคุณ :)


2

ใช้นิพจน์เงื่อนไขเพื่อความกระชับสำหรับ python> = 2.5

def prenext(l,v) : 
   i=l.index(v)
   return l[i-1] if i>0 else None,l[i+1] if i<len(l)-1 else None


# example
x=range(10)
prenext(x,3)
>>> (2,4)
prenext(x,0)
>>> (None,2)
prenext(x,9)
>>> (8,None)

2

สำหรับใครก็ตามที่กำลังมองหาวิธีแก้ปัญหานี้โดยที่ต้องการหมุนเวียนองค์ประกอบด้านล่างอาจใช้ได้ -

from collections import deque  

foo = ['A', 'B', 'C', 'D']

def prev_and_next(input_list):
    CURRENT = input_list
    PREV = deque(input_list)
    PREV.rotate(-1)
    PREV = list(PREV)
    NEXT = deque(input_list)
    NEXT.rotate(1)
    NEXT = list(NEXT)
    return zip(PREV, CURRENT, NEXT)

for previous_, current_, next_ in prev_and_next(foo):
    print(previous_, current_, next)

ขีดล่างใน next_ สุดท้าย? แก้ไขไม่ได้ - "ต้องมีอย่างน้อย 6 ... "
Xpector

ทำไมนี้ในทางใด ๆ ที่เป็นที่นิยมในการที่ง่ายสำหรับวงและเข้าถึงobjects[i-1], objects[i], objects[i+1]? หรือเครื่องกำเนิดไฟฟ้า? ดูเหมือนว่าเป็นสิ่งที่น่ารังเกียจสำหรับฉันโดยสิ้นเชิง นอกจากนี้ยังใช้หน่วยความจำ 3x โดยไม่จำเป็นเนื่องจาก PREV และ NEXT ทำสำเนาข้อมูล
smci

@smci คุณได้รับi+1แนวทางในการทำงานกับองค์ประกอบสุดท้ายในรายการอย่างไร? องค์ประกอบถัดไปควรเป็นองค์ประกอบแรก ฉันออกนอกขอบเขต
ElectRocnic

1

การใช้เครื่องกำเนิดไฟฟ้ามันค่อนข้างง่าย:

signal = ['→Signal value←']
def pniter( iter, signal=signal ):
    iA = iB = signal
    for iC in iter:
        if iB is signal:
            iB = iC
            continue
        else:
            yield iA, iB, iC
        iA = iB
        iB = iC
    iC = signal
    yield iA, iB, iC

if __name__ == '__main__':
    print('test 1:')
    for a, b, c in pniter( range( 10 )):
        print( a, b, c )
    print('\ntest 2:')
    for a, b, c in pniter([ 20, 30, 40, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 3:')
    cam = { 1: 30, 2: 40, 10: 9, -5: 36 }
    for a, b, c in pniter( cam ):
        print( a, b, c )
    for a, b, c in pniter( cam ):
        print( a, a if a is signal else cam[ a ], b, b if b is signal else cam[ b ], c, c if c is signal else cam[ c ])
    print('\ntest 4:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ]):
        print( a, b, c )
    print('\ntest 5:')
    for a, b, c in pniter([ 20, 30, None, 50, 60, 70, 80 ], ['sig']):
        print( a, b, c )
    print('\ntest 6:')
    for a, b, c in pniter([ 20, ['→Signal value←'], None, '→Signal value←', 60, 70, 80 ], signal ):
        print( a, b, c )

โปรดทราบว่าการทดสอบที่มี None และค่าเดียวกับค่าสัญญาณยังคงใช้งานได้เนื่องจากการตรวจสอบค่าสัญญาณใช้ "is" และสัญญาณเป็นค่าที่ Python ไม่อยู่ภายใน สามารถใช้ค่าเครื่องหมายซิงเกิลตันเป็นสัญญาณได้ซึ่งอาจทำให้รหัสผู้ใช้ง่ายขึ้นในบางสถานการณ์


9
"มันค่อนข้างง่าย"
มิเกลสตีเวนส์

อย่าพูดว่า 'สัญญาณ' เมื่อคุณหมายถึง 'แมวมอง' นอกจากนี้อย่าใช้if iB is signalเพื่อเปรียบเทียบวัตถุเพื่อความเท่าเทียมกันเว้นแต่สัญญาณ = ไม่มีซึ่งในกรณีนี้เพิ่งเขียนโดยตรงNoneไปแล้ว อย่าใช้iterเป็นชื่ออาร์กิวเมนต์ตั้งแต่เงาที่ iter()builtin Ditto next. อย่างไรก็ตามแนวทางของเครื่องกำเนิดไฟฟ้าสามารถทำได้yield prev, curr, next_
smci

@smci บางทีพจนานุกรมของคุณอาจมีคำจำกัดความที่แตกต่างออกไปเกี่ยวกับสัญญาณและแมวมอง ฉันใช้ "is" โดยเฉพาะเนื่องจากฉันต้องการทดสอบรายการที่เฉพาะเจาะจงไม่ใช่สำหรับรายการอื่นที่มีค่าเท่ากัน "is" คือตัวดำเนินการที่ถูกต้องสำหรับการทดสอบนั้น ใช้ iter และเงาถัดไปเฉพาะสิ่งที่ไม่มีการอ้างอิงเป็นอย่างอื่นจึงไม่ใช่ปัญหา แต่ตกลงกันไม่ใช่แนวทางปฏิบัติที่ดีที่สุด คุณต้องแสดงรหัสเพิ่มเติมเพื่อระบุบริบทสำหรับการอ้างสิทธิ์ครั้งล่าสุดของคุณ
Victoria

@ Victoria: 'sentinel [value]' เป็นคำศัพท์ของซอฟต์แวร์ที่กำหนดไว้อย่างดี 'signal' ไม่ใช่ (ไม่ได้พูดถึงการประมวลผลสัญญาณหรือสัญญาณเคอร์เนล) สำหรับ [การเปรียบเทียบสิ่งต่าง ๆ ใน Python ด้วยisแทนที่จะเป็น==] มันเป็นข้อผิดพลาดที่รู้จักกันดีนี่คือสาเหตุหลายประการ: คุณสามารถหลีกเลี่ยงสิ่งนี้สำหรับสตริงได้เนื่องจากคุณใช้สตริงภายใน cPython แต่ถึงอย่างนั้นv1 = 'monkey'; v2 = 'mon'; v3 = 'keyก็v1 is (v2 + v3)ให้False. และถ้ารหัสของคุณเคยเปลี่ยนไปใช้วัตถุแทน ints / สตริงการใช้isจะหยุดทำงาน ดังนั้นโดยทั่วไปคุณควรใช้==เพื่อเปรียบเทียบความเท่าเทียมกัน
smci

@smci ปัญหาที่ยากที่สุดในซอฟต์แวร์คอมพิวเตอร์คือการสื่อสารระบบเครือข่ายไม่ทำงานเนื่องจากกลุ่มคนที่ใช้คำต่างกัน ตามที่กล่าวไปมาตรฐานดีมากทุกคนมี ฉันเข้าใจถึงความแตกต่างระหว่าง Python == และเป็นตัวดำเนินการและนั่นคือเหตุผลที่ฉันเลือกใช้คือ หากคุณมองผ่าน "คำศัพท์และกฎ" ที่เป็นอุปาทานของคุณคุณจะรู้ว่า == จะอนุญาตให้รายการใด ๆ ที่เปรียบเทียบเท่ากับยุติลำดับในขณะที่การใช้จะสิ้นสุดที่วัตถุเฉพาะที่ถูกใช้เป็นสัญญาณเท่านั้น (หรือแมวมองถ้าคุณต้องการ)
Victoria

1

สองวิธีง่ายๆ:

  1. หากต้องกำหนดตัวแปรสำหรับทั้งค่าก่อนหน้าและค่าถัดไป:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = alist[0]
curr = alist[1]

for nxt in alist[2:]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[1]:
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
  1. หากค่าทั้งหมดในรายการต้องข้ามผ่านตัวแปรค่าปัจจุบัน:
alist = ['Zero', 'One', 'Two', 'Three', 'Four', 'Five']

prev = None
curr = alist[0]

for nxt in alist[1:] + [None]:
    print(f'prev: {prev}, curr: {curr}, next: {nxt}')
    prev = curr
    curr = nxt

Output[2]:
prev: None, curr: Zero, next: One
prev: Zero, curr: One, next: Two
prev: One, curr: Two, next: Three
prev: Two, curr: Three, next: Four
prev: Three, curr: Four, next: Five
prev: Four, curr: Five, next: None

0

คุณสามารถใช้indexในรายการเพื่อค้นหาว่าอยู่ที่ไหนsomevalueแล้วรับก่อนหน้าและถัดไปตามต้องการ:


def find_prev_next(elem, elements):
    previous, next = None, None
    index = elements.index(elem)
    if index > 0:
        previous = elements[index -1]
    if index < (len(elements)-1):
        next = elements[index +1]
    return previous, next


foo = 'three'
list = ['one','two','three', 'four', 'five']

previous, next = find_prev_next(foo, list)

print previous # should print 'two'
print next # should print 'four'



0

AFAIK สิ่งนี้น่าจะเร็ว แต่ฉันไม่ได้ทดสอบ:

def iterate_prv_nxt(my_list):
    prv, cur, nxt = None, iter(my_list), iter(my_list)
    next(nxt, None)

    while True:
        try:
            if prv:
                yield next(prv), next(cur), next(nxt, None)
            else:
                yield None, next(cur), next(nxt, None)
                prv = iter(my_list)
        except StopIteration:
            break

ตัวอย่างการใช้งาน:

>>> my_list = ['a', 'b', 'c']
>>> for prv, cur, nxt in iterate_prv_nxt(my_list):
...    print prv, cur, nxt
... 
None a b
a b c
b c None

0

ฉันคิดว่ามันใช้งานได้และไม่ซับซ้อน

array= [1,5,6,6,3,2]
for i in range(0,len(array)):
    Current = array[i]
    Next = array[i+1]
    Prev = array[i-1]

ฉันไม่ค่อยรู้ว่า python รองรับดัชนีเชิงลบในอาร์เรย์ขอบคุณ!
ElectRocnic

1
มันจะล้มเหลวในตอนท้ายแม้ว่า :(
ElectRocnic

0

โซลูชันสไตล์ C / C ++ มาก:

    foo = 5
    objectsList = [3, 6, 5, 9, 10]
    prev = nex = 0
    
    currentIndex = 0
    indexHigher = len(objectsList)-1 #control the higher limit of list
    
    found = False
    prevFound = False
    nexFound = False
    
    #main logic:
    for currentValue in objectsList: #getting each value of list
        if currentValue == foo:
            found = True
            if currentIndex > 0: #check if target value is in the first position   
                prevFound = True
                prev = objectsList[currentIndex-1]
            if currentIndex < indexHigher: #check if target value is in the last position
                nexFound = True
                nex = objectsList[currentIndex+1]
            break #I am considering that target value only exist 1 time in the list
        currentIndex+=1
    
    if found:
        print("Value %s found" % foo)
        if prevFound:
            print("Previous Value: ", prev)
        else:
            print("Previous Value: Target value is in the first position of list.")
        if nexFound:
            print("Next Value: ", nex)
        else:
            print("Next Value: Target value is in the last position of list.")
    else:
        print("Target value does not exist in the list.")

-1

วิธี Pythonic และสง่างาม:

objects = [1, 2, 3, 4, 5]
value = 3
if value in objects:
   index = objects.index(value)
   previous_value = objects[index-1]
   next_value = objects[index+1] if index + 1 < len(objects) else None

1
มันจะล้มเหลวถ้าvalueอยู่ในตอนท้าย นอกจากนี้ผลตอบแทนองค์ประกอบสุดท้ายเป็นprevious_valueถ้าvalueเป็นครั้งแรกที่หนึ่ง
Trang Oul

ขึ้นอยู่กับความต้องการของคุณ ดัชนีเชิงลบจากprevious_value จะส่งกลับองค์ประกอบสุดท้ายจากรายการและnext_valueจะเพิ่มขึ้นIndexErrorและนั่นคือข้อผิดพลาด
ImportError

ฉันมองหาวิธีนี้มานานแล้ว.. ตอนนี้ฉันเข้าใจแล้ว .. ขอบคุณ @ImportError ตอนนี้ฉันสามารถขยายสคริปต์ของฉันได้อย่างดี ..
Azam

ข้อ จำกัด : valueอาจเกิดขึ้นได้มากกว่าหนึ่งครั้งobjectsแต่การใช้.index()จะพบเฉพาะครั้งแรกที่เกิดขึ้น (หรือ ValueError ถ้าไม่เกิดขึ้น)
smci
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.