List Manipulation Routines
Working with lists is one of the most common procedures performed in scripts. However, the current version of AppleScript does not support the use of every...whose clauses when targeting lists. For example, the following statement will not work:
set the name_list to {"Bob", "Sal", "Wanda", "Sue"}
set every item of the name_list where it is "Bob" to "Carl"
--> Can't set {"Bob", "Sal", "Wanda", "Sue"} whose it = "Bob" to "Carl".
The following sub-routines are designed to resolve these issues and make working with lists easier.
Determining if a List Contains a Specific Item
No special sub-routine is needed to determine if a list contains a specific item. The following syntax will return a value of either true or false to indicate the presence of an item in the list:
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
this_list contains "Bill"
--> false
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
if this_list does not contain "Wanda" then
set the end of this_list to "Wanda"
end if
return this_list
--> returns: {"Sal", "Sue", "Bob", "Carl", "Sue", "Wanda"}
Offset of an Item in a List
The following sub-routine is used when it is necessary to determine an item's numeric position in a list.:
set this_list to {"Sal", "Sue", "Bob", "Carl"}
list_position("Carl", this_list)
--> 4
Here's the sub-routine called in the previous example:
on list_position(this_item, this_list)
repeat with i from 1 to the count of this_list
if item i of this_list is this_item then return i
end repeat
return 0
end list_position
The above sub-routine can be used to cross-reference data between corresponding lists. In the example script below the variable name_list contains a list of names while the second variable, phone_list, contains phone numbers corresponding to the individuals in the first list. The script uses the previous sub-routine to determine the list position of the name chosen by the user and then locates the phone number occupying the same position in the second list:
set the name_list to {"Sal", "Sue", "Bob", "Carl"}
set the phone_list to {"5-5874", "5-2435", "5-9008", "5-1037"}
set the chosen_person to choose from list the name_list with prompt "Choose a person:"
if the chosen_person is false then return "user cancelled"
set the phone_number to ¬
item (list_position((chosen_person as string), name_list)) of the phone_list
display dialog "The phone extension for " & chosen_person & ¬
" is " & phone_number & "." buttons {"OK"} default button 1
The following sub-routine is a variation of the previous sub-routine that will return the offset of multiple occurrences of an item within a list:
on list_positions(this_list, this_item, list_all)
set the offset_list to {}
repeat with i from 1 to the count of this_list
if item i of this_list is this_item then
set the end of the offset_list to i
if list_all is false then return item 1 of offset_list
end if
end repeat
if list_all is false and offset_list is {} then return 0
return the offset_list
end list_positions
By passing a boolean value of true or false as the third passed parameter, you can use the sub-routine to retrieve all of the offsets of an item in a list:
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
list_positions(this_list, "Sue", true)
--> {2, 5}
Or to retrieve the offset of the first matching item:
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
list_positions(this_list, "Sue", false)
--> 2
Counting Occurrences of an Item in a List
The following sub-routine can be used to count how many times an item is in a specified list:
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
count_matches(this_list, "Sue")
--> 2
And here's the sub-routine:
on count_matches(this_list, this_item)
set the match_counter to 0
repeat with i from 1 to the count of this_list
if item i of this_list is this_item then ¬
set the match_counter to the match_counter + 1
end repeat
return the match_counter
end count_matches
Insert Item(s) Into List by Position
The following sub-routine can be used to insert an item into a list. The passed parameters include the list to edit, the item to add, and the desired list position for the added item. Note that the item position can be specified in relation to the end of the list by passing a negative number as the offset from the end of the list:
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set this_list to insert_listitem(this_list, "Wanda", 3)
--> {"Sal", "Sue", "Wanda", "Bob", "Carl"}
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set this_list to insert_listitem(this_list, {"Wanda", "Alberta"}, 3)
--> {"Sal", "Sue", "Wanda", "Alberta", "Bob", "Carl"}
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set this_list to insert_listitem(this_list, {{"Wanda", "Alberta"}}, 3)
--> {"Sal", "Sue", {"Wanda", "Alberta"}, "Bob", "Carl"}
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set this_list to insert_listitem(this_list, "Wanda", -1)
--> {"Sal", "Sue", "Bob", "Carl", "Wanda"}
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set this_list to insert_listitem(this_list, "Wanda", -2)
--> {"Sal", "Sue", "Bob", "Wanda", "Carl"}
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set this_list to insert_listitem(this_list, "Wanda", 5)
--> {"Sal", "Sue", "Bob", "Carl", "Wanda"}
on insert_listitem(this_list, this_item, list_position)
set the list_count to the count of this_list
-- THE LIST POSITION INDICATES THE POSITION IN THE LIST
-- YOU WANT THE ADDED ITEM TO OCCUPY
if the list_position is 0 then
return false
else if the list_position is less than 0 then
if (the list_position * -1) is greater than the list_count + 1 then ¬
return false
else
if the list_position is greater than the list_count + 1 then return false
end if
if the list_position is less than 0 then
if (the list_position * -1) is the list_count + 1 then
set the beginning of this_list to this_item
else
set this_list to the reverse of this_list
set the list_position to (list_position * -1)
set this_list to (items 1 thru (list_position - 1) of this_list) & ¬
this_item & (items list_position thru -1 of this_list)
set this_list to the reverse of this_list
end if
else
if the list_position is 1 then
set the beginning of this_list to this_item
else if the list_position is (the list_count + 1) then
set the end of this_list to this_item
else
set this_list to (items 1 thru (list_position - 1) of this_list) & ¬
this_item & (items list_position thru -1 of this_list)
end if
end if
return this_list
end insert_listitem
Replacing List Items
You can replace an item in a list using the following syntax if you know the offset of the target item:
set this_list to {"Sal", "Sue", "Bob", "Carl"}
set item 3 of this_list to "Wanda"
return this_list
--> {"Sal", "Sue", "Wanda", "Carl"}
Replacing List Items by Matching
Use the following sub-routine to replace an item in a list if the position of the item to be replaced is not known. Note that the sub-routine has a passed parameter for indicating whether all matching items should be replaced or just the first occurrence:
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
replace_matches(this_list, "Sue", "Wanda", false)
--> {"Sal", "Wanda", "Bob", "Carl", "Sue"}
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
replace_matches(this_list, "Sue", "Wanda", true)
--> {"Sal", "Wanda", "Bob", "Carl", "Wanda"}
set this_list to {"Sal", "Sue", "Bob", "Carl", "Sue"}
replace_matches(this_list, "Bill", "Wanda", true)
--> {"Sal", "Sue", "Bob", "Carl", "Sue"}
on replace_matches(this_list, match_item, replacement_item, replace_all)
repeat with i from 1 to the count of this_list
set this_item to item i of this_list
if this_item is the match_item then
set item i of this_list to the replacement_item
if replace_all is false then return this_list
end if
end repeat
return this_list
end replace_matches