从阵列中删除重复项,但按频率排序

时间:2019-03-10 12:07:43

标签: arrays swift

我有一个带有标签的数组,其中包含某个图片的每个类别。可以是自然,人像,动物...

一个相册包含例如10张图片。我加载了所有标签,其中包含每张图片的类别,所以我得到一个数组:

albumTags = ["Nature", "Animals", "Nature", "Portrait", "Architecture", "Nature", "Travel", "Travel", "Portrait", "Nature"]

我现在正在寻找一种删除重复值的方法,但同时要根据标签的频率对数组重新排序。如果标签的数量相等,我希望数组中的第一个保留第一个值(因此不是按字母顺序排列,而是按相册中图片的顺序排列)。我想要的结果:

albumTags = ["Nature", "Portrait", "Travel", "Animals", "Architecture"]

我该如何执行这两个动作?

3 个答案:

答案 0 :(得分:3)

有一种解决方案,代码更少。

  1. reduce将该数组放入字典中,以标签名称为键,出现次数为值。
  2. sort按值(以大者为准)。
  3. map通过保存字符串的键将其返回到数组。
let albumTags = ["Nature", "Animals", "Nature", "Portrait", "Architecture", "Nature", "Travel", "Travel", "Portrait", "Nature"]
let sortedTags = albumTags
    .reduce(into: [:], { $0[$1, default: 0] += 1 })
    .sorted(by: { $0.value > $1.value })
    .map({ $0.key })
// After `reduce`:
// ["Nature": 4, "Portrait": 2, "Animals": 1, "Travel": 2, "Architecture": 1]
// After `sort`:
// [(key: "Nature", value: 4), (key: "Travel", value: 2), (key: "Portrait", value: 2), (key: "Animals", value: 1), (key: "Architecture", value: 1)]
// After `map`:
// ["Nature", "Portrait", "Travel", "Animals", "Architecture"]

答案 1 :(得分:0)

您可以使用NSCountedSet

let albumTags = ["Nature", "Animals", "Nature", "Portrait", "Architecture", "Nature", "Travel", "Travel", "Portrait", "Nature"]

let countedSet = NSCountedSet(array: albumTags)
let result = countedSet.map({ $0 }).sorted { (first, second) -> Bool in
    let countForFirst = countedSet.count(for: first)
    let countForSecond = countedSet.count(for: second)
    if countForFirst == countForSecond {
        //Same count, return first the one which appears before the other one in initial array
        let indexForFirst = albumTags.firstIndex(of: first as! String)!
        let indexForSecond = albumTags.firstIndex(of: second as! String)!
        return indexForFirst < indexForSecond
    } else {
        //Return the bigger one first
        return countForFirst > countForSecond
    }
}
print("Result:\n\(result)")

输出:

$>Result:
[Nature, Portrait, Travel, Animals, Architecture]

之所以强制使用as! String是因为NSCountedSet具有Any作为元素(来自NSMutableSet)。 在索引计算上施加了一定的力量,但是由于它是从那里获取的,因此这不应该成为问题。如果需要,可以使用if let / guard let

答案 2 :(得分:0)

以一种减少的方式,也许索引需要考虑:

var albumTags = ["Nature", "Animals", "Nature", "Portrait", "Architecture", "Nature", "Travel", "Travel", "Portrait", "Nature"]

albumTags = (albumTags.enumerated().reduce(into: [String :(Int, Int)]()){
$0[$1.element] =  (($0[$1.element]?.0 ?? 0) + 1 , $0[$1.element]?.1 ?? $1.offset)
} as [String :(Int, Int)])

.sorted {   $0.value.0 == $1.value.0 ?  $0.value.1 <  $1.value.1: $0.value.0 > $1.value.0   }

.map{$0.key}

print(albumTags)

两次通过方法可以对数组的反向顺序使用count sort,这应该更快。

在第一遍中,构建一个按计数0 .. <(最大出现次数)倒退的[[String]]。因此,第一个出现的字符串将添加到subArray的最后一个字符串中。

第二遍如下: 结果的flatMap和反向(就地),给出正确的顺序,在此期间删除所有重复项。

let result = albumTags.reversed().reduce(into: (([String:Int](), [[String]]()))) {
$0.0[$1]  = ($0.0[$1] ?? 0) + 1
$0.0[$1]!  > $0.1.count  ?  $0.1.append ([$1] )  : $0.1[$0.0[$1]! - 1].append($1)
}.1.flatMap{$0}.reversed()
.reduce(into: ([String](),[String:Int]())) {
       if $0.1[$1] == nil  { $0.0.append($1)  ; $0.1[$1] = 0 }
}.0



//[["Nature", "Portrait", "Travel", "Architecture", "Animals"], ["Travel", "Nature", "Portrait"], ["Nature"], ["Nature"]]

//["Nature", "Nature", "Portrait", "Nature", "Travel", "Animals", "Architecture", "Travel", "Portrait", "Nature"]

 //["Nature", "Portrait", "Travel", "Animals", "Architecture"]