我怎样才能重新present的方式,可以快速插入任何索引行的音符?音符、索引、快速、方式

2023-09-11 22:57:59 作者:戴着小红帽的大灰狼

有关好玩,并学习函数式编程,我发展Clojure中,做使用的想法,从这个理论的音乐被称为Westergaardian论算法组成的程序。它产生的音乐(其中一条线是一个单独的工作人员组成的音符,每一个球场和持续时间序列)的线路。它基本上是这样的:

For "fun", and to learn functional programming, I'm developing a program in Clojure that does algorithmic composition using ideas from this theory of music called "Westergaardian Theory". It generates lines of music (where a line is just a single staff consisting of a sequence of notes, each with pitches and durations). It basically works like this:

在开始用它由三个音符(如何将这些被选择的细节并不重要)一行。 随机执行这条线上的几个业务之一。操作随机选取来自所有对相邻指出,符合一定条件(对于每一对,标准只依赖于对,是独立于该线路的其他票据)的。它插入1个或数所选择的一对之间的笔记(取决于操作)。每个操作都有自己独特的标准。 继续随机就行执行这些操作,直到该行的期望的长度。

我碰到的问题是,我实现了,这是相当缓慢的,我怀疑它可以做出更快。我是新来的Clojure和一般的函数式编程(虽然我经历了OO),所以我希望有人有更多的经验可以指出,如果我没有想到的功能模式,或在某些计划生育技术遗漏

The issue I've run into is that my implementation of this is quite slow, and I suspect it could be made faster. I'm new to Clojure and functional programming in general (though I'm experienced with OO), so I'm hoping someone with more experience can point out if I'm not thinking in a functional paradigm or missing out on some FP technique.

我目前的执行情况是,每一行是一个包含地图的载体。每个地图有:说明和:DUR。 :注释的值是一个关键字再presenting一个音符,如:A4或:C#3。 :DUR的值是一个分数,再presenting音符的持续时间(1为全音符,1/4四分音符,等...)。因此,例如,一条线再presenting启动C3上的C大调音阶是这样的:

My current implementation is that each line is a vector containing maps. Each map has a :note and a :dur. :note's value is a keyword representing a musical note like :A4 or :C#3. :dur's value is a fraction, representing the duration of the note (1 is a whole note, 1/4 is a quarter note, etc...). So, for example, a line representing the C major scale starting on C3 would look like this:

[
{:note :C3 :dur 1}
{:note :D3 :dur 1}
{:note :E3 :dur 1}
{:note :F3 :dur 1}
{:note :G3 :dur 1}
{:note :A4 :dur 1}
{:note :B4 :dur 1}
]

这是一个有问题的再presentation因为没有真正的快捷方式插入到载体的任意索引。但是插入在这些线上最频繁执行的操作。我现在可怕的功能,插入纸币成一条线基本上拆分使用subvec在插入点的载体,用连词加入第一部分+笔记+最后一部分,然后使用扁平化和血管内皮细胞,使它们全部是在一维向量。例如,如果我想插入C3和D3进入C大调音阶在指数3(其中F3的),它会做到这一点(我会用音符名称代替:注意和:DUR地图):

This is a problematic representation because there's not really a quick way to insert into an arbitrary index of a vector. But insertion is the most frequently performed operation on these lines. My current terrible function for inserting notes into a line basically splits the vector using subvec at the point of insertion, uses conj to join the first part + notes + last part, then uses flatten and vec to make them all be in a one-dimensional vector. For example if I want to insert C3 and D3 into the the C major scale at index 3 (where the F3 is), it would do this (I'll use the note name in place of the :note and :dur maps):

(连词[C3 D3 E3] [C3 D3] [F3 G3 A4 B4]),它创建[C3 D3 E3 [C3 D3] [F3 G3 A4 B4]] (VEC(压扁previous矢量)),这给[C3 D3 E3 C3 D3 F3 G3 A4 B4]

中的运行时间为O(n),AFAIK。

The run time of that is O(n), AFAIK.

我在寻找一种方法,使这种插入更快。我搜索了在具有快速插入,但没有发现任何东西,将工作Clojure的数据结构的信息。我发现手指树,但他们只允许快速插入在列表的开始或结束。

I'm looking for a way to make this insertion faster. I've searched for information on Clojure data structures that have fast insertion but haven't found anything that would work. I found "finger trees" but they only allow fast insertion at the start or end of the list.

编辑:我分成两个问题。 另一部分是在这里。

I split this into two questions. The other part is here.

推荐答案

您已经错过的一件事是,在理论上是这样,手指树做给快速插入任何索引。他们唯一的直接的允许你插入两端,但他们也提供快速分裂和快速的串联,所以快速插入任何地方的功能,可以诬陷为分为两个序列,追加到其中一个,然后CONCAT他们再次相聚。

One thing you have missed is that, in theory anyway, finger trees do give fast insertion at any index. They only directly allow you to insert at either end, but they also provide fast splitting and fast concatenation, so a fast insert-anywhere function can be framed as "split into two sequences, append to one of them, and then concat them together again".

我说理论上,是因为手指的树木依靠固定时间的内存访问,但它们产生比一个简单的载体更多的高速缓存未命中,而且往往不执行,以及你所期望的。手指树是好玩,但不Clojure中常用的,我不会真的建议使用它们真实的。

I say "theoretically" because finger trees rely on constant-time memory access, but they generate many more cache misses than a simpler vector, and often don't perform as well as you'd expect. Finger trees are fun to play with, but aren't commonly used in clojure and I wouldn't really recommend using them for real.

一种可能性是只继续使用慢的操作。如果你的载体是永远不会很长,而且性能不是关键,那么O(n)的插入操作就不会是很大。

One possibility is to just continue using the slow operations. If your vectors are never very long, and performance isn't crucial, then the O(n) insertion operation just won't matter very much.

如果这不是什么好,有一个解决方案,具有所需的O(日志(N))的插入,虽然它不是一个很大的乐趣。答案是...模拟可变三分球!这是一个经常的工作方法:如果指针是可变的,你可能只是有一个链表,其中每个细胞都知道它的两个邻国,并作为插入时需要进行更新。但是你不能在这里,因为循环引用并不大的功能数据。但是,你可以添加一个间接层:给每个单​​元一个独特的标签,并将其只存储其邻国的标签。那么你有没有循环引用,并可以使本地更新便宜。下面是我所描述的布局的一个例子,你的C大调音阶:

If that's no good, there is a solution that has the O(log(n)) insertion that you want, although it's not a lot of fun. The answer is...to simulate mutable pointers! This is an approach that often works: if pointers were mutable, you could just have a linked list, where each cell knows its two neighbors, and update them as needed when inserting. But you can't here, because circular references aren't great for functional data. But, you can add a level of indirection: give each cell a unique "label", and have it only store the labels of its neighbors. Then you have no circular references, and you can make local updates cheaply. Here's an example of the layout I'm describing, your C-major scale:

{:cell-data {0 {:left nil :right 1, :note :C3 :dur 1}
             1 {:left 0 :right 2, :note :D3 :dur 1}
             2 {:left 1 :right 3, :note :E3 :dur 1}
             3 {:left 2 :right 4, :note :F3 :dur 1}
             4 {:left 3 :right 5, :note :G3 :dur 1}
             5 {:left 4 :right 6, :note :A4 :dur 1}
             6 {:left 5 :right nil, :note :B4 :dur 1}}
 :first-node 0, :last-node 6}

下面的数字是连续的,但你可以看到在5和6,通过创建一个新的节点如何可以在添加一个节点{:左5:右6} ,并更改:权节点5,而:离开节点6

Here the numbers are sequential, but you can see how you could add a node in between 5 and 6, by creating a new node with {:left 5 :right 6}, and changing the :right of node 5, and the :left of node 6.

这个组织​​是有点麻烦,但它确实满足您的需求。

This organization is kinda a hassle, but it does meet your needs.