Is there a way either with python scripts or other solution to dynamically segment the following table?
Input table (not a feature class)
| Item ID | Begin | End |
| 1000 | 0 | 1 |
| 1001 | 0 | 5 |
| 1002 | 1 | 7 |
| 1003 | 4 | 5 |
| 1004 | 3 | 5 |
Each segmented section (from-to) should have all the item numbers written to a field that is within a certain range. From the input table, the final table should look like the one below:
Output table
| Begin | End | Item ID | Item ID |
| 0 | 1 | 1000 | 1001 |
| 1 | 3 | 1001 | 1001; 1002 |
| 3 | 4 | 1002 | 1001; 1002; 1004 |
| 4 | 5 | 1003 | 1001; 1002; 1003; 1004 |
| 5 | 7 | 1004 | 1002 |
Thanks.
Solved! Go to Solution.
I tested it with your sample data, but I don't know how well it will work with more a more complex dataset. Note that the output field that lists all of the ItemID values has to have a limit, which I arbitrarily set to 255. Of course, you'll also need to change the paths and names accordingly.
def main():
import arcpy
import os
# Local variables
mygdb = r"N:\TechTemp\BlakeT\Work\TEMP.gdb"
mytable = os.path.join(mygdb, "SegmentSequence")
# Read input table into dictionary
itemsdict = {}
with arcpy.da.SearchCursor(mytable, ["ItemID", "Begin", "End"]) as s_cursor:
for item, begin, end in s_cursor:
itemsdict[item] = (begin, end)
# Identify segments
allsegments = [i[0] for i in itemsdict.values()] + [i[1] for i in itemsdict.values()]
segments = tuple(sorted(set(allsegments)))
del allsegments
# Create output table
arcpy.CreateTable_management(mygdb, "SegmentSequence_output")
mytable_out = os.path.join(mygdb, "SegmentSequence_output")
arcpy.AddField_management(mytable_out, "Begin", "SHORT")
arcpy.AddField_management(mytable_out, "End", "SHORT")
arcpy.AddField_management(mytable_out, "SegmentItems", "TEXT", field_length=255)
# Write segment items to output table
with arcpy.da.InsertCursor(mytable_out, ["Begin", "End", "SegmentItems"]) as i_cursor:
for enum, seg in enumerate(segments):
try:
begin = seg
end = segments[enum + 1]
except IndexError:
break
seg_items = [k for k, v in itemsdict.items() if v[0] <= begin and v[1] >= end]
row = (begin, end, str(seg_items).strip('[]'))
i_cursor.insertRow(row)
if __name__ == '__main__':
main()
Are all of the the "Item ID" values unique in the source table?
Yes, they are all unique
In the output table, how did you get a Begin range value of 5 when there was no Begin value of 5 in the input table?
there's an end value of 5 which the next range starts from.
So it's kind of like this?
1004__________ 1003_____ 1002______________________________ 1001_________________________ 1000_____ 0 1 2 3 4 5 6 7
So the segment from 0 to 1 includes items 1000, 1001
1 to 2 includes items 1001, 1002
2 to 3 includes items 1001, 1002
3 to 4 includes items 1001, 1002, 1004
4 to 5 includes items 1001, 1002, 1003, 1004
5 to 6 includes items 1002
6 to 7 includes items 1002
But it looks like you dissolve the segments that have the same items. So 1-2 and 2-3 become 1-3? Same with 5-6 and 6-7 becoming 5-7?
Ooops, the output table should look like:
| Begin | End | Item ID |
| 0 | 1 | 1001 |
| 1 | 3 | 1001; 1002 |
| 3 | 4 | 1001; 1002; 1004 |
| 4 | 5 | 1001; 1002; 1003; 1004 |
| 5 | 7 | 1002 |
Sorry about that.
I tested it with your sample data, but I don't know how well it will work with more a more complex dataset. Note that the output field that lists all of the ItemID values has to have a limit, which I arbitrarily set to 255. Of course, you'll also need to change the paths and names accordingly.
def main():
import arcpy
import os
# Local variables
mygdb = r"N:\TechTemp\BlakeT\Work\TEMP.gdb"
mytable = os.path.join(mygdb, "SegmentSequence")
# Read input table into dictionary
itemsdict = {}
with arcpy.da.SearchCursor(mytable, ["ItemID", "Begin", "End"]) as s_cursor:
for item, begin, end in s_cursor:
itemsdict[item] = (begin, end)
# Identify segments
allsegments = [i[0] for i in itemsdict.values()] + [i[1] for i in itemsdict.values()]
segments = tuple(sorted(set(allsegments)))
del allsegments
# Create output table
arcpy.CreateTable_management(mygdb, "SegmentSequence_output")
mytable_out = os.path.join(mygdb, "SegmentSequence_output")
arcpy.AddField_management(mytable_out, "Begin", "SHORT")
arcpy.AddField_management(mytable_out, "End", "SHORT")
arcpy.AddField_management(mytable_out, "SegmentItems", "TEXT", field_length=255)
# Write segment items to output table
with arcpy.da.InsertCursor(mytable_out, ["Begin", "End", "SegmentItems"]) as i_cursor:
for enum, seg in enumerate(segments):
try:
begin = seg
end = segments[enum + 1]
except IndexError:
break
seg_items = [k for k, v in itemsdict.items() if v[0] <= begin and v[1] >= end]
row = (begin, end, str(seg_items).strip('[]'))
i_cursor.insertRow(row)
if __name__ == '__main__':
main()
this is awesome Blake, i will test it and get back to you.
Thanks
Yep, Blake T answer is the correct one. I did a test and it yields the correct result.
Thought it was fun so I tried something too. See code below:
def main():
import arcpy
import numpy
tbl_in = r'C:\Forum\TableFun\data.gdb\tbl158276'
tbl_out = r'C:\Forum\TableFun\data.gdb\tbl158276_out'
dct = {r[0]:(r[1], r[2]) for r in arcpy.da.SearchCursor(tbl_in, ('Item_ID', 'Begin_', 'End_'))}
lst_tot = list(set([v[0] for v in dct.values()] + [v[1] for v in dct.values()]))
dct_ranges = {}
for i in range(len(lst_tot)-1):
key_range = (lst_tot, lst_tot[i+1])
dct_ranges[key_range] = []
for key, tpl_range in sorted(dct.items()):
if isInRange(tpl_range[0], tpl_range[1], key_range):
dct_ranges[key_range].append(key)
lst_numpy = []
for k, v in sorted(dct_ranges.items()):
lst_numpy.append((k[0], k[1], ",".join([str(i) for i in v])))
array = numpy.array(lst_numpy,
numpy.dtype([('Begin', numpy.int32),
('End', numpy.int32),
('Item_ID', numpy.str_, 255)]))
arcpy.da.NumPyArrayToTable(array, tbl_out)
def isInRange(begin, end, key_range):
return key_range[0] < end and key_range[1] > begin
if __name__ == '__main__':
main()