微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

涉及大排列的性能问题

如何解决涉及大排列的性能问题

我正在创建一个程序来计算弦乐器上的和弦指法。这就是我所拥有的:

from itertools import product

# Returns the notes you get from a certain fingering with a certain tuning
def notes(tuning,fingering):
    return map(lambda x,y: (x+y)%12,tuning,fingering)

# Just a constraint function to filter out chords impossible to finger due to too large spreading. 
def spread(fingering):
    return max(fingering) - min(i for i in fingering if i > 0)

# Get all the possible fingerings for a certain chord
def fingering(tuning,chord):
    return [i for i in product(range(12),repeat=len(tuning)) if
            set(notes(tuning,i)) == set(chord) and
            spread(i) < 5]

示例输出

>>> cf.fingering([0,5,10],[2,7])
[[2,2,4],[7,9,9]]
>>> cf.fingering([0,1,2],3])
[[2,1],0],[3,0]]

到目前为止,这似乎有效,只是它太慢了。当我将它用于 7 弦吉他(tuning一个长度为 7 的列表)时,计算需要大约 45 秒。

我想把它降低到对人类来说是瞬时的,所以大约 0.1 秒左右。几秒钟是可以接受的。

我怀疑问题在于 product 生成所有可能的列表,然后再过滤它们。在生成之前过滤会更有效,但我不知道如何实现这一点。

解决方法

按照我的评论:

找出每根弦上的哪个品格会演奏和弦所需的音符应该相当容易。然后得到这些品丝集的乘积,而不是迭代所有可能的组合。

import itertools

MAX_FRETS = 24
FRETS_PER_OCTAVE = 12
MAX_SPREAD = 5 # Because everyone isn't John Petrucci https://i.stack.imgur.com/tKIKA.png


def fingering(tuning,chord):
    chord = set(chord)
    allowed_frets = [] # {note: [] for note in chord}
    for string_num,open_string_note in enumerate(tuning):
        string_frets = []
        for wanted_note in chord:
            wanted_fret = wanted_note - open_string_note
            while wanted_fret <= MAX_FRETS:
                if wanted_fret >= 0:
                    string_frets.append((wanted_fret,wanted_note))
                wanted_fret += FRETS_PER_OCTAVE
        
        # Now we have all the frets on this string that will give us one of the 
        # notes we need in string_frets
        allowed_frets.append(string_frets)
        
    
    fingerings = []    
    # Now,allowed_frets[i] gives us the frets that are useful on the i-th string
    # You can only select one fret per string,so you'd run product() on these and
    # then check if that product makes the chord.
    for selection in itertools.product(*allowed_frets):
        # Check if selection contains all the required notes
        selection_notes = set()
        selection_frets = []
        for fret,note in selection:
            selection_notes.add(note)
            selection_frets.append(fret)
        
        if chord == selection_notes and max(selection_frets) - min(selection_frets) < MAX_SPREAD:
            fingerings.append(selection)
            
    return fingerings

# Running for your first example:
f = fingering([0,5,10],{2,7})

使用 print(f),我们得到:

((2,2),(2,7),(4,2))
((14,(14,(16,2))
((7,(9,7))
((19,(21,7))

记住,每个元组的第一个元素是品格,第二个是音符。此方法找到了您的方法的 [2,2,4][7,9,9] 结果,但在指板更高处也找到了相同的和弦。如果您不想这样做,可以将 while wanted_fret <= MAX_FRETS 循环更改为 while wanted_fret <= FRETS_PER_OCTAVE + MAX_SPREAD

用三个字符串对两个函数进行计时,我们看到了大约 25 倍的加速:

%timeit fingering_yours([0,[2,7])
2.37 ms ± 294 µs per loop (mean ± std. dev. of 7 runs,100 loops each)

%timeit fingering([0,7})
96.1 µs ± 14.1 µs per loop (mean ± std. dev. of 7 runs,10000 loops each)

实际的三音和弦 (Cmaj)在标准调的也无关六弦吉他上并不重要,它提供了更大的加速~50x

%timeit fingering_yours([4,7,11,4],[0,4,7])
6.3 s ± 554 ms per loop (mean ± std. dev. of 7 runs,1 loop each)

%timeit fingering([4,{0,7})
127 ms ± 4.68 ms per loop (mean ± std. dev. of 7 runs,10 loops each)

也许可以进行更多优化,但这留给读者作为练习。


如果我们不想强制要求必须播放所有字符串,我们可以将 None 值添加到 string_frets 数组以指示“没有音符" 是一个有效的可能性。

# Change this line:
        string_frets = [] 
# to this:
        string_frets = [(None,None)] # Start off allowing "None" to be a valid note played on this string

接下来,当我们检查 None 中的每个 selection 时,我们需要过滤掉 itertools.product(...)

        for fret,note in selection:
            # New condition: Only append if the string is actually being played
            if fret is not None: 
                selection_notes.add(note)
                selection_frets.append(fret)

        # New condition: No strings are being played,so this is not a chord,# skip this iteration
        if not selection_frets:
            continue

在三个字符串上用二元组试试这个,我们得到以下组合,看起来很合理:

((None,None),7))
((None,2))
((None,2))
((2,(None,None))
((2,7))
((14,None))
((14,7))
((7,None))
((7,2))
((19,None))
((19,2))

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?