有条件地交换数组中的项目

Conditional swapping of items in an array

我收集了 A、B 和 C 类型的项目。 我想处理集合并交换所有 A 和 B 对,但是如果有 C(也是一个集合),我想递归处理它。

所以

#(A1 A2 B1 B2 A3 #(A4 A5 B3) )

会被翻译成

#(A1 B1 A2 B2 A3 #(A4 B3 A5) )

交换不可传递,因此 #(A1 B1 B2) 将被翻译成 #(B1 A1 B2) 而不是 #(B1 B2 A1)

我想使用 overlappingPairsDo: 但问题是第二个元素总是被处理两次。

这可以通过 Collection API 以某种方式实现,而无需诉诸原始的 forloops 吗?

我正在寻找可读性强但性能不佳的解决方案。

这是一个使用两步方法的无递归解决方案:

result := OrderedCollection new.
#(1 3 4 6 7) 
    piecesCutWhere: [ :a :b | a even = b even ]
    do: [ :run |
        result addAll: ((result isEmpty or: [ result last even ~= run first even ])
            ifTrue: [ run ]
            ifFalse: [ run reverse ]) ].

result asArray = #(1 4 3 6 7) "--> true"

因此,我们首先将集合拆分为我们认为可以交换的地方。然后,在第二步中,如果结果集合的最后一个元素仍然允许交换,我们只交换。

向其中添加递归应该很简单。

认为 我下面的解决方案应该可以满足您的需求,但需要注意以下几点:

  • "requirements" 似乎有点人为 - 例如,我很难想象您想要这种交换的用例。但这可能只是我缺乏想象力,当然,或者由于您试图简化问题。
  • 在我看来,正确的解决方案应该创建所需的对象,以便可以将代码移动到它所属的位置。为了演示目的,我的解决方案只是将它(大部分)插入 class 端方法。
  • 您要求的是 "achieved somehow with [the] Collection API without resorting to primitive for[-]loops" - 我不会这么快就放弃深入了解基础知识。毕竟,如果您查看 #overlappingPairsDo: 的实现,这正是他们所做的,并且由于您是在 标签内提出问题,我们非常欢迎您做出贡献你做事的新方法对 "Collections API" 有用,这样我们都能从中受益。

为了提供帮助,我添加了一个 class SwapPairsDemo 和两个 class 端方法。第一个只是一个助手,因为出于演示目的,我们使用您示例中的 Array 对象,它们包含 ByteSymbol 个实例作为您的 AB 我们想要与 C 集合类型区分开来的类型 - 只有 ByteSymbol 本身当然是集合,所以让我们假装它们不仅仅是为了这个练习。

isRealCollection: anObject

^anObject isCollection
    and: [anObject isString not
    and: [anObject isSymbol not]]

第二种方法包含显示交换和允许递归的代码:

swapPairsIn: aCollection ifTypeA: isTypeABlock andTypeB: isTypeBBlock

| shouldSwapValues wasJustSwapped |
shouldSwapValues := OrderedCollection new: aCollection size - 1 withAll: false.
aCollection overlappingPairsWithIndexDo: [:firstItem :secondItem :eachIndex |
    (self isRealCollection: firstItem)
        ifTrue: [self swapPairsIn: firstItem ifTypeA: isTypeABlock andTypeB: isTypeBBlock]
        ifFalse: [
            shouldSwapValues at: eachIndex put: ((self isRealCollection: secondItem) not
                and: [(isTypeABlock value: firstItem)
                and: [isTypeBBlock value: secondItem]])
            ]
    ].
(self isRealCollection: aCollection last)
    ifTrue: [self swapPairsIn: aCollection last ifTypeA: isTypeABlock andTypeB: isTypeBBlock].
wasJustSwapped := false.
shouldSwapValues withIndexDo: [:eachBoolean :eachIndex |
    (eachBoolean and: [wasJustSwapped not])
        ifTrue: [
            aCollection swap: eachIndex with: eachIndex + 1.
            wasJustSwapped := true
            ]
        ifFalse: [wasJustSwapped := false]
    ]

这有点少,我通常会重构这么大的方法,而且您可能想要处理 nil、空列表等问题,但希望您能找到一种方法来解决您的问题问题。代码包括三个步骤:

  1. 构建一个布尔值集合(大小比主集合的大小小一),以确定两个项目是否应该通过迭代overlappingPairsWithIndexDo:来交换。
  2. 这个迭代本身不处理最后一个元素,所以我们需要在单独的步骤中处理这个元素可能是一个集合。
  3. 最后,我们使用我们的布尔值集合来执行交换,但是如果我们上次刚刚交换过,我们就不会再次交换(我认为这就是交换不可传递的意思)。

对于 运行 代码,您需要提供您的集合以及一种判断事物类型是 "A" 还是 "B" 的方法 - 我刚刚使用了您的示例,所以我只是问他们是否以这些字母开头 - 显然可以用适合您的用例的任何字母代替。

| collection target isTypeA isTypeB  |
collection := #(A1 A2 B1 B2 A3 #(A4 A5 B3) ).
target := #(A1 B1 A2 B2 A3 #(A4 B3 A5) ).
isTypeA := [:anItem | anItem beginsWith: 'A'].
isTypeB := [:anItem | anItem beginsWith: 'B'].
SwapPairsDemo swapPairsIn: collection ifTypeA: isTypeA andTypeB: isTypeB.
^collection = target

在工作区 returns true 中对此进行检查,即 collection 上的交换已经执行,因此它现在与 target 相同。