删除列表中的重复项

2024-11-18 08:41:00
admin
原创
15
摘要:问题描述:如何检查列表是否有重复项并返回没有重复项的新列表?解决方案 1:获取唯一项目集合的常用方法是使用set。集合是不同对象的无序集合。要从任何可迭代对象创建集合,只需将其传递给内置函数即可。如果您以后再次需要真实列表,也可以类似地将集合传递给函数。set()`list()`下面的例子应该涵盖了你想做的事...

问题描述:

如何检查列表是否有重复项并返回没有重复项的新列表?


解决方案 1:

获取唯一项目集合的常用方法是使用set。集合是不同对象的无序集合。要从任何可迭代对象创建集合,只需将其传递给内置函数即可。如果您以后再次需要真实列表,也可以类似地将集合传递给函数。set()`list()`

下面的例子应该涵盖了你想做的事情:

>>> t = [1, 2, 3, 1, 2, 3, 5, 6, 7, 8]
>>> list(set(t))
[1, 2, 3, 5, 6, 7, 8]
>>> s = [1, 2, 3]
>>> list(set(t) - set(s))
[8, 5, 6, 7]

从示例结果可以看出,原始顺序没有得到保持。如上所述,集合本身是无序集合,因此顺序会丢失。将集合转换回列表时,会创建任意顺序。

维持秩序

如果顺序对你来说很重要,那么你必须使用不同的机制。一个非常常见的解决方案是OrderedDict在插入过程中保持键的顺序:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys(t))
[1, 2, 3, 5, 6, 7, 8]

从 Python 3.7 开始,内置字典也保证保持插入顺序,因此如果你使用的是 Python 3.7 或更高版本(或 CPython 3.6),你也可以直接使用它:

>>> list(dict.fromkeys(t))
[1, 2, 3, 5, 6, 7, 8]

请注意,这可能需要先创建字典,然后从中创建列表,从而产生一些开销。如果您实际上不需要保留顺序,则通常最好使用集合,尤其是因为它可以为您提供更多操作。查看此问题以了解更多详细信息以及在删除重复项时保留顺序的其他方法。


最后请注意,set以及OrderedDict/dict解决方案都要求您的项目是可哈希的。这通常意味着它们必须是不可变的。如果您必须处理不可哈希的项目(例如列表对象),那么您将不得不使用一种缓慢的方法,基本上必须在嵌套循环中将每个项目与其他每个项目进行比较。

解决方案 2:

在 Python 2.7 中,从可迭代对象中删除重复项同时保持其原始顺序的新方法是:

>>> from collections import OrderedDict
>>> list(OrderedDict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

在 Python 3.5 中,OrderedDict 有一个 C 实现。我的计时表明,这是 Python 3.5 中各种方法中速度最快、代码最短的方法。

在 Python 3.6 中,常规字典变得既有序又紧凑。(此功能适用于 CPython 和 PyPy,但可能不存在于其他实现中)。这为我们提供了一种新的最快的重复数据删除方法,同时保留了顺序:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

在 Python 3.7 中,常规字典在所有实现中都保证有序。 因此,最短且最快的解决方案是:

>>> list(dict.fromkeys('abracadabra'))
['a', 'b', 'r', 'c', 'd']

解决方案 3:

只需一句话:list(set(source_list))就可以了。

Aset是不可能有重复的。

更新:保序方法有两行:

from collections import OrderedDict
OrderedDict((x, True) for x in source_list).keys()

这里我们使用了记住键的插入顺序这一事实OrderedDict,并且在更新特定键的值时不会更改它。我们将其True作为值插入,但我们可以插入任何内容,只是不使用值。(set其工作原理也与忽略值非常相似dict。)

解决方案 4:

>>> t = [1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> t
[1, 2, 3, 1, 2, 5, 6, 7, 8]
>>> s = []
>>> for i in t:
       if i not in s:
          s.append(i)
>>> s
[1, 2, 3, 5, 6, 7, 8]

解决方案 5:

如果你不关心顺序,只需执行以下操作:

def remove_duplicates(l):
    return list(set(l))

保证Aset没有重复。

解决方案 6:

要创建一个新列表,并保留重复项中第一个元素的顺序L

newlist = [ii for n,ii in enumerate(L) if ii not in L[:n]]

例如:如果L = [1, 2, 2, 3, 4, 2, 4, 3, 5],那么newlist将是[1, 2, 3, 4, 5]

在添加新元素之前,会检查列表中是否曾出现过新元素。此外,它不需要导入。

解决方案 7:

超晚的答案:

如果您不关心列表顺序,您可以使用*arg具有唯一性的扩展set来删除重复项,即:

l = [*{*l}]

Python3 演示

解决方案 8:

.tolist()也有使用 Pandas 和 Numpy 的解决方案。它们都返回 numpy 数组,因此如果您想要列表,则必须使用该函数。

t=['a','a','b','b','b','c','c','c']
t2= ['c','c','b','b','b','a','a','a']

Pandas 解决方案

使用 Pandas 函数unique()

import pandas as pd
pd.unique(t).tolist()
>>>['a','b','c']
pd.unique(t2).tolist()
>>>['c','b','a']

Numpy 解决方案

使用 numpy 函数unique()

import numpy as np
np.unique(t).tolist()
>>>['a','b','c']
np.unique(t2).tolist()
>>>['a','b','c']

请注意,numpy.unique() 还会对值进行排序。因此返回的列表t2是按顺序排序的。如果要保留顺序,请使用以下答案:

_, idx = np.unique(t2, return_index=True)
t2[np.sort(idx)].tolist()
>>>['c','b','a']

与其他解决方案相比,该解决方案并不那么优雅,但是,与 pandas.unique() 相比,numpy.unique() 还允许您检查嵌套数组沿某个选定轴是否唯一。

解决方案 9:

在这个答案中,将有两个部分:两个独特的解决方案和一个特定解决方案的速度图。

删除重复项

大多数这些答案仅删除可散列的重复项,但这个问题并不意味着它不仅需要可散列项,这意味着我将提供一些不需要可散列项的解决方案。

collections.Counter是标准库中的一个强大工具,非常适合此用途。只有一个其他解决方案甚至包含 Counter。但是,该解决方案也仅限于可哈希键。

为了允许 Counter 中不可哈希的键,我创建了一个 Container 类,它将尝试获取对象的默认哈希函数,但如果失败,它将尝试其身份函数。它还定义了一个eq和一个hash方法。这应该足以在我们的解决方案中允许不可哈希的项目。不可哈希的对象将被视为可哈希的对象。但是,此哈希函数对不可哈希的对象使用身份,这意味着两个相等但都不可哈希的对象将不起作用。我建议您覆盖它,并将其更改为使用等效可变类型的哈希(例如使用hash(tuple(my_list))if my_listis a list)。

我还提出了两个解决方案。另一个解决方案保持项目的顺序,使用 OrderedDict 和 Counter 的子类,名为“OrderedCounter”。现在,以下是函数:

from collections import OrderedDict, Counter

class Container:
    def __init__(self, obj):
        self.obj = obj
    def __eq__(self, obj):
        return self.obj == obj
    def __hash__(self):
        try:
            return hash(self.obj)
        except:
            return id(self.obj)

class OrderedCounter(Counter, OrderedDict):
     'Counter that remembers the order elements are first encountered'

     def __repr__(self):
         return '%s(%r)' % (self.__class__.__name__, OrderedDict(self))

     def __reduce__(self):
         return self.__class__, (OrderedDict(self),)
    
def remd(sequence):
    cnt = Counter()
    for x in sequence:
        cnt[Container(x)] += 1
    return [item.obj for item in cnt]

def oremd(sequence):
    cnt = OrderedCounter()
    for x in sequence:
        cnt[Container(x)] += 1
    return [item.obj for item in cnt]

remd是无序排序,而oremd是有序排序。你可以清楚地看出哪一个更快,但我还是要解释一下。无序排序稍快一些,因为它不存储项目的顺序。

现在,我还想展示每个答案的速度比较。所以,我现在就这么做。

哪个函数最快?

为了删除重复项,我从几个答案中收集了 10 个函数。我计算了每个函数的速度,并使用matplotlib.pyplot将其放入图表中。

我将其分为三轮绘图。可哈希对象是指任何可以哈希的对象,不可哈希对象是指任何不能哈希的对象。有序序列是保留顺序的序列,无序序列不保留顺序。现在,这里还有几个术语:

无序哈希适用于任何删除重复项的方法,这些方法不必保持顺序。它不必适用于不可哈希项,但它可以。

Ordered Hashable适用于任何保持列表中项目顺序的方法,但它不必适用于不可散列的方法,但它可以。

有序不可散列方法是任何能保持列表中项目顺序的方法,并且适用于不可散列的内容。

y 轴表示花费的秒数。

x 轴是应用该函数的数字。

我使用以下理解为无序哈希值和有序哈希值生成了序列:[list(range(x)) + list(range(x)) for x in range(0, 1000, 10)]

对于有序的不可哈希值:[[list(range(y)) + list(range(y)) for y in range(x)] for x in range(0, 1000, 10)]

请注意,范围中有一个,step因为如果没有它,这将花费 10 倍的时间。另外,我个人认为,它看起来可能更容易阅读。

还请注意,图例上的键是我试图猜测的函数实现中最重要的部分。至于哪个函数表现最差或最好?图表说明了一切。

解决了这个问题之后,下面是图表。

无序哈希表

无序哈希表
(放大)
无序哈希表放大

有序哈希表

有序哈希表
(放大)
有序哈希表缩放

有序不可哈希值

有序不可哈希值
(放大)
有序不可哈希值放大

解决方案 10:

今天,一位同事将他代码中被接受的答案发送给我进行代码审查。虽然我非常欣赏这个答案的优雅,但我对它的性能并不满意。我试过这个解决方案(我使用set来减少查找时间)

def ordered_set(in_list):
    out_list = []
    added = set()
    for val in in_list:
        if not val in added:
            out_list.append(val)
            added.add(val)
    return out_list

为了比较效率,我使用了 100 个整数的随机样本 - 其中 62 个是唯一的

from random import randint
x = [randint(0,100) for _ in xrange(100)]

In [131]: len(set(x))
Out[131]: 62

以下是测量结果

In [129]: %timeit list(OrderedDict.fromkeys(x))
10000 loops, best of 3: 86.4 us per loop

In [130]: %timeit ordered_set(x)
100000 loops, best of 3: 15.1 us per loop

那么,如果从解决方案中删除集合,会发生什么情况?

def ordered_set(inlist):
    out_list = []
    for val in inlist:
        if not val in out_list:
            out_list.append(val)
    return out_list

结果不像OrderedDict那么糟糕,但仍然是原始解决方案的 3 倍多

In [136]: %timeit ordered_set(x)
10000 loops, best of 3: 52.6 us per loop

解决方案 11:

簡單又容易:

myList = [1, 2, 3, 1, 2, 5, 6, 7, 8]
cleanlist = []
[cleanlist.append(x) for x in myList if x not in cleanlist]

输出:

>>> cleanlist 
[1, 2, 3, 5, 6, 7, 8]

解决方案 12:

另一种做法:

>>> seq = [1,2,3,'a', 'a', 1,2]
>> dict.fromkeys(seq).keys()
['a', 1, 2, 3]

解决方案 13:

我的列表中有一个字典,因此无法使用上述方法。我收到错误:

TypeError: unhashable type:

因此,如果你关心顺序和/或某些项目是不可散列的。 那么你可能会发现这很有用:

def make_unique(original_list):
    unique_list = []
    [unique_list.append(obj) for obj in original_list if obj not in unique_list]
    return unique_list

有些人可能认为带有副作用的列表推导不是一个好的解决方案。这里有一个替代方案:

def make_unique(original_list):
    unique_list = []
    map(lambda x: unique_list.append(x) if (x not in unique_list) else False, original_list)
    return unique_list

解决方案 14:

如果您想保留顺序并且不使用任何外部模块,这里有一个简单的方法:

>>> t = [1, 9, 2, 3, 4, 5, 3, 6, 7, 5, 8, 9]
>>> list(dict.fromkeys(t))
[1, 9, 2, 3, 4, 5, 6, 7, 8]

注意:此方法保留了出现的顺序,因此,如上所示,九将排在一之后,因为它是第一次出现。然而,这与执行以下操作得到的结果相同

from collections import OrderedDict
ulist=list(OrderedDict.fromkeys(l))

但它更短,并且运行速度更快。

之所以有效,是因为每次fromkeys函数尝试创建新键时,如果值已经存在,它就会直接覆盖它。但是,这根本不会影响字典,因为fromkeys会创建一个所有键都具有值的字典None,因此它以这种方式有效地消除了所有重复项。

解决方案 15:

到目前为止,我在这里看到的所有保序方法要么使用简单比较(时间复杂度最好为 O(n^2)),要么使用仅限于可哈希输入的重量级OrderedDicts/ set+组合。这是一个独立于哈希的 O(nlogn) 解决方案:list

更新添加了key参数、文档和 Python 3 兼容性。

# from functools import reduce <-- add this import on Python 3

def uniq(iterable, key=lambda x: x):
    """
    Remove duplicates from an iterable. Preserves order. 
    :type iterable: Iterable[Ord => A]
    :param iterable: an iterable of objects of any orderable type
    :type key: Callable[A] -> (Ord => B)
    :param key: optional argument; by default an item (A) is discarded 
    if another item (B), such that A == B, has already been encountered and taken. 
    If you provide a key, this condition changes to key(A) == key(B); the callable 
    must return orderable objects.
    """
    # Enumerate the list to restore order lately; reduce the sorted list; restore order
    def append_unique(acc, item):
        return acc if key(acc[-1][1]) == key(item[1]) else acc.append(item) or acc 
    srt_enum = sorted(enumerate(iterable), key=lambda item: key(item[1]))
    return [item[1] for item in sorted(reduce(append_unique, srt_enum, [srt_enum[0]]))] 

解决方案 16:

我将各种建议与perfplot进行了比较。事实证明,如果输入数组没有重复元素,则所有方法的速度大致相同,无论输入数据是 Python 列表还是 NumPy 数组。

在此处输入图片描述

如果输入数组很大,但只包含一个唯一元素,并且输入数据是列表,set则、dictnp.unique方法是常量时间。如果它是一个 NumPy 数组,则比其他替代方案快 10 倍左右。np.unique

在此处输入图片描述

令我有些惊讶的是,这些也不是恒定时间的操作。


重现情节的代码:

import perfplot
import numpy as np
import matplotlib.pyplot as plt


def setup_list(n):
    # return list(np.random.permutation(np.arange(n)))
    return [0] * n


def setup_np_array(n):
    # return np.random.permutation(np.arange(n))
    return np.zeros(n, dtype=int)


def list_set(data):
    return list(set(data))


def numpy_unique(data):
    return np.unique(data)


def list_dict(data):
    return list(dict.fromkeys(data))


b = perfplot.bench(
    setup=[
        setup_list,
        setup_list,
        setup_list,
        setup_np_array,
        setup_np_array,
        setup_np_array,
    ],
    kernels=[list_set, numpy_unique, list_dict, list_set, numpy_unique, list_dict],
    labels=[
        "list(set(lst))",
        "np.unique(lst)",
        "list(dict(lst))",
        "list(set(arr))",
        "np.unique(arr)",
        "list(dict(arr))",
    ],
    n_range=[2 ** k for k in range(23)],
    xlabel="len(array)",
    equality_check=None,
)
# plt.title("input array = [0, 1, 2,..., n]")
plt.title("input array = [0, 0,..., 0]")
b.save("out.png")
b.show()

解决方案 17:

你也可以这样做:

>>> t = [1, 2, 3, 3, 2, 4, 5, 6]
>>> s = [x for i, x in enumerate(t) if i == t.index(x)]
>>> s
[1, 2, 3, 4, 5, 6]

上述方法之所以有效,是因为该index方法仅返回元素的第一个索引。重复元素的索引更高。请参阅此处:

list.index(x[, start[, end]])

返回列表中第一个值为 x 的项目的从零开始的索引。如果不存在这样的项目,则引发 ValueError。

解决方案 18:

从列表中删除重复项的最佳方法是使用Python 中的set()函数,再次将该集合转换为列表

In [2]: some_list = ['a','a','v','v','v','c','c','d']
In [3]: list(set(some_list))
Out[3]: ['a', 'c', 'd', 'v']

解决方案 19:

您可以使用set删除重复项:

mylist = list(set(mylist))

但请注意,结果将是无序的。如果这是一个问题:

mylist.sort()

解决方案 20:

尝试使用集合:

import sets
t = sets.Set(['a', 'b', 'c', 'd'])
t1 = sets.Set(['a', 'b', 'c'])

print t | t1
print t - t1

解决方案 21:

还有许多其他答案建议使用不同的方法来实现这一点,但它们都是批量操作,其中一些会丢弃原始顺序。根据您的需要,这可能没问题,但是如果您想按每个值的第一个实例的顺序迭代值,并且想要即时删除重复项而不是一次性全部删除,则可以使用此生成器:

def uniqify(iterable):
    seen = set()
    for item in iterable:
        if item not in seen:
            seen.add(item)
            yield item

这将返回一个生成器/迭代器,因此您可以在任何可以使用迭代器的地方使用它。

for unique_item in uniqify([1, 2, 3, 4, 3, 2, 4, 5, 6, 7, 6, 8, 8]):
    print(unique_item, end=' ')

print()

输出:

1 2 3 4 5 6 7 8

如果你确实想要list,你可以这样做:

unique_list = list(uniqify([1, 2, 3, 4, 3, 2, 4, 5, 6, 7, 6, 8, 8]))

print(unique_list)

输出:

[1, 2, 3, 4, 5, 6, 7, 8]

解决方案 22:

一个更好的方法可能是,

import pandas as pd

myList = [1, 2, 3, 1, 2, 5, 6, 7, 8]
cleanList = pd.Series(myList).drop_duplicates().tolist()
print(cleanList)

#> [1, 2, 3, 5, 6, 7, 8]

并且顺序仍然保留。

解决方案 23:

这个方法可以不用太多麻烦就可以处理订单(OrderdDict 和其他方法)。可能不是最 Python 化的方法,也不是最短的方法,但可以解决问题:

def remove_duplicates(item_list):
    ''' Removes duplicate items from a list '''
    singles_list = []
    for element in item_list:
        if element not in singles_list:
            singles_list.append(element)
    return singles_list

解决方案 24:

按顺序保留减少变体:

假设我们有列表:

l = [5, 6, 6, 1, 1, 2, 2, 3, 4]

减少变体(效率低下):

>>> reduce(lambda r, v: v in r and r or r + [v], l, [])
[5, 6, 1, 2, 3, 4]

速度快 5 倍,但更复杂

>>> reduce(lambda r, v: v in r[1] and r or (r[0].append(v) or r[1].add(v)) or r, l, ([], set()))[0]
[5, 6, 1, 2, 3, 4]

解释:

default = (list(), set())
# user list to keep order
# use set to make lookup faster

def reducer(result, item):
    if item not in result[1]:
        result[0].append(item)
        result[1].add(item)
    return result

reduce(reducer, l, default)[0]

解决方案 25:

您可以使用以下函数:

def rem_dupes(dup_list): 
    yooneeks = [] 
    for elem in dup_list: 
        if elem not in yooneeks: 
            yooneeks.append(elem) 
    return yooneeks

例子

my_list = ['this','is','a','list','with','dupicates','in', 'the', 'list']

用法:

rem_dupes(my_list)

['this', 'is', 'a', 'list', 'with', 'dupicates', 'in', 'the']

解决方案 26:

使用集合

a = [0,1,2,3,4,3,3,4]
a = list(set(a))
print a

使用独特的

import numpy as np
a = [0,1,2,3,4,3,3,4]
a = np.unique(a).tolist()
print a

解决方案 27:

不使用集合

data=[1, 2, 3, 1, 2, 5, 6, 7, 8]
uni_data=[]
for dat in data:
    if dat not in uni_data:
        uni_data.append(dat)

print(uni_data) 

解决方案 28:

Python 内置类型的魔力

在python中,只需使用python的内置类型就可以很容易地处理这种复杂的情况。

让我告诉你怎么做!

方法 1:一般情况

删除列表中重复元素并保持排序顺序的方法(1 行代码)

line = [1, 2, 3, 1, 2, 5, 6, 7, 8]
new_line = sorted(set(line), key=line.index) # remove duplicated element
print(new_line)

你会得到结果

[1, 2, 3, 5, 6, 7, 8]

方法 2:特殊情况

TypeError: unhashable type: 'list'

处理不可散列的特殊情况(3行代码

line=[['16.4966155686595', '-27.59776154691', '52.3786295521147']
,['16.4966155686595', '-27.59776154691', '52.3786295521147']
,['17.6508629295574', '-27.143305738671', '47.534955022564']
,['17.6508629295574', '-27.143305738671', '47.534955022564']
,['18.8051102904552', '-26.688849930432', '42.6912804930134']
,['18.8051102904552', '-26.688849930432', '42.6912804930134']
,['19.5504702331098', '-26.205884452727', '37.7709192714727']
,['19.5504702331098', '-26.205884452727', '37.7709192714727']
,['20.2929416861422', '-25.722717575124', '32.8500163147157']
,['20.2929416861422', '-25.722717575124', '32.8500163147157']]

tuple_line = [tuple(pt) for pt in line] # convert list of list into list of tuple
tuple_new_line = sorted(set(tuple_line),key=tuple_line.index) # remove duplicated element
new_line = [list(t) for t in tuple_new_line] # convert list of tuple into list of list

print (new_line)

您将获得结果:

[
  ['16.4966155686595', '-27.59776154691', '52.3786295521147'], 
  ['17.6508629295574', '-27.143305738671', '47.534955022564'], 
  ['18.8051102904552', '-26.688849930432', '42.6912804930134'], 
  ['19.5504702331098', '-26.205884452727', '37.7709192714727'], 
  ['20.2929416861422', '-25.722717575124', '32.8500163147157']
]

因为元组是可哈希的,所以您可以轻松地在列表和元组之间转换数据

解决方案 29:

下面的代码很简单,用于删除列表中的重复项

def remove_duplicates(x):
    a = []
    for i in x:
        if i not in a:
            a.append(i)
    return a

print remove_duplicates([1,2,2,3,3,4])

它返回 [1,2,3,4]

解决方案 30:

与回复中列出的其他解决方案相比,这是最快的 Python 解决方案。

使用短路求值的实现细节允许使用列表推导,这足够快。visited.add(item)总是返回None一个结果,其求值方式为False,所以的右边or总是这种表达式的结果。

自己计时

def deduplicate(sequence):
    visited = set()
    adder = visited.add  # get rid of qualification overhead
    out = [adder(item) or item for item in sequence if item not in visited]
    return out
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用