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

时间:2015-08-05 16:28:37

标签: smalltalk pharo

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

所以

SELECT KaizenData.TrackingNumber, KaizenData.KaizenLevel, KaizenData.Plant, KaizenData.Year1, KaizenData.[Tracking No], KaizenData.MainPillar, KaizenData.Area AS Dept, KaizenData.Team AS Area, KaizenData.[OP/STA] AS [Machine/Station], KaizenData.ProjectName AS [Project Name], KaizenData.IssueDescription AS [Description Phenomena], KaizenData.SolutionDescription AS [Solution Description], KaizenData.[Project Board], KaizenData.[Loss Category], KaizenData.[Loss Type], KaizenData.EnteredDate AS [Entered Date], KaizenData.StartingDate AS [Starting Date], KaizenData.EndDate AS [Completion Data], KaizenData.[Wk Comp], KaizenData.Benefit, KaizenData.Cost, KaizenData.BC, KaizenData.Savings, IIf([closedstatus]=True,"Closed") AS Status
FROM KaizenData
WHERE (((IIf([closedstatus]=True,"Closed")) Is Not Null));

将被翻译成

#(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),但问题是第二个元素总是被处理两次。

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

我正在寻找可读性而非高效的解决方案。

2 个答案:

答案 0 :(得分:1)

认为我的解决方案应该做你想要的事情,但事先做了几点说明:

  • “要求”似乎有点人为 - 比如,我很难想象一个你需要这种交换的用例。但这可能只是我缺乏想象力,当然,或者是因为你试图简化问题。
  • 在我看来,正确的解决方案应该创建所需的对象,以便代码可以移动到它所属的位置。我的解决方案只是为了演示目的而将其(主要)用于类侧方法。
  • 你要求这是“以某种方式用[收集] API实现而不诉诸于[ - ]循环的原语” - 我不会那么快就忽略了基础知识。毕竟,如果你看一下#overlappingPairsDo:的实现,那就是他们所做的,而且因为你在标签内提问,所以非常欢迎你贡献你对“Collections API”做一些有用的新方法,这样我们都可以从中受益。

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

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]
    ]

这有点寥寥无几,我通常会重构一个这么大的方法,而且你可能想要处理零,空列表等等,但希望你能够找到解决问题的方法。该代码包含三个步骤:

  1. 通过使用overlappingPairsWithIndexDo:进行迭代来构建一个集合(小于主集合大小的集合),以确定是否应该交换两个项目
  2. 此迭代本身不处理最后一个元素,因此我们需要在单独的步骤中处理此元素可能是一个集合。
  3. 最后,我们使用我们的布尔值集合来执行交换,但是如果我们刚刚交换了之前的时间,我们就不会再次交换(我认为这就是交换不是传递的意思)。
  4. 要运行代码,您需要提供集合以及判断事物是否为“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
    

    在工作区中检查此内容会返回true,即collection上的互换已经执行,现在它与target相同。

答案 1 :(得分:0)

这是一个没有递归的解决方案,采用两步法:

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"

首先,我们在任何可能进行交换的地方拆分集合。然后,在第二步中,我们只交换结果集合的最后一个元素是否仍然允许交换。

向此添加递归应该是直截了当的。