【问题标题】:Efficient one-liner to subsample `i`th `n` elements out of every `m` elements on a list从列表中的每个“m”元素中对“i”个“n”元素进行子采样的高效单行器
【发布时间】:2018-01-24 19:19:48
【问题描述】:

我正在寻找一种内存/cpu 高效的单线器来从列表中的每个 m 元素中对 n 进行子采样。到目前为止,我得到了:

sb = [11,12,21,22,31,32]*4 #stream buffer, e.g. 4 identical frames
ci = 1 #1-indexed channel index
cs = 2 #channel (sample) size
nc = 3 #number of channels in each frame
fs = nc*cs #frame size

[i for l in [sb[j+ci-1:j+ci-1+cs] for j
    in [x*fs+ci-1 for x in xrange(len(sb)/fs)]] for i in l]

Out: [11, 12, 11, 12, 11, 12, 11, 12]

分解它我正在创建一个示例列表列表,然后使用[i for l in ll for i in l]将其展平为一维列表

或者,不是单行,但更容易阅读,我可以这样做:

os = []
for i in [sb[j+ci-1:j+ci-1+cs] for j in [x*fs+ci-1 for x in xrange(len(sb)/fs)]]: os = os+i

cs=1 特定情况的超级简单简写相比,这两种解决方案看起来都过于复杂:sb[ci-1::fs]

你能帮我想出一个体面的解决方案吗?

【问题讨论】:

  • 您需要随机抽样吗? random.sample([11,12,21,22,31,32]*4,8) 将从列表中抽取 8 个随机样本,而不会重复任何采样索引 - 您的解决方案看起来更像是每 n.th sublis 采样的线性
  • @Patrick Artner:不。我需要从流中提取一个通道(通道解复用)。流在元素列表上被展平。每个通道每个样本都采用 cs 连续元素。流上的每一帧都包含 nc 个连续样本,每个通道一个。我想从流中的特定通道中提取每个样本到一个新列表中,当然要保持顺序。
  • 您是否尝试从流缓冲区中获取第一个 n 个相同帧,在这种情况下是 4 个相同帧中的每个帧中的 2 个元素?跨度>
  • @Idlehands:或多或少。是的,在这种情况下,来自所选通道的 4 个相同帧中的每一个中有 2 个元素。 (很抱歉第一条评论是我误解了你写的内容)。

标签: python arrays python-2.7 list-comprehension multiplexing


【解决方案1】:

以下内容对我来说似乎相当易读(而且效率也相当高):

from itertools import chain

sb = [11, 12, 21, 22, 31, 32]*4  # stream buffer, e.g. 4 identical frames

ci = 1      # 1-indexed channel index
cs = 2      # channel size
nc = 3      # number of channels in each frame
fs = nc*cs  # frame size

result = list(chain.from_iterable(sb[i: i+cs] for i in xrange(ci-1, len(sb), fs)))
print(result)  # -> [11, 12, 11, 12, 11, 12, 11, 12]

【讨论】:

  • 我也在研究 itertools,如果你熟悉它会更清楚,所以它是一个不错的选择。我仍然希望从一个可以帮助您进行迭代的库中获得更简单的解决方案。这是我想要解决的一个不常见的问题吗?
  • @NotGaeL 你能描述一下你正在寻找的简单性吗?
  • NotGaeL:您可能没有意识到,但传递给chain.from_iterable() 的参数就是所谓的generator expression,这意味着它也正在以迭代方式执行。当我说代码相当有效时,这就是我的意思的一部分。主要的一点是 Python 会迭代地做一些事情,而不需要使用 itertools 来实现它。听起来您希望某些现有的库函数可以完成您想要完成的所有专门工作。
  • @pylang: sb[ci-1::fs]
  • @martineau:我期待一个 itertools 专用库来提供迭代工具来简化一些专门的迭代内容(迭代并从每个 m 元素中选择 kth n 元素) .这是一项如此简单的任务,即使是自然语言也能用一句话完成工作。我不想把讨论变成讽刺,我的意思是这正是整个库所期望的,致力于促进元素的迭代。
【解决方案2】:

我将大部分索引移到range() 计算中。它比将索引显示为子列表更快 - 请参阅下面的时间:

sb = [11,12,21,22,31,32]*4 #stream buffer, e.g. 4 identical frames
ci = 1 #1-indexed channel index
cs = 2 #channel size
nc = 3 #number of channels in each frame
fs = nc*cs #frame size

for ci in range(1,4):
    print [x for y in [sb[x:x+cs] for x in range((ci-1)*cs,len(sb),fs)] for x in y] 

输出:

[11, 12, 11, 12, 11, 12, 11, 12]
[21, 22, 21, 22, 21, 22, 21, 22]
[31, 32, 31, 32, 31, 32, 31, 32]

我将大部分工作转移到 range() 调用中 - 生成子列表列表,其余的是将子列表简单分解为一个列表。

range((ci-1)*cs,len(sb), fs)
         |         |     |________  frame size, range will use steps the size of the frame
         |         |______________  till end of data
         |________________________  starting at (ci-1) * channel size   

for ci = 1 it starts at 0,   6,12,18,....
for ci = 2 it starts at 2,   8,14,....
for ci = 3 it starts at 4,  10,...  
for ci = 4 it starts at 6,  ...  
    and increases by fs = 6 until end of data. The list comp then gets a sublist of len cs
    and the rest of the list-comp flattens it down from list of list to a simpler list

时间:

import timeit

print timeit.timeit(stmt='''
sb = [11,12,21,22,31,32]*4*5 #stream buffer, e.g. 4 identical frames
ci = 1 #1-indexed channel index
cs = 2 #channel size
nc = 3 #number of channels in each frame
fs = nc*cs #frame size
for ci in range(1,4):
    [x for y in [sb[x:x+cs] for x in range((ci-1)*cs,len(sb),fs)] for x in y] 

''', setup='pass', number=10000)  #  0.588474035263

print timeit.timeit(stmt='''
sb = [11,12,21,22,31,32]*4*5 #stream buffer, e.g. 4 identical frames
ci = 1 #1-indexed channel index
cs = 2 #channel size
nc = 3 #number of channels in each frame
fs = nc*cs #frame size
for ci in range(1,4):
    [i for l in [sb[j+ci-1:j+ci-1+cs] for j in [x*fs+ci-1 for x in xrange(len(sb)/fs)]] for i in l] 

''', setup='pass', number=10000)   # 0.734045982361

【讨论】:

  • 我更喜欢你的方式,因为它更具可读性,但它本质上是相同的解决方案,对吧?它仍然太复杂,并不能提高性能。没有更简单的方法吗?
  • @NotGaeL 基本上我通过在高度优化的生成器 range() 对象中执行索引数学来摆脱一个清单列表,所以如果“应该”表现得更好。您需要根据您的数据对其进行测量以确保。
  • @NotGaeL 将它提高到 *20 并执行了 10000 次,我的版本快了将近 20%
  • 哇,这比我想象的要多。阅读起来仍然很痛苦,但绝对是一个很大的进步谢谢你的帮助!
  • 如果您从执行中删除昂贵的数据设置,定时部分将更改为 0.281064033508 与 0.385651111603 - 大约 72.88% 的解决方案
【解决方案3】:

我建议使用更清晰的变量名称而不是 cmets 并且不要使用单行符

给定

import itertools as it 


stream = [11, 12, 21, 22, 31, 32] * 4
ch_idx = 1
ch_size = 2 
num_chs = 3

代码

使用grouperitertools recipe

channels = grouper(ch_size, stream)
frames = grouper(num_chs, channels)
list(it.chain.from_iterable(*it.islice(zip(*frames), ch_idx)))
# [11, 12, 11, 12, 11, 12, 11, 12]

作为一个单行,它看起来如下:

list(it.chain.from_iterable(*it.islice(zip(*grouper(num_chs, grouper(ch_size, stream))), ch_idx)))
# [11, 12, 11, 12, 11, 12, 11, 12]

详情

grouper 配方实现如下:

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return zip_longest(*args, fillvalue=fillvalue)

另请参阅more_itertools 第三方库,了解预实施的配方。

【讨论】:

    【解决方案4】:

    代码

    sb = [11,12,21,22,31,32] * 4
    ci = 0
    cs = 2
    nc = 3
    fs = cs * nc
    result = list(sum(zip(*[sb[i::fs] for i in range(ci, ci+cs)]),()))
    

    输出

    [11, 12, 11, 12, 11, 12, 11, 12]
    

    我建议将ci 设置为基于0 的索引以匹配python 的语法,但如果您坚持,更新函数很简单,只需将所有ci 替换为ci-1

    这与您的原始方法基本相同,只是更简洁一些,它可以扩展到不同的cicsnc

    【讨论】:

    • 这不会产生该输出,而且它不是单个列表,而是列表列表。
    • 好的,让我看看。我仍在尝试破译ci 应该是什么。这就是为什么我的评论询问您是否仅在给定的帧大小中寻找 n 元素。
    • ci 是 1-indexed 频道索引
    • 如果ci==1 and cs==2 则从列表中获取nc*cs 元素的每一帧上的第一个和第二个元素。如果ci==2 and cs==2,那么您将从每一帧中获取第三个和第四个元素,依此类推...
    • 使用list(it.chain(*results))
    猜你喜欢
    • 1970-01-01
    • 2014-02-16
    • 2010-09-15
    • 2022-07-24
    • 2021-11-02
    • 1970-01-01
    • 2015-03-07
    • 2020-01-05
    • 1970-01-01
    相关资源
    最近更新 更多