ตัวเลื่อนหน้าต่างตัวเลื่อนหรือเลื่อน?


151

ฉันต้องการหน้าต่างกลิ้ง (หรือที่รู้จักกันในหน้าต่างเลื่อน) ทำซ้ำได้มากกว่าตัวเรียงลำดับ / ตัววนซ้ำ / ตัวสร้าง การทำซ้ำ Python เริ่มต้นถือเป็นกรณีพิเศษโดยที่ความยาวของหน้าต่างคือ 1 ฉันกำลังใช้รหัสต่อไปนี้ ไม่มีใครมีวิธี Pythonic, verbose น้อยลงหรือมีประสิทธิภาพมากขึ้นสำหรับการทำเช่นนี้?

def rolling_window(seq, window_size):
    it = iter(seq)
    win = [it.next() for cnt in xrange(window_size)] # First window
    yield win
    for e in it: # Subsequent windows
        win[:-1] = win[1:]
        win[-1] = e
        yield win

if __name__=="__main__":
    for w in rolling_window(xrange(6), 3):
        print w

"""Example output:

   [0, 1, 2]
   [1, 2, 3]
   [2, 3, 4]
   [3, 4, 5]
"""

3
หากคุณต้องการดำเนินการบางอย่างกับแต่ละหน้าต่างในขณะที่คุณทำซ้ำ (เช่น sum()หรือmax()) ควรคำนึงว่ามีอัลกอริทึมที่มีประสิทธิภาพในการคำนวณค่าใหม่สำหรับแต่ละหน้าต่างในเวลาคงที่ (โดยไม่คำนึงถึงขนาดของหน้าต่าง) ฉันได้เก็บบางส่วนของขั้นตอนวิธีการเหล่านี้ร่วมกันในห้องสมุดหลาม: กลิ้ง
Alex Riley

คำตอบ:


123

มีหนึ่งในเอกสาร Python เวอร์ชันเก่าที่มีitertoolsตัวอย่าง :

from itertools import islice

def window(seq, n=2):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(islice(it, n))
    if len(result) == n:
        yield result
    for elem in it:
        result = result[1:] + (elem,)
        yield result

หนึ่งในเอกสารนั้นสั้นกว่าเล็กน้อยและใช้itertoolsเพื่อผลที่ดีกว่าที่ฉันจินตนาการ


2
คำตอบที่ดี แต่ (และฉันรู้ว่าคุณเพิ่งทำสูตรเป็นลิงค์) ฉันสงสัยว่าทำไมขนาดหน้าต่างเริ่มต้นควรเป็น 2? มันควรจะเป็นค่าเริ่มต้นเลยหรือไม่?
SingleNegationElimination

19
@TakenMacGuy: ฉันไม่รู้ว่าผู้เขียนเหตุผลของสูตรนั้นคืออะไร แต่ฉันก็เลือก 2 2 เป็นขนาดหน้าต่างที่เล็กที่สุดที่มีประโยชน์ (ไม่อย่างนั้นคุณแค่ทำซ้ำและไม่ต้องการหน้าต่าง) และมันก็เป็นเรื่องธรรมดา จำเป็นต้องรู้รายการก่อนหน้า (หรือถัดไป) เนื้อหามากกว่า n เฉพาะอื่น ๆ
ใจดี

27
ไม่มีใครรู้ว่าทำไมตัวอย่างนี้ถูกลบออกจากเอกสารหรือไม่ มีบางอย่างผิดปกติกับมันหรือมีทางเลือกอื่นง่ายขึ้นในตอนนี้?
Wim


2
เมื่อไหร่จะเข้าสู่for elem in itวง?
Glassjawed

47

ดูเหมือนว่าจะเหมาะสำหรับcollections.dequeคุณเนื่องจากมี FIFO เป็นหลัก (เพิ่มที่ปลายด้านหนึ่งออกจากอีกด้านหนึ่ง) อย่างไรก็ตามแม้ว่าคุณจะใช้listคุณไม่ควรหั่นสองครั้ง แต่คุณน่าจะpop(0)มาจากรายการและappend()ไอเท็มใหม่แทน

นี่คือการใช้งานแบบ deque ที่ได้รับการออกแบบให้เหมาะสมที่สุดหลังจากที่ต้นฉบับของคุณ

from collections import deque

def window(seq, n=2):
    it = iter(seq)
    win = deque((next(it, None) for _ in xrange(n)), maxlen=n)
    yield win
    append = win.append
    for e in it:
        append(e)
        yield win

ในการทดสอบของฉันมันเต้นได้อย่างคล่องแคล่วทุกสิ่งทุกอย่างที่โพสต์ที่นี่เกือบตลอดเวลาถึงแม้ว่ายาเม็ดคุมกำเนิดจะteeเต้นมันสำหรับ iterables ขนาดใหญ่และหน้าต่างเล็ก ๆ บนหน้าต่างที่ใหญ่ขึ้นการdequeดึงไปข้างหน้าอีกครั้งด้วยความเร็วแบบดิบ

การเข้าถึงแต่ละรายการในdequeอาจเร็วกว่าหรือช้ากว่ารายการหรือสิ่งอันดับ (รายการที่อยู่ใกล้จุดเริ่มต้นนั้นเร็วกว่าหรือรายการใกล้ถึงจุดสิ้นสุดหากคุณใช้ดัชนีติดลบ) ฉันใส่ a ลงsum(w)ในเนื้อความของลูปของฉัน สิ่งนี้เล่นกับความแข็งแกร่งของ deque (การวนซ้ำจากไอเท็มหนึ่งไปยังไอเท็มถัดไปนั้นเร็วมากดังนั้นลูปนี้จึงวิ่งได้เร็วขึ้น 20% เร็วกว่าวิธีที่เร็วที่สุดถัดไปคือยาคุมกำเนิด) เมื่อฉันเปลี่ยนเป็นค้นหาแบบเอกเทศและเพิ่มรายการในหน้าต่างสิบตารางเปิดและteeวิธีได้เร็วขึ้น 20% ฉันสามารถกู้คืนความเร็วได้โดยใช้ดัชนีเชิงลบสำหรับห้าคำล่าสุดในการเพิ่ม แต่teeก็ยังเร็วขึ้นเล็กน้อย โดยรวมแล้วฉันคาดว่าทั้งอันนั้นเร็วมากสำหรับการใช้งานส่วนใหญ่และหากคุณต้องการประสิทธิภาพที่มากกว่าเล็กน้อยโปรไฟล์และเลือกอันที่ดีที่สุด


11
yield winควรyield tuple(win)หรือyield list(win)เพื่อป้องกันการส่งคืนตัววนซ้ำของการอ้างอิงไปยังdequeวัตถุเดียวกัน
Joel Cornett

1
ฉันส่งนี้จะ PyPI ติดตั้งและทำงานกับpip install sliding_window from sliding_window import window
โทมัสเลวีน

1
คุณกำลังตกตะลึงถ้าคุณคิดว่าlist(window(range(10)))ควรจะสร้างบางอย่างเช่น [[0,1], [1,2], [2,3], ... ]
พอล

1
เห็นได้ชัดว่ามันจะไม่; คุณต้องทำสิ่งที่ชอบlist(list(x) for x in window(range(10)))หรือเพิ่มไว้ในตัววนซ้ำ สำหรับบางแอปพลิเคชั่นนี้จะสำคัญสำหรับผู้อื่นที่ไม่ได้และเนื่องจากฉันต้องการความเร็วฉันจึงเลือกที่จะไม่ติดตั้งและวาง onus บนผู้โทรเพื่อคัดลอกหน้าต่างถ้าจำเป็น
ใจดี

1
หากคุณบวกกลับที่จำเป็นtuple()ก่อนที่จะให้ผลผลิตวิธีนี้จะไม่ได้เปรียบกว่าคนอื่น ๆ
kawing-chiu

35

ฉันชอบtee():

from itertools import tee, izip

def window(iterable, size):
    iters = tee(iterable, size)
    for i in xrange(1, size):
        for each in iters[i:]:
            next(each, None)
    return izip(*iters)

for each in window(xrange(6), 3):
    print list(each)

ให้:

[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]

จากtimeitการทดสอบอย่างรวดเร็วของฉันมันช้ากว่า Daniel DePaolo มาก (โดยอัตราส่วน 2: 1) และไม่รู้สึก "ดีกว่า" มากนัก
เดวิดบี

@ David B: ในกล่องของฉันมันช้ากว่า Daniel DePaolo ประมาณ 8% เท่านั้น
Pillmuncher

@pillmuncher: Python 2.7 หรือ 3.x? ฉันใช้ 2.7 sizeอัตราส่วนยังค่อนข้างไวต่อค่าของ ถ้าคุณเพิ่มมัน (เช่นถ้า iterable มีความยาว 100,000 องค์ประกอบทำให้ขนาดหน้าต่าง 1,000) คุณอาจเห็นการเพิ่มขึ้น
David B.

2
@ David B .: สิ่งที่คุณพูดมีเหตุผล ในรหัสของฉันเวลาติดตั้งสำหรับitersคือ O (ขนาด!) และการโทรnext()หลายครั้ง (ในizip()) อาจใช้เวลานานกว่าการคัดลอก tuple สองครั้ง ฉันใช้ Python 2.6.5, BTW
pillmuncher

@pillmuncher: คุณหมายถึงเวลาการตั้งค่าสำหรับitersคือ O (ขนาด ^ 2) ใช่ไหม?
David B.

20

นี่คือลักษณะทั่วไปที่เพิ่มการสนับสนุนสำหรับstep, fillvalueพารามิเตอร์:

from collections import deque
from itertools import islice

def sliding_window(iterable, size=2, step=1, fillvalue=None):
    if size < 0 or step < 1:
        raise ValueError
    it = iter(iterable)
    q = deque(islice(it, size), maxlen=size)
    if not q:
        return  # empty iterable or size == 0
    q.extend(fillvalue for _ in range(size - len(q)))  # pad to size
    while True:
        yield iter(q)  # iter() to avoid accidental outside modifications
        try:
            q.append(next(it))
        except StopIteration: # Python 3.5 pep 479 support
            return
        q.extend(next(it, fillvalue) for _ in range(step - 1))

มันให้ผลผลิตเป็นชิ้น ๆsizeที่stepตำแหน่งการหมุนครั้งต่อการทำซ้ำแต่ละชิ้นด้วยfillvalueถ้าจำเป็น ตัวอย่างสำหรับsize=4, step=3, fillvalue='*':

 [a b c d]e f g h i j k l m n o p q r s t u v w x y z
  a b c[d e f g]h i j k l m n o p q r s t u v w x y z
  a b c d e f[g h i j]k l m n o p q r s t u v w x y z
  a b c d e f g h i[j k l m]n o p q r s t u v w x y z
  a b c d e f g h i j k l[m n o p]q r s t u v w x y z
  a b c d e f g h i j k l m n o[p q r s]t u v w x y z
  a b c d e f g h i j k l m n o p q r[s t u v]w x y z
  a b c d e f g h i j k l m n o p q r s t u[v w x y]z
  a b c d e f g h i j k l m n o p q r s t u v w x[y z * *]

สำหรับตัวอย่างของกรณีการใช้งานสำหรับที่stepพารามิเตอร์ให้ดูที่การประมวลผลไฟล์ .txt ขนาดใหญ่ในหลามได้อย่างมีประสิทธิภาพ


17

มีห้องสมุดที่ทำสิ่งที่คุณต้องการ:

import more_itertools
list(more_itertools.windowed([1,2,3,4,5,6,7,8,9,10,11,12,13,14,15],n=3, step=3))

Out: [(1, 2, 3), (4, 5, 6), (7, 8, 9), (10, 11, 12), (13, 14, 15)]

step=3ควรลบออกจริงเพื่อให้ตรงกับคำขอของ OP:list(more_itertools.windowed(range(6), 3))
3780389

10

เพียงมีส่วนร่วมอย่างรวดเร็ว

เนื่องจากเอกสารหลามในปัจจุบันไม่มี "หน้าต่าง" ในตัวอย่างของ itertool (เช่นที่ด้านล่างของhttp://docs.python.org/library/itertools.html ที่ด้านล่าง) นี่เป็นตัวอย่างโค้ดตามรหัสสำหรับปลาเก๋า เป็นหนึ่งในตัวอย่างที่ได้รับ:

import itertools as it
def window(iterable, size):
    shiftedStarts = [it.islice(iterable, s, None) for s in xrange(size)]
    return it.izip(*shiftedStarts)

โดยทั่วไปเราสร้างชุดของตัววนซ้ำแบบแบ่งส่วนโดยแต่ละจุดเริ่มต้นหนึ่งจุดจะเลื่อนไปข้างหน้า จากนั้นเราซิปเหล่านี้เข้าด้วยกัน หมายเหตุฟังก์ชั่นนี้จะส่งคืนเครื่องกำเนิดไฟฟ้า (ไม่ใช่เครื่องกำเนิดไฟฟ้าโดยตรง)

เหมือนกับ appending-element และ advancing-iterator เวอร์ชั่นด้านบนประสิทธิภาพ (เช่นที่ดีที่สุด) จะแตกต่างกันไปตามขนาดของรายการและขนาดของหน้าต่าง ฉันชอบอันนี้เพราะมันเป็นสองซับ (อาจเป็นหนึ่งซับ แต่ฉันชอบแนวคิดการตั้งชื่อ)

แต่กลับกลายเป็นว่ารหัสดังกล่าวเป็นความผิด มันทำงานได้ถ้าพารามิเตอร์ที่ส่งไปยังiterableเป็นลำดับ แต่ไม่ว่ามันจะเป็นตัววนซ้ำ หากเป็นตัววนซ้ำตัววนซ้ำเดียวกันจะถูกแชร์ (แต่ไม่ใช่ทีที) ระหว่างการโทรของ islice และสิ่งนี้จะทำลายสิ่งที่ไม่ดี

นี่คือรหัสคงที่บางส่วน:

import itertools as it
def window(iterable, size):
    itrs = it.tee(iterable, size)
    shiftedStarts = [it.islice(anItr, s, None) for s, anItr in enumerate(itrs)]
    return it.izip(*shiftedStarts)

นอกจากนี้ยังมีอีกหนึ่งเวอร์ชันสำหรับหนังสือ แทนที่จะคัดลอกตัววนซ้ำแล้วเลื่อนสำเนาหลาย ๆ ครั้งรุ่นนี้ทำสำเนาตามตัวอักษรของตัววนซ้ำแต่ละตัวในขณะที่เราเลื่อนตำแหน่งเริ่มต้นไปข้างหน้า ดังนั้น iterator t ให้ทั้งตัว "สมบูรณ์" กับตัวเริ่มต้นที่จุด t และพื้นฐานสำหรับการสร้างตัววนซ้ำ t + 1:

import itertools as it
def window4(iterable, size):
    complete_itr, incomplete_itr = it.tee(iterable, 2)
    iters = [complete_itr]
    for i in xrange(1, size):
        incomplete_itr.next()
        complete_itr, incomplete_itr = it.tee(incomplete_itr, 2)
        iters.append(complete_itr)
    return it.izip(*iters)

9

เพียงเพื่อแสดงว่าคุณสามารถรวมitertoolsสูตรอาหารได้อย่างไรฉันกำลังขยายpairwiseสูตรกลับไปสู่windowสูตรโดยตรงโดยใช้consumeสูตร:

def consume(iterator, n):
    "Advance the iterator n-steps ahead. If n is none, consume entirely."
    # Use functions that consume iterators at C speed.
    if n is None:
        # feed the entire iterator into a zero-length deque
        collections.deque(iterator, maxlen=0)
    else:
        # advance to the empty slice starting at position n
        next(islice(iterator, n, n), None)

def window(iterable, n=2):
    "s -> (s0, ...,s(n-1)), (s1, ...,sn), (s2, ..., s(n+1)), ..."
    iters = tee(iterable, n)
    # Could use enumerate(islice(iters, 1, None), 1) to avoid consume(it, 0), but that's
    # slower for larger window sizes, while saving only small fixed "noop" cost
    for i, it in enumerate(iters):
        consume(it, i)
    return zip(*iters)

windowสูตรเช่นเดียวกับpairwiseก็แค่แทนที่องค์ประกอบเดียว "กิน" ที่สองtee iterator -ed ที่มีเพิ่มขึ้นเรื่อยกินบนn - 1iterators การใช้consumeแทนที่จะห่อแต่ละตัววนซ้ำในisliceนั้นเร็วกว่าเล็กน้อย (สำหรับการวนซ้ำขนาดใหญ่พอ) เนื่องจากคุณจ่ายisliceค่าโสหุ้ยการตัดในconsumeช่วงเท่านั้นไม่ใช่ในระหว่างกระบวนการแยกแต่ละค่าหน้าต่าง -ed (ดังนั้นจะถูก จำกัด ด้วยnจำนวนรายการในiterable)

ประสิทธิภาพที่ชาญฉลาดเมื่อเทียบกับโซลูชันอื่น ๆ นี่ค่อนข้างดี (และดีกว่าโซลูชันอื่น ๆ ที่ฉันทดสอบเมื่อปรับขนาด) ทดสอบกับ Python 3.5.0, Linux x86-64 โดยใช้ipython %timeitเวทย์มนตร์

kindall เป็นdequeทางออก , เอ็นดูสำหรับประสิทธิภาพ / ความถูกต้องโดยใช้isliceแทนของบ้านรีดแสดงออกกำเนิดและการทดสอบความยาวที่เกิดขึ้นจึงไม่ได้ผลผลเมื่อ iterable สั้นกว่าหน้าต่างเช่นเดียวกับการส่งผ่านmaxlenของdequepositionally แทน โดยคำหลัก (สร้างความแตกต่างที่น่าประหลาดใจสำหรับอินพุตที่เล็กกว่า):

>>> %timeit -r5 deque(windowkindall(range(10), 3), 0)
100000 loops, best of 5: 1.87 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 3), 0)
10000 loops, best of 5: 72.6 μs per loop
>>> %timeit -r5 deque(windowkindall(range(1000), 30), 0)
1000 loops, best of 5: 71.6 μs per loop

เหมือนกับโซลูชัน kindall ดัดแปลงก่อนหน้านี้ แต่การyield winเปลี่ยนแปลงแต่ละครั้งyield tuple(win)เพื่อเก็บผลลัพธ์จากเครื่องกำเนิดไฟฟ้าโดยไม่มีผลลัพธ์ที่เก็บไว้ทั้งหมดเป็นมุมมองของผลลัพธ์ล่าสุด (โซลูชันที่สมเหตุสมผลอื่น ๆ ทั้งหมดปลอดภัยในสถานการณ์นี้) และเพิ่มtuple=tupleคำจำกัดความของฟังก์ชัน เพื่อย้ายการใช้tupleจากBในLEGBเป็นL:

>>> %timeit -r5 deque(windowkindalltupled(range(10), 3), 0)
100000 loops, best of 5: 3.05 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 3), 0)
10000 loops, best of 5: 207 μs per loop
>>> %timeit -r5 deque(windowkindalltupled(range(1000), 30), 0)
1000 loops, best of 5: 348 μs per loop

consumeโซลูชั่นพื้นฐานที่แสดงด้านบน:

>>> %timeit -r5 deque(windowconsume(range(10), 3), 0)
100000 loops, best of 5: 3.92 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 3), 0)
10000 loops, best of 5: 42.8 μs per loop
>>> %timeit -r5 deque(windowconsume(range(1000), 30), 0)
1000 loops, best of 5: 232 μs per loop

เหมือนกันconsumeแต่เป็นelseกรณีของconsumeเพื่อหลีกเลี่ยงการเรียกใช้ฟังก์ชันและn is Noneการทดสอบเพื่อลด runtime, โดยเฉพาะอย่างยิ่งสำหรับอินพุตขนาดเล็กที่ค่าใช้จ่ายการตั้งค่าเป็นส่วนที่มีความหมายของงาน:

>>> %timeit -r5 deque(windowinlineconsume(range(10), 3), 0)
100000 loops, best of 5: 3.57 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 3), 0)
10000 loops, best of 5: 40.9 μs per loop
>>> %timeit -r5 deque(windowinlineconsume(range(1000), 30), 0)
1000 loops, best of 5: 211 μs per loop

(ด้านข้างหมายเหตุ: ความแตกต่างในpairwiseการใช้งานที่teeมีอาร์กิวเมนต์เริ่มต้นของ 2 ซ้ำ ๆ เพื่อให้ซ้อนกันteeวัตถุดังนั้นใดก็ตาม iterator เป็นขั้นสูงเพียงครั้งเดียวไม่ได้บริโภคอิสระจำนวนที่เพิ่มขึ้นครั้งคล้ายกับคำตอบ MrDrFenner ของคล้ายกับที่ไม่ใช่ inlined consumeและช้ากว่า inlined consumeในการทดสอบทั้งหมดดังนั้นฉันจึงไม่เห็นผลลัพธ์เหล่านั้นเพื่อความกระชับ)

อย่างที่เห็น, ถ้าคุณไม่สนใจเกี่ยวกับความเป็นไปได้ของผู้โทรที่ต้องการจัดเก็บผลลัพธ์โซลูชันของ kindall ที่ได้รับการปรับปรุงของฉันจะชนะเวลาส่วนใหญ่ยกเว้นในกรณี "ขนาดใหญ่ iterable ขนาดหน้าต่างเล็ก" (ที่อินไลน์consumeชนะ ); มันลดขนาดลงอย่างรวดเร็วเมื่อขนาดเพิ่มขึ้นในขณะที่ไม่ลดขนาดเลยเมื่อขนาดของหน้าต่างเพิ่มขึ้น (โซลูชันอื่น ๆ จะลดลงช้ากว่าสำหรับขนาดที่เพิ่มได้ แต่ยังลดขนาดของหน้าต่างที่เพิ่มขึ้นด้วย) มันยังสามารถปรับให้เข้ากับเคส "need tuples" ได้โดยการหุ้มmap(tuple, ...)ซึ่งจะช้ากว่าการวาง tupling ในฟังก์ชั่นเล็กน้อย แต่มันก็เล็กน้อย (ใช้เวลานานกว่า 1-5%) และช่วยให้คุณทำงานได้เร็วขึ้น เมื่อคุณสามารถทนการส่งคืนค่าเดียวกันซ้ำ ๆ

หากคุณต้องการความปลอดภัยจากผลตอบแทนที่ถูกเก็บไว้ inlined consumeชนะกับทุกคนยกเว้นขนาดอินพุตที่เล็กที่สุด (โดยไม่อินไลน์consumeช้ากว่าเล็กน้อย แต่มีขนาดใกล้เคียงกัน) deque& tupling ตามชนะโซลูชั่นสำหรับปัจจัยการผลิตที่มีขนาดเล็กที่สุดเนื่องจากค่าใช้จ่ายในการติดตั้งที่มีขนาดเล็กและกำไรที่มีขนาดเล็ก; มันลดระดับลงอย่างรุนแรงเมื่อ iterable ยาวขึ้น

สำหรับบันทึกการปรับรุ่นของการแก้ปัญหาที่ kindall yields tuples ผมใช้คือ:

def windowkindalltupled(iterable, n=2, tuple=tuple):
    it = iter(iterable)
    win = deque(islice(it, n), n)
    if len(win) < n:
        return
    append = win.append
    yield tuple(win)
    for e in it:
        append(e)
        yield tuple(win)

วางแคชtupleในบรรทัดคำจำกัดความของฟังก์ชั่นและการใช้งานของtupleแต่ละรายการyieldเพื่อให้ได้รุ่นที่เร็วขึ้น แต่ปลอดภัยน้อยลง


เห็นได้ชัดว่านี่มีประสิทธิภาพน้อยกว่าที่ควรจะเป็น consumeเป็นวัตถุประสงค์ทั่วไป (รวมถึงความสามารถในการทำแบบสมบูรณ์consume) จึงจำเป็นต้องมีการนำเข้าเพิ่มเติมและการทดสอบแบบใช้ต่อn is Noneครั้ง ในรหัสจริงถ้าหากฉันตัดสินว่าประสิทธิภาพเป็นปัญหาหรือฉันต้องการรหัสที่กระชับมากขึ้นฉันจะพิจารณาการใส่ชื่อelseเรื่องconsumeเข้าwindowด้วยกันโดยสมมติว่าฉันไม่ได้ใช้consumeสิ่งอื่นใด แต่ถ้าการแสดงไม่ได้เป็นปัญหาฉันก็ต้องแยกคำจำกัดความไว้ consumeฟังก์ชั่นที่มีชื่อทำให้การดำเนินการขลัง / เอกสารด้วยตนเองน้อยลง
ShadowRanger

7

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

ฉันรวมไว้ที่นี่เพราะฉันยังไม่เห็นวิธีนี้ใช้ อีกครั้งฉันไม่อ้างสิทธิ์เกี่ยวกับประสิทธิภาพเปรียบเทียบ

def slidingWindow(sequence,winSize,step=1):
"""Returns a generator that will iterate through
the defined chunks of input sequence. Input sequence
must be sliceable."""

    # Verify the inputs
    if not ((type(winSize) == type(0)) and (type(step) == type(0))):
        raise Exception("**ERROR** type(winSize) and type(step) must be int.")
    if step > winSize:
        raise Exception("**ERROR** step must not be larger than winSize.")
    if winSize > len(sequence):
        raise Exception("**ERROR** winSize must not be larger than sequence length.")

    # Pre-compute number of chunks to emit
    numOfChunks = ((len(sequence)-winSize)/step)+1

    # Do the work
    for i in range(0,numOfChunks*step,step):
        yield sequence[i:i+winSize]

3
ข้อเสียเปรียบหลักที่นี่คือการlen(sequence)โทร สิ่งนี้จะไม่ทำงานหากsequenceเป็นตัววนซ้ำหรือตัวสร้าง เมื่ออินพุตมีขนาดพอดีกับหน่วยความจำนี่จะให้โซลูชันที่อ่านได้มากกว่าตัวทำซ้ำ
David B.

ใช่คุณพูดถูก. กรณีนี้มีความหมายเดิมสำหรับการสแกนลำดับดีเอ็นเอซึ่งมักจะแสดงเป็นสตริง แน่นอนมันมีข้อ จำกัด ที่คุณพูดถึง หากคุณต้องการคุณสามารถทดสอบแต่ละชิ้นเพื่อให้แน่ใจว่ายังคงความยาวที่ถูกต้องแล้วลืมที่จะต้องรู้ความยาวของลำดับทั้งหมด แต่มันจะเพิ่มค่าใช้จ่ายอีกเล็กน้อย (a len () ทดสอบทุก ๆ การทำซ้ำ)
Gus

6
def GetShiftingWindows(thelist, size):
    return [ thelist[x:x+size] for x in range( len(thelist) - size + 1 ) ]

>> a = [1, 2, 3, 4, 5]
>> GetShiftingWindows(a, 3)
[ [1, 2, 3], [2, 3, 4], [3, 4, 5] ]

ทันทีที่คุณเห็น "range (len") ใน Python มันเป็นกลิ่นรหัส
Mark Lawrence

@ MarkLawrence อะไรที่ทำให้คุณคิดว่าrange(lenเป็นรูปแบบที่ไม่ดีในหลาม?
duhaime

5

เวอร์ชัน deque window ที่ได้รับการแก้ไขเล็กน้อยเพื่อให้เป็นหน้าต่างกลิ้งจริง เพื่อให้มันเริ่มมีประชากรเพียงองค์ประกอบเดียวจากนั้นขยายเป็นขนาดสูงสุดของหน้าต่างจากนั้นย่อขนาดลงเมื่อขอบด้านซ้ายใกล้กับจุดสิ้นสุด:

from collections import deque
def window(seq, n=2):
    it = iter(seq)
    win = deque((next(it, None) for _ in xrange(1)), maxlen=n)
    yield win
    append = win.append
    for e in it:
        append(e)
        yield win
    for _ in xrange(len(win)-1):
        win.popleft()
        yield win

for wnd in window(range(5), n=3):
    print(list(wnd))

สิ่งนี้ให้

[0]
[0, 1]
[0, 1, 2]
[1, 2, 3]
[2, 3, 4]
[3, 4]
[4]

3
def rolling_window(list, degree):
    for i in range(len(list)-degree+1):
        yield [list[i+o] for o in range(degree)]

ทำสิ่งนี้เพื่อฟังก์ชั่นเฉลี่ยหมุน


3

ทำไมจะไม่ล่ะ

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

มันเป็นเอกสารในหลามdoc คุณสามารถขยายไปยังหน้าต่างที่กว้างขึ้นได้อย่างง่ายดาย


2

วนซ้ำหลายตัว!

def window(seq, size, step=1):
    # initialize iterators
    iters = [iter(seq) for i in range(size)]
    # stagger iterators (without yielding)
    [next(iters[i]) for j in range(size) for i in range(-1, -j-1, -1)]
    while(True):
        yield [next(i) for i in iters]
        # next line does nothing for step = 1 (skips iterations for step > 1)
        [next(i) for i in iters for j in range(step-1)]

next(it)เพิ่มขึ้นStopIterationเมื่อลำดับเสร็จสิ้นและด้วยเหตุผลที่ยอดเยี่ยมเกินกว่าที่ฉันคำสั่งผลตอบแทนที่นี่ยกเว้นมันและฟังก์ชั่นส่งกลับโดยไม่สนใจค่าที่เหลือที่ไม่ได้เต็มหน้าต่าง

อย่างไรก็ตามนี่เป็นวิธีแก้ปัญหาสายน้อยที่สุด แต่มีข้อกำหนดเพียงอย่างเดียวที่seqใช้__iter__หรือ__getitem__ไม่พึ่งพาitertoolsหรือcollectionsนอกเหนือจากโซลูชันของ @ dansalmo :)


หมายเหตุ: ขั้นตอนที่ซวนเซคือ O (n ^ 2) โดยที่ n คือขนาดของหน้าต่างและเกิดขึ้นเฉพาะในการโทรครั้งแรกเท่านั้น มันสามารถปรับให้เหมาะกับ O (n) ได้ แต่มันจะทำให้โค้ดมีความยุ่งเหยิงเล็กน้อย: P
jameh

2

มาทำให้ขี้เกียจกันเถอะ!

from itertools import islice, tee

def window(iterable, size): 
    iterators = tee(iterable, size) 
    iterators = [islice(iterator, i, None) for i, iterator in enumerate(iterators)]  
    yield from zip(*iterators)

list(window(range(5), 3))
# [(0, 1, 2), (1, 2, 3), (2, 3, 4)]

1
#Importing the numpy library
import numpy as np
arr = np.arange(6) #Sequence
window_size = 3
np.lib.stride_tricks.as_strided(arr, shape= (len(arr) - window_size +1, window_size), 
strides = arr.strides*2)

"""Example output:

  [0, 1, 2]
  [1, 2, 3]
  [2, 3, 4]
  [3, 4, 5]

"""


3
โปรดเขียนข้อความเกี่ยวกับคำตอบของคุณ
jrswgtr

1

ฉันทดสอบวิธีแก้ปัญหาไม่กี่ข้อแล้วพบวิธีแก้ปัญหาที่เร็วที่สุดดังนั้นฉันคิดว่าฉันจะแชร์มัน

import itertools
import sys

def windowed(l, stride):
    return zip(*[itertools.islice(l, i, sys.maxsize) for i in range(stride)])

1
ดูเหมือนกับคำตอบแรกจากคำตอบนี้: stackoverflow.com/a/11249883/7851470
Georgy

@georgy ฉันคิดว่าฉันข้ามคำตอบนั้นไปเพราะมันถูกเขียนใน Python2 แต่ฉันก็เห็นด้วยเหมือนกันจริงๆ!
Ryan Codrai


0

วิธีการเกี่ยวกับการใช้สิ่งต่อไปนี้:

mylist = [1, 2, 3, 4, 5, 6, 7]

def sliding_window(l, window_size=2):
    if window_size > len(l):
        raise ValueError("Window size must be smaller or equal to the number of elements in the list.")

    t = []
    for i in xrange(0, window_size):
        t.append(l[i:])

    return zip(*t)

print sliding_window(mylist, 3)

เอาท์พุท:

[(1, 2, 3), (2, 3, 4), (3, 4, 5), (4, 5, 6), (5, 6, 7)]

@ keocra zip (* t) หมายถึงอะไร ฉันจะหาเอกสารเกี่ยวกับคำสั่งประเภทนั้นได้ที่ไหน
Shejo284

1
Python 2.7: docs.python.org/2/library/functions.html#zipดาวปลดล็อครายการและจัดให้องค์ประกอบแต่ละรายการเป็นอินพุตไปยัง zip ( อาร์กิวเมนต์ที่เปิดออกมา )
keocra

0

นี่เป็นคำถามเก่า แต่สำหรับผู้ที่สนใจยังมีการใช้ตัวเลื่อนหน้าต่างที่ยอดเยี่ยมโดยใช้เครื่องกำเนิดไฟฟ้าในหน้านี้ (โดย Adrian Rosebrock)

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

def sliding_window(image, stepSize, windowSize):
    # slide a window across the image
    for y in xrange(0, image.shape[0], stepSize):
        for x in xrange(0, image.shape[1], stepSize):
            # yield the current window
            yield (x, y, image[y:y + windowSize[1], x:x + windowSize[0]])

เคล็ดลับ:คุณสามารถตรวจสอบ.shapeหน้าต่างเมื่อทำการวนซ้ำตัวกำเนิดเพื่อทิ้งสิ่งที่ไม่ตรงตามความต้องการของคุณ

ไชโย


0

แก้ไขคำตอบของ DiPaoloเพื่ออนุญาตการเติมและขนาดของขั้นตอนตามอำเภอใจ

import itertools
def window(seq, n=2,step=1,fill=None,keep=0):
    "Returns a sliding window (of width n) over data from the iterable"
    "   s -> (s0,s1,...s[n-1]), (s1,s2,...,sn), ...                   "
    it = iter(seq)
    result = tuple(itertools.islice(it, n))    
    if len(result) == n:
        yield result
    while True:        
#         for elem in it:        
        elem = tuple( next(it, fill) for _ in range(step))
        result = result[step:] + elem        
        if elem[-1] is fill:
            if keep:
                yield result
            break
        yield result

0

นี่คือหนึ่งซับ ฉันหมดเวลาและมันประกอบไปด้วยประสิทธิภาพการทำงานของคำตอบยอดนิยมและดีขึ้นเรื่อย ๆ ด้วย seq ที่ใหญ่กว่าจากช้ากว่า 20% เมื่อ len (seq) = 20 และ 7% ช้าลงด้วย len (seq) = 10,000

zip(*[seq[i:(len(seq) - n - 1 + i)] for i in range(n)])

โปรดเพิ่มข้อความอธิบายพร้อมคำตอบของคุณ ไม่ใช่ทุกคนที่สะดุดในหัวข้อนี้เป็น Python Ninja
Abhijit Sarkar

นั่นคือปิดโดย 2, งานนี้: zip (* [seq [i: (len (seq) - n + 1 + i)]) สำหรับฉันในช่วง (n)])
Gösta Forsum

0

ลองใช้ชิ้นส่วนของฉันแบบง่าย ๆ แบบหนึ่งเดียวโดยใช้ไอริซ แต่อาจไม่มีประสิทธิภาพสูงสุด

from itertools import islice
array = range(0, 10)
window_size = 4
map(lambda i: list(islice(array, i, i + window_size)), range(0, len(array) - window_size + 1))
# output = [[0, 1, 2, 3], [1, 2, 3, 4], [2, 3, 4, 5], [3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9]]

คำอธิบาย: สร้างหน้าต่างโดยใช้ islice ของ window_size และวนการดำเนินการนี้โดยใช้แผนที่เหนืออาร์เรย์ทั้งหมด


0

ฟังก์ชั่นที่เหมาะสำหรับการเลื่อนข้อมูลหน้าต่างในการเรียนรู้ลึก

def SlidingWindow(X, window_length, stride):
    indexer = np.arange(window_length)[None, :] + stride*np.arange(int(len(X)/stride)-window_length+4)[:, None]
    return X.take(indexer)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.