function updateListObject(patch, obj, updated) {
  const objectId = patch.objectId
  if (!updated[objectId]) {
    updated[objectId] = cloneListObject(obj, objectId)
  }

  const list = updated[objectId], conflicts = list[CONFLICTS], elemIds = list[ELEM_IDS]
  for (let i = 0; i < patch.edits.length; i++) {
    const edit = patch.edits[i]

    if (edit.action === 'insert' || edit.action === 'update') {
      const oldValue = conflicts[edit.index] && conflicts[edit.index][edit.opId]
      let lastValue = getValue(edit.value, oldValue, updated)
      let values = {[edit.opId]: lastValue}

      // Successive updates for the same index are an indication of a conflict on that list element.
      // Edits are sorted in increasing order by Lamport timestamp, so the last value (with the
      // greatest timestamp) is the default resolution of the conflict.
      while (i < patch.edits.length - 1 && patch.edits[i + 1].index === edit.index &&
             patch.edits[i + 1].action === 'update') {
        i++
        const conflict = patch.edits[i]
        const oldValue2 = conflicts[conflict.index] && conflicts[conflict.index][conflict.opId]
        lastValue = getValue(conflict.value, oldValue2, updated)
        values[conflict.opId] = lastValue
      }

      if (edit.action === 'insert') {
        list.splice(edit.index, 0, lastValue)
        conflicts.splice(edit.index, 0, values)
        elemIds.splice(edit.index, 0, edit.elemId)
      } else {
        list[edit.index] = lastValue
        conflicts[edit.index] = values
      }

    } else if (edit.action === 'multi-insert') {
      const startElemId = parseOpId(edit.elemId), newElems = [], newValues = [], newConflicts = []
      const datatype = edit.datatype
      edit.values.forEach((value, index) => {
        const elemId = `${startElemId.counter + index}@${startElemId.actorId}`
        value = getValue({ value, datatype }, undefined, updated)
        newValues.push(value)
        newConflicts.push({[elemId]: {value, datatype, type: 'value'}})
        newElems.push(elemId)
      })
      list.splice(edit.index, 0, ...newValues)
      conflicts.splice(edit.index, 0, ...newConflicts)
      elemIds.splice(edit.index, 0, ...newElems)

    } else if (edit.action === 'remove') {
      list.splice(edit.index, edit.count)
      conflicts.splice(edit.index, edit.count)
      elemIds.splice(edit.index, edit.count)
    }
  }
  return list
}
Updates the list object `obj` according to the modifications described in
`patch`, or creates a new object if `obj` is undefined. Mutates `updated`
to map the objectId to the new object, and returns the new object.