使用Swift查找数组中的重复元素

59
如何在数组中查找重复元素?我有一个电话号码的数组, 我需要从右到左搜索电话号码, 并找到相似的6个数字。然后将它们打印出来。

为什么你想要在 cellForRow 中调用你的函数... 正确的方法是先找到重复项,然后再显示它们... - Volker
使用这个函数,我应该将所有联系人信息保存在数组中,然后在这个数组中执行搜索,是吗?之后,我将获得一个包含重复项的新数组,这样在单元格中显示就很简单了,我理解正确吗? - C0mrade
1
你的问题太宽泛了,涉及到两个不相关的问题:1)如何在通讯录中查找“重复联系人”。2)如何在表格视图中显示结果。我建议你将问题限制在一个单独的问题上。如果解决了这个问题,必要时可以再发布另一个问题。 - Martin R
我已经修改了问题并发布了单个问题。 - C0mrade
17个回答

67

要查找重复项,可以通过电话号码建立交叉引用,然后将其缩小到仅包含重复项。例如:

let contacts = [
    Contact(name: "Rob",     phone: "555-1111"),
    Contact(name: "Richard", phone: "555-2222"),
    Contact(name: "Rachel",  phone: "555-1111"),
    Contact(name: "Loren",   phone: "555-2222"),
    Contact(name: "Mary",    phone: "555-3333"),
    Contact(name: "Susie",   phone: "555-2222")
]

您可以使用以下方法构建交叉引用字典:

let crossReference = Dictionary(grouping: contacts, by: \.phone)

接下来,要查找重复项:

let duplicates = crossReference
    .filter { $1.count > 1 }

显然,您应该使用适合您的模型类型,但上面使用了以下Contact类型:

struct Contact {
    let name: String
    let phone: String
}

有很多种方法可以实现这一点,因此我不会关注上面的实现细节,而是专注于概念:通过某个键(例如电话号码)构建交叉参考原始数组,然后将结果过滤为仅具有重复值的那些键。


听起来你想要将反映重复项的这个结构展开成一个联系人的单个数组(我不确定为什么要这样做,因为你失去了识别哪些是彼此重复的结构),但如果你想要这样做,你可以用flatMap来实现:

let flattenedDuplicates = crossReference
    .filter { $1.count > 1 }                 // filter down to only those with multiple contacts
    .flatMap { $0.1 }                        // flatten it down to just array of contacts that are duplicates of something else

让我想知道原始数组是什么样子的。(在你的另一个问题中,你正在cellForRowAtIndexPath内部添加行。)仔细检查原始数组(例如查看总计数),确保输入正确。 - Rob
谢谢Rob,这些都非常有帮助! - C0mrade
你的算法运行良好,它给我了一个副本,但在联系人数组中,我已将电话号码更改为我的联系方式,现在它给我联系人的数量,并且每个联系人都是自身的副本。我该如何修复这个问题? - C0mrade
我已经修复了所有的错误,但是在尝试了两天后,我得到了这个结果:当我手动输入电话号码时,它可以正常工作并且只会给出重复的号码,但是当我用我的电话号码数组替换电话号码时,它却给出了一个实际上在电话中没有重复的号码... 我感到很困惑... - C0mrade
我正在尝试将您提供的代码中的数据传递到表视图,但每当我调用它们时,它都会给我一个空白的空间。我尝试了一些变量和一些愚蠢的东西,但没有帮助我...你能帮我吗?我只想在tableView上显示结果而没有任何特殊的东西。这是代码:https://gist.github.com/geowarsong/edb3b2b46dd875aecd12 - C0mrade
显示剩余6条评论

56

感觉很聪明。给定一个Int数组。

let x = [1, 1, 2, 3, 4, 5, 5]
let duplicates = Array(Set(x.filter({ (i: Int) in x.filter({ $0 == i }).count > 1})))
// [1, 5]

请注意,这对于所有涉及方,包括编译器和您本人都极其低效。

我只是在炫耀而已。

编辑:哈哈,有人给这个点了踩,所以我要再强调一遍:请务必不要在生产环境或其他任何地方使用此方法。


真聪明!! - Mathew Varghese
你知道我在实现这个方法时需要遵循哪个协议吗? - Ennabah
1
我喜欢这个一行代码!太酷了! - Yassine ElBadaoui
7
答案按预期工作。但它不是一种内存效率高的解决方案。如果您在X-code Playground中运行上面的代码,那么那一行代码将运行57次。因此,如果您专注于解决大型数据数组的问题。在这种情况下,我不会使用这个解决方案。 - Mehul D
1
好答案。在Swift 4中,你可以用i代替(i: Int)让编译器处理类型。 - sudo
显示剩余2条评论

38

Swift 4+

一行代码,快速解决:

var numbers = [1,2,3,4,5,6,6,6,7,8,8]
let dups = Dictionary(grouping: numbers, by: {$0}).filter { $1.count > 1 }.keys

//Results: [6, 8]

1
如果你在处理 Identifiables(可识别的对象)时,可以使用以下有用的代码片段:func duplicateIds() -> Dictionary.Keys { Dictionary(grouping: self, by: { $0.id }) .filter { $1.count > 1 } .keys } - H K

14

整个代码的灵感来自Rob的非常棒的答案。我将它转换成了一个Array扩展,并为中间步骤赋予了名称以提高可读性:

extension Array where Element: Hashable {
    func duplicates() -> Array {
        let groups = Dictionary(grouping: self, by: {$0})
        let duplicateGroups = groups.filter {$1.count > 1}
        let duplicates = Array(duplicateGroups.keys)
        return duplicates
    }
}

[1, 2, 2, 3, 1].duplicates() -> [1, 2]

7
你可以使用"归并排序"来实现它,但需要进行一些修改,在合并步骤中应该忽略重复项。
最简单的方法是,如果电话号码只是一个6位数字并且类型为Int,则可以对电话号码数组进行排序,然后过滤出重复项。
var phoneNumbers = [123456, 234567, 345678, 123456, 456789, 135790, 456789, 142638]

func findDuplicates(sortedArray array: [Int]) -> [Int]
{
    var duplicates: [Int] = []

    var prevItem: Int = 0
    var addedItem: Int = 0

    for item in array
    {
        if(prevItem == item && addedItem != item)
        {
            duplicates.append(item)
            addedItem = item
        }

        prevItem = item
    }

    return duplicates
}

func sortPhoneNumbers(phoneNumbers: [Int]) -> [Int]
{
    return phoneNumbers.sorted({ return $0<$1 })
}

sortPhoneNumbers(phoneNumbers)
findDuplicates(sortPhoneNumbers(phoneNumbers))

此外,你可以用不同的方式实现findDuplicates方法:
使用Set(Swift 1.2+):
func findDuplicates(array: [Int]) -> [Int]
{
    var duplicates = Set<Int>()
    var prevItem = 0       

    for item in array
    {
        if(prevItem == item)
        {
            duplicates.insert(item)
        }

        prevItem = item
    }

    return Array(duplicates)
}

等等。


@Mazyod - 从答案中可以看出:"你可以对电话号码数组进行排序,然后过滤以查找重复项。" - tikhop
  1. 该函数应该这样做,因为名称并没有暗示输入已排序。
  2. 仔细看,你在每次迭代中将 prevItem 重置为 0
- Mazyod
如果函数名为findDuplicates(sortedArray array: [Int]) -> [Int],那么这将是有意义的。但是,读取当前名称的开发人员将无法意识到您传递的数组必须是已排序的。 - Mazyod
@Mazyod 如果开发者从头到尾阅读答案,它也是有意义的。我提供了一段代码片段来帮助解决问题。就这样。另外,如果你发现了拼写错误,你可以直接修正它(比如2.)。 - tikhop
让我们在聊天中继续这个讨论。点击此处进入聊天室 - tikhop

6

如果想要根据属性来筛选数组,您可以使用以下方法:

extension Array {

    func filterDuplicates(@noescape includeElement: (lhs:Element, rhs:Element) -> Bool) -> [Element]{
        var results = [Element]()

        forEach { (element) in
            let existingElements = results.filter {
                return includeElement(lhs: element, rhs: $0)
            }
            if existingElements.count == 0 {
                results.append(element)
            }
        }

        return results
    }
}

您可以像以下这样调用它,以Rob的联系人示例为基础:
let filteredContacts = myContacts.filterDuplicates { $0.name == $1.name && $0.phone == $1.phone }

3
我通过使用reduce找到了一种方法,以下是代码(Swift 4):
let testNumbers = [1,1,2,3,4,5,2]
let nondupicate = testNumbers.reduce(into: [Int]()) {
    if !$0.contains($1) {
        $0.append($1)
    } else {
        print("Found duplicate: \($1)")
    }
}

作为一个副作用,它返回一个没有重复元素的数组。
你可以很容易地修改它来计算重复元素数量,检查字符串数组等。

2
let inputArray = [9820213496, 9546533545, 9820213496, 995543567]
var outputArray = [Int]()
for element in inputArray{
    if outputArray.contains(element){
        print("\(element) is Duplicate")
    }else{
        outputArray.append(element)
    }
}
print(outputArray) // print Array without duplication

1

Antoine's solutionSwift 3+ 语法中

extension Array {

    func filterDuplicates(includeElement: @escaping (_ lhs: Element, _ rhs: Element) -> Bool) -> [Element] {

        var results = [Element]()

        forEach { (element) in

            let existingElements = results.filter {
                return includeElement(element, $0)
            }

            if existingElements.count == 0 {
                results.append(element)
            }
        }
        return results
    }
}

1

简单解决方案:

let numbers = ["1","2","3","6","8","3","6","3","5","8","9","7"]

func findDuplicate(list: [String]) -> [String] {
    var duplicates = Set<String>()
    for element in list {
        if list.firstIndex(of: element) != list.lastIndex(of: element) {
            duplicates.insert(element)
        }
    }
    
    return duplicates.sorted()
}

请阅读 [答案] 并 [编辑] 你的问题,以包含关于为什么这段代码实际上能解决问题的解释。始终记住,你不仅要解决问题,还要教育原作者和任何未来阅读此帖子的读者。 - Adriaan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接