【问题标题】:Filter a dictionary of lists过滤列表字典
【发布时间】:2022-03-24 15:56:20
【问题描述】:

我有一本如下形式的字典:

{"level": [1, 2, 3],
 "conf": [-1, 1, 2],
 "text": ["here", "hel", "llo"]}

我想过滤列表以删除索引i 处的每个项目,其中值"conf" 中的索引不是>0。

所以对于上面的dict,输出应该是这样的:

{"level": [2, 3],
 "conf": [1, 2],
 "text": ["hel", "llo"]}

因为conf 的第一个值不是> 0。

我尝试过这样的事情:

new_dict = {i: [a for a in j if a >= min_conf] for i, j in my_dict.items()}

但这仅适用于一键。

【问题讨论】:

标签: python dictionary


【解决方案1】:

尝试:

from operator import itemgetter


def filter_dictionary(d):
    positive_indices = [i for i, item in enumerate(d['conf']) if item > 0]
    f = itemgetter(*positive_indices)
    return {k: list(f(v)) for k, v in d.items()}


d = {"level": [1, 2, 3], "conf": [-1, 1, 2], "text": ["-1", "hel", "llo"]}
print(filter_dictionary(d))

输出:

{'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}

我首先尝试查看 'conf' 的哪些索引是正数,然后使用 itemgetter 从字典中的值中挑选这些索引。

更紧凑的版本 + 没有使用生成器表达式的临时列表:

def filter_dictionary(d):
    f = itemgetter(*(i for i, item in enumerate(d['conf']) if item > 0))
    return {k: list(f(v)) for k, v in d.items()}

【讨论】:

    【解决方案2】:

    我会保留有效元素(大于 0)的索引:

    kept_keys = [i for i in range(len(my_dict['conf'])) if my_dict['conf'][i] > 0]
    

    然后你可以过滤每个列表,检查列表中某个元素的索引是否包含在kept_keys中:

    {k: list(map(lambda x: x[1], filter(lambda x: x[0] in kept_keys, enumerate(my_dict[k])))) for k in my_dict}
    

    输出:

    {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
    

    【讨论】:

      【解决方案3】:

      这是一个单行:

      dct = {k: [x for i, x in enumerate(v) if d['conf'][i] > 0] for k, v in d.items()}
      

      输出:

      >>> dct
      {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
      

      有样本数据:

      d = {"level":[1,2,3], "conf":[-1,1,2], "text":["here","hel","llo"]
      

      【讨论】:

        【解决方案4】:

        您所描述的数据结构听起来可能更自然地建模为pandas DataFrame:您实际上是将数据视为二维网格,并且您想要过滤掉该网格的行基于一列中的值。

        以下 sn-p 将使用 DataFrame 作为中间表示来满足您的需求:

        import pandas as pd
        
        data = {"level":[1,2,3], "conf":[-1,1,2], "text":["here","hel","llo"]}
        df = pd.DataFrame(data)
        df = df.loc[df["conf"] > 0]
        result = df.to_dict(orient="list")
        

        输出:

        {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
        

        但是,请注意,如果您首先将数据表示为 DataFrame,并在完成后将其保持为该格式,则这会简化为,

        data = pd.DataFrame({
            "level":[1,2,3],
            "conf":[-1,1,2],
            "text":["here","hel","llo"],
        })
        
        result = data.loc[data["conf"] > 0]
        

        输出:

           level  conf text
        1      2     1  hel
        2      3     2  llo
        

        与任何“纯 dict”解决方案相比,它更简洁、更具表现力,并且(在大量输入上)性能更高。

        如果您想对该数据执行的其他操作是相似的(在真正是“二维数组”操作的意义上),它们很可能也会更自然地以 DataFrame 表示,因此保持您的作为 DataFrame 的数据可能比转换回字典更有优势。

        【讨论】:

        • 也许这是一件小事,但 “首先将您的数据表示为 DataFrame” 似乎是一个红鲱鱼。如果数据是从第三方函数返回的,或者数据非常大,那么将数据内联到 DataFrame 调用中是没有意义的。重要的是将数据放入 df 中,不管它是如何到达那里的。
        【解决方案5】:

        我用这个解决了:

        from typing import Dict, List, Any, Set
        
        d = {"level":[1,2,3], "conf":[-1,1,2], "text":["-1", "hel", "llo"]}
        
        # First, we create a set that stores the indices which should be kept.
        # I chose a set instead of a list because it has a O(1) lookup time.
        # We only want to keep the items on indices where the value in d["conf"] is greater than 0
        filtered_indexes = {i for i, value in enumerate(d.get('conf', [])) if value > 0}
        
        def filter_dictionary(d: Dict[str, List[Any]], filtered_indexes: Set[int]) -> Dict[str, List[Any]]:
            filtered_dictionary = d.copy()  # We'll return a modified copy of the original dictionary
            for key, list_values in d.items():
                # In the next line the actual filtering for each key/value pair takes place. 
                # The original lists get overwritten with the filtered lists.
                filtered_dictionary[key] = [value for i, value in enumerate(list_values) if i in filtered_indexes]
            return filtered_dictionary
        
        print(filter_dictionary(d, filtered_indexes))
        

        输出:

        {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
        

        【讨论】:

          【解决方案6】:

          您可以使用一个函数来计算要保留哪些索引并仅使用这些索引重新构建每个列表:

          my_dict = {"level":[1,2,3], "conf":[-1,1,2],'text':["-1","hel","llo"]}
          
          def remove_corresponding_items(d, key):
              keep_indexes = [idx for idx, value in enumerate(d[key]) if value>0]
              for key, lst in d.items():
                  d[key] = [lst[idx] for idx in keep_indexes]
          
          remove_corresponding_items(my_dict, 'conf')
          print(my_dict)
          

          按要求输出

          【讨论】:

            【解决方案7】:

            很多很好的答案。这是另一种 2-pass 方法:

            mydict = {"level": [1, 2, 3], "conf": [-1, 1, 2], 'text': ["-1", "hel", "llo"]}
            
            for i, v in enumerate(mydict['conf']):
                if v <= 0:
                    for key in mydict.keys():
                        mydict[key][i] = None
            
            for key in mydict.keys():
                mydict[key] = [v for v in mydict[key] if v is not None]
            
            print(mydict)
            

            输出:

            {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
            

            【讨论】:

            • 添加对这段代码如何工作的解释会有所帮助。只需要一两句话就可以了,比如“获取每个否定 conf 值的索引,在整个字典中将其设置为 None,然后将它们过滤掉。”
            【解决方案8】:

            试试这个,简单易懂,特别适合初学者:

            a_dict = {"level": [1, 2, 3, 4, 5, 8], "conf": [-1, 1, -1, -2], "text": ["-1", "hel", "llo", "ai", 0, 9]}
            
            # iterate backwards over the list keeping the indexes
            for index, item in reversed(list(enumerate(a_dict["conf"]))):
                if item <= 0:
                    for lists in a_dict.values():
                        del lists[index]
            print(a_dict)
            

            输出:

            {'level': [2, 5, 8], 'conf': [1], 'text': ['hel', 0, 9]}
            

            【讨论】:

            • 它应该只包含每个列表的最后 2 个元素
            • 哦好的我现在明白了
            • 很好,这很聪明!其他答案首先建立索引列表,但一个接一个地挑选它们更简单。虽然可能性能较差,但更容易理解。
            【解决方案9】:

            这是一个简单的方法:

            dct = {"level":[1,2,3], "conf":[-1,1,2], "text":["here","hel","llo"]}
            dct = {k: np.array(v) for k, v in d.items()}
            dct = {k: v[a['conf'] > 0].tolist() for k, v in a.items()}
            

            输出:

            >>> dct
            {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
            

            【讨论】:

            • da 是什么?他们应该都是dct,对吧?
            • 你什么时候用这个?看起来像the Pandas solution,但更笨拙,因为数据是标记的,而不是有序的。
            • 实际上没有看到(好的)pandas 解决方案! :)
            【解决方案10】:

            我相信这会奏效: 对于每个列表,我们将过滤conf 为负数的值,然后我们将过滤conf 本身。

            d = {"level":[1,2,3], "conf":[-1,1,2], "text":["-1","hel","llo"]}
            for key in d:
                if key != "conf":
                    d[key] = [d[key][i] for i in range(len(d[key])) if d["conf"][i] >= 0]
            d["conf"] = [i for i in d["conf"] if i>=0]
            print(d)
            

            一个更简单的解决方案将是(完全相同但使用列表理解,因此我们不需要为conf 和其余部分单独执行:

            d = {"level":[1,2,3], "conf":[-1,1,2], "text":["-1","hel","llo"]}
            
            d = {i:[d[i][j] for j in range(len(d[i])) if d["conf"][j] >= 0] for i in d}
            

            输出: {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}

            【讨论】:

            • 但是过滤器只是通过比较“conf”键来实现的,应该适用于同一索引的所有其他列表
            • 这不是他要找的,他特别想删除 conf 键对应索引为 -1 的列表的所有值
            • 是的,如果不清楚,抱歉
            • @SrinjoyChoudhury 看看他的示例输入和输出
            • 更新了答案以跟随问题更新,这是你现在需要的吗?
            【解决方案11】:
            a = {"level":[1,2,3,4], "conf": [-1,1,2,-1],"text": ["-1","hel","llo","test"]}
            
            # inefficient solution
            # for k, v in a.items():
            #     if k == "conf":
            #         start_search = 0
            #         to_delete = [] #it will store the index numbers of the conf that you want to delete(conf<0)
            #         for element in v:
            #             if element < 0:
            #                 to_delete.append(v.index(element,start_search))
            #                 start_search = v.index(element) + 1
            
            #more efficient and elegant solution
            to_delete = [i for i, element in enumerate(a["conf"]) if element < 0]
            for position in list(reversed(to_delete)):
                for k, v in a.items():
                    v.pop(position)
            

            结果是

            >>> a
            {'level': [2, 3], 'conf': [1, 2], 'text': ['hel', 'llo']}
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2016-03-25
              • 1970-01-01
              • 2023-03-23
              • 1970-01-01
              • 2015-12-22
              • 1970-01-01
              • 2020-04-08
              • 1970-01-01
              相关资源
              最近更新 更多