将整数列表映射到python

时间:2017-09-08 09:08:10

标签: python pandas numpy iterator range

我想将列表合并到范围中,但保留原始顺序。同时提供定制差距支持。

例如,当输入列表[0, 1, 3, 7, 4, 2, 8, 9, 11, 11]时,它应该返回范围列表["0-4", "0-4", "7-9", "0-4", "0-4", "0-4", "7-9", "7-9", "11-11", "11-11"]

def fun(a_list, gap_length=0):
    return a_list_of_range

# from
# [0, 1, 3, 7, 4, 2, 8, 9, 11, 11]
# to
# ["0-4", "0-4", "7-9", "0-4", "0-4", "0-4", "7-9", "7-9", "11-11", "11-11"]
# or to
# {0:"0-4", 1:"0-4", 2:"0-4", 3:"0-4", 4:"0-4", 7:"7-9", 8:"7-9", 9:"7-9", 10:"11-11"}

stackoverflow上有similar question,但所有答案都不能以相应的顺序返回范围。

  

你的解决方案是什么?

我写了一个丑陋的函数来解决问题,但速度很糟糕。 以下函数支持自定义间隙长度,以便将列表合并到范围内。

def to_ranges_with_gap(input_list, gap_len=20):
    """list into range with gap"""
    loc2range = {}
    input_list = sorted(set(input_list))
    start_loc = input_list[0]
    stop_loc = input_list[0]
    range_loc_list = []
    for element in input_list:
        if element < stop_loc + gap_len:
            range_loc_list.append(element)
            stop_loc = element
        else:

            for loc in range_loc_list:
                loc2range[loc] = "{}-{}".format(start_loc, stop_loc)

            start_loc = element
            stop_loc = element
            range_loc_list = [element]

        for loc in range_loc_list:
            loc2range[loc] = "{}-{}".format(start_loc, stop_loc)

    return loc2range

你能告诉我一个更好的方法吗?

  

列表的剂量是多少?

列表是:

  • 重复
  • 未排序
  • 不连续
  • 大量元素。数十亿的数字从0到10 ^ 10,因此速度很重要。
  

在结果列表中重复范围的目的是什么?你可以写一个更优雅的解决方案而不需要那个怪癖。 - timgeb

例如,如果我想处理下面的数据框,并尝试对年龄范围进行分组以计算中位数高度。

Age  Gender  Height 
2    M       30
4    M       60
2    M       33
3    F       50
20   M       180
22   F       166
40   F       150
33   M       172
...

我希望得到这样的结果。而年龄列是上面提到的list

2-5  M    40.5
2-6  F    50.9
10-25 M   150.8
...

因此,如果我可以直接合并数据帧,而不生成映射器并再次将其重新映射到数据帧,那将会更好。

4 个答案:

答案 0 :(得分:1)

我修改了您提供的similar question中已接受的答案代码,并且它对我有用

import itertools

def ranges(i):
    for a, b in itertools.groupby(enumerate(i), lambda i: i[1] - i[0]):
        b = list(b)
        if(b[0][1] - b[-1][1] == 0):
                yield "%d-%d"%(b[0][1], b[-1][1])
        for ele in range(b[0][1], b[-1][1]):
                yield "%d-%d"%(b[0][1], b[-1][1])

print ([ele for ele in ranges([0, 1, 2, 3, 4, 7, 8, 9, 11])])

['0-4','0-4','0-4','0-4','7-9','7-9','11 -11']

注意:请告诉我如果这是错误的回答方式,将从下次开始处理。我的意图只是给予适当的答案并帮助他人,而不是采取别人的回答等等。

请在下面发表评论,如果是,请删除我的答案。

我知道,这是一个不好的补丁。

答案 1 :(得分:0)

这将返回您似乎正在寻找的结果。它并不比你拥有的更漂亮,但它确实有效:

#!/usr/bin/python

arr = []
l = [1,2,3,5,6,7,8,9,11,12,13,14,20]
start,counter,i = (0,0,0)

while i < len(l):
    start = i
    counter = 0
    while (i < len(l) - 1) and (l[i+1] == l[i] +1):
        counter += 1
        i += 1
    for x in range(counter+1):
        arr.append("{}-{}".format(l[start], l[start+counter]))
    i += 1

print(arr)

输出:

['1-3', '1-3', '1-3', '5-9', '5-9', '5-9', '5-9', '5-9', '11-14', '11-14', '11-14', '11-14', '20-20']

答案 2 :(得分:0)

<强>代码

import itertools as it
import collections as ct


# Given
a = [0, 1, 2, 3, 4, 7, 8, 9, 11]
b = [0, 1, 3, 7, 4, 2, 8, 9, 11]                       # unsorted
c = [0, 15, 2, 3, 4, 7, 8, 9, 11, 14]                  # unsorted
d = [0, 15, 2, 3, 4, 7, 8, 9, 11, 14, 2, 4]            # duplicates 


def find_ranges(iterable):
    """Return a defaultdict of ranges."""
    # Find ranges
    sorted_it = sorted(set(iterable))
    keyfunc = lambda i:  sorted_it[i[0]] - i[0]
    ranges = [[item[1] for item in g] 
            for k, g in it.groupby(enumerate(sorted_it), keyfunc)]
    # Out: [[0, 1, 2, 3, 4], [7, 8, 9], [11]]

    # Build dictionary
    dd = ct.defaultdict(int)
    for r in ranges:
        s = "{}-{}".format(min(r), max(r))
        for i in r:
            if i in r:
                dd[i] = s
    return dd

find_ranges(a)

输出

defaultdict(int,
            {0: '0-4',
            1: '0-4',
            2: '0-4',
            3: '0-4',
            4: '0-4',
            7: '7-9',
            8: '7-9',
            9: '7-9',
            11: '11-11'})

获得此查找表后,创建范围列表很简单:

[find_ranges(b)[i] for i in b]
# ['0-4', '0-4', '0-4', '7-9', '0-4', '0-4', '7-9', '7-9', '11-11']

<强>详情

此外,此函数查找未排序的iterables(bc)的范围,并处理重复项(d)。

assert find_ranges(a) == find_ranges(b)
assert find_ranges(c) == find_ranges(d)

这里我们将确认结果对于排序和未排序的输入是等效的。接下来,我们将确认未排序输入的等效性和重复元素的输入。最后,我们演示了一个示例输出:

find_ranges(d)

输出

defaultdict(int,
        {0: '0-0',
         2: '2-4',
         3: '2-4',
         4: '2-4',
         7: '7-9',
         8: '7-9',
         9: '7-9',
         11: '11-11',
         14: '14-15',
         15: '14-15'})

注意:“查找范围”部分的灵感来自@ Nirmi的帖子,这是一个很好的贡献。

答案 3 :(得分:0)

this post@pylang的启发,我找到了O(n)解决方案。

import itertools
import collections

# an example list
l = [100, 107, 0, 1, 2, 3, 5, 6, 10, 11, 65, 66, 68, 68]

class Gap(object):
    def __init__(self, diff):
        self.diff, self.flag, self.prev = diff, [0,1], None
    def __call__(self, elem):
        if self.prev and abs(self.prev - elem) > self.diff:
            self.flag = self.flag[::-1]
        self.prev= elem
        return self.flag[0]


def list_to_ranges_with_gap(raw_list, gap_length = 1):
    """Return a defaultdict of ranges."""

    # Find ranges
    sorted_list = sorted(set(raw_list))
    merged_ranges = [list(g) for k, g in 
                     itertools.groupby(sorted_list, key = Gap(gap_length))]

    # Build dictionary
    list2range = collections.defaultdict(int)
    for r in merged_ranges:
        for i in r:
            list2range[i] = "{}-{}".format(r[0], r[-1])

    return list2range
list_to_ranges_with_gap(l, 10)

defaultdict(int,
            {0: '0-11',
             1: '0-11',
             2: '0-11',
             3: '0-11',
             5: '0-11',
             6: '0-11',
             10: '0-11',
             11: '0-11',
             65: '65-68',
             66: '65-68',
             68: '65-68',
             100: '100-107',
             107: '100-107'})